From 4339e2dc4afb5035221398a88cc04f6718d8d523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 12:41:07 +0200 Subject: [PATCH 001/326] Move `AudioLeadIn` out of `BeatmapInfo` --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 ++-- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | 4 ++-- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs | 2 +- .../Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs | 4 ++-- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ osu.Game/Screens/Play/MasterGameplayClockContainer.cs | 4 ++-- 16 files changed, 30 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index a4cd888823..9ffb3327b9 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var metadata = beatmap.Metadata; Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", metadata.AudioFile); - Assert.AreEqual(0, beatmapInfo.AudioLeadIn); + Assert.AreEqual(0, beatmap.AudioLeadIn); Assert.AreEqual(164471, metadata.PreviewTime); Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); @@ -950,7 +950,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.Multiple(() => { - Assert.That(decoded.BeatmapInfo.AudioLeadIn, Is.EqualTo(0)); + Assert.That(decoded.AudioLeadIn, Is.EqualTo(0)); Assert.That(decoded.BeatmapInfo.StackLeniency, Is.EqualTo(0.7f)); Assert.That(decoded.BeatmapInfo.SpecialStyle, Is.False); Assert.That(decoded.BeatmapInfo.LetterboxInBreaks, Is.False); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 3764467047..3fd05b692d 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { var beatmap = decodeAsJson(normal); var beatmapInfo = beatmap.BeatmapInfo; - Assert.AreEqual(0, beatmapInfo.AudioLeadIn); + Assert.AreEqual(0, beatmap.AudioLeadIn); Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); Assert.AreEqual(false, beatmapInfo.SpecialStyle); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index 5a71369976..a6b8e679b9 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay { loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) { - BeatmapInfo = { AudioLeadIn = leadIn } + AudioLeadIn = leadIn }); checkFirstFrameTime(expectedStartTime); @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Gameplay { Text = $"GameplayStartTime: {DrawableRuleset.GameplayStartTime} " + $"FirstHitObjectTime: {FirstHitObjectTime} " - + $"LeadInTime: {Beatmap.Value.BeatmapInfo.AudioLeadIn} " + + $"LeadInTime: {Beatmap.Value.Beatmap.AudioLeadIn} " + $"FirstFrameClockTime: {FirstFrameClockTime}" }); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 1949808dfe..c17405c2ec 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -135,7 +135,7 @@ namespace osu.Game.Tests.Visual.Gameplay var workingBeatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); // Add intro time to test quick retry skipping (TestQuickRetry). - workingBeatmap.BeatmapInfo.AudioLeadIn = 60000; + workingBeatmap.Beatmap.AudioLeadIn = 60000; // Set up data for testing disclaimer display. workingBeatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning ?? false; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs index ae10207de0..81dd23661c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayPlayer.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay { loadPlayerWithBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo) { - BeatmapInfo = { AudioLeadIn = 60000 } + AudioLeadIn = 60000 }); AddUntilStep("wait for skip overlay", () => Player.ChildrenOfType().First().IsButtonVisible); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 2b17f91e68..6108260481 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -406,13 +406,13 @@ namespace osu.Game.Tests.Visual.Multiplayer } /// - /// Tests spectating with a beatmap that has a high value. + /// Tests spectating with a beatmap that has a high value. /// /// This test is not intended not to check the correct initial time value, but only to guard against /// gameplay potentially getting stuck in a stopped state due to lead in time being present. /// [Test] - public void TestAudioLeadIn() => testLeadIn(b => b.BeatmapInfo.AudioLeadIn = 2000); + public void TestAudioLeadIn() => testLeadIn(b => b.Beatmap.AudioLeadIn = 2000); /// /// Tests spectating with a beatmap that has a storyboard element with a negative start time (i.e. intro storyboard element). diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index ae77e4adcf..f3ad02558a 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -114,6 +114,8 @@ namespace osu.Game.Beatmaps return mostCommon.beatLength; } + public double AudioLeadIn { get; set; } + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index b68c80d4b3..f33cdaf81f 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -67,6 +67,7 @@ namespace osu.Game.Beatmaps beatmap.HitObjects = convertHitObjects(original.HitObjects, original, cancellationToken).OrderBy(s => s.StartTime).ToList(); beatmap.Breaks = original.Breaks; beatmap.UnhandledEventLines = original.UnhandledEventLines; + beatmap.AudioLeadIn = original.AudioLeadIn; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 2137f33e77..b8e253527b 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -414,7 +414,6 @@ namespace osu.Game.Beatmaps Hash = hash, DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, - AudioLeadIn = decodedInfo.AudioLeadIn, StackLeniency = decodedInfo.StackLeniency, SpecialStyle = decodedInfo.SpecialStyle, LetterboxInBreaks = decodedInfo.LetterboxInBreaks, diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 425fd98d27..e1580dc74e 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -138,8 +138,6 @@ namespace osu.Game.Beatmaps #region Properties we may not want persisted (but also maybe no harm?) - public double AudioLeadIn { get; set; } - public float StackLeniency { get; set; } = 0.7f; public bool SpecialStyle { get; set; } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index c2f4097889..5966658c93 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -238,7 +238,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"AudioLeadIn": - beatmap.BeatmapInfo.AudioLeadIn = Parsing.ParseInt(pair.Value); + beatmap.AudioLeadIn = Parsing.ParseInt(pair.Value); break; case @"PreviewTime": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 186b565c39..072223c8fb 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -79,7 +79,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine("[General]"); if (!string.IsNullOrEmpty(beatmap.Metadata.AudioFile)) writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}")); - writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}")); + writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.AudioLeadIn}")); writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}")); writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}")); writer.WriteLine(FormattableString.Invariant( diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 5cc38e5b84..993155a32e 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -68,6 +68,8 @@ namespace osu.Game.Beatmaps /// double GetMostCommonBeatLength(); + double AudioLeadIn { get; internal set; } + /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index d37cfc28b9..5557051f05 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -337,6 +337,12 @@ namespace osu.Game.Rulesets.Difficulty public double GetMostCommonBeatLength() => baseBeatmap.GetMostCommonBeatLength(); public IBeatmap Clone() => new ProgressiveCalculationBeatmap(baseBeatmap.Clone()); + public double AudioLeadIn + { + get => baseBeatmap.AudioLeadIn; + set => baseBeatmap.AudioLeadIn = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 5be1d27805..7392c66a26 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -184,6 +184,12 @@ namespace osu.Game.Screens.Edit public double GetMostCommonBeatLength() => PlayableBeatmap.GetMostCommonBeatLength(); + public double AudioLeadIn + { + get => PlayableBeatmap.AudioLeadIn; + set => PlayableBeatmap.AudioLeadIn = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; diff --git a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs index b2f0ae5561..3851806788 100644 --- a/osu.Game/Screens/Play/MasterGameplayClockContainer.cs +++ b/osu.Game/Screens/Play/MasterGameplayClockContainer.cs @@ -95,8 +95,8 @@ namespace osu.Game.Screens.Play // some beatmaps specify a current lead-in time which should be used instead of the ruleset-provided value when available. // this is not available as an option in the live editor but can still be applied via .osu editing. double firstHitObjectTime = beatmap.Beatmap.HitObjects.First().StartTime; - if (beatmap.BeatmapInfo.AudioLeadIn > 0) - time = Math.Min(time, firstHitObjectTime - beatmap.BeatmapInfo.AudioLeadIn); + if (beatmap.Beatmap.AudioLeadIn > 0) + time = Math.Min(time, firstHitObjectTime - beatmap.Beatmap.AudioLeadIn); return time; } From 0a4560a03e0c5dfccecc6be661586d378d3b88aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 12:50:41 +0200 Subject: [PATCH 002/326] Move `StackLeniency` out of `BeatmapInfo` --- .../Mods/TestSceneOsuModFlashlight.cs | 5 +---- .../Mods/TestSceneOsuModRandom.cs | 2 +- .../TestSceneSliderLateHitJudgement.cs | 2 +- .../Beatmaps/OsuBeatmapProcessor.cs | 14 +++++++------- .../Edit/Setup/OsuSetupSection.cs | 4 ++-- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 ++-- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ .../Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ 16 files changed, 34 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs index 075fdd88ca..1a3b0310f7 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModFlashlight.cs @@ -83,10 +83,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods }) } }, - BeatmapInfo = - { - StackLeniency = 0, - } + StackLeniency = 0, }, ReplayFrames = new List { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs index 060a845137..75a5d36f32 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRandom.cs @@ -74,12 +74,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { BeatmapInfo = new BeatmapInfo { - StackLeniency = 0, Difficulty = new BeatmapDifficulty { ApproachRate = 8.5f } }, + StackLeniency = 0, ControlPointInfo = controlPointInfo }; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs index 1ba4a60b75..d089e924ca 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderLateHitJudgement.cs @@ -465,7 +465,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void performTest(List frames, Beatmap beatmap) { beatmap.BeatmapInfo.Ruleset = new OsuRuleset().RulesetInfo; - beatmap.BeatmapInfo.StackLeniency = 0; + beatmap.StackLeniency = 0; beatmap.BeatmapInfo.Difficulty = new BeatmapDifficulty { SliderMultiplier = 4, diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index d335913586..9cc22b764f 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -51,13 +51,13 @@ namespace osu.Game.Rulesets.Osu.Beatmaps h.StackHeight = 0; if (Beatmap.BeatmapInfo.BeatmapVersion >= 6) - applyStacking(Beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1); + applyStacking(Beatmap, hitObjects, 0, hitObjects.Count - 1); else - applyStackingOld(Beatmap.BeatmapInfo, hitObjects); + applyStackingOld(Beatmap, hitObjects); } } - private void applyStacking(BeatmapInfo beatmapInfo, List hitObjects, int startIndex, int endIndex) + private void applyStacking(IBeatmap beatmap, List hitObjects, int startIndex, int endIndex) { ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex, endIndex); ArgumentOutOfRangeException.ThrowIfNegative(startIndex); @@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps continue; double endTime = stackBaseObject.GetEndTime(); - double stackThreshold = objectN.TimePreempt * beatmapInfo.StackLeniency; + double stackThreshold = objectN.TimePreempt * beatmap.StackLeniency; if (objectN.StartTime - endTime > stackThreshold) // We are no longer within stacking range of the next object. @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps OsuHitObject objectI = hitObjects[i]; if (objectI.StackHeight != 0 || objectI is Spinner) continue; - double stackThreshold = objectI.TimePreempt * beatmapInfo.StackLeniency; + double stackThreshold = objectI.TimePreempt * beatmap.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. @@ -209,7 +209,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps } } - private void applyStackingOld(BeatmapInfo beatmapInfo, List hitObjects) + private void applyStackingOld(IBeatmap beatmap, List hitObjects) { for (int i = 0; i < hitObjects.Count; i++) { @@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps for (int j = i + 1; j < hitObjects.Count; j++) { - double stackThreshold = hitObjects[i].TimePreempt * beatmapInfo.StackLeniency; + double stackThreshold = hitObjects[i].TimePreempt * beatmap.StackLeniency; if (hitObjects[j].StartTime - stackThreshold > startTime) break; diff --git a/osu.Game.Rulesets.Osu/Edit/Setup/OsuSetupSection.cs b/osu.Game.Rulesets.Osu/Edit/Setup/OsuSetupSection.cs index 552b887081..e1a588a32a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Setup/OsuSetupSection.cs +++ b/osu.Game.Rulesets.Osu/Edit/Setup/OsuSetupSection.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup { Label = "Stack Leniency", Description = "In play mode, osu! automatically stacks notes which occur at the same location. Increasing this value means it is more likely to snap notes of further time-distance.", - Current = new BindableFloat(Beatmap.BeatmapInfo.StackLeniency) + Current = new BindableFloat(Beatmap.StackLeniency) { Default = 0.7f, MinValue = 0, @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Setup private void updateBeatmap() { - Beatmap.BeatmapInfo.StackLeniency = stackLeniency.Current.Value; + Beatmap.StackLeniency = stackLeniency.Current.Value; Beatmap.UpdateAllHitObjects(); Beatmap.SaveState(); } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 9ffb3327b9..565c481920 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", metadata.AudioFile); Assert.AreEqual(0, beatmap.AudioLeadIn); Assert.AreEqual(164471, metadata.PreviewTime); - Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); + Assert.AreEqual(0.7f, beatmap.StackLeniency); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.IsFalse(beatmapInfo.LetterboxInBreaks); Assert.IsFalse(beatmapInfo.SpecialStyle); @@ -951,7 +951,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.Multiple(() => { Assert.That(decoded.AudioLeadIn, Is.EqualTo(0)); - Assert.That(decoded.BeatmapInfo.StackLeniency, Is.EqualTo(0.7f)); + Assert.That(decoded.StackLeniency, Is.EqualTo(0.7f)); Assert.That(decoded.BeatmapInfo.SpecialStyle, Is.False); Assert.That(decoded.BeatmapInfo.LetterboxInBreaks, Is.False); Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 3fd05b692d..5d2d9e006e 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var beatmap = decodeAsJson(normal); var beatmapInfo = beatmap.BeatmapInfo; Assert.AreEqual(0, beatmap.AudioLeadIn); - Assert.AreEqual(0.7f, beatmapInfo.StackLeniency); + Assert.AreEqual(0.7f, beatmap.StackLeniency); Assert.AreEqual(false, beatmapInfo.SpecialStyle); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index f3ad02558a..ecee6e3416 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -116,6 +116,8 @@ namespace osu.Game.Beatmaps public double AudioLeadIn { get; set; } + public float StackLeniency { get; set; } = 0.7f; + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index f33cdaf81f..a5e9025404 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -68,6 +68,7 @@ namespace osu.Game.Beatmaps beatmap.Breaks = original.Breaks; beatmap.UnhandledEventLines = original.UnhandledEventLines; beatmap.AudioLeadIn = original.AudioLeadIn; + beatmap.StackLeniency = original.StackLeniency; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index b8e253527b..e589b0a754 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -414,7 +414,6 @@ namespace osu.Game.Beatmaps Hash = hash, DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, - StackLeniency = decodedInfo.StackLeniency, SpecialStyle = decodedInfo.SpecialStyle, LetterboxInBreaks = decodedInfo.LetterboxInBreaks, WidescreenStoryboard = decodedInfo.WidescreenStoryboard, diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index e1580dc74e..fa2911438b 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -138,8 +138,6 @@ namespace osu.Game.Beatmaps #region Properties we may not want persisted (but also maybe no harm?) - public float StackLeniency { get; set; } = 0.7f; - public bool SpecialStyle { get; set; } public bool LetterboxInBreaks { get; set; } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 5966658c93..86552b21dd 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -255,7 +255,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"StackLeniency": - beatmap.BeatmapInfo.StackLeniency = Parsing.ParseFloat(pair.Value); + beatmap.StackLeniency = Parsing.ParseFloat(pair.Value); break; case @"Mode": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 072223c8fb..8c371026ff 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -84,7 +84,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}")); writer.WriteLine(FormattableString.Invariant( $"SampleSet: {toLegacySampleBank(((beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePoints.FirstOrDefault() ?? SampleControlPoint.DEFAULT).SampleBank)}")); - writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.BeatmapInfo.StackLeniency}")); + writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.StackLeniency}")); writer.WriteLine(FormattableString.Invariant($"Mode: {onlineRulesetID}")); writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}")); // if (beatmap.BeatmapInfo.UseSkinSprites) diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 993155a32e..28d601620a 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -70,6 +70,8 @@ namespace osu.Game.Beatmaps double AudioLeadIn { get; internal set; } + float StackLeniency { get; internal set; } + /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 5557051f05..616d6d0848 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -343,6 +343,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.AudioLeadIn = value; } + public float StackLeniency + { + get => baseBeatmap.StackLeniency; + set => baseBeatmap.StackLeniency = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 7392c66a26..c02a22ae03 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -190,6 +190,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.AudioLeadIn = value; } + public float StackLeniency + { + get => PlayableBeatmap.StackLeniency; + set => PlayableBeatmap.StackLeniency = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; From 011c2e3651fe1485eca8663697630777a848a440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 13:12:30 +0200 Subject: [PATCH 003/326] Move `SpecialStyle` out of `BeatmapInfo` --- osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs | 4 ++-- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 ++-- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ 12 files changed, 24 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs index d5a9a311bc..8778c18c38 100644 --- a/osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs +++ b/osu.Game.Rulesets.Mania/Edit/Setup/ManiaSetupSection.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup { Label = "Use special (N+1) style", Description = "Changes one column to act as a classic \"scratch\" or \"special\" column, which can be moved around by the user's skin (to the left/right/centre). Generally used in 6K (5+1) or 8K (7+1) configurations.", - Current = { Value = Beatmap.BeatmapInfo.SpecialStyle } + Current = { Value = Beatmap.SpecialStyle } } }; } @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Setup private void updateBeatmap() { - Beatmap.BeatmapInfo.SpecialStyle = specialStyle.Current.Value; + Beatmap.SpecialStyle = specialStyle.Current.Value; Beatmap.SaveState(); } } diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 565c481920..ad3721220a 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(0.7f, beatmap.StackLeniency); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.IsFalse(beatmapInfo.LetterboxInBreaks); - Assert.IsFalse(beatmapInfo.SpecialStyle); + Assert.IsFalse(beatmap.SpecialStyle); Assert.IsFalse(beatmapInfo.WidescreenStoryboard); Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate); Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); @@ -952,7 +952,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { Assert.That(decoded.AudioLeadIn, Is.EqualTo(0)); Assert.That(decoded.StackLeniency, Is.EqualTo(0.7f)); - Assert.That(decoded.BeatmapInfo.SpecialStyle, Is.False); + Assert.That(decoded.SpecialStyle, Is.False); Assert.That(decoded.BeatmapInfo.LetterboxInBreaks, Is.False); Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False); Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 5d2d9e006e..18f4651e94 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Beatmaps.Formats var beatmapInfo = beatmap.BeatmapInfo; Assert.AreEqual(0, beatmap.AudioLeadIn); Assert.AreEqual(0.7f, beatmap.StackLeniency); - Assert.AreEqual(false, beatmapInfo.SpecialStyle); + Assert.AreEqual(false, beatmap.SpecialStyle); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks); Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index ecee6e3416..f06d884bd1 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -118,6 +118,8 @@ namespace osu.Game.Beatmaps public float StackLeniency { get; set; } = 0.7f; + public bool SpecialStyle { get; set; } + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index a5e9025404..3b3b68de0a 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -69,6 +69,7 @@ namespace osu.Game.Beatmaps beatmap.UnhandledEventLines = original.UnhandledEventLines; beatmap.AudioLeadIn = original.AudioLeadIn; beatmap.StackLeniency = original.StackLeniency; + beatmap.SpecialStyle = original.SpecialStyle; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index e589b0a754..435a282b52 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -414,7 +414,6 @@ namespace osu.Game.Beatmaps Hash = hash, DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, - SpecialStyle = decodedInfo.SpecialStyle, LetterboxInBreaks = decodedInfo.LetterboxInBreaks, WidescreenStoryboard = decodedInfo.WidescreenStoryboard, EpilepsyWarning = decodedInfo.EpilepsyWarning, diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index fa2911438b..4a1fa9b0b4 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -138,8 +138,6 @@ namespace osu.Game.Beatmaps #region Properties we may not want persisted (but also maybe no harm?) - public bool SpecialStyle { get; set; } - public bool LetterboxInBreaks { get; set; } public bool WidescreenStoryboard { get; set; } = true; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 86552b21dd..ea34c7d924 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -289,7 +289,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"SpecialStyle": - beatmap.BeatmapInfo.SpecialStyle = Parsing.ParseInt(pair.Value) == 1; + beatmap.SpecialStyle = Parsing.ParseInt(pair.Value) == 1; break; case @"WidescreenStoryboard": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 8c371026ff..805ce49ca3 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -100,7 +100,7 @@ namespace osu.Game.Beatmaps.Formats if (beatmap.BeatmapInfo.CountdownOffset > 0) writer.WriteLine(FormattableString.Invariant($@"CountdownOffset: {beatmap.BeatmapInfo.CountdownOffset}")); if (onlineRulesetID == 3) - writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.BeatmapInfo.SpecialStyle ? '1' : '0')}")); + writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.SpecialStyle ? '1' : '0')}")); writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.BeatmapInfo.WidescreenStoryboard ? '1' : '0')}")); if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate) writer.WriteLine(@"SamplesMatchPlaybackRate: 1"); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 28d601620a..3ac48c09b4 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -72,6 +72,8 @@ namespace osu.Game.Beatmaps float StackLeniency { get; internal set; } + bool SpecialStyle { get; internal set; } + /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 616d6d0848..87a20eec0b 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -349,6 +349,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.StackLeniency = value; } + public bool SpecialStyle + { + get => baseBeatmap.SpecialStyle; + set => baseBeatmap.SpecialStyle = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index c02a22ae03..96216c6b1a 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -196,6 +196,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.StackLeniency = value; } + public bool SpecialStyle + { + get => PlayableBeatmap.SpecialStyle; + set => PlayableBeatmap.SpecialStyle = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; From a6b7600bf2b85f154624d8893fd9e658b098ab3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 13:15:41 +0200 Subject: [PATCH 004/326] Move `LetterboxInBreaks` out of `BeatmapInfo` --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 ++-- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ osu.Game/Screens/Edit/Setup/DesignSection.cs | 4 ++-- osu.Game/Screens/Play/Player.cs | 2 +- 13 files changed, 25 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index ad3721220a..103bafb2d8 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(164471, metadata.PreviewTime); Assert.AreEqual(0.7f, beatmap.StackLeniency); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); - Assert.IsFalse(beatmapInfo.LetterboxInBreaks); + Assert.IsFalse(beatmap.LetterboxInBreaks); Assert.IsFalse(beatmap.SpecialStyle); Assert.IsFalse(beatmapInfo.WidescreenStoryboard); Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate); @@ -953,7 +953,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decoded.AudioLeadIn, Is.EqualTo(0)); Assert.That(decoded.StackLeniency, Is.EqualTo(0.7f)); Assert.That(decoded.SpecialStyle, Is.False); - Assert.That(decoded.BeatmapInfo.LetterboxInBreaks, Is.False); + Assert.That(decoded.LetterboxInBreaks, Is.False); Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False); Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False); Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 18f4651e94..c1c996fd42 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(0.7f, beatmap.StackLeniency); Assert.AreEqual(false, beatmap.SpecialStyle); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); - Assert.AreEqual(false, beatmapInfo.LetterboxInBreaks); + Assert.AreEqual(false, beatmap.LetterboxInBreaks); Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard); Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); Assert.AreEqual(0, beatmapInfo.CountdownOffset); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index f06d884bd1..614bb4f42a 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -120,6 +120,8 @@ namespace osu.Game.Beatmaps public bool SpecialStyle { get; set; } + public bool LetterboxInBreaks { get; set; } + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index 3b3b68de0a..a56ce58532 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -70,6 +70,7 @@ namespace osu.Game.Beatmaps beatmap.AudioLeadIn = original.AudioLeadIn; beatmap.StackLeniency = original.StackLeniency; beatmap.SpecialStyle = original.SpecialStyle; + beatmap.LetterboxInBreaks = original.LetterboxInBreaks; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 435a282b52..c54eece9f8 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -414,7 +414,6 @@ namespace osu.Game.Beatmaps Hash = hash, DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, - LetterboxInBreaks = decodedInfo.LetterboxInBreaks, WidescreenStoryboard = decodedInfo.WidescreenStoryboard, EpilepsyWarning = decodedInfo.EpilepsyWarning, SamplesMatchPlaybackRate = decodedInfo.SamplesMatchPlaybackRate, diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 4a1fa9b0b4..fafca5e014 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -138,8 +138,6 @@ namespace osu.Game.Beatmaps #region Properties we may not want persisted (but also maybe no harm?) - public bool LetterboxInBreaks { get; set; } - public bool WidescreenStoryboard { get; set; } = true; public bool EpilepsyWarning { get; set; } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index ea34c7d924..b8469f27dd 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -285,7 +285,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"LetterboxInBreaks": - beatmap.BeatmapInfo.LetterboxInBreaks = Parsing.ParseInt(pair.Value) == 1; + beatmap.LetterboxInBreaks = Parsing.ParseInt(pair.Value) == 1; break; case @"SpecialStyle": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 805ce49ca3..5a3fd0a2f3 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -86,7 +86,7 @@ namespace osu.Game.Beatmaps.Formats $"SampleSet: {toLegacySampleBank(((beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePoints.FirstOrDefault() ?? SampleControlPoint.DEFAULT).SampleBank)}")); writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.StackLeniency}")); writer.WriteLine(FormattableString.Invariant($"Mode: {onlineRulesetID}")); - writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}")); + writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.LetterboxInBreaks ? '1' : '0')}")); // if (beatmap.BeatmapInfo.UseSkinSprites) // writer.WriteLine(@"UseSkinSprites: 1"); // if (b.AlwaysShowPlayfield) diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 3ac48c09b4..e5562b608e 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -74,6 +74,8 @@ namespace osu.Game.Beatmaps bool SpecialStyle { get; internal set; } + bool LetterboxInBreaks { get; internal set; } + /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 87a20eec0b..04dcb2a552 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -355,6 +355,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.SpecialStyle = value; } + public bool LetterboxInBreaks + { + get => baseBeatmap.LetterboxInBreaks; + set => baseBeatmap.LetterboxInBreaks = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 96216c6b1a..7470505712 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -202,6 +202,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.SpecialStyle = value; } + public bool LetterboxInBreaks + { + get => PlayableBeatmap.LetterboxInBreaks; + set => PlayableBeatmap.LetterboxInBreaks = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index b05a073146..c1fe7f405d 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Edit.Setup { Label = EditorSetupStrings.LetterboxDuringBreaks, Description = EditorSetupStrings.LetterboxDuringBreaksDescription, - Current = { Value = Beatmap.BeatmapInfo.LetterboxInBreaks } + Current = { Value = Beatmap.LetterboxInBreaks } }, samplesMatchPlaybackRate = new LabelledSwitchButton { @@ -123,7 +123,7 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.BeatmapInfo.WidescreenStoryboard = widescreenSupport.Current.Value; Beatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning.Current.Value; - Beatmap.BeatmapInfo.LetterboxInBreaks = letterboxDuringBreaks.Current.Value; + Beatmap.LetterboxInBreaks = letterboxDuringBreaks.Current.Value; Beatmap.BeatmapInfo.SamplesMatchPlaybackRate = samplesMatchPlaybackRate.Current.Value; Beatmap.SaveState(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 42ff1d74f3..eaadc236f7 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -430,7 +430,7 @@ namespace osu.Game.Screens.Play Children = new[] { DimmableStoryboard.OverlayLayerContainer.CreateProxy(), - BreakOverlay = new BreakOverlay(working.Beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) + BreakOverlay = new BreakOverlay(working.Beatmap.LetterboxInBreaks, ScoreProcessor) { Clock = DrawableRuleset.FrameStableClock, ProcessCustomClock = false, From 1ab86ebd249e31812ee07af2191957d6115a02c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 13:23:53 +0200 Subject: [PATCH 005/326] Move `WidescreenStoryboard` out of `BeatmapInfo` --- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 ++-- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 10 +++++----- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ osu.Game/Screens/Edit/Setup/DesignSection.cs | 4 ++-- osu.Game/Storyboards/Drawables/DrawableStoryboard.cs | 2 +- osu.Game/Storyboards/Storyboard.cs | 1 + 15 files changed, 31 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 103bafb2d8..cd36b6b986 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.IsFalse(beatmap.LetterboxInBreaks); Assert.IsFalse(beatmap.SpecialStyle); - Assert.IsFalse(beatmapInfo.WidescreenStoryboard); + Assert.IsFalse(beatmap.WidescreenStoryboard); Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate); Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); Assert.AreEqual(0, beatmapInfo.CountdownOffset); @@ -954,7 +954,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decoded.StackLeniency, Is.EqualTo(0.7f)); Assert.That(decoded.SpecialStyle, Is.False); Assert.That(decoded.LetterboxInBreaks, Is.False); - Assert.That(decoded.BeatmapInfo.WidescreenStoryboard, Is.False); + Assert.That(decoded.WidescreenStoryboard, Is.False); Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False); Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False); Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.Normal)); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index c1c996fd42..92715b6aa2 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(false, beatmap.SpecialStyle); Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.AreEqual(false, beatmap.LetterboxInBreaks); - Assert.AreEqual(false, beatmapInfo.WidescreenStoryboard); + Assert.AreEqual(false, beatmap.WidescreenStoryboard); Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); Assert.AreEqual(0, beatmapInfo.CountdownOffset); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs index 893b9f11f4..95aee43456 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneStoryboard.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("load storyboard with only video", () => { // LegacyStoryboardDecoder doesn't parse WidescreenStoryboard, so it is set manually - loadStoryboard("storyboard_only_video.osu", s => s.BeatmapInfo.WidescreenStoryboard = false); + loadStoryboard("storyboard_only_video.osu", s => s.Beatmap.WidescreenStoryboard = false); }); AddAssert("storyboard is correct width", () => Precision.AlmostEquals(storyboard?.Width ?? 0f, 480 * 16 / 9f)); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 614bb4f42a..d909e87417 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -122,6 +122,8 @@ namespace osu.Game.Beatmaps public bool LetterboxInBreaks { get; set; } + public bool WidescreenStoryboard { get; set; } = true; + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index a56ce58532..c097389c56 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -71,6 +71,7 @@ namespace osu.Game.Beatmaps beatmap.StackLeniency = original.StackLeniency; beatmap.SpecialStyle = original.SpecialStyle; beatmap.LetterboxInBreaks = original.LetterboxInBreaks; + beatmap.WidescreenStoryboard = original.WidescreenStoryboard; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index c54eece9f8..0bb2ddda7d 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -414,7 +414,6 @@ namespace osu.Game.Beatmaps Hash = hash, DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, - WidescreenStoryboard = decodedInfo.WidescreenStoryboard, EpilepsyWarning = decodedInfo.EpilepsyWarning, SamplesMatchPlaybackRate = decodedInfo.SamplesMatchPlaybackRate, DistanceSpacing = decodedInfo.DistanceSpacing, diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index fafca5e014..1bfa65a3fb 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -138,8 +138,6 @@ namespace osu.Game.Beatmaps #region Properties we may not want persisted (but also maybe no harm?) - public bool WidescreenStoryboard { get; set; } = true; - public bool EpilepsyWarning { get; set; } public bool SamplesMatchPlaybackRate { get; set; } = true; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index b8469f27dd..355114fd0b 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -81,7 +81,7 @@ namespace osu.Game.Beatmaps.Formats this.beatmap = beatmap; this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion; - applyLegacyDefaults(this.beatmap.BeatmapInfo); + applyLegacyDefaults(this.beatmap); base.ParseStreamInto(stream, beatmap); @@ -183,10 +183,10 @@ 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 static void applyLegacyDefaults(BeatmapInfo beatmapInfo) + private static void applyLegacyDefaults(Beatmap beatmap) { - beatmapInfo.WidescreenStoryboard = false; - beatmapInfo.SamplesMatchPlaybackRate = false; + beatmap.WidescreenStoryboard = false; + beatmap.BeatmapInfo.SamplesMatchPlaybackRate = false; } protected override void ParseLine(Beatmap beatmap, Section section, string line) @@ -293,7 +293,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"WidescreenStoryboard": - beatmap.BeatmapInfo.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1; + beatmap.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1; break; case @"EpilepsyWarning": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 5a3fd0a2f3..478b78fa29 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -101,7 +101,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($@"CountdownOffset: {beatmap.BeatmapInfo.CountdownOffset}")); if (onlineRulesetID == 3) writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.SpecialStyle ? '1' : '0')}")); - writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.BeatmapInfo.WidescreenStoryboard ? '1' : '0')}")); + writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.WidescreenStoryboard ? '1' : '0')}")); if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate) writer.WriteLine(@"SamplesMatchPlaybackRate: 1"); } diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index e5562b608e..3091e02054 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -76,6 +76,8 @@ namespace osu.Game.Beatmaps bool LetterboxInBreaks { get; internal set; } + bool WidescreenStoryboard { get; internal set; } + /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 04dcb2a552..bd051383de 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -361,6 +361,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.LetterboxInBreaks = value; } + public bool WidescreenStoryboard + { + get => baseBeatmap.WidescreenStoryboard; + set => baseBeatmap.WidescreenStoryboard = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 7470505712..a217e132d0 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -208,6 +208,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.LetterboxInBreaks = value; } + public bool WidescreenStoryboard + { + get => PlayableBeatmap.WidescreenStoryboard; + set => PlayableBeatmap.WidescreenStoryboard = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index c1fe7f405d..8c420b979f 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Edit.Setup { Label = EditorSetupStrings.WidescreenSupport, Description = EditorSetupStrings.WidescreenSupportDescription, - Current = { Value = Beatmap.BeatmapInfo.WidescreenStoryboard } + Current = { Value = Beatmap.WidescreenStoryboard } }, epilepsyWarning = new LabelledSwitchButton { @@ -121,7 +121,7 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.BeatmapInfo.Countdown = EnableCountdown.Current.Value ? CountdownSpeed.Current.Value : CountdownType.None; Beatmap.BeatmapInfo.CountdownOffset = int.TryParse(CountdownOffset.Current.Value, NumberStyles.None, CultureInfo.InvariantCulture, out int offset) ? offset : 0; - Beatmap.BeatmapInfo.WidescreenStoryboard = widescreenSupport.Current.Value; + Beatmap.WidescreenStoryboard = widescreenSupport.Current.Value; Beatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning.Current.Value; Beatmap.LetterboxInBreaks = letterboxDuringBreaks.Current.Value; Beatmap.BeatmapInfo.SamplesMatchPlaybackRate = samplesMatchPlaybackRate.Current.Value; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index fc5ef12fb8..858c257e85 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -78,7 +78,7 @@ namespace osu.Game.Storyboards.Drawables bool onlyHasVideoElements = Storyboard.Layers.SelectMany(l => l.Elements).All(e => e is StoryboardVideo); - Width = Height * (storyboard.BeatmapInfo.WidescreenStoryboard || onlyHasVideoElements ? 16 / 9f : 4 / 3f); + Width = Height * (storyboard.Beatmap.WidescreenStoryboard || onlyHasVideoElements ? 16 / 9f : 4 / 3f); Anchor = Anchor.Centre; Origin = Anchor.Centre; diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 8c43b99702..aca5fc34f4 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -16,6 +16,7 @@ namespace osu.Game.Storyboards public IEnumerable Layers => layers.Values; public BeatmapInfo BeatmapInfo = new BeatmapInfo(); + public IBeatmap Beatmap { get; set; } = new Beatmap(); /// /// Whether the storyboard should prefer textures from the current skin before using local storyboard textures. From f64a0624a5b1808a7c0ca91db99ecc7d679c4ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 13:28:41 +0200 Subject: [PATCH 006/326] Move `EpilepsyWarning` out of `BeatmapInfo` --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ osu.Game/Screens/Edit/Setup/DesignSection.cs | 4 ++-- osu.Game/Screens/Play/PlayerLoader.cs | 2 +- 13 files changed, 24 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index cd36b6b986..d3b027d253 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -955,7 +955,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decoded.SpecialStyle, Is.False); Assert.That(decoded.LetterboxInBreaks, Is.False); Assert.That(decoded.WidescreenStoryboard, Is.False); - Assert.That(decoded.BeatmapInfo.EpilepsyWarning, Is.False); + Assert.That(decoded.EpilepsyWarning, Is.False); Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False); Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.Normal)); Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0)); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index c17405c2ec..64211cddf3 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.Gameplay workingBeatmap.Beatmap.AudioLeadIn = 60000; // Set up data for testing disclaimer display. - workingBeatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning ?? false; + workingBeatmap.Beatmap.EpilepsyWarning = epilepsyWarning ?? false; workingBeatmap.BeatmapInfo.Status = onlineStatus ?? BeatmapOnlineStatus.Ranked; Beatmap.Value = workingBeatmap; diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index d909e87417..7516c8958c 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -124,6 +124,8 @@ namespace osu.Game.Beatmaps public bool WidescreenStoryboard { get; set; } = true; + public bool EpilepsyWarning { get; set; } + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index c097389c56..f9be44599f 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -72,6 +72,7 @@ namespace osu.Game.Beatmaps beatmap.SpecialStyle = original.SpecialStyle; beatmap.LetterboxInBreaks = original.LetterboxInBreaks; beatmap.WidescreenStoryboard = original.WidescreenStoryboard; + beatmap.EpilepsyWarning = original.EpilepsyWarning; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 0bb2ddda7d..232c8b7e24 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -414,7 +414,6 @@ namespace osu.Game.Beatmaps Hash = hash, DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, - EpilepsyWarning = decodedInfo.EpilepsyWarning, SamplesMatchPlaybackRate = decodedInfo.SamplesMatchPlaybackRate, DistanceSpacing = decodedInfo.DistanceSpacing, BeatDivisor = decodedInfo.BeatDivisor, diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 1bfa65a3fb..41a0260250 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -138,8 +138,6 @@ namespace osu.Game.Beatmaps #region Properties we may not want persisted (but also maybe no harm?) - public bool EpilepsyWarning { get; set; } - public bool SamplesMatchPlaybackRate { get; set; } = true; /// diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 355114fd0b..1152e5f30d 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -297,7 +297,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"EpilepsyWarning": - beatmap.BeatmapInfo.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1; + beatmap.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1; break; case @"SamplesMatchPlaybackRate": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 478b78fa29..d7078a71d9 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -95,7 +95,7 @@ namespace osu.Game.Beatmaps.Formats // writer.WriteLine(@"OverlayPosition: " + b.OverlayPosition); // if (!string.IsNullOrEmpty(b.SkinPreference)) // writer.WriteLine(@"SkinPreference:" + b.SkinPreference); - if (beatmap.BeatmapInfo.EpilepsyWarning) + if (beatmap.EpilepsyWarning) writer.WriteLine(@"EpilepsyWarning: 1"); if (beatmap.BeatmapInfo.CountdownOffset > 0) writer.WriteLine(FormattableString.Invariant($@"CountdownOffset: {beatmap.BeatmapInfo.CountdownOffset}")); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 3091e02054..3d563004d1 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -78,6 +78,8 @@ namespace osu.Game.Beatmaps bool WidescreenStoryboard { get; internal set; } + bool EpilepsyWarning { get; internal set; } + /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index bd051383de..7f9d2ae6ab 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -367,6 +367,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.WidescreenStoryboard = value; } + public bool EpilepsyWarning + { + get => baseBeatmap.EpilepsyWarning; + set => baseBeatmap.EpilepsyWarning = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index a217e132d0..063803ab42 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -214,6 +214,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.WidescreenStoryboard = value; } + public bool EpilepsyWarning + { + get => PlayableBeatmap.EpilepsyWarning; + set => PlayableBeatmap.EpilepsyWarning = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index 8c420b979f..5d729cf4f8 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -74,7 +74,7 @@ namespace osu.Game.Screens.Edit.Setup { Label = EditorSetupStrings.EpilepsyWarning, Description = EditorSetupStrings.EpilepsyWarningDescription, - Current = { Value = Beatmap.BeatmapInfo.EpilepsyWarning } + Current = { Value = Beatmap.EpilepsyWarning } }, letterboxDuringBreaks = new LabelledSwitchButton { @@ -122,7 +122,7 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.BeatmapInfo.CountdownOffset = int.TryParse(CountdownOffset.Current.Value, NumberStyles.None, CultureInfo.InvariantCulture, out int offset) ? offset : 0; Beatmap.WidescreenStoryboard = widescreenSupport.Current.Value; - Beatmap.BeatmapInfo.EpilepsyWarning = epilepsyWarning.Current.Value; + Beatmap.EpilepsyWarning = epilepsyWarning.Current.Value; Beatmap.LetterboxInBreaks = letterboxDuringBreaks.Current.Value; Beatmap.BeatmapInfo.SamplesMatchPlaybackRate = samplesMatchPlaybackRate.Current.Value; diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 51a0c94ff0..fc7e7fb58f 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -228,7 +228,7 @@ namespace osu.Game.Screens.Play sampleRestart = new SkinnableSound(new SampleInfo(@"Gameplay/restart", @"pause-retry-click")) }; - if (Beatmap.Value.BeatmapInfo.EpilepsyWarning) + if (Beatmap.Value.Beatmap.EpilepsyWarning) { disclaimers.Add(epilepsyWarning = new PlayerLoaderDisclaimer(PlayerLoaderStrings.EpilepsyWarningTitle, PlayerLoaderStrings.EpilepsyWarningContent)); } From c216283bf4ee5964e793869f2a63d941e58e6d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 13:32:23 +0200 Subject: [PATCH 007/326] Move `SamplesMatchPlaybackRate` out of `BeatmapInfo` --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 ++-- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 4 ++-- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ osu.Game/Screens/Edit/Setup/DesignSection.cs | 4 ++-- 11 files changed, 24 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index d3b027d253..51c1e51d3b 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -87,7 +87,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsFalse(beatmap.LetterboxInBreaks); Assert.IsFalse(beatmap.SpecialStyle); Assert.IsFalse(beatmap.WidescreenStoryboard); - Assert.IsFalse(beatmapInfo.SamplesMatchPlaybackRate); + Assert.IsFalse(beatmap.SamplesMatchPlaybackRate); Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); Assert.AreEqual(0, beatmapInfo.CountdownOffset); } @@ -956,7 +956,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decoded.LetterboxInBreaks, Is.False); Assert.That(decoded.WidescreenStoryboard, Is.False); Assert.That(decoded.EpilepsyWarning, Is.False); - Assert.That(decoded.BeatmapInfo.SamplesMatchPlaybackRate, Is.False); + Assert.That(decoded.SamplesMatchPlaybackRate, Is.False); Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.Normal)); Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0)); Assert.That(decoded.BeatmapInfo.Metadata.PreviewTime, Is.EqualTo(-1)); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 7516c8958c..76864a1d70 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -126,6 +126,8 @@ namespace osu.Game.Beatmaps public bool EpilepsyWarning { get; set; } + public bool SamplesMatchPlaybackRate { get; set; } = true; + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index f9be44599f..b86a445aa8 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -73,6 +73,7 @@ namespace osu.Game.Beatmaps beatmap.LetterboxInBreaks = original.LetterboxInBreaks; beatmap.WidescreenStoryboard = original.WidescreenStoryboard; beatmap.EpilepsyWarning = original.EpilepsyWarning; + beatmap.SamplesMatchPlaybackRate = original.SamplesMatchPlaybackRate; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 232c8b7e24..650b0bf510 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -414,7 +414,6 @@ namespace osu.Game.Beatmaps Hash = hash, DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, - SamplesMatchPlaybackRate = decodedInfo.SamplesMatchPlaybackRate, DistanceSpacing = decodedInfo.DistanceSpacing, BeatDivisor = decodedInfo.BeatDivisor, GridSize = decodedInfo.GridSize, diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 41a0260250..93abfa8d9b 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -138,8 +138,6 @@ namespace osu.Game.Beatmaps #region Properties we may not want persisted (but also maybe no harm?) - public bool SamplesMatchPlaybackRate { get; set; } = true; - /// /// The time at which this beatmap was last played by the local user. /// diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 1152e5f30d..0c8770782d 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -186,7 +186,7 @@ namespace osu.Game.Beatmaps.Formats private static void applyLegacyDefaults(Beatmap beatmap) { beatmap.WidescreenStoryboard = false; - beatmap.BeatmapInfo.SamplesMatchPlaybackRate = false; + beatmap.SamplesMatchPlaybackRate = false; } protected override void ParseLine(Beatmap beatmap, Section section, string line) @@ -301,7 +301,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"SamplesMatchPlaybackRate": - beatmap.BeatmapInfo.SamplesMatchPlaybackRate = Parsing.ParseInt(pair.Value) == 1; + beatmap.SamplesMatchPlaybackRate = Parsing.ParseInt(pair.Value) == 1; break; case @"Countdown": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index d7078a71d9..860ca68f6b 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -102,7 +102,7 @@ namespace osu.Game.Beatmaps.Formats if (onlineRulesetID == 3) writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.SpecialStyle ? '1' : '0')}")); writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.WidescreenStoryboard ? '1' : '0')}")); - if (beatmap.BeatmapInfo.SamplesMatchPlaybackRate) + if (beatmap.SamplesMatchPlaybackRate) writer.WriteLine(@"SamplesMatchPlaybackRate: 1"); } diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 3d563004d1..9900609f18 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -80,6 +80,8 @@ namespace osu.Game.Beatmaps bool EpilepsyWarning { get; internal set; } + bool SamplesMatchPlaybackRate { get; internal set; } + /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 7f9d2ae6ab..960eab6f2c 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -373,6 +373,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.EpilepsyWarning = value; } + public bool SamplesMatchPlaybackRate + { + get => baseBeatmap.SamplesMatchPlaybackRate; + set => baseBeatmap.SamplesMatchPlaybackRate = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 063803ab42..4303764fa7 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -220,6 +220,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.EpilepsyWarning = value; } + public bool SamplesMatchPlaybackRate + { + get => PlayableBeatmap.SamplesMatchPlaybackRate; + set => PlayableBeatmap.SamplesMatchPlaybackRate = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index 5d729cf4f8..4c4755064f 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Edit.Setup { Label = EditorSetupStrings.SamplesMatchPlaybackRate, Description = EditorSetupStrings.SamplesMatchPlaybackRateDescription, - Current = { Value = Beatmap.BeatmapInfo.SamplesMatchPlaybackRate } + Current = { Value = Beatmap.SamplesMatchPlaybackRate } } }; } @@ -124,7 +124,7 @@ namespace osu.Game.Screens.Edit.Setup Beatmap.WidescreenStoryboard = widescreenSupport.Current.Value; Beatmap.EpilepsyWarning = epilepsyWarning.Current.Value; Beatmap.LetterboxInBreaks = letterboxDuringBreaks.Current.Value; - Beatmap.BeatmapInfo.SamplesMatchPlaybackRate = samplesMatchPlaybackRate.Current.Value; + Beatmap.SamplesMatchPlaybackRate = samplesMatchPlaybackRate.Current.Value; Beatmap.SaveState(); } From 3634307d7ceadc23604014fba8aaa077682f2988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 13:36:27 +0200 Subject: [PATCH 008/326] Move `DistanceSpacing` out of `BeatmapInfo` --- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 14 +++++++------- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- .../Visual/Editing/TestSceneHitObjectComposer.cs | 4 ++-- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 14 -------------- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 13 +++++++++++++ .../Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ .../Rulesets/Edit/ComposerDistanceSnapProvider.cs | 4 ++-- osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs | 2 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ 14 files changed, 43 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 51c1e51d3b..71dcd38bcd 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new LineBufferedReader(resStream)) { - var beatmapInfo = decoder.Decode(stream).BeatmapInfo; + var beatmap = decoder.Decode(stream); int[] expectedBookmarks = { @@ -109,13 +109,13 @@ namespace osu.Game.Tests.Beatmaps.Formats 95901, 106450, 116999, 119637, 130186, 140735, 151285, 161834, 164471, 175020, 185570, 196119, 206669, 209306 }; - Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length); + Assert.AreEqual(expectedBookmarks.Length, beatmap.BeatmapInfo.Bookmarks.Length); for (int i = 0; i < expectedBookmarks.Length; i++) - Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]); - Assert.AreEqual(1.8, beatmapInfo.DistanceSpacing); - Assert.AreEqual(4, beatmapInfo.BeatDivisor); - Assert.AreEqual(4, beatmapInfo.GridSize); - Assert.AreEqual(2, beatmapInfo.TimelineZoom); + Assert.AreEqual(expectedBookmarks[i], beatmap.BeatmapInfo.Bookmarks[i]); + Assert.AreEqual(1.8, beatmap.DistanceSpacing); + Assert.AreEqual(4, beatmap.BeatmapInfo.BeatDivisor); + Assert.AreEqual(4, beatmap.BeatmapInfo.GridSize); + Assert.AreEqual(2, beatmap.BeatmapInfo.TimelineZoom); } } diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 92715b6aa2..4832ee26b7 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length); for (int i = 0; i < expectedBookmarks.Length; i++) Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]); - Assert.AreEqual(1.8, beatmapInfo.DistanceSpacing); + Assert.AreEqual(1.8, beatmap.DistanceSpacing); Assert.AreEqual(4, beatmapInfo.BeatDivisor); Assert.AreEqual(4, beatmapInfo.GridSize); Assert.AreEqual(2, beatmapInfo.TimelineZoom); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs index f392841ac7..d77c86729a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneHitObjectComposer.cs @@ -165,7 +165,7 @@ namespace osu.Game.Tests.Visual.Editing { double originalSpacing = 0; - AddStep("retrieve original spacing", () => originalSpacing = editorBeatmap.BeatmapInfo.DistanceSpacing); + AddStep("retrieve original spacing", () => originalSpacing = editorBeatmap.DistanceSpacing); AddStep("hold ctrl", () => InputManager.PressKey(Key.LControl)); AddStep("hold alt", () => InputManager.PressKey(Key.LAlt)); @@ -175,7 +175,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("release alt", () => InputManager.ReleaseKey(Key.LAlt)); AddStep("release ctrl", () => InputManager.ReleaseKey(Key.LControl)); - AddAssert("distance spacing increased by 0.5", () => editorBeatmap.BeatmapInfo.DistanceSpacing == originalSpacing + 0.5); + AddAssert("distance spacing increased by 0.5", () => editorBeatmap.DistanceSpacing == originalSpacing + 0.5); } public partial class EditorBeatmapContainer : PopoverContainer diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 76864a1d70..2f7c00af4a 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -128,6 +128,8 @@ namespace osu.Game.Beatmaps public bool SamplesMatchPlaybackRate { get; set; } = true; + public double DistanceSpacing { get; set; } = 1.0; + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index b86a445aa8..eda7f8025f 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -74,6 +74,7 @@ namespace osu.Game.Beatmaps beatmap.WidescreenStoryboard = original.WidescreenStoryboard; beatmap.EpilepsyWarning = original.EpilepsyWarning; beatmap.SamplesMatchPlaybackRate = original.SamplesMatchPlaybackRate; + beatmap.DistanceSpacing = original.DistanceSpacing; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 650b0bf510..a8964a365a 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -414,7 +414,6 @@ namespace osu.Game.Beatmaps Hash = hash, DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, - DistanceSpacing = decodedInfo.DistanceSpacing, BeatDivisor = decodedInfo.BeatDivisor, GridSize = decodedInfo.GridSize, TimelineZoom = decodedInfo.TimelineZoom, diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 93abfa8d9b..8328e3df95 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -6,14 +6,12 @@ using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using Newtonsoft.Json; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Collections; using osu.Game.Database; using osu.Game.Models; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets; -using osu.Game.Rulesets.Edit; using osu.Game.Scoring; using Realms; @@ -143,18 +141,6 @@ namespace osu.Game.Beatmaps /// public DateTimeOffset? LastPlayed { get; set; } - /// - /// The ratio of distance travelled per time unit. - /// Generally used to decouple the spacing between hit objects from the enforced "velocity" of the beatmap (see ). - /// - /// - /// The most common method of understanding is that at a default value of 1.0, the time-to-distance ratio will match the slider velocity of the beatmap - /// at the current point in time. Increasing this value will make hit objects more spaced apart when compared to the cursor movement required to track a slider. - /// - /// This is only a hint property, used by the editor in implementations. It does not directly affect the beatmap or gameplay. - /// - public double DistanceSpacing { get; set; } = 1.0; - public int BeatDivisor { get; set; } = 4; public int GridSize { get; set; } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 0c8770782d..92a26464ee 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -329,7 +329,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"DistanceSpacing": - beatmap.BeatmapInfo.DistanceSpacing = Math.Max(0, Parsing.ParseDouble(pair.Value)); + beatmap.DistanceSpacing = Math.Max(0, Parsing.ParseDouble(pair.Value)); break; case @"BeatDivisor": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 860ca68f6b..a07e8d2226 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -112,7 +112,7 @@ namespace osu.Game.Beatmaps.Formats if (beatmap.BeatmapInfo.Bookmarks.Length > 0) writer.WriteLine(FormattableString.Invariant($"Bookmarks: {string.Join(',', beatmap.BeatmapInfo.Bookmarks)}")); - writer.WriteLine(FormattableString.Invariant($"DistanceSpacing: {beatmap.BeatmapInfo.DistanceSpacing}")); + writer.WriteLine(FormattableString.Invariant($"DistanceSpacing: {beatmap.DistanceSpacing}")); writer.WriteLine(FormattableString.Invariant($"BeatDivisor: {beatmap.BeatmapInfo.BeatDivisor}")); writer.WriteLine(FormattableString.Invariant($"GridSize: {beatmap.BeatmapInfo.GridSize}")); writer.WriteLine(FormattableString.Invariant($"TimelineZoom: {beatmap.BeatmapInfo.TimelineZoom}")); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 9900609f18..b2c8b7604a 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -82,6 +83,18 @@ namespace osu.Game.Beatmaps bool SamplesMatchPlaybackRate { get; internal set; } + /// + /// The ratio of distance travelled per time unit. + /// Generally used to decouple the spacing between hit objects from the enforced "velocity" of the beatmap (see ). + /// + /// + /// The most common method of understanding is that at a default value of 1.0, the time-to-distance ratio will match the slider velocity of the beatmap + /// at the current point in time. Increasing this value will make hit objects more spaced apart when compared to the cursor movement required to track a slider. + /// + /// This is only a hint property, used by the editor in implementations. It does not directly affect the beatmap or gameplay. + /// + double DistanceSpacing { get; internal set; } + /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 960eab6f2c..7473882c15 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -379,6 +379,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.SamplesMatchPlaybackRate = value; } + public double DistanceSpacing + { + get => baseBeatmap.DistanceSpacing; + set => baseBeatmap.DistanceSpacing = value; + } + #endregion } } diff --git a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs index b9850a94a3..665e6ba074 100644 --- a/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/ComposerDistanceSnapProvider.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Edit } }); - DistanceSpacingMultiplier.Value = editorBeatmap.BeatmapInfo.DistanceSpacing; + DistanceSpacingMultiplier.Value = editorBeatmap.DistanceSpacing; DistanceSpacingMultiplier.BindValueChanged(multiplier => { distanceSpacingSlider.ContractedLabelText = $"D. S. ({multiplier.NewValue:0.##x})"; @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Edit if (multiplier.NewValue != multiplier.OldValue) onScreenDisplay?.Display(new DistanceSpacingToast(multiplier.NewValue.ToLocalisableString(@"0.##x"), multiplier)); - editorBeatmap.BeatmapInfo.DistanceSpacing = multiplier.NewValue; + editorBeatmap.DistanceSpacing = multiplier.NewValue; }, true); DistanceSpacingMultiplier.BindDisabledChanged(disabled => distanceSpacingSlider.Alpha = disabled ? 0 : 1, true); diff --git a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs index 380038eadf..c312642fbd 100644 --- a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Edit /// A multiplier which changes the ratio of distance travelled per time unit. /// Importantly, this is provided for manual usage, and not multiplied into any of the methods exposed by this interface. /// - /// + /// Bindable DistanceSpacingMultiplier { get; } /// diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 4303764fa7..aa63dfab8d 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -226,6 +226,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.SamplesMatchPlaybackRate = value; } + public double DistanceSpacing + { + get => PlayableBeatmap.DistanceSpacing; + set => PlayableBeatmap.DistanceSpacing = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; From 6685c5ab741441140e7e9ba4d1e67b29f3ce828a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 13:44:36 +0200 Subject: [PATCH 009/326] Move `GridSize` out of `BeatmapInfo` --- .../Editor/TestSceneOsuEditorGrids.cs | 2 +- osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs | 4 ++-- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 2 +- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ 13 files changed, 24 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs index 48aa74c5bf..cb9347b177 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuEditorGrids.cs @@ -185,6 +185,6 @@ 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); + && EditorBeatmap.GridSize == size); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs index 21cce553b1..3740c54752 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuGridToolboxGroup.cs @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Edit }, }; - Spacing.Value = editorBeatmap.BeatmapInfo.GridSize; + Spacing.Value = editorBeatmap.GridSize; } protected override void LoadComplete() @@ -137,7 +137,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; + editorBeatmap.GridSize = (int)spacing.NewValue; }, true); GridLinesRotation.BindValueChanged(rotation => diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 71dcd38bcd..a15485cdf1 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -114,7 +114,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(expectedBookmarks[i], beatmap.BeatmapInfo.Bookmarks[i]); Assert.AreEqual(1.8, beatmap.DistanceSpacing); Assert.AreEqual(4, beatmap.BeatmapInfo.BeatDivisor); - Assert.AreEqual(4, beatmap.BeatmapInfo.GridSize); + Assert.AreEqual(4, beatmap.GridSize); Assert.AreEqual(2, beatmap.BeatmapInfo.TimelineZoom); } } diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 4832ee26b7..95cba082a7 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -78,7 +78,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]); Assert.AreEqual(1.8, beatmap.DistanceSpacing); Assert.AreEqual(4, beatmapInfo.BeatDivisor); - Assert.AreEqual(4, beatmapInfo.GridSize); + Assert.AreEqual(4, beatmap.GridSize); Assert.AreEqual(2, beatmapInfo.TimelineZoom); } diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 2f7c00af4a..aacbe359b1 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -130,6 +130,8 @@ namespace osu.Game.Beatmaps public double DistanceSpacing { get; set; } = 1.0; + public int GridSize { get; set; } + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index eda7f8025f..a70d449fc5 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -75,6 +75,7 @@ namespace osu.Game.Beatmaps beatmap.EpilepsyWarning = original.EpilepsyWarning; beatmap.SamplesMatchPlaybackRate = original.SamplesMatchPlaybackRate; beatmap.DistanceSpacing = original.DistanceSpacing; + beatmap.GridSize = original.GridSize; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index a8964a365a..ff9bf4b477 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -415,7 +415,6 @@ namespace osu.Game.Beatmaps DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, BeatDivisor = decodedInfo.BeatDivisor, - GridSize = decodedInfo.GridSize, TimelineZoom = decodedInfo.TimelineZoom, MD5Hash = memoryStream.ComputeMD5Hash(), EndTimeObjectCount = decoded.HitObjects.Count(h => h is IHasDuration), diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 8328e3df95..6b192063e8 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -143,8 +143,6 @@ namespace osu.Game.Beatmaps public int BeatDivisor { get; set; } = 4; - public int GridSize { get; set; } - public double TimelineZoom { get; set; } = 1.0; /// diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 92a26464ee..80bfae3036 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -337,7 +337,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"GridSize": - beatmap.BeatmapInfo.GridSize = Parsing.ParseInt(pair.Value); + beatmap.GridSize = Parsing.ParseInt(pair.Value); break; case @"TimelineZoom": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index a07e8d2226..1bfba0962c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -114,7 +114,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"Bookmarks: {string.Join(',', beatmap.BeatmapInfo.Bookmarks)}")); writer.WriteLine(FormattableString.Invariant($"DistanceSpacing: {beatmap.DistanceSpacing}")); writer.WriteLine(FormattableString.Invariant($"BeatDivisor: {beatmap.BeatmapInfo.BeatDivisor}")); - writer.WriteLine(FormattableString.Invariant($"GridSize: {beatmap.BeatmapInfo.GridSize}")); + writer.WriteLine(FormattableString.Invariant($"GridSize: {beatmap.GridSize}")); writer.WriteLine(FormattableString.Invariant($"TimelineZoom: {beatmap.BeatmapInfo.TimelineZoom}")); } diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index b2c8b7604a..ecfc8ea398 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -95,6 +95,8 @@ namespace osu.Game.Beatmaps /// double DistanceSpacing { get; internal set; } + int GridSize { get; internal set; } + /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 7473882c15..d4a95558cc 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -385,6 +385,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.DistanceSpacing = value; } + public int GridSize + { + get => baseBeatmap.GridSize; + set => baseBeatmap.GridSize = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index aa63dfab8d..b1df60126d 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -232,6 +232,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.DistanceSpacing = value; } + public int GridSize + { + get => PlayableBeatmap.GridSize; + set => PlayableBeatmap.GridSize = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; From 7f2a6f6f5a143e44ca427060eee77c5acad84d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 13:54:31 +0200 Subject: [PATCH 010/326] Move `TimelineZoom` out of `BeatmapInfo` --- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 2 +- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 8 ++++---- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapImporter.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 2 -- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ .../Screens/Edit/Compose/Components/Timeline/Timeline.cs | 6 +++--- osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ 13 files changed, 28 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index a15485cdf1..af0e4a8b3c 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -115,7 +115,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(1.8, beatmap.DistanceSpacing); Assert.AreEqual(4, beatmap.BeatmapInfo.BeatDivisor); Assert.AreEqual(4, beatmap.GridSize); - Assert.AreEqual(2, beatmap.BeatmapInfo.TimelineZoom); + Assert.AreEqual(2, beatmap.TimelineZoom); } } diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 95cba082a7..1fed9633f7 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(1.8, beatmap.DistanceSpacing); Assert.AreEqual(4, beatmapInfo.BeatDivisor); Assert.AreEqual(4, beatmap.GridSize); - Assert.AreEqual(2, beatmapInfo.TimelineZoom); + Assert.AreEqual(2, beatmap.TimelineZoom); } [Test] diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index 64c48e74cf..429b458b9f 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("Set beat divisor", () => Editor.Dependencies.Get().Value = 16); AddStep("Set timeline zoom", () => { - originalTimelineZoom = EditorBeatmap.BeatmapInfo.TimelineZoom; + originalTimelineZoom = EditorBeatmap.TimelineZoom; var timeline = Editor.ChildrenOfType().Single(); InputManager.MoveMouseTo(timeline); @@ -81,19 +81,19 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Ensure timeline zoom changed", () => { - changedTimelineZoom = EditorBeatmap.BeatmapInfo.TimelineZoom; + changedTimelineZoom = EditorBeatmap.TimelineZoom; return !Precision.AlmostEquals(changedTimelineZoom, originalTimelineZoom); }); SaveEditor(); AddAssert("Beatmap has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16); - AddAssert("Beatmap has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom); + AddAssert("Beatmap has correct timeline zoom", () => EditorBeatmap.TimelineZoom == changedTimelineZoom); ReloadEditorToSameBeatmap(); AddAssert("Beatmap still has correct beat divisor", () => EditorBeatmap.BeatmapInfo.BeatDivisor == 16); - AddAssert("Beatmap still has correct timeline zoom", () => EditorBeatmap.BeatmapInfo.TimelineZoom == changedTimelineZoom); + AddAssert("Beatmap still has correct timeline zoom", () => EditorBeatmap.TimelineZoom == changedTimelineZoom); } [Test] diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index aacbe359b1..35bd935d66 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -132,6 +132,8 @@ namespace osu.Game.Beatmaps public int GridSize { get; set; } + public double TimelineZoom { get; set; } = 1.0; + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index a70d449fc5..140771a5d5 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -76,6 +76,7 @@ namespace osu.Game.Beatmaps beatmap.SamplesMatchPlaybackRate = original.SamplesMatchPlaybackRate; beatmap.DistanceSpacing = original.DistanceSpacing; beatmap.GridSize = original.GridSize; + beatmap.TimelineZoom = original.TimelineZoom; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index ff9bf4b477..e230f912ab 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -415,7 +415,6 @@ namespace osu.Game.Beatmaps DifficultyName = decodedInfo.DifficultyName, OnlineID = decodedInfo.OnlineID, BeatDivisor = decodedInfo.BeatDivisor, - TimelineZoom = decodedInfo.TimelineZoom, MD5Hash = memoryStream.ComputeMD5Hash(), EndTimeObjectCount = decoded.HitObjects.Count(h => h is IHasDuration), TotalObjectCount = decoded.HitObjects.Count diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 6b192063e8..39cd320ad7 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -143,8 +143,6 @@ namespace osu.Game.Beatmaps public int BeatDivisor { get; set; } = 4; - public double TimelineZoom { get; set; } = 1.0; - /// /// The time in milliseconds when last exiting the editor with this beatmap loaded. /// diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 80bfae3036..cb2b1820d7 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -341,7 +341,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"TimelineZoom": - beatmap.BeatmapInfo.TimelineZoom = Math.Max(0, Parsing.ParseDouble(pair.Value)); + beatmap.TimelineZoom = Math.Max(0, Parsing.ParseDouble(pair.Value)); break; } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 1bfba0962c..d705deb5df 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -115,7 +115,7 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"DistanceSpacing: {beatmap.DistanceSpacing}")); writer.WriteLine(FormattableString.Invariant($"BeatDivisor: {beatmap.BeatmapInfo.BeatDivisor}")); writer.WriteLine(FormattableString.Invariant($"GridSize: {beatmap.GridSize}")); - writer.WriteLine(FormattableString.Invariant($"TimelineZoom: {beatmap.BeatmapInfo.TimelineZoom}")); + writer.WriteLine(FormattableString.Invariant($"TimelineZoom: {beatmap.TimelineZoom}")); } private void handleMetadata(TextWriter writer) diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index ecfc8ea398..99a9d31807 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -97,6 +97,8 @@ namespace osu.Game.Beatmaps int GridSize { get; internal set; } + double TimelineZoom { get; internal set; } + /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index d4a95558cc..ebdde0fca6 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -391,6 +391,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.GridSize = value; } + public double TimelineZoom + { + get => baseBeatmap.TimelineZoom; + set => baseBeatmap.TimelineZoom = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index a2704e550c..7c1f2e3730 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Scheduler.AddOnce(applyVisualOffset, beatmap); }, true); - Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); + Zoom = (float)(defaultTimelineZoom * editorBeatmap.TimelineZoom); } private void applyVisualOffset(IBindable beatmap) @@ -215,7 +215,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline float minimumZoom = getZoomLevelForVisibleMilliseconds(10000); float maximumZoom = getZoomLevelForVisibleMilliseconds(500); - float initialZoom = (float)Math.Clamp(defaultTimelineZoom * (editorBeatmap.BeatmapInfo.TimelineZoom == 0 ? 1 : editorBeatmap.BeatmapInfo.TimelineZoom), minimumZoom, maximumZoom); + float initialZoom = (float)Math.Clamp(defaultTimelineZoom * (editorBeatmap.TimelineZoom == 0 ? 1 : editorBeatmap.TimelineZoom), minimumZoom, maximumZoom); SetupZoom(initialZoom, minimumZoom, maximumZoom); @@ -237,7 +237,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override void OnZoomChanged() { base.OnZoomChanged(); - editorBeatmap.BeatmapInfo.TimelineZoom = Zoom / defaultTimelineZoom; + editorBeatmap.TimelineZoom = Zoom / defaultTimelineZoom; } protected override void UpdateAfterChildren() diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index b1df60126d..0d07e16828 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -238,6 +238,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.GridSize = value; } + public double TimelineZoom + { + get => PlayableBeatmap.TimelineZoom; + set => PlayableBeatmap.TimelineZoom = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; From d373f752d667c35ae68ef47eced2dcb3c94920ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 13:58:00 +0200 Subject: [PATCH 011/326] Move `Countdown` out of `BeatmapInfo` --- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 ++-- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs | 8 ++++---- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapInfo.cs | 3 --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ osu.Game/Screens/Edit/Setup/DesignSection.cs | 6 +++--- 11 files changed, 28 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index af0e4a8b3c..9cea6ef507 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -88,7 +88,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsFalse(beatmap.SpecialStyle); Assert.IsFalse(beatmap.WidescreenStoryboard); Assert.IsFalse(beatmap.SamplesMatchPlaybackRate); - Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); + Assert.AreEqual(CountdownType.None, beatmap.Countdown); Assert.AreEqual(0, beatmapInfo.CountdownOffset); } } @@ -957,7 +957,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decoded.WidescreenStoryboard, Is.False); Assert.That(decoded.EpilepsyWarning, Is.False); Assert.That(decoded.SamplesMatchPlaybackRate, Is.False); - Assert.That(decoded.BeatmapInfo.Countdown, Is.EqualTo(CountdownType.Normal)); + Assert.That(decoded.Countdown, Is.EqualTo(CountdownType.Normal)); Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0)); Assert.That(decoded.BeatmapInfo.Metadata.PreviewTime, Is.EqualTo(-1)); Assert.That(decoded.BeatmapInfo.Ruleset.OnlineID, Is.EqualTo(0)); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 1fed9633f7..bc6628cea0 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsTrue(beatmapInfo.Ruleset.OnlineID == 0); Assert.AreEqual(false, beatmap.LetterboxInBreaks); Assert.AreEqual(false, beatmap.WidescreenStoryboard); - Assert.AreEqual(CountdownType.None, beatmapInfo.Countdown); + Assert.AreEqual(CountdownType.None, beatmap.Countdown); Assert.AreEqual(0, beatmapInfo.CountdownOffset); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs index 9a66e1676d..c91c22a145 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Editing { AddStep("turn countdown off", () => designSection.EnableCountdown.Current.Value = false); - AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.None); + AddAssert("beatmap has correct type", () => editorBeatmap.Countdown == CountdownType.None); AddUntilStep("other controls hidden", () => !designSection.CountdownSettings.IsPresent); } @@ -60,12 +60,12 @@ namespace osu.Game.Tests.Visual.Editing { AddStep("turn countdown on", () => designSection.EnableCountdown.Current.Value = true); - AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.Normal); + AddAssert("beatmap has correct type", () => editorBeatmap.Countdown == CountdownType.Normal); AddUntilStep("other controls shown", () => designSection.CountdownSettings.IsPresent); AddStep("change countdown speed", () => designSection.CountdownSpeed.Current.Value = CountdownType.DoubleSpeed); - AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.DoubleSpeed); + AddAssert("beatmap has correct type", () => editorBeatmap.Countdown == CountdownType.DoubleSpeed); AddUntilStep("other controls still shown", () => designSection.CountdownSettings.IsPresent); } @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.Editing { AddStep("turn countdown on", () => designSection.EnableCountdown.Current.Value = true); - AddAssert("beatmap has correct type", () => editorBeatmap.BeatmapInfo.Countdown == CountdownType.Normal); + AddAssert("beatmap has correct type", () => editorBeatmap.Countdown == CountdownType.Normal); checkOffsetAfter("1", 1); checkOffsetAfter(string.Empty, 0); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 35bd935d66..54fb1fd3b1 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -134,6 +134,8 @@ namespace osu.Game.Beatmaps public double TimelineZoom { get; set; } = 1.0; + public CountdownType Countdown { get; set; } = CountdownType.Normal; + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 39cd320ad7..0214ae4c3a 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -148,9 +148,6 @@ namespace osu.Game.Beatmaps /// public double? EditorTimestamp { get; set; } - [Ignored] - public CountdownType Countdown { get; set; } = CountdownType.Normal; - /// /// The number of beats to move the countdown backwards (compared to its default location). /// diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index cb2b1820d7..48959025c9 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -305,7 +305,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"Countdown": - beatmap.BeatmapInfo.Countdown = Enum.Parse(pair.Value); + beatmap.Countdown = Enum.Parse(pair.Value); break; case @"CountdownOffset": diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index d705deb5df..73399e93d0 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -81,7 +81,7 @@ namespace osu.Game.Beatmaps.Formats if (!string.IsNullOrEmpty(beatmap.Metadata.AudioFile)) writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}")); writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.AudioLeadIn}")); writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}")); - writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}")); + writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.Countdown}")); writer.WriteLine(FormattableString.Invariant( $"SampleSet: {toLegacySampleBank(((beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePoints.FirstOrDefault() ?? SampleControlPoint.DEFAULT).SampleBank)}")); writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.StackLeniency}")); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 99a9d31807..cf7bd29088 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -99,6 +99,8 @@ namespace osu.Game.Beatmaps double TimelineZoom { get; internal set; } + CountdownType Countdown { get; internal set; } + /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index ebdde0fca6..a444cc135b 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -397,6 +397,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.TimelineZoom = value; } + public CountdownType Countdown + { + get => baseBeatmap.Countdown; + set => baseBeatmap.Countdown = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 0d07e16828..a86d1fbaef 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -244,6 +244,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.TimelineZoom = value; } + public CountdownType Countdown + { + get => PlayableBeatmap.Countdown; + set => PlayableBeatmap.Countdown = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index 4c4755064f..b40f1bea72 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Edit.Setup EnableCountdown = new LabelledSwitchButton { Label = EditorSetupStrings.EnableCountdown, - Current = { Value = Beatmap.BeatmapInfo.Countdown != CountdownType.None }, + Current = { Value = Beatmap.Countdown != CountdownType.None }, Description = EditorSetupStrings.CountdownDescription }, CountdownSettings = new FillFlowContainer @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Edit.Setup CountdownSpeed = new LabelledEnumDropdown { Label = EditorSetupStrings.CountdownSpeed, - Current = { Value = Beatmap.BeatmapInfo.Countdown != CountdownType.None ? Beatmap.BeatmapInfo.Countdown : CountdownType.Normal }, + Current = { Value = Beatmap.Countdown != CountdownType.None ? Beatmap.Countdown : CountdownType.Normal }, Items = Enum.GetValues().Where(type => type != CountdownType.None) }, CountdownOffset = new LabelledNumberBox @@ -118,7 +118,7 @@ namespace osu.Game.Screens.Edit.Setup private void updateBeatmap() { - Beatmap.BeatmapInfo.Countdown = EnableCountdown.Current.Value ? CountdownSpeed.Current.Value : CountdownType.None; + Beatmap.Countdown = EnableCountdown.Current.Value ? CountdownSpeed.Current.Value : CountdownType.None; Beatmap.BeatmapInfo.CountdownOffset = int.TryParse(CountdownOffset.Current.Value, NumberStyles.None, CultureInfo.InvariantCulture, out int offset) ? offset : 0; Beatmap.WidescreenStoryboard = widescreenSupport.Current.Value; From dd50d6fa6e61d02e7e0a995a8e47569a5d7564e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 14:03:02 +0200 Subject: [PATCH 012/326] Move `CountdownOffset` out of `BeatmapInfo` --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 ++-- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- .../Database/RealmSubscriptionRegistrationTests.cs | 2 +- osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapInfo.cs | 5 ----- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 4 ++-- osu.Game/Beatmaps/IBeatmap.cs | 5 +++++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ osu.Game/Screens/Edit/Setup/DesignSection.cs | 6 +++--- 13 files changed, 31 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 9cea6ef507..ab0ec7ee39 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsFalse(beatmap.WidescreenStoryboard); Assert.IsFalse(beatmap.SamplesMatchPlaybackRate); Assert.AreEqual(CountdownType.None, beatmap.Countdown); - Assert.AreEqual(0, beatmapInfo.CountdownOffset); + Assert.AreEqual(0, beatmap.CountdownOffset); } } @@ -958,7 +958,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(decoded.EpilepsyWarning, Is.False); Assert.That(decoded.SamplesMatchPlaybackRate, Is.False); Assert.That(decoded.Countdown, Is.EqualTo(CountdownType.Normal)); - Assert.That(decoded.BeatmapInfo.CountdownOffset, Is.EqualTo(0)); + Assert.That(decoded.CountdownOffset, Is.EqualTo(0)); Assert.That(decoded.BeatmapInfo.Metadata.PreviewTime, Is.EqualTo(-1)); Assert.That(decoded.BeatmapInfo.Ruleset.OnlineID, Is.EqualTo(0)); }); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index bc6628cea0..e57a4fff62 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(false, beatmap.LetterboxInBreaks); Assert.AreEqual(false, beatmap.WidescreenStoryboard); Assert.AreEqual(CountdownType.None, beatmap.Countdown); - Assert.AreEqual(0, beatmapInfo.CountdownOffset); + Assert.AreEqual(0, beatmap.CountdownOffset); } [Test] diff --git a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs index 45842a952a..541f3b0417 100644 --- a/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs +++ b/osu.Game.Tests/Database/RealmSubscriptionRegistrationTests.cs @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Database Assert.That(lastChanges?.ModifiedIndices, Is.Empty); Assert.That(lastChanges?.NewModifiedIndices, Is.Empty); - realm.Write(r => r.All().First().Beatmaps.First().CountdownOffset = 5); + realm.Write(r => r.All().First().Beatmaps.First().EditorTimestamp = 5); realm.Run(r => r.Refresh()); Assert.That(collectionChanges, Is.EqualTo(1)); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs index c91c22a145..0011a4ceb4 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDesignSection.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("commit text", () => InputManager.Key(Key.Enter)); AddAssert($"displayed value is {expectedFinalValue}", () => designSection.CountdownOffset.Current.Value == expectedFinalValue.ToString(CultureInfo.InvariantCulture)); - AddAssert($"beatmap value is {expectedFinalValue}", () => editorBeatmap.BeatmapInfo.CountdownOffset == expectedFinalValue); + AddAssert($"beatmap value is {expectedFinalValue}", () => editorBeatmap.CountdownOffset == expectedFinalValue); } private partial class TestDesignSection : DesignSection diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 54fb1fd3b1..19a7ee3303 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -136,6 +136,8 @@ namespace osu.Game.Beatmaps public CountdownType Countdown { get; set; } = CountdownType.Normal; + public int CountdownOffset { get; set; } + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index 140771a5d5..8e917a179e 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -77,6 +77,7 @@ namespace osu.Game.Beatmaps beatmap.DistanceSpacing = original.DistanceSpacing; beatmap.GridSize = original.GridSize; beatmap.TimelineZoom = original.TimelineZoom; + beatmap.CountdownOffset = original.CountdownOffset; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 0214ae4c3a..3ed15f52fb 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -148,11 +148,6 @@ namespace osu.Game.Beatmaps /// public double? EditorTimestamp { get; set; } - /// - /// The number of beats to move the countdown backwards (compared to its default location). - /// - public int CountdownOffset { get; set; } - #endregion public bool Equals(BeatmapInfo? other) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 48959025c9..14de31a2a3 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -309,7 +309,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"CountdownOffset": - beatmap.BeatmapInfo.CountdownOffset = Parsing.ParseInt(pair.Value); + beatmap.CountdownOffset = Parsing.ParseInt(pair.Value); break; } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 73399e93d0..b924b7aea5 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -97,8 +97,8 @@ namespace osu.Game.Beatmaps.Formats // writer.WriteLine(@"SkinPreference:" + b.SkinPreference); if (beatmap.EpilepsyWarning) writer.WriteLine(@"EpilepsyWarning: 1"); - if (beatmap.BeatmapInfo.CountdownOffset > 0) - writer.WriteLine(FormattableString.Invariant($@"CountdownOffset: {beatmap.BeatmapInfo.CountdownOffset}")); + if (beatmap.CountdownOffset > 0) + writer.WriteLine(FormattableString.Invariant($@"CountdownOffset: {beatmap.CountdownOffset}")); if (onlineRulesetID == 3) writer.WriteLine(FormattableString.Invariant($"SpecialStyle: {(beatmap.SpecialStyle ? '1' : '0')}")); writer.WriteLine(FormattableString.Invariant($"WidescreenStoryboard: {(beatmap.WidescreenStoryboard ? '1' : '0')}")); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index cf7bd29088..f08fdfaf6a 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -101,6 +101,11 @@ namespace osu.Game.Beatmaps CountdownType Countdown { get; internal set; } + /// + /// The number of beats to move the countdown backwards (compared to its default location). + /// + int CountdownOffset { get; internal set; } + /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index a444cc135b..6dd85cefe4 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -403,6 +403,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.Countdown = value; } + public int CountdownOffset + { + get => baseBeatmap.CountdownOffset; + set => baseBeatmap.CountdownOffset = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index a86d1fbaef..deb46c3d2e 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -250,6 +250,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.Countdown = value; } + public int CountdownOffset + { + get => PlayableBeatmap.CountdownOffset; + set => PlayableBeatmap.CountdownOffset = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index b40f1bea72..3ed0a78175 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Edit.Setup CountdownOffset = new LabelledNumberBox { Label = EditorSetupStrings.CountdownOffset, - Current = { Value = Beatmap.BeatmapInfo.CountdownOffset.ToString() }, + Current = { Value = Beatmap.CountdownOffset.ToString() }, Description = EditorSetupStrings.CountdownOffsetDescription, } } @@ -113,13 +113,13 @@ namespace osu.Game.Screens.Edit.Setup { updateBeatmap(); // update displayed text to ensure parsed value matches display (i.e. if empty string was provided). - CountdownOffset.Current.Value = Beatmap.BeatmapInfo.CountdownOffset.ToString(CultureInfo.InvariantCulture); + CountdownOffset.Current.Value = Beatmap.CountdownOffset.ToString(CultureInfo.InvariantCulture); } private void updateBeatmap() { Beatmap.Countdown = EnableCountdown.Current.Value ? CountdownSpeed.Current.Value : CountdownType.None; - Beatmap.BeatmapInfo.CountdownOffset = int.TryParse(CountdownOffset.Current.Value, NumberStyles.None, CultureInfo.InvariantCulture, out int offset) ? offset : 0; + Beatmap.CountdownOffset = int.TryParse(CountdownOffset.Current.Value, NumberStyles.None, CultureInfo.InvariantCulture, out int offset) ? offset : 0; Beatmap.WidescreenStoryboard = widescreenSupport.Current.Value; Beatmap.EpilepsyWarning = epilepsyWarning.Current.Value; From 9fbf2872e1a63547f44adafc790f8bc57a1c18a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 14:27:35 +0200 Subject: [PATCH 013/326] Remove no longer applicable region marking --- osu.Game/Beatmaps/BeatmapInfo.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3ed15f52fb..0a6719a96a 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -134,8 +134,6 @@ namespace osu.Game.Beatmaps Status = BeatmapOnlineStatus.None; } - #region Properties we may not want persisted (but also maybe no harm?) - /// /// The time at which this beatmap was last played by the local user. /// @@ -148,8 +146,6 @@ namespace osu.Game.Beatmaps /// public double? EditorTimestamp { get; set; } - #endregion - public bool Equals(BeatmapInfo? other) { if (ReferenceEquals(this, other)) return true; From c67e2dc301696654132b2d31f9f07abc4ede47c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 12 Jun 2024 14:51:20 +0200 Subject: [PATCH 014/326] Bump schema version --- osu.Game/Database/RealmAccess.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 1ece81be50..33b06f32b1 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -93,8 +93,9 @@ namespace osu.Game.Database /// 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. + /// 42 2024-06-12 Removed several properties from ScoreInfo which did not need to be persisted to realm. /// - private const int schema_version = 41; + private const int schema_version = 42; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. From 04527f3c9da63b5fc54d9afd3c7304b0634a8434 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 13 Jun 2024 09:30:00 +0200 Subject: [PATCH 015/326] Fix `TestBeatmap` not transferring newly migrated properties --- osu.Game/Tests/Beatmaps/TestBeatmap.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index de7bcfcfaa..863badbd4a 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -28,6 +28,17 @@ namespace osu.Game.Tests.Beatmaps ControlPointInfo = baseBeatmap.ControlPointInfo; Breaks = baseBeatmap.Breaks; UnhandledEventLines = baseBeatmap.UnhandledEventLines; + AudioLeadIn = baseBeatmap.AudioLeadIn; + StackLeniency = baseBeatmap.StackLeniency; + SpecialStyle = baseBeatmap.SpecialStyle; + LetterboxInBreaks = baseBeatmap.LetterboxInBreaks; + WidescreenStoryboard = baseBeatmap.WidescreenStoryboard; + EpilepsyWarning = baseBeatmap.EpilepsyWarning; + SamplesMatchPlaybackRate = baseBeatmap.SamplesMatchPlaybackRate; + DistanceSpacing = baseBeatmap.DistanceSpacing; + GridSize = baseBeatmap.GridSize; + TimelineZoom = baseBeatmap.TimelineZoom; + CountdownOffset = baseBeatmap.CountdownOffset; if (withHitObjects) HitObjects = baseBeatmap.HitObjects; From 1d4d8063622dbc552fd695bfb6561fbe14b63e57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 23 Jul 2024 12:19:45 +0200 Subject: [PATCH 016/326] Fix `WidescreenStoryboard` breakage after moving out of `BeatmapInfo` --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 4 ++-- .../Beatmaps/Formats/LegacyStoryboardDecoder.cs | 15 +++++++++++++++ osu.Game/Beatmaps/WorkingBeatmap.cs | 7 ++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index f873eaf535..bd81892d95 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -80,7 +80,7 @@ namespace osu.Game.Beatmaps.Formats this.beatmap = beatmap; this.beatmap.BeatmapInfo.BeatmapVersion = FormatVersion; - applyLegacyDefaults(this.beatmap); + ApplyLegacyDefaults(this.beatmap); base.ParseStreamInto(stream, beatmap); @@ -186,7 +186,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 static void applyLegacyDefaults(Beatmap beatmap) + internal static void ApplyLegacyDefaults(Beatmap beatmap) { beatmap.WidescreenStoryboard = false; beatmap.SamplesMatchPlaybackRate = false; diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 2f9a256d31..dc96c2ff82 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -37,6 +37,17 @@ namespace osu.Game.Beatmaps.Formats SetFallbackDecoder(() => new LegacyStoryboardDecoder()); } + protected override Storyboard CreateTemplateObject() + { + var sb = base.CreateTemplateObject(); + + var beatmap = new Beatmap(); + LegacyBeatmapDecoder.ApplyLegacyDefaults(beatmap); + sb.Beatmap = beatmap; + + return sb; + } + protected override void ParseStreamInto(LineBufferedReader stream, Storyboard storyboard) { this.storyboard = storyboard; @@ -72,6 +83,10 @@ namespace osu.Game.Beatmaps.Formats case "UseSkinSprites": storyboard.UseSkinSprites = pair.Value == "1"; break; + + case @"WidescreenStoryboard": + storyboard.Beatmap.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1; + break; } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 25159996f3..8b0d3dda6a 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -62,7 +62,12 @@ namespace osu.Game.Beatmaps #region Resource getters protected virtual Waveform GetWaveform() => new Waveform(null); - protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo }; + + protected virtual Storyboard GetStoryboard() => new Storyboard + { + BeatmapInfo = BeatmapInfo, + Beatmap = Beatmap, + }; protected abstract IBeatmap GetBeatmap(); public abstract Texture GetBackground(); From 29e7adcd3b195bc466cb7c434ce493c32e3e94c5 Mon Sep 17 00:00:00 2001 From: Sheppsu <49356627+Sheppsu@users.noreply.github.com> Date: Mon, 18 Nov 2024 03:57:50 -0500 Subject: [PATCH 017/326] add player settings to multi spectator screen --- .../Spectate/MultiSpectatorScreen.cs | 3 +- .../Spectate/MultiSpectatorSettings.cs | 81 +++++++++++++++++++ 2 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index cb00763e6b..841aaf7a45 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -126,7 +126,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate syncManager = new SpectatorSyncManager(masterClockContainer) { ReadyToStart = performInitialSeek, - } + }, + new MultiSpectatorSettings() }; for (int i = 0; i < Users.Count; i++) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs new file mode 100644 index 0000000000..7ed6b95110 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.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 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.Screens.Play.HUD; +using osuTK; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public partial class MultiSpectatorSettings : CompositeDrawable + { + private const double slide_duration = 200; + + private readonly PlayerSettingsOverlay playerSettingsOverlay; + private readonly Container slidingContainer; + + private readonly BindableBool opened = new BindableBool(); + + public MultiSpectatorSettings() + { + Origin = Anchor.TopLeft; + Anchor = Anchor.TopRight; + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + slidingContainer = new Container + { + Origin = Anchor.TopLeft, + Anchor = Anchor.TopLeft, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new IconButton + { + Icon = FontAwesome.Solid.Cog, + Origin = Anchor.TopLeft, + Anchor = Anchor.TopLeft, + Scale = new Vector2(1f), + Position = new Vector2(-30, 0), + Action = () => opened.Toggle() + }, + playerSettingsOverlay = new PlayerSettingsOverlay() + } + } + }; + + playerSettingsOverlay.Show(); + + opened.BindValueChanged(value => + { + if (value.NewValue) + open(); + else + close(); + }); + } + + private void open() + { + slidingContainer.MoveToOffset(new Vector2(-playerSettingsOverlay.Width, 0), slide_duration, Easing.Out).Then().OnComplete(c => + { + c.Origin = Anchor.TopRight; + c.Position = Vector2.Zero; + }); + } + + private void close() + { + slidingContainer.MoveToOffset(new Vector2(playerSettingsOverlay.Width, 0), slide_duration, Easing.Out).Then().OnComplete(c => + { + c.Origin = Anchor.TopLeft; + c.Position = Vector2.Zero; + }); + } + } +} From 7d4062d2adba94f38805f9b59ad047ce3822f260 Mon Sep 17 00:00:00 2001 From: Sheppsu <49356627+Sheppsu@users.noreply.github.com> Date: Mon, 18 Nov 2024 04:04:28 -0500 Subject: [PATCH 018/326] remove redundant Scale attribute --- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs index 7ed6b95110..64c798b092 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs @@ -40,7 +40,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Icon = FontAwesome.Solid.Cog, Origin = Anchor.TopLeft, Anchor = Anchor.TopLeft, - Scale = new Vector2(1f), Position = new Vector2(-30, 0), Action = () => opened.Toggle() }, From 5ac3bb73ee2569b62f5a928606b16bfc2b969a9e Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Mon, 18 Nov 2024 22:19:08 +0100 Subject: [PATCH 019/326] Adds an option to the catch editor to convert sliders to fruits --- .../JuiceStreamSelectionBlueprint.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index 3eb8d6c018..2f2ccae38b 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -10,10 +10,12 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit; using osuTK; using osuTK.Input; @@ -54,6 +56,12 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints [Resolved] private EditorBeatmap? editorBeatmap { get; set; } + [Resolved] + private IEditorChangeHandler? changeHandler { get; set; } + + [Resolved] + private BindableBeatDivisor? beatDivisor { get; set; } + public JuiceStreamSelectionBlueprint(JuiceStream hitObject) : base(hitObject) { @@ -119,6 +127,20 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints return base.OnMouseDown(e); } + protected override bool OnKeyDown(KeyDownEvent e) + { + if (!IsSelected) + return false; + + if (e.Key == Key.F && e.ControlPressed && e.ShiftPressed) + { + convertToFruits(); + return true; + } + + return false; + } + private void onDefaultsApplied(HitObject _) { computeObjectBounds(); @@ -168,6 +190,48 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints lastSliderPathVersion = HitObject.Path.Version.Value; } + private void convertToFruits() + { + if (editorBeatmap == null || beatDivisor == null) + return; + + var timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(HitObject.StartTime); + double streamSpacing = timingPoint.BeatLength / beatDivisor.Value; + + changeHandler?.BeginChange(); + + int i = 0; + double time = HitObject.StartTime; + + while (!Precision.DefinitelyBigger(time, HitObject.GetEndTime(), 1)) + { + // positionWithRepeats is a fractional number in the range of [0, HitObject.SpanCount()] + // and indicates how many fractional spans of a slider have passed up to time. + double positionWithRepeats = (time - HitObject.StartTime) / HitObject.Duration * HitObject.SpanCount(); + double pathPosition = positionWithRepeats - (int)positionWithRepeats; + // every second span is in the reverse direction - need to reverse the path position. + if (positionWithRepeats % 2 >= 1) + pathPosition = 1 - pathPosition; + + float fruitXValue = HitObject.OriginalX + HitObject.Path.PositionAt(pathPosition).X; + + editorBeatmap.Add(new Fruit + { + StartTime = time, + OriginalX = fruitXValue, + NewCombo = i == 0 && HitObject.NewCombo, + Samples = HitObject.Samples.Select(s => s.With()).ToList() + }); + + i += 1; + time = HitObject.StartTime + i * streamSpacing; + } + + editorBeatmap.Remove(HitObject); + + changeHandler?.EndChange(); + } + private IEnumerable getContextMenuItems() { yield return new OsuMenuItem("Add vertex", MenuItemType.Standard, () => @@ -177,6 +241,11 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints { Hotkey = new Hotkey(new KeyCombination(InputKey.Control, InputKey.MouseLeft)) }; + + yield return new OsuMenuItem("Convert to fruits", MenuItemType.Destructive, convertToFruits) + { + Hotkey = new Hotkey(new KeyCombination(InputKey.Control, InputKey.Shift, InputKey.F)) + }; } protected override void Dispose(bool isDisposing) From 00e3b20ff0bb3d6f001fc375034cef8406fab799 Mon Sep 17 00:00:00 2001 From: Darius Wattimena Date: Mon, 18 Nov 2024 22:30:15 +0100 Subject: [PATCH 020/326] Change text to stream instead of fruits as that is the term by catch mappers --- .../Edit/Blueprints/JuiceStreamSelectionBlueprint.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index 2f2ccae38b..a61478f5d5 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints if (e.Key == Key.F && e.ControlPressed && e.ShiftPressed) { - convertToFruits(); + convertToStream(); return true; } @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints lastSliderPathVersion = HitObject.Path.Version.Value; } - private void convertToFruits() + private void convertToStream() { if (editorBeatmap == null || beatDivisor == null) return; @@ -242,7 +242,7 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints Hotkey = new Hotkey(new KeyCombination(InputKey.Control, InputKey.MouseLeft)) }; - yield return new OsuMenuItem("Convert to fruits", MenuItemType.Destructive, convertToFruits) + yield return new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream) { Hotkey = new Hotkey(new KeyCombination(InputKey.Control, InputKey.Shift, InputKey.F)) }; From a679f0736ed05367d2ab471d364914c024bc9d90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 7 Nov 2024 14:58:06 +0100 Subject: [PATCH 021/326] Add ability to close playlists within grace period after creation --- .../API/Requests/ClosePlaylistRequest.cs | 27 ++++++++++++ .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 42 ++++++++++++++++--- .../OnlinePlay/Lounge/LoungeSubScreen.cs | 2 + .../Playlists/ClosePlaylistDialog.cs | 19 +++++++++ 4 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Online/API/Requests/ClosePlaylistRequest.cs create mode 100644 osu.Game/Screens/OnlinePlay/Playlists/ClosePlaylistDialog.cs diff --git a/osu.Game/Online/API/Requests/ClosePlaylistRequest.cs b/osu.Game/Online/API/Requests/ClosePlaylistRequest.cs new file mode 100644 index 0000000000..545266491e --- /dev/null +++ b/osu.Game/Online/API/Requests/ClosePlaylistRequest.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 System.Net.Http; +using osu.Framework.IO.Network; + +namespace osu.Game.Online.API.Requests +{ + public class ClosePlaylistRequest : APIRequest + { + private readonly long roomId; + + public ClosePlaylistRequest(long roomId) + { + this.roomId = roomId; + } + + protected override WebRequest CreateWebRequest() + { + var request = base.CreateWebRequest(); + request.Method = HttpMethod.Delete; + return request; + } + + protected override string Target => $@"rooms/{roomId}"; + } +} diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index d396d18b4f..76de649ef8 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.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.ComponentModel; using osu.Framework.Allocation; @@ -22,9 +23,13 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Input.Bindings; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.Rooms; +using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge.Components; +using osu.Game.Screens.OnlinePlay.Playlists; using osuTK; using osuTK.Graphics; using Container = osu.Framework.Graphics.Containers.Container; @@ -48,6 +53,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge [Resolved(canBeNull: true)] private LoungeSubScreen? lounge { get; set; } + [Resolved] + private IDialogOverlay? dialogOverlay { get; set; } + + [Resolved] + private IAPIProvider api { get; set; } = null!; + private readonly BindableWithCurrent selectedRoom = new BindableWithCurrent(); private Sample? sampleSelect; private Sample? sampleJoin; @@ -144,13 +155,34 @@ namespace osu.Game.Screens.OnlinePlay.Lounge public Popover GetPopover() => new PasswordEntryPopover(Room); - public MenuItem[] ContextMenuItems => new MenuItem[] + public MenuItem[] ContextMenuItems { - new OsuMenuItem("Create copy", MenuItemType.Standard, () => + get { - lounge?.OpenCopy(Room); - }) - }; + var items = new List + { + new OsuMenuItem("Create copy", MenuItemType.Standard, () => + { + lounge?.OpenCopy(Room); + }) + }; + + if (Room.Type == MatchType.Playlists && Room.Host?.Id == api.LocalUser.Value.Id && Room.StartDate?.AddMinutes(5) >= DateTimeOffset.Now) + { + items.Add(new OsuMenuItem("Close playlist", MenuItemType.Destructive, () => + { + dialogOverlay?.Push(new ClosePlaylistDialog(Room, () => + { + var request = new ClosePlaylistRequest(Room.RoomID!.Value); + request.Success += () => lounge?.RefreshRooms(); + api.Queue(request); + })); + })); + } + + return items.ToArray(); + } + } public bool OnPressed(KeyBindingPressEvent e) { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index 90288a1067..e3ec97e157 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -382,6 +382,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge this.Push(CreateRoomSubScreen(room)); } + public void RefreshRooms() => ListingPollingComponent.PollImmediately(); + private void updateLoadingLayer() { if (operationInProgress.Value || !ListingPollingComponent.InitialRoomsReceived.Value) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/ClosePlaylistDialog.cs b/osu.Game/Screens/OnlinePlay/Playlists/ClosePlaylistDialog.cs new file mode 100644 index 0000000000..08fed037d3 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Playlists/ClosePlaylistDialog.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 System; +using osu.Game.Online.Rooms; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.OnlinePlay.Playlists +{ + public partial class ClosePlaylistDialog : DeletionDialog + { + public ClosePlaylistDialog(Room room, Action closeAction) + { + HeaderText = "Are you sure you want to close the following playlist:"; + BodyText = room.Name; + DangerousAction = closeAction; + } + } +} From 69c2c988a1e1d4c7ff3cbbbd70a6a8836ecdab00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Nov 2024 09:54:56 +0100 Subject: [PATCH 022/326] Add extra check to ensure closed rooms can't be closed harder --- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 76de649ef8..7d36cec7ba 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -26,6 +26,7 @@ using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Rooms; +using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge.Components; @@ -167,7 +168,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge }) }; - if (Room.Type == MatchType.Playlists && Room.Host?.Id == api.LocalUser.Value.Id && Room.StartDate?.AddMinutes(5) >= DateTimeOffset.Now) + if (Room.Type == MatchType.Playlists && Room.Host?.Id == api.LocalUser.Value.Id && Room.StartDate?.AddMinutes(5) >= DateTimeOffset.Now && Room.Status is not RoomStatusEnded) { items.Add(new OsuMenuItem("Close playlist", MenuItemType.Destructive, () => { From cfc38df88940eb1ea0cbb0e87fea36bd1476a3ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Nov 2024 09:55:28 +0100 Subject: [PATCH 023/326] Add close button to playlists footer --- .../TestScenePlaylistsRoomSubScreen.cs | 32 +++++++ .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 12 +-- .../Playlists/PlaylistsRoomFooter.cs | 95 +++++++++++++++++-- .../Playlists/PlaylistsRoomSubScreen.cs | 15 ++- 4 files changed, 140 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index 4306fc1e6a..5f9e06fda5 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -2,8 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; @@ -14,6 +19,9 @@ namespace osu.Game.Tests.Visual.Playlists { public partial class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene { + [Resolved] + private IAPIProvider api { get; set; } = null!; + protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; [Test] @@ -37,5 +45,29 @@ namespace osu.Game.Tests.Visual.Playlists AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen()); AddAssert("status is still ended", () => roomScreen.Room.Status, Is.TypeOf); } + + [Test] + public void TestCloseButtonGoesAwayAfterGracePeriod() + { + Room room = null!; + PlaylistsRoomSubScreen roomScreen = null!; + + AddStep("create room", () => + { + RoomManager.AddRoom(room = new Room + { + Name = @"Test Room", + Host = api.LocalUser.Value, + Category = RoomCategory.Normal, + StartDate = DateTimeOffset.Now.AddMinutes(-5).AddSeconds(3), + EndDate = DateTimeOffset.Now.AddMinutes(30) + }); + }); + + AddStep("push screen", () => LoadScreen(roomScreen = new PlaylistsRoomSubScreen(room))); + AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen()); + AddAssert("close button present", () => roomScreen.ChildrenOfType().Any()); + AddUntilStep("wait for close button to disappear", () => !roomScreen.ChildrenOfType().Any()); + } } } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index ffea3878fa..4ef31c02c3 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -71,7 +71,7 @@ namespace osu.Game.Screens.OnlinePlay.Match protected RulesetStore Rulesets { get; private set; } = null!; [Resolved] - private IAPIProvider api { get; set; } = null!; + protected IAPIProvider API { get; private set; } = null!; [Resolved(canBeNull: true)] protected OnlinePlayScreen? ParentScreen { get; private set; } @@ -80,7 +80,7 @@ namespace osu.Game.Screens.OnlinePlay.Match private PreviewTrackManager previewTrackManager { get; set; } = null!; [Resolved(canBeNull: true)] - private IDialogOverlay? dialogOverlay { get; set; } + protected IDialogOverlay? DialogOverlay { get; private set; } [Cached] private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); @@ -282,7 +282,7 @@ namespace osu.Game.Screens.OnlinePlay.Match } } - protected virtual bool IsConnected => api.State.Value == APIState.Online; + protected virtual bool IsConnected => API.State.Value == APIState.Online; public override bool OnBackButton() { @@ -361,17 +361,17 @@ namespace osu.Game.Screens.OnlinePlay.Match bool hasUnsavedChanges = Room.RoomID == null && Room.Playlist.Count > 0; - if (dialogOverlay == null || !hasUnsavedChanges) + if (DialogOverlay == null || !hasUnsavedChanges) return true; // if the dialog is already displayed, block exiting until the user explicitly makes a decision. - if (dialogOverlay.CurrentDialog is ConfirmDiscardChangesDialog discardChangesDialog) + if (DialogOverlay.CurrentDialog is ConfirmDiscardChangesDialog discardChangesDialog) { discardChangesDialog.Flash(); return false; } - dialogOverlay.Push(new ConfirmDiscardChangesDialog(() => + DialogOverlay.Push(new ConfirmDiscardChangesDialog(() => { ExitConfirmed = true; settingsOverlay.Hide(); diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs index 0d837423a6..7838bd2fc8 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs @@ -2,9 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.ComponentModel; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Online.Rooms; +using osu.Game.Online.Rooms.RoomStatuses; using osuTK; namespace osu.Game.Screens.OnlinePlay.Playlists @@ -12,22 +17,98 @@ namespace osu.Game.Screens.OnlinePlay.Playlists public partial class PlaylistsRoomFooter : CompositeDrawable { public Action? OnStart; + public Action? OnClose; + + private readonly Room room; + private DangerousRoundedButton closeButton = null!; + + [Resolved] + private IAPIProvider api { get; set; } = null!; public PlaylistsRoomFooter(Room room) + { + this.room = room; + } + + [BackgroundDependencyLoader] + private void load() { RelativeSizeAxes = Axes.Both; - InternalChildren = new[] + InternalChild = new FillFlowContainer { - new PlaylistsReadyButton(room) + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10), + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Size = new Vector2(600, 1), - Action = () => OnStart?.Invoke() + new PlaylistsReadyButton(room) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Size = new Vector2(600, 1), + Action = () => OnStart?.Invoke() + }, + closeButton = new DangerousRoundedButton + { + Text = "Close", + Action = () => OnClose?.Invoke(), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 1), + Alpha = 0, + RelativeSizeAxes = Axes.Y, + } } }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + room.PropertyChanged += onRoomChanged; + updateState(); + } + + private void hideCloseButton() + { + closeButton?.ResizeWidthTo(0, 100, Easing.OutQuint) + .Then().FadeOut().Expire(); + } + + private void onRoomChanged(object? sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(Room.Status) || e.PropertyName == nameof(Room.Host) || e.PropertyName == nameof(Room.StartDate)) + updateState(); + } + + private void updateState() + { + TimeSpan? deletionGracePeriodRemaining = room.StartDate?.AddMinutes(5) - DateTimeOffset.Now; + + if (room.Host?.Id == api.LocalUser.Value.Id) + { + if (deletionGracePeriodRemaining > TimeSpan.Zero && room.Status is not RoomStatusEnded) + { + closeButton.FadeIn(); + using (BeginDelayedSequence(deletionGracePeriodRemaining.Value.TotalMilliseconds)) + hideCloseButton(); + } + else if (closeButton.Alpha > 0) + hideCloseButton(); + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + room.PropertyChanged -= onRoomChanged; + } } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 44d1841fb8..bac99123d5 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -12,7 +12,9 @@ using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Graphics.Cursor; using osu.Game.Input; +using osu.Game.Online.API.Requests; using osu.Game.Online.Rooms; +using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Match.Components; @@ -255,7 +257,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected override Drawable CreateFooter() => new PlaylistsRoomFooter(Room) { - OnStart = StartPlay + OnStart = StartPlay, + OnClose = closePlaylist, }; protected override RoomSettingsOverlay CreateRoomSettingsOverlay(Room room) => new PlaylistsRoomSettingsOverlay(room) @@ -273,6 +276,16 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Logger.Log($"Polling adjusted (selection: {selectionPollingComponent.TimeBetweenPolls.Value})"); } + private void closePlaylist() + { + DialogOverlay?.Push(new ClosePlaylistDialog(Room, () => + { + var request = new ClosePlaylistRequest(Room.RoomID!.Value); + request.Success += () => Room.Status = new RoomStatusEnded(); + API.Queue(request); + })); + } + protected override Screen CreateGameplayScreen(PlaylistItem selectedItem) { return new PlayerLoader(() => new PlaylistsPlayer(Room, selectedItem) From 8b68859d9d2ef11ee48a425a818a7f4f390b13c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Nov 2024 09:55:40 +0100 Subject: [PATCH 024/326] Fix `Room.CopyFrom()` skipping a field Was making the close button not display when creating a room anew. --- osu.Game/Online/Rooms/Room.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 486f70c0ed..094fe4ce56 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -379,6 +379,7 @@ namespace osu.Game.Online.Rooms Type = other.Type; MaxParticipants = other.MaxParticipants; ParticipantCount = other.ParticipantCount; + StartDate = other.StartDate; EndDate = other.EndDate; UserScore = other.UserScore; QueueMode = other.QueueMode; From ead7e99c591aa037110635a304d9368a8ea2435a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Nov 2024 11:06:36 +0100 Subject: [PATCH 025/326] Fix incorrect 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 698acc9822..a520040ad1 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -94,7 +94,7 @@ namespace osu.Game.Database /// 41 2024-04-17 Add ScoreInfo.TotalScoreWithoutMods for future mod multiplier rebalances. /// 42 2024-08-07 Update mania key bindings to reflect changes to ManiaAction /// 43 2024-10-14 Reset keybind for toggling FPS display to avoid conflict with "convert to stream" in the editor, if not already changed by user. - /// 44 2024-11-22 Removed several properties from ScoreInfo which did not need to be persisted to realm. + /// 44 2024-11-22 Removed several properties from BeatmapInfo which did not need to be persisted to realm. /// private const int schema_version = 44; From c844d65a81da4606eee240ad58774d38953cedb4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2024 19:11:14 +0900 Subject: [PATCH 026/326] Use `TryGetValue` wherever possible Rider says so. --- .../Skinning/Legacy/ManiaLegacySkinTransformer.cs | 4 ++-- osu.Game/Overlays/Chat/ChannelList/ChannelList.cs | 7 +++---- osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs | 4 ++-- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs index cb42b2b62a..8f425edc44 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Legacy/ManiaLegacySkinTransformer.cs @@ -164,10 +164,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Legacy private Drawable getResult(HitResult result) { - if (!hit_result_mapping.ContainsKey(result)) + if (!hit_result_mapping.TryGetValue(result, out var value)) return null; - string filename = this.GetManiaSkinConfig(hit_result_mapping[result])?.Value + string filename = this.GetManiaSkinConfig(value)?.Value ?? default_hit_result_skin_filenames[result]; var animation = this.GetAnimation(filename, true, true, frameLength: 1000 / 20d); diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index fc0060d86a..3b657e7056 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -120,10 +120,9 @@ namespace osu.Game.Overlays.Chat.ChannelList public void RemoveChannel(Channel channel) { - if (!channelMap.ContainsKey(channel)) + if (!channelMap.TryGetValue(channel, out var item)) return; - ChannelListItem item = channelMap[channel]; FillFlowContainer flow = getFlowForChannel(channel); channelMap.Remove(channel); @@ -134,10 +133,10 @@ namespace osu.Game.Overlays.Chat.ChannelList public ChannelListItem GetItem(Channel channel) { - if (!channelMap.ContainsKey(channel)) + if (!channelMap.TryGetValue(channel, out var item)) throw new ArgumentOutOfRangeException(); - return channelMap[channel]; + return item; } public void ScrollChannelIntoView(Channel channel) => scroll.ScrollIntoView(GetItem(channel)); diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index c27e7f15ca..a311531088 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -78,12 +78,12 @@ namespace osu.Game.Tests.Visual.Spectator /// The spectator state to end play with. public void SendEndPlay(int userId, SpectatedUserState state = SpectatedUserState.Quit) { - if (!userBeatmapDictionary.ContainsKey(userId)) + if (!userBeatmapDictionary.TryGetValue(userId, out int value)) return; ((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState { - BeatmapID = userBeatmapDictionary[userId], + BeatmapID = value, RulesetID = 0, Mods = userModsDictionary[userId], State = state From 9930922769f092d5edd3968404bdd266ad8367fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Nov 2024 19:51:55 +0900 Subject: [PATCH 027/326] Fix chat channel listing not being ordered to expectations - Public channels (and announcements) are now alphabetically ordered. - Private message channels are now ordered by most recent activity. Closes https://github.com/ppy/osu/issues/30835. --- .../Overlays/Chat/ChannelList/ChannelList.cs | 66 ++++++++++++++----- 1 file changed, 51 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index 3b657e7056..a2ec385a7e 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -77,10 +77,10 @@ namespace osu.Game.Overlays.Chat.ChannelList RelativeSizeAxes = Axes.X, } }, - announceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper()), - publicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper()), + announceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper(), false), + publicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper(), false), selector = new ChannelListItem(ChannelListingChannel), - privateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper()), + privateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper(), true), }, }, }, @@ -111,9 +111,9 @@ namespace osu.Game.Overlays.Chat.ChannelList item.OnRequestSelect += chan => OnRequestSelect?.Invoke(chan); item.OnRequestLeave += chan => OnRequestLeave?.Invoke(chan); - FillFlowContainer flow = getFlowForChannel(channel); + ChannelGroup group = getGroupFromChannel(channel); channelMap.Add(channel, item); - flow.Add(item); + group.AddChannel(item); updateVisibility(); } @@ -123,10 +123,10 @@ namespace osu.Game.Overlays.Chat.ChannelList if (!channelMap.TryGetValue(channel, out var item)) return; - FillFlowContainer flow = getFlowForChannel(channel); + ChannelGroup group = getGroupFromChannel(channel); channelMap.Remove(channel); - flow.Remove(item, true); + group.RemoveChannel(item); updateVisibility(); } @@ -141,21 +141,21 @@ namespace osu.Game.Overlays.Chat.ChannelList public void ScrollChannelIntoView(Channel channel) => scroll.ScrollIntoView(GetItem(channel)); - private FillFlowContainer getFlowForChannel(Channel channel) + private ChannelGroup getGroupFromChannel(Channel channel) { switch (channel.Type) { case ChannelType.Public: - return publicChannelGroup.ItemFlow; + return publicChannelGroup; case ChannelType.PM: - return privateChannelGroup.ItemFlow; + return privateChannelGroup; case ChannelType.Announce: - return announceChannelGroup.ItemFlow; + return announceChannelGroup; default: - return publicChannelGroup.ItemFlow; + return publicChannelGroup; } } @@ -169,9 +169,9 @@ namespace osu.Game.Overlays.Chat.ChannelList private partial class ChannelGroup : FillFlowContainer { - public readonly FillFlowContainer ItemFlow; + public readonly ChannelListItemFlow ItemFlow; - public ChannelGroup(LocalisableString label) + public ChannelGroup(LocalisableString label, bool sortByRecent) { Direction = FillDirection.Vertical; RelativeSizeAxes = Axes.X; @@ -186,7 +186,7 @@ namespace osu.Game.Overlays.Chat.ChannelList Margin = new MarginPadding { Left = 18, Bottom = 5 }, Font = OsuFont.Torus.With(size: 12, weight: FontWeight.SemiBold), }, - ItemFlow = new FillFlowContainer + ItemFlow = new ChannelListItemFlow(sortByRecent) { Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.X, @@ -194,6 +194,42 @@ namespace osu.Game.Overlays.Chat.ChannelList }, }; } + + public partial class ChannelListItemFlow : FillFlowContainer + { + private readonly bool sortByRecent; + + public ChannelListItemFlow(bool sortByRecent) + { + this.sortByRecent = sortByRecent; + } + + public void Reflow() => InvalidateLayout(); + + public override IEnumerable FlowingChildren => sortByRecent + ? base.FlowingChildren.OfType().OrderByDescending(i => i.Channel.LastMessageId) + : base.FlowingChildren.OfType().OrderBy(i => i.Channel.Name); + } + + public void AddChannel(ChannelListItem item) + { + ItemFlow.Add(item); + + item.Channel.NewMessagesArrived += newMessagesArrived; + item.Channel.PendingMessageResolved += pendingMessageResolved; + + ItemFlow.Reflow(); + } + + public void RemoveChannel(ChannelListItem item) + { + item.Channel.NewMessagesArrived -= newMessagesArrived; + item.Channel.PendingMessageResolved -= pendingMessageResolved; + ItemFlow.Remove(item, true); + } + + private void pendingMessageResolved(LocalEchoMessage _, Message __) => ItemFlow.Reflow(); + private void newMessagesArrived(IEnumerable _) => ItemFlow.Reflow(); } private partial class ChannelSearchTextBox : BasicSearchTextBox From 82a63228de1984249136d9593405921127346d69 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Nov 2024 20:40:01 +0900 Subject: [PATCH 028/326] Improve handling of multiplayer room status --- .../Multiplayer/TestSceneDrawableRoom.cs | 16 ++++---- .../TestScenePlaylistsRoomSubScreen.cs | 41 ------------------- osu.Game/Graphics/OsuColour.cs | 21 ++++++++++ .../Online/Multiplayer/MultiplayerClient.cs | 10 ++--- osu.Game/Online/Rooms/Room.cs | 21 ++-------- osu.Game/Online/Rooms/RoomStatus.cs | 14 ++----- .../Rooms/RoomStatuses/RoomStatusEnded.cs | 14 ------- .../Rooms/RoomStatuses/RoomStatusOpen.cs | 14 ------- .../RoomStatuses/RoomStatusOpenPrivate.cs | 14 ------- .../Rooms/RoomStatuses/RoomStatusPlaying.cs | 14 ------- .../Components/StatusColouredContainer.cs | 15 +++++-- .../Lounge/Components/RoomStatusPill.cs | 24 +++++++++-- .../Multiplayer/MultiplayerRoomManager.cs | 3 +- 13 files changed, 74 insertions(+), 147 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs delete mode 100644 osu.Game/Online/Rooms/RoomStatuses/RoomStatusEnded.cs delete mode 100644 osu.Game/Online/Rooms/RoomStatuses/RoomStatusOpen.cs delete mode 100644 osu.Game/Online/Rooms/RoomStatuses/RoomStatusOpenPrivate.cs delete mode 100644 osu.Game/Online/Rooms/RoomStatuses/RoomStatusPlaying.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index e5938a796c..abfe613b65 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -14,7 +14,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Overlays; using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay.Lounge; @@ -76,7 +75,6 @@ namespace osu.Game.Tests.Visual.Multiplayer createLoungeRoom(new Room { Name = "Multiplayer room", - Status = new RoomStatusOpen(), EndDate = DateTimeOffset.Now.AddDays(1), Type = MatchType.HeadToHead, Playlist = [item1], @@ -85,7 +83,6 @@ namespace osu.Game.Tests.Visual.Multiplayer createLoungeRoom(new Room { Name = "Private room", - Status = new RoomStatusOpenPrivate(), Password = "*", EndDate = DateTimeOffset.Now.AddDays(1), Type = MatchType.HeadToHead, @@ -95,27 +92,29 @@ namespace osu.Game.Tests.Visual.Multiplayer createLoungeRoom(new Room { Name = "Playlist room with multiple beatmaps", - Status = new RoomStatusPlaying(), + Status = RoomStatus.Playing, EndDate = DateTimeOffset.Now.AddDays(1), Playlist = [item1, item2], CurrentPlaylistItem = item1 }), createLoungeRoom(new Room { - Name = "Finished room", - Status = new RoomStatusEnded(), + Name = "Closing soon", + EndDate = DateTimeOffset.Now.AddSeconds(5), + }), + createLoungeRoom(new Room + { + Name = "Closed room", EndDate = DateTimeOffset.Now, }), createLoungeRoom(new Room { Name = "Spotlight room", - Status = new RoomStatusOpen(), Category = RoomCategory.Spotlight, }), createLoungeRoom(new Room { Name = "Featured artist room", - Status = new RoomStatusOpen(), Category = RoomCategory.FeaturedArtist, }), } @@ -136,7 +135,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create room", () => Child = drawableRoom = createLoungeRoom(room = new Room { Name = "Room with password", - Status = new RoomStatusOpen(), Type = MatchType.HeadToHead, })); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs deleted file mode 100644 index 4306fc1e6a..0000000000 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.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. - -using System; -using NUnit.Framework; -using osu.Framework.Screens; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Rooms; -using osu.Game.Online.Rooms.RoomStatuses; -using osu.Game.Screens.OnlinePlay.Playlists; -using osu.Game.Tests.Visual.OnlinePlay; - -namespace osu.Game.Tests.Visual.Playlists -{ - public partial class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene - { - protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; - - [Test] - public void TestStatusUpdateOnEnter() - { - Room room = null!; - PlaylistsRoomSubScreen roomScreen = null!; - - AddStep("create room", () => - { - RoomManager.AddRoom(room = new Room - { - Name = @"Test Room", - Host = new APIUser { Username = @"Host" }, - Category = RoomCategory.Normal, - EndDate = DateTimeOffset.Now.AddMinutes(-1) - }); - }); - - AddStep("push screen", () => LoadScreen(roomScreen = new PlaylistsRoomSubScreen(room))); - AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen()); - AddAssert("status is still ended", () => roomScreen.Room.Status, Is.TypeOf); - } - } -} diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index c479d0cfe4..20e65323f8 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -195,6 +195,27 @@ namespace osu.Game.Graphics } } + /// + /// Retrieves the accent colour representing a 's current status. + /// + public Color4 ForRoomStatus(Room room) + { + if (DateTimeOffset.Now >= room.EndDate) + return YellowDarker; + + switch (room.Status) + { + case RoomStatus.Playing: + return Purple; + + default: + if (room.HasPassword) + return GreenDark; + + return GreenLight; + } + } + /// /// Retrieves colour for a . /// See https://www.figma.com/file/YHWhp9wZ089YXgB7pe6L1k/Tier-Colours diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 998a34931d..4a28124583 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -18,7 +18,6 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer.Countdown; using osu.Game.Online.Rooms; -using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -395,15 +394,17 @@ namespace osu.Game.Online.Multiplayer switch (state) { case MultiplayerRoomState.Open: - APIRoom.Status = APIRoom.HasPassword ? new RoomStatusOpenPrivate() : new RoomStatusOpen(); + APIRoom.Status = RoomStatus.Idle; break; + case MultiplayerRoomState.WaitingForLoad: case MultiplayerRoomState.Playing: - APIRoom.Status = new RoomStatusPlaying(); + APIRoom.Status = RoomStatus.Playing; break; case MultiplayerRoomState.Closed: - APIRoom.Status = new RoomStatusEnded(); + APIRoom.EndDate = DateTimeOffset.Now; + APIRoom.Status = RoomStatus.Idle; break; } @@ -821,7 +822,6 @@ namespace osu.Game.Online.Multiplayer Room.Settings = settings; APIRoom.Name = Room.Settings.Name; APIRoom.Password = Room.Settings.Password; - APIRoom.Status = string.IsNullOrEmpty(Room.Settings.Password) ? new RoomStatusOpen() : new RoomStatusOpenPrivate(); APIRoom.Type = Room.Settings.MatchType; APIRoom.QueueMode = Room.Settings.QueueMode; APIRoom.AutoStartDuration = Room.Settings.AutoStartDuration; diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index e1813c7e4e..6e073bdcd7 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -6,12 +6,10 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; -using System.Runtime.Serialization; using Newtonsoft.Json; using osu.Game.IO.Serialization.Converters; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; -using osu.Game.Online.Rooms.RoomStatuses; namespace osu.Game.Online.Rooms { @@ -248,7 +246,7 @@ namespace osu.Game.Online.Rooms } /// - /// The current room status. + /// The current status of the room. /// public RoomStatus Status { @@ -265,18 +263,6 @@ namespace osu.Game.Online.Rooms set => SetField(ref availability, value); } - [OnDeserialized] - private void onDeserialised(StreamingContext context) - { - // API doesn't populate status so let's do it here. - if (EndDate != null && DateTimeOffset.Now >= EndDate) - Status = new RoomStatusEnded(); - else if (HasPassword) - Status = new RoomStatusOpenPrivate(); - else - Status = new RoomStatusOpen(); - } - [JsonProperty("id")] private long? roomId; @@ -349,8 +335,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("channel_id")] private int channelId; - // Not serialised (see: GetRoomsRequest). - private RoomStatus status = new RoomStatusOpen(); + [JsonProperty("status")] + [JsonConverter(typeof(SnakeCaseStringEnumConverter))] + private RoomStatus status; // Not yet serialised (not implemented). private RoomAvailability availability; diff --git a/osu.Game/Online/Rooms/RoomStatus.cs b/osu.Game/Online/Rooms/RoomStatus.cs index 4b890b00b7..d048486f19 100644 --- a/osu.Game/Online/Rooms/RoomStatus.cs +++ b/osu.Game/Online/Rooms/RoomStatus.cs @@ -1,19 +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.Game.Graphics; -using osuTK.Graphics; - namespace osu.Game.Online.Rooms { - public abstract class RoomStatus + public enum RoomStatus { - public abstract string Message { get; } - public abstract Color4 GetAppropriateColour(OsuColour colours); - - public override int GetHashCode() => GetType().GetHashCode(); - public override bool Equals(object obj) => GetType() == obj?.GetType(); + Idle, + Playing, } } diff --git a/osu.Game/Online/Rooms/RoomStatuses/RoomStatusEnded.cs b/osu.Game/Online/Rooms/RoomStatuses/RoomStatusEnded.cs deleted file mode 100644 index 0fc27d26b8..0000000000 --- a/osu.Game/Online/Rooms/RoomStatuses/RoomStatusEnded.cs +++ /dev/null @@ -1,14 +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.Graphics; -using osuTK.Graphics; - -namespace osu.Game.Online.Rooms.RoomStatuses -{ - public class RoomStatusEnded : RoomStatus - { - public override string Message => "Ended"; - public override Color4 GetAppropriateColour(OsuColour colours) => colours.YellowDarker; - } -} diff --git a/osu.Game/Online/Rooms/RoomStatuses/RoomStatusOpen.cs b/osu.Game/Online/Rooms/RoomStatuses/RoomStatusOpen.cs deleted file mode 100644 index 5cc664cf36..0000000000 --- a/osu.Game/Online/Rooms/RoomStatuses/RoomStatusOpen.cs +++ /dev/null @@ -1,14 +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.Graphics; -using osuTK.Graphics; - -namespace osu.Game.Online.Rooms.RoomStatuses -{ - public class RoomStatusOpen : RoomStatus - { - public override string Message => "Open"; - public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight; - } -} diff --git a/osu.Game/Online/Rooms/RoomStatuses/RoomStatusOpenPrivate.cs b/osu.Game/Online/Rooms/RoomStatuses/RoomStatusOpenPrivate.cs deleted file mode 100644 index d71e706c76..0000000000 --- a/osu.Game/Online/Rooms/RoomStatuses/RoomStatusOpenPrivate.cs +++ /dev/null @@ -1,14 +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.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; - } -} diff --git a/osu.Game/Online/Rooms/RoomStatuses/RoomStatusPlaying.cs b/osu.Game/Online/Rooms/RoomStatuses/RoomStatusPlaying.cs deleted file mode 100644 index 4d0c93b8ab..0000000000 --- a/osu.Game/Online/Rooms/RoomStatuses/RoomStatusPlaying.cs +++ /dev/null @@ -1,14 +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.Graphics; -using osuTK.Graphics; - -namespace osu.Game.Online.Rooms.RoomStatuses -{ - public class RoomStatusPlaying : RoomStatus - { - public override string Message => "Playing"; - public override Color4 GetAppropriateColour(OsuColour colours) => colours.Purple; - } -} diff --git a/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs b/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs index 2b1233506f..a811ee3371 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs @@ -29,18 +29,27 @@ namespace osu.Game.Screens.OnlinePlay.Components base.LoadComplete(); room.PropertyChanged += onRoomPropertyChanged; + + Scheduler.AddDelayed(updateRoomStatus, 5000, true); updateRoomStatus(); } private void onRoomPropertyChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.Status)) - updateRoomStatus(); + switch (e.PropertyName) + { + case nameof(Room.Category): + case nameof(Room.Status): + case nameof(Room.EndDate): + case nameof(Room.HasPassword): + updateRoomStatus(); + break; + } } private void updateRoomStatus() { - this.FadeColour(colours.ForRoomCategory(room.Category) ?? room.Status.GetAppropriateColour(colours), transitionDuration); + this.FadeColour(colours.ForRoomCategory(room.Category) ?? colours.ForRoomStatus(room), transitionDuration); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs index b3dc617fd6..cc495d19d6 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.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.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -35,8 +36,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Pill.Background.Alpha = 1; room.PropertyChanged += onRoomPropertyChanged; - updateDisplay(); + Scheduler.AddDelayed(updateDisplay, 5000, true); + updateDisplay(); FinishTransforms(true); } @@ -46,6 +48,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { case nameof(Room.Status): case nameof(Room.EndDate): + case nameof(Room.HasPassword): updateDisplay(); break; } @@ -53,8 +56,23 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void updateDisplay() { - Pill.Background.FadeColour(room.Status.GetAppropriateColour(colours), 100); - TextFlow.Text = room.Status.Message; + Pill.Background.FadeColour(colours.ForRoomStatus(room), 100); + + if (DateTimeOffset.Now >= room.EndDate) + TextFlow.Text = "Ended"; + else + { + switch (room.Status) + { + case RoomStatus.Playing: + TextFlow.Text = "Playing"; + break; + + default: + TextFlow.Text = room.HasPassword ? "Open (Private)" : "Open"; + break; + } + } } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs index e16582a6e1..b6f4b0e8d9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs @@ -8,7 +8,6 @@ using osu.Framework.Extensions.ExceptionExtensions; using osu.Framework.Logging; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Screens.OnlinePlay.Components; namespace osu.Game.Screens.OnlinePlay.Multiplayer @@ -31,7 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer // this is done here as a pre-check to avoid clicking on already closed rooms in the lounge from triggering a server join. // should probably be done at a higher level, but due to the current structure of things this is the easiest place for now. - if (room.Status is RoomStatusEnded) + if (DateTimeOffset.Now >= room.EndDate) { onError?.Invoke("Cannot join an ended room."); return; From 5ebaab7e9aafcd2c220bbc737c1f5354a217dc11 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Nov 2024 21:04:57 +0900 Subject: [PATCH 029/326] Add localisation --- .../Localisation/RoomStatusPillStrings.cs | 34 +++++++++++++++++++ .../Lounge/Components/RoomStatusPill.cs | 7 ++-- 2 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Localisation/RoomStatusPillStrings.cs diff --git a/osu.Game/Localisation/RoomStatusPillStrings.cs b/osu.Game/Localisation/RoomStatusPillStrings.cs new file mode 100644 index 0000000000..5b4aa776ab --- /dev/null +++ b/osu.Game/Localisation/RoomStatusPillStrings.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 osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class RoomStatusPillStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.RoomStatusPill"; + + /// + /// "Ended" + /// + public static LocalisableString Ended => new TranslatableString(getKey(@"ended"), @"Ended"); + + /// + /// "Playing" + /// + public static LocalisableString Playing => new TranslatableString(getKey(@"playing"), @"Playing"); + + /// + /// "Open (Private)" + /// + public static LocalisableString OpenPrivate => new TranslatableString(getKey(@"open_private"), @"Open (Private)"); + + /// + /// "Open" + /// + public static LocalisableString Open => new TranslatableString(getKey(@"open"), @"Open"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} \ No newline at end of file diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs index cc495d19d6..5d2c4b28e6 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Online.Rooms; +using osu.Game.Localisation; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { @@ -59,17 +60,17 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components Pill.Background.FadeColour(colours.ForRoomStatus(room), 100); if (DateTimeOffset.Now >= room.EndDate) - TextFlow.Text = "Ended"; + TextFlow.Text = RoomStatusPillStrings.Ended; else { switch (room.Status) { case RoomStatus.Playing: - TextFlow.Text = "Playing"; + TextFlow.Text = RoomStatusPillStrings.Playing; break; default: - TextFlow.Text = room.HasPassword ? "Open (Private)" : "Open"; + TextFlow.Text = room.HasPassword ? RoomStatusPillStrings.OpenPrivate : RoomStatusPillStrings.Open; break; } } From 1b8db7cfd6c7fc094c6c1367bd3a2c267f7b4947 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Nov 2024 21:27:44 +0900 Subject: [PATCH 030/326] Fix test --- osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index abfe613b65..021c0abf1d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -121,9 +121,9 @@ namespace osu.Game.Tests.Visual.Multiplayer }; }); - AddUntilStep("wait for panel load", () => rooms.Count == 6); + AddUntilStep("wait for panel load", () => rooms.Count == 7); AddUntilStep("correct status text", () => rooms.ChildrenOfType().Count(s => s.Text.ToString().StartsWith("Currently playing", StringComparison.Ordinal)) == 2); - AddUntilStep("correct status text", () => rooms.ChildrenOfType().Count(s => s.Text.ToString().StartsWith("Ready to play", StringComparison.Ordinal)) == 4); + AddUntilStep("correct status text", () => rooms.ChildrenOfType().Count(s => s.Text.ToString().StartsWith("Ready to play", StringComparison.Ordinal)) == 5); } [Test] From 62837c7e53de8e9130c216b3b6c3158ef2504d78 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 9 Nov 2024 10:27:23 -0800 Subject: [PATCH 031/326] Fix discord "view beatmap" button being shown when editing and hide identifiable information is set --- 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 5a7a01df1b..ba61f4be34 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -167,7 +167,9 @@ namespace osu.Desktop 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) + if (getBeatmapID(activity.Value) is int beatmapId + && beatmapId > 0 + && !(activity.Value is UserActivity.EditingBeatmap && hideIdentifiableInformation)) { presence.Buttons = new[] { From 3713bb48b775c00da2fbbb6d86b98fc7b8ddafa5 Mon Sep 17 00:00:00 2001 From: Sheppsu <49356627+Sheppsu@users.noreply.github.com> Date: Sat, 23 Nov 2024 01:09:58 -0500 Subject: [PATCH 032/326] expand and contract settings from hover --- .../Spectate/MultiSpectatorSettings.cs | 78 +++++++------------ 1 file changed, 29 insertions(+), 49 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs index 64c798b092..dfb26d104a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs @@ -1,80 +1,60 @@ // 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.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play.HUD; -using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { - public partial class MultiSpectatorSettings : CompositeDrawable + public partial class MultiSpectatorSettings : ExpandingContainer { - private const double slide_duration = 200; - - private readonly PlayerSettingsOverlay playerSettingsOverlay; - private readonly Container slidingContainer; - - private readonly BindableBool opened = new BindableBool(); + public const float CONTRACTED_WIDTH = 30; + public const int EXPANDED_WIDTH = 300; public MultiSpectatorSettings() + : base(CONTRACTED_WIDTH, EXPANDED_WIDTH) { - Origin = Anchor.TopLeft; + Origin = Anchor.TopRight; Anchor = Anchor.TopRight; - AutoSizeAxes = Axes.Both; - InternalChildren = new Drawable[] + PlayerSettingsOverlay playerSettingsOverlay; + + InternalChild = new FillFlowContainer { - slidingContainer = new Container + Origin = Anchor.TopLeft, + Anchor = Anchor.TopLeft, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] { - Origin = Anchor.TopLeft, - Anchor = Anchor.TopLeft, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] + new IconButton { - new IconButton - { - Icon = FontAwesome.Solid.Cog, - Origin = Anchor.TopLeft, - Anchor = Anchor.TopLeft, - Position = new Vector2(-30, 0), - Action = () => opened.Toggle() - }, - playerSettingsOverlay = new PlayerSettingsOverlay() + Icon = FontAwesome.Solid.Cog, + Origin = Anchor.TopLeft, + Anchor = Anchor.TopLeft, + Action = () => Expanded.Toggle() + }, + playerSettingsOverlay = new PlayerSettingsOverlay + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft } } }; playerSettingsOverlay.Show(); - - opened.BindValueChanged(value => - { - if (value.NewValue) - open(); - else - close(); - }); } - private void open() + protected override void OnHoverLost(HoverLostEvent e) { - slidingContainer.MoveToOffset(new Vector2(-playerSettingsOverlay.Width, 0), slide_duration, Easing.Out).Then().OnComplete(c => - { - c.Origin = Anchor.TopRight; - c.Position = Vector2.Zero; - }); - } - - private void close() - { - slidingContainer.MoveToOffset(new Vector2(playerSettingsOverlay.Width, 0), slide_duration, Easing.Out).Then().OnComplete(c => - { - c.Origin = Anchor.TopLeft; - c.Position = Vector2.Zero; - }); + // Prevent unexpanding when hovering player settings + if (!Contains(e.ScreenSpaceMousePosition)) + base.OnHoverLost(e); } } } From eed02c2ab143b0fd1973906aa5a3d629f581eb49 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sat, 23 Nov 2024 15:45:29 -0500 Subject: [PATCH 033/326] Fix daily challenge results screen beginning score fetch from user highest --- .../DailyChallenge/DailyChallenge.cs | 2 +- .../DailyChallenge/DailyChallengePlayer.cs | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengePlayer.cs diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 0dc7e7930a..6cb8a87a2a 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -532,7 +532,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private void startPlay() { sampleStart?.Play(); - this.Push(new PlayerLoader(() => new PlaylistsPlayer(room, playlistItem) + this.Push(new PlayerLoader(() => new DailyChallengePlayer(room, playlistItem) { Exited = () => Scheduler.AddOnce(() => leaderboard.RefetchScores()) })); diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengePlayer.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengePlayer.cs new file mode 100644 index 0000000000..cfc0898e5a --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengePlayer.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 System.Diagnostics; +using osu.Game.Online.Rooms; +using osu.Game.Scoring; +using osu.Game.Screens.OnlinePlay.Playlists; +using osu.Game.Screens.Play; +using osu.Game.Screens.Ranking; + +namespace osu.Game.Screens.OnlinePlay.DailyChallenge +{ + public partial class DailyChallengePlayer : PlaylistsPlayer + { + public DailyChallengePlayer(Room room, PlaylistItem playlistItem, PlayerConfiguration? configuration = null) + : base(room, playlistItem, configuration) + { + } + + protected override ResultsScreen CreateResults(ScoreInfo score) + { + Debug.Assert(Room.RoomID != null); + + if (score.OnlineID >= 0) + { + return new PlaylistItemScoreResultsScreen(Room.RoomID.Value, PlaylistItem, score.OnlineID) + { + AllowRetry = true, + ShowUserStatistics = true, + }; + } + + // If the score has failed submission, fall back to displaying scores from user's highest. + return new PlaylistItemUserResultsScreen(score, Room.RoomID.Value, PlaylistItem) + { + AllowRetry = true, + ShowUserStatistics = true, + }; + } + } +} From 259ad8ae0f55f7802d252529b0cc80373c5504a5 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sat, 23 Nov 2024 23:28:22 -0500 Subject: [PATCH 034/326] Add failing test cases --- .../Editing/TestSceneEditorBeatmapCreation.cs | 131 +++++++++++++++--- 1 file changed, 113 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index db87987815..b15ee0cab8 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -104,28 +104,12 @@ namespace osu.Game.Tests.Visual.Editing { var setup = Editor.ChildrenOfType().First(); - string temp = TestResources.GetTestBeatmapForImport(); - - string extractedFolder = $"{temp}_extracted"; - Directory.CreateDirectory(extractedFolder); - - try + return setFile(TestResources.GetTestBeatmapForImport(), extractedFolder => { - using (var zip = ZipArchive.Open(temp)) - zip.WriteToDirectory(extractedFolder); - bool success = setup.ChildrenOfType().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"))); - - // ensure audio file is copied to beatmap as "audio.mp3" rather than original filename. Assert.That(Beatmap.Value.Metadata.AudioFile == "audio.mp3"); - return success; - } - finally - { - File.Delete(temp); - Directory.Delete(extractedFolder, true); - } + }); }); AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual); @@ -530,5 +514,116 @@ namespace osu.Game.Tests.Visual.Editing return set != null && set.PerformRead(s => s.Beatmaps.Count == 3 && s.Files.Count == 3); }); } + + [Test] + public void TestMultipleBackgroundFiles() + { + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddAssert("set background", () => setBackground(expected: "bg.jpg")); + + AddStep("save", () => Editor.Save()); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty"; + }); + + AddAssert("new difficulty uses same background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("set background", () => setBackground(expected: "bg (1).jpg")); + AddAssert("new difficulty uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); + + AddStep("save", () => Editor.Save()); + AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + AddAssert("old difficulty uses old background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); + AddAssert("old background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg.jpg")); + + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddStep("set background", () => setBackground(expected: "bg.jpg")); + AddAssert("other background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); + + bool setBackground(string expected) + { + var setup = Editor.ChildrenOfType().First(); + + return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder => + { + bool success = setup.ChildrenOfType().First().ChangeBackgroundImage(new FileInfo(Path.Combine(extractedFolder, "machinetop_background.jpg"))); + Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected)); + return success; + }); + } + } + + [Test] + public void TestMultipleAudioFiles() + { + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddAssert("set audio", () => setAudio(expected: "audio.mp3")); + + AddStep("save", () => Editor.Save()); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty"; + }); + + AddAssert("new difficulty uses same audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("set audio", () => setAudio(expected: "audio (1).mp3")); + AddAssert("new difficulty uses new audio", () => Beatmap.Value.Metadata.AudioFile == "audio (1).mp3"); + + AddStep("save", () => Editor.Save()); + AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + AddAssert("old difficulty uses old audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); + AddAssert("old audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio.mp3")); + + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddStep("set audio", () => setAudio(expected: "audio.mp3")); + AddAssert("other audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); + + bool setAudio(string expected) + { + var setup = Editor.ChildrenOfType().First(); + + return setFile(TestResources.GetTestBeatmapForImport(), extractedFolder => + { + bool success = setup.ChildrenOfType().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"))); + Assert.That(Beatmap.Value.Metadata.AudioFile, Is.EqualTo(expected)); + return success; + }); + } + } + + private bool setFile(string archivePath, Func func) + { + string temp = archivePath; + + string extractedFolder = $"{temp}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); + + return func(extractedFolder); + } + finally + { + File.Delete(temp); + Directory.Delete(extractedFolder, true); + } + } } } From 871c365fd8d0ff1525f07462bf2173e3848c7de9 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 00:32:27 -0500 Subject: [PATCH 035/326] Preserve existing beatmap background/audio files if used elsewhere --- .../Screens/Edit/Setup/ResourcesSection.cs | 58 ++++++++++++------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 845c21b598..8ab26a74a2 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -77,27 +77,35 @@ namespace osu.Game.Screens.Edit.Setup if (!source.Exists) return false; + var beatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; - var destination = new FileInfo($@"bg{source.Extension}"); + string[] filenames = set.Files.Select(f => f.Filename).Where(f => + f.StartsWith(@"bg", StringComparison.OrdinalIgnoreCase) && + f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - // remove the previous background for now. - // in the future we probably want to check if this is being used elsewhere (other difficulties?) - var oldFile = set.GetFile(working.Value.Metadata.BackgroundFile); + string currentFilename = working.Value.Metadata.BackgroundFile; + string? newFilename = null; + + var oldFile = set.GetFile(currentFilename); + + if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.BackgroundFile != currentFilename)) + { + beatmaps.DeleteFile(set, oldFile); + newFilename = currentFilename; + } + + newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"bg{source.Extension}"); using (var stream = source.OpenRead()) - { - if (oldFile != null) - beatmaps.DeleteFile(set, oldFile); + beatmaps.AddFile(set, stream, newFilename); - beatmaps.AddFile(set, stream, destination.Name); - } + working.Value.Metadata.BackgroundFile = newFilename; + updateAllDifficultiesButton.Enabled.Value = true; editorBeatmap.SaveState(); - working.Value.Metadata.BackgroundFile = destination.Name; headerBackground.UpdateBackground(); - editor?.ApplyToBackground(bg => bg.RefreshBackground()); return true; @@ -108,23 +116,31 @@ namespace osu.Game.Screens.Edit.Setup if (!source.Exists) return false; + var beatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; - var destination = new FileInfo($@"audio{source.Extension}"); + string[] filenames = set.Files.Select(f => f.Filename).Where(f => + f.StartsWith(@"audio", StringComparison.OrdinalIgnoreCase) && + f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - // remove the previous audio track for now. - // in the future we probably want to check if this is being used elsewhere (other difficulties?) - var oldFile = set.GetFile(working.Value.Metadata.AudioFile); + string currentFilename = working.Value.Metadata.AudioFile; + string? newFilename = null; - using (var stream = source.OpenRead()) + var oldFile = set.GetFile(currentFilename); + + if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.AudioFile != currentFilename)) { - if (oldFile != null) - beatmaps.DeleteFile(set, oldFile); - - beatmaps.AddFile(set, stream, destination.Name); + beatmaps.DeleteFile(set, oldFile); + newFilename = currentFilename; } - working.Value.Metadata.AudioFile = destination.Name; + newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"audio{source.Extension}"); + + using (var stream = source.OpenRead()) + beatmaps.AddFile(set, stream, newFilename); + + working.Value.Metadata.AudioFile = newFilename; + updateAllDifficultiesButton.Enabled.Value = true; editorBeatmap.SaveState(); music.ReloadCurrentTrack(); From 8e20dc7e9def53de9cc45706fec3e9d77a2c00d4 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 00:32:50 -0500 Subject: [PATCH 036/326] Add option to update all difficulties with new background/audio file --- .../Screens/Edit/Setup/ResourcesSection.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 8ab26a74a2..50c7072f84 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.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.IO; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -10,6 +12,8 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osu.Game.Localisation; +using osu.Game.Models; +using osu.Game.Utils; namespace osu.Game.Screens.Edit.Setup { @@ -36,6 +40,7 @@ namespace osu.Game.Screens.Edit.Setup private Editor? editor { get; set; } private SetupScreenHeaderBackground headerBackground = null!; + private RoundedButton updateAllDifficultiesButton = null!; [BackgroundDependencyLoader] private void load() @@ -58,6 +63,13 @@ namespace osu.Game.Screens.Edit.Setup Caption = EditorSetupStrings.AudioTrack, PlaceholderText = EditorSetupStrings.ClickToSelectTrack, }, + updateAllDifficultiesButton = new RoundedButton + { + RelativeSizeAxes = Axes.X, + Text = "Update all difficulties", + Action = updateAllDifficulties, + Enabled = { Value = false }, + } }; backgroundChooser.PreviewContainer.Add(headerBackground); @@ -148,6 +160,41 @@ namespace osu.Game.Screens.Edit.Setup return true; } + private void updateAllDifficulties() + { + var beatmap = working.Value.BeatmapInfo; + var set = working.Value.BeatmapSetInfo; + + string backgroundFile = working.Value.Metadata.BackgroundFile; + string audioFile = working.Value.Metadata.AudioFile; + + foreach (var otherBeatmap in set.Beatmaps.Where(b => !b.Equals(beatmap))) + { + var otherWorking = beatmaps.GetWorkingBeatmap(otherBeatmap); + + if (!string.Equals(otherBeatmap.Metadata.BackgroundFile, backgroundFile, StringComparison.OrdinalIgnoreCase)) + { + if (set.GetFile(otherBeatmap.Metadata.BackgroundFile) is RealmNamedFileUsage file) + beatmaps.DeleteFile(set, file); + + otherBeatmap.Metadata.BackgroundFile = backgroundFile; + } + + if (!string.Equals(otherBeatmap.Metadata.AudioFile, audioFile, StringComparison.OrdinalIgnoreCase)) + { + if (set.GetFile(otherBeatmap.Metadata.AudioFile) is RealmNamedFileUsage file) + beatmaps.DeleteFile(set, file); + + otherBeatmap.Metadata.AudioFile = audioFile; + } + + beatmaps.Save(otherBeatmap, otherWorking.Beatmap); + } + + editorBeatmap.SaveState(); + updateAllDifficultiesButton.Enabled.Value = false; + } + private void backgroundChanged(ValueChangedEvent file) { if (file.NewValue == null || !ChangeBackgroundImage(file.NewValue)) From e348b3a7aa929b31116751e665c2b1bf402a3df9 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 00:34:03 -0500 Subject: [PATCH 037/326] Only enable button if there are multiple difficulties --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 50c7072f84..5c904f6ce1 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Edit.Setup beatmaps.AddFile(set, stream, newFilename); working.Value.Metadata.BackgroundFile = newFilename; - updateAllDifficultiesButton.Enabled.Value = true; + updateAllDifficultiesButton.Enabled.Value = set.Beatmaps.Count > 1; editorBeatmap.SaveState(); @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Edit.Setup beatmaps.AddFile(set, stream, newFilename); working.Value.Metadata.AudioFile = newFilename; - updateAllDifficultiesButton.Enabled.Value = true; + updateAllDifficultiesButton.Enabled.Value = set.Beatmaps.Count > 1; editorBeatmap.SaveState(); music.ReloadCurrentTrack(); From dc210d59b5a4b4954cd87194c941857d20637d9b Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 00:45:11 -0500 Subject: [PATCH 038/326] Add test coverage for sync button --- .../Editing/TestSceneEditorBeatmapCreation.cs | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index b15ee0cab8..9fabed346b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -15,6 +15,8 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Collections; using osu.Game.Database; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Localisation; using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; @@ -605,6 +607,60 @@ namespace osu.Game.Tests.Visual.Editing } } + [Test] + public void TestUpdateBackgroundOnAllDifficulties() + { + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddAssert("button disabled", () => !getButton().Enabled.Value); + AddAssert("set background", () => setBackground(expected: "bg.jpg")); + + // there is only one diff so this should still be disabled. + AddAssert("button still disabled", () => !getButton().Enabled.Value); + + AddStep("save", () => Editor.Save()); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty"; + }); + + AddAssert("new difficulty uses same background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("button disabled", () => !getButton().Enabled.Value); + AddAssert("set background", () => setBackground(expected: "bg (1).jpg")); + AddAssert("new background added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); + AddAssert("new difficulty uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); + + AddAssert("button enabled", () => getButton().Enabled.Value); + AddStep("press button", () => getButton().TriggerClick()); + + AddAssert("new difficulty still uses new background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps[1].Metadata.BackgroundFile == "bg (1).jpg"); + AddAssert("old difficulty uses new background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps[0].Metadata.BackgroundFile == "bg (1).jpg"); + AddAssert("old background removed", () => Beatmap.Value.BeatmapSetInfo.Files.All(f => f.Filename != "bg.jpg")); + + AddStep("save", () => Editor.Save()); + AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + AddAssert("old difficulty still uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); + + bool setBackground(string expected) + { + var setup = Editor.ChildrenOfType().First(); + + return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder => + { + bool success = setup.ChildrenOfType().First().ChangeBackgroundImage(new FileInfo(Path.Combine(extractedFolder, "machinetop_background.jpg"))); + Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected)); + return success; + }); + } + + RoundedButton getButton() => Editor.ChildrenOfType().Single(b => b.Text == EditorSetupStrings.ResourcesUpdateAllDifficulties); + } + private bool setFile(string archivePath, Func func) { string temp = archivePath; From c8b13b726dc23feebffa628deab6fa0f2252813a Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 00:45:17 -0500 Subject: [PATCH 039/326] Add localisation support --- osu.Game/Localisation/EditorSetupStrings.cs | 5 +++++ osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Localisation/EditorSetupStrings.cs b/osu.Game/Localisation/EditorSetupStrings.cs index 350517734f..60e677757e 100644 --- a/osu.Game/Localisation/EditorSetupStrings.cs +++ b/osu.Game/Localisation/EditorSetupStrings.cs @@ -188,6 +188,11 @@ namespace osu.Game.Localisation /// public static LocalisableString AudioTrack => new TranslatableString(getKey(@"audio_track"), @"Audio Track"); + /// + /// "Update all difficulties" + /// + public static LocalisableString ResourcesUpdateAllDifficulties => new TranslatableString(getKey(@"resources_update_all_difficulties"), @"Update all difficulties"); + /// /// "Click to select a track" /// diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 5c904f6ce1..aa28e56218 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Edit.Setup updateAllDifficultiesButton = new RoundedButton { RelativeSizeAxes = Axes.X, - Text = "Update all difficulties", + Text = EditorSetupStrings.ResourcesUpdateAllDifficulties, Action = updateAllDifficulties, Enabled = { Value = false }, } From a872f749740b8f1bcf755add2cbe0d7298fa489e Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 01:02:34 -0500 Subject: [PATCH 040/326] Make sync button only affect changed resource type --- .../Screens/Edit/Setup/ResourcesSection.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index aa28e56218..8c9b9796ed 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -84,6 +84,9 @@ namespace osu.Game.Screens.Edit.Setup audioTrackChooser.Current.BindValueChanged(audioTrackChanged); } + private string? newBackgroundFile; + private string? newAudioFile; + public bool ChangeBackgroundImage(FileInfo source) { if (!source.Exists) @@ -112,7 +115,7 @@ namespace osu.Game.Screens.Edit.Setup using (var stream = source.OpenRead()) beatmaps.AddFile(set, stream, newFilename); - working.Value.Metadata.BackgroundFile = newFilename; + working.Value.Metadata.BackgroundFile = newBackgroundFile = newFilename; updateAllDifficultiesButton.Enabled.Value = set.Beatmaps.Count > 1; editorBeatmap.SaveState(); @@ -151,7 +154,7 @@ namespace osu.Game.Screens.Edit.Setup using (var stream = source.OpenRead()) beatmaps.AddFile(set, stream, newFilename); - working.Value.Metadata.AudioFile = newFilename; + working.Value.Metadata.AudioFile = newAudioFile = newFilename; updateAllDifficultiesButton.Enabled.Value = set.Beatmaps.Count > 1; editorBeatmap.SaveState(); @@ -165,27 +168,24 @@ namespace osu.Game.Screens.Edit.Setup var beatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; - string backgroundFile = working.Value.Metadata.BackgroundFile; - string audioFile = working.Value.Metadata.AudioFile; - foreach (var otherBeatmap in set.Beatmaps.Where(b => !b.Equals(beatmap))) { var otherWorking = beatmaps.GetWorkingBeatmap(otherBeatmap); - if (!string.Equals(otherBeatmap.Metadata.BackgroundFile, backgroundFile, StringComparison.OrdinalIgnoreCase)) + if (newBackgroundFile != null && !string.Equals(otherBeatmap.Metadata.BackgroundFile, newBackgroundFile, StringComparison.OrdinalIgnoreCase)) { if (set.GetFile(otherBeatmap.Metadata.BackgroundFile) is RealmNamedFileUsage file) beatmaps.DeleteFile(set, file); - otherBeatmap.Metadata.BackgroundFile = backgroundFile; + otherBeatmap.Metadata.BackgroundFile = newBackgroundFile; } - if (!string.Equals(otherBeatmap.Metadata.AudioFile, audioFile, StringComparison.OrdinalIgnoreCase)) + if (newAudioFile != null && !string.Equals(otherBeatmap.Metadata.AudioFile, newAudioFile, StringComparison.OrdinalIgnoreCase)) { if (set.GetFile(otherBeatmap.Metadata.AudioFile) is RealmNamedFileUsage file) beatmaps.DeleteFile(set, file); - otherBeatmap.Metadata.AudioFile = audioFile; + otherBeatmap.Metadata.AudioFile = newAudioFile; } beatmaps.Save(otherBeatmap, otherWorking.Beatmap); From 95a6226413a29f82b64040ebd081939a354d7a06 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 01:11:28 -0500 Subject: [PATCH 041/326] Only enable button if there are different filenames --- .../Screens/Edit/Setup/ResourcesSection.cs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 8c9b9796ed..863cf9f241 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Edit.Setup private Editor? editor { get; set; } private SetupScreenHeaderBackground headerBackground = null!; - private RoundedButton updateAllDifficultiesButton = null!; + private RoundedButton syncResourcesButton = null!; [BackgroundDependencyLoader] private void load() @@ -63,11 +63,11 @@ namespace osu.Game.Screens.Edit.Setup Caption = EditorSetupStrings.AudioTrack, PlaceholderText = EditorSetupStrings.ClickToSelectTrack, }, - updateAllDifficultiesButton = new RoundedButton + syncResourcesButton = new RoundedButton { RelativeSizeAxes = Axes.X, Text = EditorSetupStrings.ResourcesUpdateAllDifficulties, - Action = updateAllDifficulties, + Action = syncResources, Enabled = { Value = false }, } }; @@ -116,7 +116,7 @@ namespace osu.Game.Screens.Edit.Setup beatmaps.AddFile(set, stream, newFilename); working.Value.Metadata.BackgroundFile = newBackgroundFile = newFilename; - updateAllDifficultiesButton.Enabled.Value = set.Beatmaps.Count > 1; + syncResourcesButton.Enabled.Value = set.Beatmaps.Count > 1; editorBeatmap.SaveState(); @@ -155,7 +155,7 @@ namespace osu.Game.Screens.Edit.Setup beatmaps.AddFile(set, stream, newFilename); working.Value.Metadata.AudioFile = newAudioFile = newFilename; - updateAllDifficultiesButton.Enabled.Value = set.Beatmaps.Count > 1; + updateSyncResourcesButton(); editorBeatmap.SaveState(); music.ReloadCurrentTrack(); @@ -163,7 +163,16 @@ namespace osu.Game.Screens.Edit.Setup return true; } - private void updateAllDifficulties() + private void updateSyncResourcesButton() + { + var set = working.Value.BeatmapSetInfo; + + syncResourcesButton.Enabled.Value = + (newBackgroundFile != null && set.Beatmaps.DistinctBy(b => b.Metadata.BackgroundFile, StringComparer.OrdinalIgnoreCase).Count() > 1) || + (newAudioFile != null && set.Beatmaps.DistinctBy(b => b.Metadata.AudioFile, StringComparer.OrdinalIgnoreCase).Count() > 1); + } + + private void syncResources() { var beatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; @@ -192,7 +201,7 @@ namespace osu.Game.Screens.Edit.Setup } editorBeatmap.SaveState(); - updateAllDifficultiesButton.Enabled.Value = false; + syncResourcesButton.Enabled.Value = false; } private void backgroundChanged(ValueChangedEvent file) From 3480da22d2dcde78dd23cb20d8131784ac9d5522 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 01:13:39 -0500 Subject: [PATCH 042/326] Remove no-op `SaveState` call --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 863cf9f241..a52e42c7c0 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -200,7 +200,6 @@ namespace osu.Game.Screens.Edit.Setup beatmaps.Save(otherBeatmap, otherWorking.Beatmap); } - editorBeatmap.SaveState(); syncResourcesButton.Enabled.Value = false; } From 242079346661b3bb5734ef6db066627addea2681 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 05:39:42 -0500 Subject: [PATCH 043/326] Allow controlling back button visibility state from screens --- osu.Game/OsuGame.cs | 13 ++++++++----- osu.Game/Screens/IOsuScreen.cs | 12 ++++++++++++ osu.Game/Screens/OsuScreen.cs | 5 +++++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index dce24c6ee7..4d6bc4fd14 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1581,12 +1581,20 @@ namespace osu.Game if (current is IOsuScreen currentOsuScreen) { + if (currentOsuScreen.AllowBackButton) + BackButton.State.UnbindFrom(currentOsuScreen.BackButtonState); + OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode); API.Activity.UnbindFrom(currentOsuScreen.Activity); } if (newScreen is IOsuScreen newOsuScreen) { + if (newOsuScreen.AllowBackButton) + ((IBindable)BackButton.State).BindTo(newOsuScreen.BackButtonState); + else + BackButton.Hide(); + OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode); API.Activity.BindTo(newOsuScreen.Activity); @@ -1597,11 +1605,6 @@ namespace osu.Game else Toolbar.Show(); - if (newOsuScreen.AllowBackButton) - BackButton.Show(); - else - BackButton.Hide(); - if (newOsuScreen.ShowFooter) { BackButton.Hide(); diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index b80c1f87a4..7025460daa 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -3,8 +3,11 @@ using System.Collections.Generic; using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Screens.Footer; @@ -59,6 +62,15 @@ namespace osu.Game.Screens /// IBindable OverlayActivationMode { get; } + /// + /// Controls the visibility state of to better work with screen-specific transitions (i.e. quick restart in player). + /// The back button can still be triggered by the action even while hidden. + /// + /// + /// This is ignored when is set to false. + /// + IBindable BackButtonState { get; } + /// /// The current for this screen. /// diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 695a074907..2c5c889154 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -11,6 +11,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Overlays; @@ -56,6 +57,10 @@ namespace osu.Game.Screens IBindable IOsuScreen.OverlayActivationMode => OverlayActivationMode; + public readonly Bindable BackButtonState = new Bindable(Visibility.Visible); + + IBindable IOsuScreen.BackButtonState => BackButtonState; + public virtual bool CursorVisible => true; protected new OsuGameBase Game => base.Game as OsuGameBase; From ae9119eef044c348805e4b2488fb768fc342e621 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 05:40:06 -0500 Subject: [PATCH 044/326] Hide back button when quick-restarting unless load time takes long --- osu.Game/Screens/Play/PlayerLoader.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 3e36c630db..a6e171ba02 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -478,6 +478,8 @@ namespace osu.Game.Screens.Play if (quickRestart) { + BackButtonState.Value = Visibility.Hidden; + // A quick restart starts by triggering a fade to black AddInternal(quickRestartBlackLayer = new Box { @@ -496,6 +498,8 @@ namespace osu.Game.Screens.Play .Delay(quick_restart_initial_delay) .ScaleTo(1) .FadeInFromZero(500, Easing.OutQuint); + + this.Delay(quick_restart_initial_delay).Schedule(() => BackButtonState.Value = Visibility.Visible); } else { From 146838555999a69386aebb3d67f6af4bc860e1ba Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 24 Nov 2024 22:38:48 -0500 Subject: [PATCH 045/326] Reset new file states after syncing --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index a52e42c7c0..90603a6366 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -200,6 +200,8 @@ namespace osu.Game.Screens.Edit.Setup beatmaps.Save(otherBeatmap, otherWorking.Beatmap); } + newAudioFile = null; + newBackgroundFile = null; syncResourcesButton.Enabled.Value = false; } From f708466a9bc8593f18700426b8b40d23865b3899 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 Nov 2024 23:43:31 +0900 Subject: [PATCH 046/326] Add test coverage --- .../Visual/Online/TestSceneChatOverlay.cs | 55 +++++++++++++++++++ .../Overlays/Chat/ChannelList/ChannelList.cs | 32 ++++++----- 2 files changed, 72 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 3d6fe50d34..ab9ee1d8cc 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -457,6 +457,61 @@ namespace osu.Game.Tests.Visual.Online waitForChannel1Visible(); } + [Test] + public void TestPublicChannelsSortedByName() + { + // Intentionally join back to front. + AddStep("Show overlay with channel 2", () => + { + channelManager.CurrentChannel.Value = channelManager.JoinChannel(testChannel2); + chatOverlay.Show(); + }); + AddUntilStep("second channel is at top of list", () => getFirstVisiblePublicChannel().Channel == testChannel2); + + AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1)); + AddUntilStep("first channel is at top of list", () => getFirstVisiblePublicChannel().Channel == testChannel1); + + AddStep("message in channel 2", () => + { + testChannel2.AddNewMessages(new Message(1) { Content = "hi!", Sender = new APIUser { Username = "person" } }); + }); + AddUntilStep("first channel still at top of list", () => getFirstVisiblePublicChannel().Channel == testChannel1); + + ChannelListItem getFirstVisiblePublicChannel() => + chatOverlay.ChildrenOfType().Single().PublicChannelGroup.ItemFlow.FlowingChildren.OfType().First(item => item.Channel.Type == ChannelType.Public); + } + + [Test] + public void TestPrivateChannelsSortedByRecent() + { + Channel pmChannel1 = createPrivateChannel(); + Channel pmChannel2 = createPrivateChannel(); + + joinChannel(pmChannel1); + joinChannel(pmChannel2); + + AddStep("Show overlay", () => chatOverlay.Show()); + + AddUntilStep("first channel is at top of list", () => getFirstVisiblePMChannel().Channel == pmChannel1); + + AddStep("message in channel 2", () => + { + pmChannel2.AddNewMessages(new Message(1) { Content = "hi!", Sender = new APIUser { Username = "person" } }); + }); + + AddUntilStep("wait for first channel raised to top of list", () => getFirstVisiblePMChannel().Channel == pmChannel2); + + AddStep("message in channel 1", () => + { + pmChannel1.AddNewMessages(new Message(1) { Content = "hi!", Sender = new APIUser { Username = "person" } }); + }); + + AddUntilStep("wait for first channel raised to top of list", () => getFirstVisiblePMChannel().Channel == pmChannel1); + + ChannelListItem getFirstVisiblePMChannel() => + chatOverlay.ChildrenOfType().Single().PrivateChannelGroup.ItemFlow.FlowingChildren.OfType().First(item => item.Channel.Type == ChannelType.PM); + } + [Test] public void TestKeyboardNewChannel() { diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index a2ec385a7e..3e8c71e645 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -37,11 +37,13 @@ namespace osu.Game.Overlays.Chat.ChannelList private readonly Dictionary channelMap = new Dictionary(); + public ChannelGroup AnnounceChannelGroup { get; private set; } = null!; + public ChannelGroup PublicChannelGroup { get; private set; } = null!; + public ChannelGroup PrivateChannelGroup { get; private set; } = null!; + private OsuScrollContainer scroll = null!; private SearchContainer groupFlow = null!; - private ChannelGroup announceChannelGroup = null!; - private ChannelGroup publicChannelGroup = null!; - private ChannelGroup privateChannelGroup = null!; + private ChannelListItem selector = null!; private TextBox searchTextBox = null!; @@ -77,10 +79,10 @@ namespace osu.Game.Overlays.Chat.ChannelList RelativeSizeAxes = Axes.X, } }, - announceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper(), false), - publicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper(), false), + AnnounceChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitleANNOUNCE.ToUpper(), false), + PublicChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePUBLIC.ToUpper(), false), selector = new ChannelListItem(ChannelListingChannel), - privateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper(), true), + PrivateChannelGroup = new ChannelGroup(ChatStrings.ChannelsListTitlePM.ToUpper(), true), }, }, }, @@ -146,28 +148,28 @@ namespace osu.Game.Overlays.Chat.ChannelList switch (channel.Type) { case ChannelType.Public: - return publicChannelGroup; + return PublicChannelGroup; case ChannelType.PM: - return privateChannelGroup; + return PrivateChannelGroup; case ChannelType.Announce: - return announceChannelGroup; + return AnnounceChannelGroup; default: - return publicChannelGroup; + return PublicChannelGroup; } } private void updateVisibility() { - if (announceChannelGroup.ItemFlow.Children.Count == 0) - announceChannelGroup.Hide(); + if (AnnounceChannelGroup.ItemFlow.Children.Count == 0) + AnnounceChannelGroup.Hide(); else - announceChannelGroup.Show(); + AnnounceChannelGroup.Show(); } - private partial class ChannelGroup : FillFlowContainer + public partial class ChannelGroup : FillFlowContainer { public readonly ChannelListItemFlow ItemFlow; @@ -207,7 +209,7 @@ namespace osu.Game.Overlays.Chat.ChannelList public void Reflow() => InvalidateLayout(); public override IEnumerable FlowingChildren => sortByRecent - ? base.FlowingChildren.OfType().OrderByDescending(i => i.Channel.LastMessageId) + ? base.FlowingChildren.OfType().OrderByDescending(i => i.Channel.LastMessageId ?? long.MinValue) : base.FlowingChildren.OfType().OrderBy(i => i.Channel.Name); } From 17347563ee5c139288f50b990a15d630c7681ea4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 13:06:47 +0900 Subject: [PATCH 047/326] Fix incorrect null handling --- osu.Game/Online/Chat/Channel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 15ce926039..9de77237b4 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -161,7 +161,7 @@ namespace osu.Game.Online.Chat Messages.AddRange(messages); long? maxMessageId = messages.Max(m => m.Id); - if (maxMessageId > LastMessageId) + if (LastMessageId == null || maxMessageId > LastMessageId) LastMessageId = maxMessageId; purgeOldMessages(); From 8585327858a00b6c15612bf29e446ccb733773d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 14:08:53 +0900 Subject: [PATCH 048/326] Ensure `DrawableMedal` loading doesn't ever block on online resources --- .../Overlays/MedalSplash/DrawableMedal.cs | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 2beed6645a..adad540c34 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Framework.Logging; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -28,16 +29,14 @@ namespace osu.Game.Overlays.MedalSplash [CanBeNull] public event Action StateChanged; - private readonly Medal medal; private readonly Container medalContainer; - private readonly Sprite medalSprite, medalGlow; + private readonly Sprite medalGlow; private readonly OsuSpriteText unlocked, name; private readonly TextFlowContainer description; private DisplayState state; public DrawableMedal(Medal medal) { - this.medal = medal; Position = new Vector2(0f, MedalAnimation.DISC_SIZE / 2); FillFlowContainer infoFlow; @@ -51,7 +50,7 @@ namespace osu.Game.Overlays.MedalSplash Alpha = 0f, Children = new Drawable[] { - medalSprite = new Sprite + new DelayedLoadWrapper(() => new MedalOnlineSprite(medal), 0) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -122,11 +121,12 @@ namespace osu.Game.Overlays.MedalSplash } [BackgroundDependencyLoader] - private void load(OsuColour colours, TextureStore textures, LargeTextureStore largeTextures) + private void load(OsuColour colours, TextureStore textures) { - medalSprite.Texture = largeTextures.Get(medal.ImageUrl); medalGlow.Texture = textures.Get(@"MedalSplash/medal-glow"); description.Colour = colours.BlueLight; + + Logger.Log("loaded"); } protected override void LoadComplete() @@ -191,6 +191,31 @@ namespace osu.Game.Overlays.MedalSplash break; } } + + private partial class MedalOnlineSprite : Sprite + { + private readonly Medal medal; + + public MedalOnlineSprite(Medal medal) + { + this.medal = medal; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours, TextureStore textures, LargeTextureStore largeTextures) + { + Texture = largeTextures.Get(medal.ImageUrl); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + this.FadeInFromZero(150, Easing.OutQuint); + } + } } public enum DisplayState From d057dc9a95cf76f6888e6e0d8f8a60dca3705343 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 14:13:07 +0900 Subject: [PATCH 049/326] Refactor `MedalOverlay` to be more readable Shouldn't really have any functionality changes, just fixing some old code that I can't easily parse these days. --- osu.Game/Overlays/MedalOverlay.cs | 78 ++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 28 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 19f61cb910..7303a57cd0 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -35,7 +35,7 @@ namespace osu.Game.Overlays private IAPIProvider api { get; set; } = null!; private Container medalContainer = null!; - private MedalAnimation? lastAnimation; + private MedalAnimation? currentMedalDisplay; [BackgroundDependencyLoader] private void load() @@ -54,11 +54,7 @@ namespace osu.Game.Overlays { base.LoadComplete(); - OverlayActivationMode.BindValueChanged(val => - { - if (val.NewValue == OverlayActivation.All && (queuedMedals.Any() || medalContainer.Any() || lastAnimation?.IsLoaded == false)) - Show(); - }, true); + OverlayActivationMode.BindValueChanged(_ => displayIfReady(), true); } private void handleMedalMessages(SocketMessage obj) @@ -86,31 +82,13 @@ namespace osu.Game.Overlays queuedMedals.Enqueue(medalAnimation); Logger.Log($"Queueing medal unlock for \"{medal.Name}\" ({queuedMedals.Count} to display)"); - if (OverlayActivationMode.Value == OverlayActivation.All) - Scheduler.AddOnce(Show); - } - - protected override void Update() - { - base.Update(); - - if (medalContainer.Any() || lastAnimation?.IsLoaded == false) - return; - - if (!queuedMedals.TryDequeue(out lastAnimation)) - { - Logger.Log("All queued medals have been displayed!"); - Hide(); - return; - } - - Logger.Log($"Preparing to display \"{lastAnimation.Medal.Name}\""); - LoadComponentAsync(lastAnimation, medalContainer.Add); + Schedule(displayIfReady); } protected override bool OnClick(ClickEvent e) { - lastAnimation?.Dismiss(); + dismissDisplayedMedal(); + loadNextMedal(); return true; } @@ -118,13 +96,57 @@ namespace osu.Game.Overlays { if (e.Action == GlobalAction.Back) { - lastAnimation?.Dismiss(); + dismissDisplayedMedal(); + loadNextMedal(); return true; } return base.OnPressed(e); } + private void dismissDisplayedMedal() + { + if (currentMedalDisplay?.IsLoaded == false) + return; + + currentMedalDisplay?.Dismiss(); + currentMedalDisplay = null; + } + + private void displayIfReady() + { + if (OverlayActivationMode.Value != OverlayActivation.All) + return; + + if (currentMedalDisplay != null) + { + Show(); + return; + } + + if (queuedMedals.Any()) + { + Show(); + loadNextMedal(); + } + } + + private void loadNextMedal() + { + if (currentMedalDisplay != null) + return; + + if (!queuedMedals.TryDequeue(out currentMedalDisplay)) + { + Logger.Log("All queued medals have been displayed!"); + Hide(); + return; + } + + Logger.Log($"Preparing to display \"{currentMedalDisplay.Medal.Name}\""); + LoadComponentAsync(currentMedalDisplay, m => medalContainer.Add(m)); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 672dbe6e03bd95971f46ace7685d91cd75729bb8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 14:42:30 +0900 Subject: [PATCH 050/326] Better control of show/hide of overlay --- osu.Game/Overlays/MedalOverlay.cs | 55 +++++++++++++++++-------------- 1 file changed, 31 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 7303a57cd0..b7e68fd557 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -57,6 +57,11 @@ namespace osu.Game.Overlays OverlayActivationMode.BindValueChanged(_ => displayIfReady(), true); } + public override void Hide() + { + // don't allow hiding the overlay via any method other than our own. + } + private void handleMedalMessages(SocketMessage obj) { if (obj.Event != @"new") @@ -87,8 +92,7 @@ namespace osu.Game.Overlays protected override bool OnClick(ClickEvent e) { - dismissDisplayedMedal(); - loadNextMedal(); + progressDisplayByUser(); return true; } @@ -96,21 +100,31 @@ namespace osu.Game.Overlays { if (e.Action == GlobalAction.Back) { - dismissDisplayedMedal(); - loadNextMedal(); + progressDisplayByUser(); return true; } return base.OnPressed(e); } - private void dismissDisplayedMedal() + private void progressDisplayByUser() { + // For now, we want to make sure that medals are definitely seen by the user. + // So we block exiting the overlay until the load of the active medal completes. if (currentMedalDisplay?.IsLoaded == false) return; currentMedalDisplay?.Dismiss(); currentMedalDisplay = null; + + if (!queuedMedals.Any()) + { + Logger.Log("All queued medals have been displayed, hiding overlay!"); + base.Hide(); + return; + } + + showNextMedal(); } private void displayIfReady() @@ -118,33 +132,26 @@ namespace osu.Game.Overlays if (OverlayActivationMode.Value != OverlayActivation.All) return; - if (currentMedalDisplay != null) - { - Show(); - return; - } - - if (queuedMedals.Any()) - { - Show(); - loadNextMedal(); - } + if (currentMedalDisplay != null || queuedMedals.Any()) + showNextMedal(); } - private void loadNextMedal() + private void showNextMedal() { + // A medal is already loading / loaded, so just ensure the overlay is visible. if (currentMedalDisplay != null) - return; - - if (!queuedMedals.TryDequeue(out currentMedalDisplay)) { - Logger.Log("All queued medals have been displayed!"); - Hide(); + Show(); return; } - Logger.Log($"Preparing to display \"{currentMedalDisplay.Medal.Name}\""); - LoadComponentAsync(currentMedalDisplay, m => medalContainer.Add(m)); + if (queuedMedals.TryDequeue(out currentMedalDisplay)) + { + Logger.Log($"Preparing to display \"{currentMedalDisplay.Medal.Name}\""); + + Show(); + LoadComponentAsync(currentMedalDisplay, m => medalContainer.Add(m)); + } } protected override void Dispose(bool isDisposing) From e8fae85e8d5b0077b9825a650180b89511c38d87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 14:45:40 +0900 Subject: [PATCH 051/326] Fix hidden dissmissing logic --- osu.Game/Overlays/MedalAnimation.cs | 5 +++-- osu.Game/Overlays/MedalOverlay.cs | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/MedalAnimation.cs b/osu.Game/Overlays/MedalAnimation.cs index daceeedf47..fdca0b2cc7 100644 --- a/osu.Game/Overlays/MedalAnimation.cs +++ b/osu.Game/Overlays/MedalAnimation.cs @@ -245,18 +245,19 @@ namespace osu.Game.Overlays this.FadeOut(200); } - public void Dismiss() + public bool Dismiss() { if (drawableMedal != null && drawableMedal.State != DisplayState.Full) { // if we haven't yet, play out the animation fully drawableMedal.State = DisplayState.Full; FinishTransforms(true); - return; + return false; } Hide(); Expire(); + return true; } private partial class BackgroundStrip : Container diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index b7e68fd557..736f744429 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -114,7 +114,10 @@ namespace osu.Game.Overlays if (currentMedalDisplay?.IsLoaded == false) return; - currentMedalDisplay?.Dismiss(); + // Dismissing may sometimes play out the medal animation rather than immediately dismissing. + if (currentMedalDisplay?.Dismiss() == false) + return; + currentMedalDisplay = null; if (!queuedMedals.Any()) From d150aeef2b019ecf1cad5f4e3f5b16ea1473297b Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Tue, 26 Nov 2024 01:01:59 -0500 Subject: [PATCH 052/326] Use score-based endpoint everywhere --- .../TestScenePlaylistsResultsScreen.cs | 4 ++-- .../Rooms/ShowPlaylistUserScoreRequest.cs | 23 ------------------- .../PlaylistItemUserResultsScreen.cs | 4 ++-- 3 files changed, 4 insertions(+), 27 deletions(-) delete mode 100644 osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 5977e67b0e..6ccbcd2859 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -231,7 +231,7 @@ namespace osu.Game.Tests.Visual.Playlists // pre-check for requests we should be handling (as they are scheduled below). switch (request) { - case ShowPlaylistUserScoreRequest: + case ShowPlaylistScoreRequest: case IndexPlaylistScoresRequest: break; @@ -253,7 +253,7 @@ namespace osu.Game.Tests.Visual.Playlists switch (request) { - case ShowPlaylistUserScoreRequest s: + case ShowPlaylistScoreRequest s: if (userScore == null) triggerFail(s); else diff --git a/osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs b/osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs deleted file mode 100644 index 8e6a1ac7c7..0000000000 --- a/osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs +++ /dev/null @@ -1,23 +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.Online.API; - -namespace osu.Game.Online.Rooms -{ - public class ShowPlaylistUserScoreRequest : APIRequest - { - private readonly long roomId; - private readonly long playlistItemId; - private readonly long userId; - - public ShowPlaylistUserScoreRequest(long roomId, long playlistItemId, long userId) - { - this.roomId = roomId; - this.playlistItemId = playlistItemId; - this.userId = userId; - } - - protected override string Target => $"rooms/{roomId}/playlist/{playlistItemId}/scores/users/{userId}"; - } -} diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs index e038cf3288..988331e213 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs @@ -11,7 +11,7 @@ using osu.Game.Scoring; namespace osu.Game.Screens.OnlinePlay.Playlists { /// - /// Shows the user's best score for a given playlist item, with scores around included. + /// Shows the user's submitted score in a given playlist item, with scores around included. /// public partial class PlaylistItemUserResultsScreen : PlaylistItemResultsScreen { @@ -20,7 +20,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { } - protected override APIRequest CreateScoreRequest() => new ShowPlaylistUserScoreRequest(RoomId, PlaylistItem.ID, API.LocalUser.Value.Id); + protected override APIRequest CreateScoreRequest() => new ShowPlaylistScoreRequest(RoomId, PlaylistItem.ID, Score?.OnlineID ?? -1); protected override ScoreInfo[] PerformSuccessCallback(Action> callback, List scores, MultiplayerScores? pivot = null) { From c1416f9920e454812bf78e8e9d770dade16de1d9 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Tue, 26 Nov 2024 01:10:12 -0500 Subject: [PATCH 053/326] Bring back user-based endpoint for viewing result screen from playlists lounge --- .../Rooms/ShowPlaylistUserScoreRequest.cs | 23 +++++++++++++++++++ .../PlaylistItemUserResultsScreen.cs | 4 +++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs diff --git a/osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs b/osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.cs new file mode 100644 index 0000000000..8e6a1ac7c7 --- /dev/null +++ b/osu.Game/Online/Rooms/ShowPlaylistUserScoreRequest.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.Game.Online.API; + +namespace osu.Game.Online.Rooms +{ + public class ShowPlaylistUserScoreRequest : APIRequest + { + private readonly long roomId; + private readonly long playlistItemId; + private readonly long userId; + + public ShowPlaylistUserScoreRequest(long roomId, long playlistItemId, long userId) + { + this.roomId = roomId; + this.playlistItemId = playlistItemId; + this.userId = userId; + } + + protected override string Target => $"rooms/{roomId}/playlist/{playlistItemId}/scores/users/{userId}"; + } +} diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs index 988331e213..b659a98802 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs @@ -20,7 +20,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { } - protected override APIRequest CreateScoreRequest() => new ShowPlaylistScoreRequest(RoomId, PlaylistItem.ID, Score?.OnlineID ?? -1); + protected override APIRequest CreateScoreRequest() => Score == null + ? new ShowPlaylistScoreRequest(RoomId, PlaylistItem.ID, Score?.OnlineID ?? -1) + : new ShowPlaylistUserScoreRequest(RoomId, PlaylistItem.ID, API.LocalUser.Value.Id); protected override ScoreInfo[] PerformSuccessCallback(Action> callback, List scores, MultiplayerScores? pivot = null) { From 7201bac60d88b33177a16a596177f431e9b06192 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Tue, 26 Nov 2024 01:10:19 -0500 Subject: [PATCH 054/326] Remove `DailyChallengePlayer` --- .../DailyChallenge/DailyChallenge.cs | 2 +- .../DailyChallenge/DailyChallengePlayer.cs | 41 ------------------- 2 files changed, 1 insertion(+), 42 deletions(-) delete mode 100644 osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengePlayer.cs diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 6cb8a87a2a..0dc7e7930a 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -532,7 +532,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private void startPlay() { sampleStart?.Play(); - this.Push(new PlayerLoader(() => new DailyChallengePlayer(room, playlistItem) + this.Push(new PlayerLoader(() => new PlaylistsPlayer(room, playlistItem) { Exited = () => Scheduler.AddOnce(() => leaderboard.RefetchScores()) })); diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengePlayer.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengePlayer.cs deleted file mode 100644 index cfc0898e5a..0000000000 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallengePlayer.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. - -using System.Diagnostics; -using osu.Game.Online.Rooms; -using osu.Game.Scoring; -using osu.Game.Screens.OnlinePlay.Playlists; -using osu.Game.Screens.Play; -using osu.Game.Screens.Ranking; - -namespace osu.Game.Screens.OnlinePlay.DailyChallenge -{ - public partial class DailyChallengePlayer : PlaylistsPlayer - { - public DailyChallengePlayer(Room room, PlaylistItem playlistItem, PlayerConfiguration? configuration = null) - : base(room, playlistItem, configuration) - { - } - - protected override ResultsScreen CreateResults(ScoreInfo score) - { - Debug.Assert(Room.RoomID != null); - - if (score.OnlineID >= 0) - { - return new PlaylistItemScoreResultsScreen(Room.RoomID.Value, PlaylistItem, score.OnlineID) - { - AllowRetry = true, - ShowUserStatistics = true, - }; - } - - // If the score has failed submission, fall back to displaying scores from user's highest. - return new PlaylistItemUserResultsScreen(score, Room.RoomID.Value, PlaylistItem) - { - AllowRetry = true, - ShowUserStatistics = true, - }; - } - } -} From 1e6c04e98b092e52a35b20d07e9a5a67e61de1b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 16:05:04 +0900 Subject: [PATCH 055/326] Remove debug logging --- osu.Game/Overlays/MedalSplash/DrawableMedal.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index adad540c34..460239f620 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osu.Framework.Logging; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -125,8 +124,6 @@ namespace osu.Game.Overlays.MedalSplash { medalGlow.Texture = textures.Get(@"MedalSplash/medal-glow"); description.Colour = colours.BlueLight; - - Logger.Log("loaded"); } protected override void LoadComplete() From 98044c108e7f7e9e8723c61ff1ca3823a95feaeb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 17:41:00 +0900 Subject: [PATCH 056/326] Revert "Ensure `DrawableMedal` loading doesn't ever block on online resources" This reverts commit 8585327858a00b6c15612bf29e446ccb733773d9. --- .../Overlays/MedalSplash/DrawableMedal.cs | 34 ++++--------------- 1 file changed, 6 insertions(+), 28 deletions(-) diff --git a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs index 460239f620..2beed6645a 100644 --- a/osu.Game/Overlays/MedalSplash/DrawableMedal.cs +++ b/osu.Game/Overlays/MedalSplash/DrawableMedal.cs @@ -28,14 +28,16 @@ namespace osu.Game.Overlays.MedalSplash [CanBeNull] public event Action StateChanged; + private readonly Medal medal; private readonly Container medalContainer; - private readonly Sprite medalGlow; + private readonly Sprite medalSprite, medalGlow; private readonly OsuSpriteText unlocked, name; private readonly TextFlowContainer description; private DisplayState state; public DrawableMedal(Medal medal) { + this.medal = medal; Position = new Vector2(0f, MedalAnimation.DISC_SIZE / 2); FillFlowContainer infoFlow; @@ -49,7 +51,7 @@ namespace osu.Game.Overlays.MedalSplash Alpha = 0f, Children = new Drawable[] { - new DelayedLoadWrapper(() => new MedalOnlineSprite(medal), 0) + medalSprite = new Sprite { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -120,8 +122,9 @@ namespace osu.Game.Overlays.MedalSplash } [BackgroundDependencyLoader] - private void load(OsuColour colours, TextureStore textures) + private void load(OsuColour colours, TextureStore textures, LargeTextureStore largeTextures) { + medalSprite.Texture = largeTextures.Get(medal.ImageUrl); medalGlow.Texture = textures.Get(@"MedalSplash/medal-glow"); description.Colour = colours.BlueLight; } @@ -188,31 +191,6 @@ namespace osu.Game.Overlays.MedalSplash break; } } - - private partial class MedalOnlineSprite : Sprite - { - private readonly Medal medal; - - public MedalOnlineSprite(Medal medal) - { - this.medal = medal; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, TextureStore textures, LargeTextureStore largeTextures) - { - Texture = largeTextures.Get(medal.ImageUrl); - - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - this.FadeInFromZero(150, Easing.OutQuint); - } - } } public enum DisplayState From 71294c312b1a29d2ca73c1f335140e4f350754d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 17:58:50 +0900 Subject: [PATCH 057/326] Change point of queueing to avoid loading-from-in-queue --- osu.Game/Overlays/MedalOverlay.cs | 34 +++++++++++++------------------ 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index 736f744429..c24b209b3a 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -54,7 +54,7 @@ namespace osu.Game.Overlays { base.LoadComplete(); - OverlayActivationMode.BindValueChanged(_ => displayIfReady(), true); + OverlayActivationMode.BindValueChanged(_ => showNextMedal(), true); } public override void Hide() @@ -84,10 +84,13 @@ namespace osu.Game.Overlays var medalAnimation = new MedalAnimation(medal); - queuedMedals.Enqueue(medalAnimation); Logger.Log($"Queueing medal unlock for \"{medal.Name}\" ({queuedMedals.Count} to display)"); - Schedule(displayIfReady); + LoadComponentAsync(medalAnimation, m => + { + queuedMedals.Enqueue(m); + showNextMedal(); + }); } protected override bool OnClick(ClickEvent e) @@ -130,30 +133,21 @@ namespace osu.Game.Overlays showNextMedal(); } - private void displayIfReady() - { - if (OverlayActivationMode.Value != OverlayActivation.All) - return; - - if (currentMedalDisplay != null || queuedMedals.Any()) - showNextMedal(); - } - private void showNextMedal() { - // A medal is already loading / loaded, so just ensure the overlay is visible. - if (currentMedalDisplay != null) - { - Show(); + // If already displayed, keep displaying medals regardless of activation mode changes. + if (OverlayActivationMode.Value != OverlayActivation.All && State.Value == Visibility.Hidden) + return; + + // A medal is already displaying. + if (currentMedalDisplay != null) return; - } if (queuedMedals.TryDequeue(out currentMedalDisplay)) { - Logger.Log($"Preparing to display \"{currentMedalDisplay.Medal.Name}\""); - + Logger.Log($"Displaying \"{currentMedalDisplay.Medal.Name}\""); + medalContainer.Add(currentMedalDisplay); Show(); - LoadComponentAsync(currentMedalDisplay, m => medalContainer.Add(m)); } } From bf29e3ae718373f16ff1edc5e35c412fa89e9902 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Nov 2024 18:00:32 +0900 Subject: [PATCH 058/326] Simplify hide code by moving to common method --- osu.Game/Overlays/MedalOverlay.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index c24b209b3a..512cb697dd 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.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.Extensions.ObjectExtensions; using osu.Framework.Graphics; @@ -112,24 +111,11 @@ namespace osu.Game.Overlays private void progressDisplayByUser() { - // For now, we want to make sure that medals are definitely seen by the user. - // So we block exiting the overlay until the load of the active medal completes. - if (currentMedalDisplay?.IsLoaded == false) - return; - // Dismissing may sometimes play out the medal animation rather than immediately dismissing. if (currentMedalDisplay?.Dismiss() == false) return; currentMedalDisplay = null; - - if (!queuedMedals.Any()) - { - Logger.Log("All queued medals have been displayed, hiding overlay!"); - base.Hide(); - return; - } - showNextMedal(); } @@ -149,6 +135,11 @@ namespace osu.Game.Overlays medalContainer.Add(currentMedalDisplay); Show(); } + else if (State.Value == Visibility.Visible) + { + Logger.Log("All queued medals have been displayed, hiding overlay!"); + base.Hide(); + } } protected override void Dispose(bool isDisposing) From 46d1f005907f83c769840974eda24630136e9701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 26 Nov 2024 11:39:03 +0100 Subject: [PATCH 059/326] Fix `Beatmap.Countdown` not being copied on conversion --- osu.Game/Beatmaps/BeatmapConverter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index c0066cc637..82b40c0318 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -83,6 +83,7 @@ namespace osu.Game.Beatmaps beatmap.DistanceSpacing = original.DistanceSpacing; beatmap.GridSize = original.GridSize; beatmap.TimelineZoom = original.TimelineZoom; + beatmap.Countdown = original.Countdown; beatmap.CountdownOffset = original.CountdownOffset; return beatmap; From af0c6fc51b7f5faf9f5ff9ba01e692c8b03a5808 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 26 Nov 2024 21:06:08 +0900 Subject: [PATCH 060/326] Add `Room.HasEnded` helper method --- osu.Game/Graphics/OsuColour.cs | 2 +- osu.Game/Online/Rooms/Room.cs | 9 +++++++++ .../OnlinePlay/Lounge/Components/RoomStatusPill.cs | 2 +- .../OnlinePlay/Multiplayer/MultiplayerRoomManager.cs | 2 +- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 20e65323f8..2c43876fb2 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -200,7 +200,7 @@ namespace osu.Game.Graphics /// public Color4 ForRoomStatus(Room room) { - if (DateTimeOffset.Now >= room.EndDate) + if (room.HasEnded) return YellowDarker; switch (room.Status) diff --git a/osu.Game/Online/Rooms/Room.cs b/osu.Game/Online/Rooms/Room.cs index 6e073bdcd7..897ba6bd70 100644 --- a/osu.Game/Online/Rooms/Room.cs +++ b/osu.Game/Online/Rooms/Room.cs @@ -374,6 +374,15 @@ namespace osu.Game.Online.Rooms RecentParticipants = other.RecentParticipants; } + /// + /// Whether the room is no longer available. + /// + /// + /// This property does not update in real-time and needs to be queried periodically. + /// Subscribe to to be notified of any immediate changes. + /// + public bool HasEnded => DateTimeOffset.Now >= EndDate; + [JsonObject(MemberSerialization.OptIn)] public class RoomPlaylistItemStats { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs index 5d2c4b28e6..32d0add5fd 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { Pill.Background.FadeColour(colours.ForRoomStatus(room), 100); - if (DateTimeOffset.Now >= room.EndDate) + if (room.HasEnded) TextFlow.Text = RoomStatusPillStrings.Ended; else { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs index b6f4b0e8d9..7f09c9cbe9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer // this is done here as a pre-check to avoid clicking on already closed rooms in the lounge from triggering a server join. // should probably be done at a higher level, but due to the current structure of things this is the easiest place for now. - if (DateTimeOffset.Now >= room.EndDate) + if (room.HasEnded) { onError?.Invoke("Cannot join an ended room."); return; From 4c7976bb9305c1e7b7b1f075ff194449d6e4b3f0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 26 Nov 2024 21:11:48 +0900 Subject: [PATCH 061/326] Remove unused using --- osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs index 32d0add5fd..6da8f3ecbd 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.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.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Graphics; From df74a177ae8ce195d79e20b6903b7477d201db10 Mon Sep 17 00:00:00 2001 From: HenintsoaSky Date: Wed, 27 Nov 2024 00:13:32 +0300 Subject: [PATCH 062/326] Add option to disable star fountain in gameplay --- .../Visual/Menus/TestSceneStarFountain.cs | 54 +++++++++++++++++++ osu.Game/Configuration/OsuConfigManager.cs | 2 + .../Localisation/GameplaySettingsStrings.cs | 5 ++ .../Sections/Gameplay/GeneralSettings.cs | 5 ++ .../Screens/Play/KiaiGameplayFountains.cs | 16 +++++- 5 files changed, 81 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs index 29fa7287d2..64a0f1f821 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs @@ -3,8 +3,10 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; @@ -73,5 +75,57 @@ namespace osu.Game.Tests.Visual.Menus ((StarFountain)Children[1]).Shoot(-1); }); } + + [Test] + public void TestGameplayKiaiStarToggle() + { + Bindable kiaiStarEffectsEnabled = null!; + + AddStep("load configuration", () => + { + var config = new OsuConfigManager(LocalStorage); + kiaiStarEffectsEnabled = config.GetBindable(OsuSetting.KiaiStarFountain); + }); + + AddStep("make fountains", () => + { + Children = new Drawable[] + { + new KiaiGameplayFountains.GameplayStarFountain + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + X = 75, + }, + new KiaiGameplayFountains.GameplayStarFountain + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + X = -75, + }, + }; + }); + + AddStep("enable KiaiStarEffects", () => kiaiStarEffectsEnabled.Value = true); + AddRepeatStep("activate fountains (enabled)", () => + { + ((KiaiGameplayFountains.GameplayStarFountain)Children[0]).Shoot(1); + ((KiaiGameplayFountains.GameplayStarFountain)Children[1]).Shoot(-1); + }, 100); + + AddStep("disable KiaiStarEffects", () => kiaiStarEffectsEnabled.Value = false); + AddRepeatStep("attempt to activate fountains (disabled)", () => + { + ((KiaiGameplayFountains.GameplayStarFountain)Children[0]).Shoot(1); + ((KiaiGameplayFountains.GameplayStarFountain)Children[1]).Shoot(-1); + }, 100); + + AddStep("re-enable KiaiStarEffects", () => kiaiStarEffectsEnabled.Value = true); + AddRepeatStep("activate fountains (re-enabled)", () => + { + ((KiaiGameplayFountains.GameplayStarFountain)Children[0]).Shoot(1); + ((KiaiGameplayFountains.GameplayStarFountain)Children[1]).Shoot(-1); + }, 100); + } } } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 33d99e9b0f..36a5328756 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -138,6 +138,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.LightenDuringBreaks, true); SetDefault(OsuSetting.HitLighting, true); + SetDefault(OsuSetting.KiaiStarFountain, true); SetDefault(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always); SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true); @@ -414,6 +415,7 @@ namespace osu.Game.Configuration NotifyOnPrivateMessage, UIHoldActivationDelay, HitLighting, + KiaiStarFountain, MenuBackgroundSource, GameplayDisableWinKey, SeasonalBackgroundMode, diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs index ff6a6102a7..3d18eacf9d 100644 --- a/osu.Game/Localisation/GameplaySettingsStrings.cs +++ b/osu.Game/Localisation/GameplaySettingsStrings.cs @@ -74,6 +74,11 @@ namespace osu.Game.Localisation /// public static LocalisableString FadePlayfieldWhenHealthLow => new TranslatableString(getKey(@"fade_playfield_when_health_low"), @"Fade playfield to red when health is low"); + /// + /// "Star fountain during kiai time" + /// + public static LocalisableString KiaiStarFountain => new TranslatableString(getKey(@"star_fountain_during_kiai_time"), @"Star fountain during kiai time"); + /// /// "Always show key overlay" /// diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 83e9140b33..136832a75b 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -31,6 +31,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = GraphicsSettingsStrings.HitLighting, Current = config.GetBindable(OsuSetting.HitLighting) }, + new SettingsCheckbox + { + LabelText = GameplaySettingsStrings.KiaiStarFountain, + Current = config.GetBindable(OsuSetting.KiaiStarFountain) + }, }; } } diff --git a/osu.Game/Screens/Play/KiaiGameplayFountains.cs b/osu.Game/Screens/Play/KiaiGameplayFountains.cs index 7659c61123..4d1d247f87 100644 --- a/osu.Game/Screens/Play/KiaiGameplayFountains.cs +++ b/osu.Game/Screens/Play/KiaiGameplayFountains.cs @@ -5,8 +5,10 @@ using System; using osu.Framework.Allocation; using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; using osu.Game.Screens.Menu; @@ -18,9 +20,13 @@ namespace osu.Game.Screens.Play private StarFountain leftFountain = null!; private StarFountain rightFountain = null!; + private Bindable kiaiStarEffectsEnabled = null!; + [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { + kiaiStarEffectsEnabled = config.GetBindable(OsuSetting.KiaiStarFountain); + RelativeSizeAxes = Axes.Both; Children = new[] @@ -48,6 +54,12 @@ namespace osu.Game.Screens.Play { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + if (!kiaiStarEffectsEnabled.Value) + return; + + if (!kiaiStarEffectsEnabled.Value) + return; + if (effectPoint.KiaiMode && !isTriggered) { bool isNearEffectPoint = Math.Abs(BeatSyncSource.Clock.CurrentTime - effectPoint.Time) < 500; @@ -76,6 +88,8 @@ namespace osu.Game.Screens.Play { protected override double ShootDuration => 400; + private readonly Bindable kiaiStarEffectsEnabled = new Bindable(); + public GameplayStarFountainSpewer() : base(perSecond: 180) { From 460471e73fc17c50cd67073de1e6eb05d0e75179 Mon Sep 17 00:00:00 2001 From: HenintsoaSky Date: Wed, 27 Nov 2024 00:27:22 +0300 Subject: [PATCH 063/326] Rename of the setting --- osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs | 2 +- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- osu.Game/Localisation/GameplaySettingsStrings.cs | 4 ++-- .../Overlays/Settings/Sections/Gameplay/GeneralSettings.cs | 4 ++-- osu.Game/Screens/Play/KiaiGameplayFountains.cs | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs index 64a0f1f821..6f73979e58 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("load configuration", () => { var config = new OsuConfigManager(LocalStorage); - kiaiStarEffectsEnabled = config.GetBindable(OsuSetting.KiaiStarFountain); + kiaiStarEffectsEnabled = config.GetBindable(OsuSetting.StarFountains); }); AddStep("make fountains", () => diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 36a5328756..4f62db8cf7 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -138,7 +138,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.LightenDuringBreaks, true); SetDefault(OsuSetting.HitLighting, true); - SetDefault(OsuSetting.KiaiStarFountain, true); + SetDefault(OsuSetting.StarFountains, true); SetDefault(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always); SetDefault(OsuSetting.ShowHealthDisplayWhenCantFail, true); @@ -415,7 +415,7 @@ namespace osu.Game.Configuration NotifyOnPrivateMessage, UIHoldActivationDelay, HitLighting, - KiaiStarFountain, + StarFountains, MenuBackgroundSource, GameplayDisableWinKey, SeasonalBackgroundMode, diff --git a/osu.Game/Localisation/GameplaySettingsStrings.cs b/osu.Game/Localisation/GameplaySettingsStrings.cs index 3d18eacf9d..2715f0b8cf 100644 --- a/osu.Game/Localisation/GameplaySettingsStrings.cs +++ b/osu.Game/Localisation/GameplaySettingsStrings.cs @@ -75,9 +75,9 @@ namespace osu.Game.Localisation public static LocalisableString FadePlayfieldWhenHealthLow => new TranslatableString(getKey(@"fade_playfield_when_health_low"), @"Fade playfield to red when health is low"); /// - /// "Star fountain during kiai time" + /// "Star fountains" /// - public static LocalisableString KiaiStarFountain => new TranslatableString(getKey(@"star_fountain_during_kiai_time"), @"Star fountain during kiai time"); + public static LocalisableString StarFountains => new TranslatableString(getKey(@"star_fountains"), @"Star fountains"); /// /// "Always show key overlay" diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 136832a75b..779d5cdf00 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -33,8 +33,8 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay }, new SettingsCheckbox { - LabelText = GameplaySettingsStrings.KiaiStarFountain, - Current = config.GetBindable(OsuSetting.KiaiStarFountain) + LabelText = GameplaySettingsStrings.StarFountains, + Current = config.GetBindable(OsuSetting.StarFountains) }, }; } diff --git a/osu.Game/Screens/Play/KiaiGameplayFountains.cs b/osu.Game/Screens/Play/KiaiGameplayFountains.cs index 4d1d247f87..011de52b2a 100644 --- a/osu.Game/Screens/Play/KiaiGameplayFountains.cs +++ b/osu.Game/Screens/Play/KiaiGameplayFountains.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - kiaiStarEffectsEnabled = config.GetBindable(OsuSetting.KiaiStarFountain); + kiaiStarEffectsEnabled = config.GetBindable(OsuSetting.StarFountains); RelativeSizeAxes = Axes.Both; From 80a66085a9497b97e6c7312a34ca52abe8e632a4 Mon Sep 17 00:00:00 2001 From: HenintsoaSky Date: Wed, 27 Nov 2024 00:41:02 +0300 Subject: [PATCH 064/326] rename and remove again --- osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs | 12 ++++++------ osu.Game/Screens/Play/KiaiGameplayFountains.cs | 10 ++++------ 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs index 6f73979e58..0d981014b8 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs @@ -77,14 +77,14 @@ namespace osu.Game.Tests.Visual.Menus } [Test] - public void TestGameplayKiaiStarToggle() + public void TestGameplayStarFountainsSetting() { - Bindable kiaiStarEffectsEnabled = null!; + Bindable starFountainsEnabled = null!; AddStep("load configuration", () => { var config = new OsuConfigManager(LocalStorage); - kiaiStarEffectsEnabled = config.GetBindable(OsuSetting.StarFountains); + starFountainsEnabled = config.GetBindable(OsuSetting.StarFountains); }); AddStep("make fountains", () => @@ -106,21 +106,21 @@ namespace osu.Game.Tests.Visual.Menus }; }); - AddStep("enable KiaiStarEffects", () => kiaiStarEffectsEnabled.Value = true); + AddStep("enable KiaiStarEffects", () => starFountainsEnabled.Value = true); AddRepeatStep("activate fountains (enabled)", () => { ((KiaiGameplayFountains.GameplayStarFountain)Children[0]).Shoot(1); ((KiaiGameplayFountains.GameplayStarFountain)Children[1]).Shoot(-1); }, 100); - AddStep("disable KiaiStarEffects", () => kiaiStarEffectsEnabled.Value = false); + AddStep("disable KiaiStarEffects", () => starFountainsEnabled.Value = false); AddRepeatStep("attempt to activate fountains (disabled)", () => { ((KiaiGameplayFountains.GameplayStarFountain)Children[0]).Shoot(1); ((KiaiGameplayFountains.GameplayStarFountain)Children[1]).Shoot(-1); }, 100); - AddStep("re-enable KiaiStarEffects", () => kiaiStarEffectsEnabled.Value = true); + AddStep("re-enable KiaiStarEffects", () => starFountainsEnabled.Value = true); AddRepeatStep("activate fountains (re-enabled)", () => { ((KiaiGameplayFountains.GameplayStarFountain)Children[0]).Shoot(1); diff --git a/osu.Game/Screens/Play/KiaiGameplayFountains.cs b/osu.Game/Screens/Play/KiaiGameplayFountains.cs index 011de52b2a..a6b2cd6fdb 100644 --- a/osu.Game/Screens/Play/KiaiGameplayFountains.cs +++ b/osu.Game/Screens/Play/KiaiGameplayFountains.cs @@ -20,12 +20,12 @@ namespace osu.Game.Screens.Play private StarFountain leftFountain = null!; private StarFountain rightFountain = null!; - private Bindable kiaiStarEffectsEnabled = null!; + private Bindable kiaiStarFountainsEnabled = null!; [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - kiaiStarEffectsEnabled = config.GetBindable(OsuSetting.StarFountains); + kiaiStarFountainsEnabled = config.GetBindable(OsuSetting.StarFountains); RelativeSizeAxes = Axes.Both; @@ -54,10 +54,10 @@ namespace osu.Game.Screens.Play { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - if (!kiaiStarEffectsEnabled.Value) + if (!kiaiStarFountainsEnabled.Value) return; - if (!kiaiStarEffectsEnabled.Value) + if (!kiaiStarFountainsEnabled.Value) return; if (effectPoint.KiaiMode && !isTriggered) @@ -88,8 +88,6 @@ namespace osu.Game.Screens.Play { protected override double ShootDuration => 400; - private readonly Bindable kiaiStarEffectsEnabled = new Bindable(); - public GameplayStarFountainSpewer() : base(perSecond: 180) { From 3e1b4f4ac564a1b69b2d8111b19d3908f99980e4 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Tue, 26 Nov 2024 16:50:23 -0500 Subject: [PATCH 065/326] Rename `AllowBackButton` to `AllowUserExit` and rewrite visibility flow structure Co-authored-by: Dean Herbert --- osu.Game/OsuGame.cs | 25 ++++++++++++------- .../Maintenance/MigrationRunScreen.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/Edit/EditorLoader.cs | 2 +- osu.Game/Screens/IOsuScreen.cs | 21 ++++++++-------- osu.Game/Screens/Menu/MainMenu.cs | 2 +- .../Screens/OnlinePlay/OnlinePlayScreen.cs | 2 +- osu.Game/Screens/OsuScreen.cs | 13 +++++++--- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Play/PlayerLoader.cs | 4 +-- osu.Game/Screens/StartupScreen.cs | 2 +- 11 files changed, 44 insertions(+), 33 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4d6bc4fd14..514209524e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -175,6 +175,11 @@ namespace osu.Game /// public readonly IBindable OverlayActivationMode = new Bindable(); + /// + /// Whether the back button is currently displayed. + /// + public readonly IBindable BackButtonVisibility = new Bindable(); + IBindable ILocalUserPlayInfo.PlayingState => playingState; private readonly Bindable playingState = new Bindable(); @@ -1019,7 +1024,7 @@ namespace osu.Game if (!(ScreenStack.CurrentScreen is IOsuScreen currentScreen)) return; - if (!((Drawable)currentScreen).IsLoaded || (currentScreen.AllowBackButton && !currentScreen.OnBackButton())) + if (!((Drawable)currentScreen).IsLoaded || (currentScreen.AllowUserExit && !currentScreen.OnBackButton())) ScreenStack.Exit(); } }, @@ -1189,6 +1194,14 @@ namespace osu.Game if (mode.NewValue != OverlayActivation.All) CloseAllOverlays(); }; + BackButtonVisibility.ValueChanged += visible => + { + if (visible.NewValue) + BackButton.Show(); + else + BackButton.Hide(); + }; + // Importantly, this should be run after binding PostNotification to the import handlers so they can present the import after game startup. handleStartupImport(); } @@ -1581,20 +1594,14 @@ namespace osu.Game if (current is IOsuScreen currentOsuScreen) { - if (currentOsuScreen.AllowBackButton) - BackButton.State.UnbindFrom(currentOsuScreen.BackButtonState); - + BackButtonVisibility.UnbindFrom(currentOsuScreen.BackButtonVisibility); OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode); API.Activity.UnbindFrom(currentOsuScreen.Activity); } if (newScreen is IOsuScreen newOsuScreen) { - if (newOsuScreen.AllowBackButton) - ((IBindable)BackButton.State).BindTo(newOsuScreen.BackButtonState); - else - BackButton.Hide(); - + BackButtonVisibility.BindTo(newOsuScreen.BackButtonVisibility); OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode); API.Activity.BindTo(newOsuScreen.Activity); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs index 3bba480aaa..c0363851ef 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/MigrationRunScreen.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance [Resolved(canBeNull: true)] private OsuGame game { get; set; } - public override bool AllowBackButton => false; + public override bool AllowUserExit => false; public override bool AllowExternalScreenChange => false; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 644e1afb3b..13e5791605 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Edit public override float BackgroundParallaxAmount => 0.1f; - public override bool AllowBackButton => false; + public override bool AllowUserExit => false; public override bool HideOverlaysOnEnter => true; diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 0e0fb9f795..7c6ee10840 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Edit public override float BackgroundParallaxAmount => 0.1f; - public override bool AllowBackButton => false; + public override bool AllowUserExit => false; public override bool HideOverlaysOnEnter => true; diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index 7025460daa..46dfbfb1ac 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -3,10 +3,8 @@ using System.Collections.Generic; using osu.Framework.Bindables; -using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; -using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Rulesets; @@ -24,15 +22,20 @@ namespace osu.Game.Screens bool DisallowExternalBeatmapRulesetChanges { get; } /// - /// Whether the user can exit this by pressing the back button. + /// Whether the user can exit this . /// - bool AllowBackButton { get; } + /// + /// When overriden to false, + /// the user is blocked from exiting the screen via the action, + /// and the back button is hidden from this screen by the initial state of being set to hidden. + /// + bool AllowUserExit { get; } /// /// Whether a footer (and a back button) should be displayed underneath the screen. /// /// - /// Temporarily, the back button is shown regardless of whether is true. + /// Temporarily, the back button is shown regardless of whether is true. /// bool ShowFooter { get; } @@ -63,13 +66,9 @@ namespace osu.Game.Screens IBindable OverlayActivationMode { get; } /// - /// Controls the visibility state of to better work with screen-specific transitions (i.e. quick restart in player). - /// The back button can still be triggered by the action even while hidden. + /// Whether the back button should be displayed in this screen. /// - /// - /// This is ignored when is set to false. - /// - IBindable BackButtonState { get; } + IBindable BackButtonVisibility { get; } /// /// The current for this screen. diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 35c6bab81b..6b94d4bdfb 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Menu public override bool HideOverlaysOnEnter => Buttons == null || Buttons.State == ButtonSystemState.Initial; - public override bool AllowBackButton => false; + public override bool AllowUserExit => false; public override bool AllowExternalScreenChange => true; diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index cc6a4e09e1..17fb667e14 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -180,7 +180,7 @@ namespace osu.Game.Screens.OnlinePlay if (!(screenStack.CurrentScreen is IOnlinePlaySubScreen onlineSubScreen)) return false; - if (((Drawable)onlineSubScreen).IsLoaded && onlineSubScreen.AllowBackButton && onlineSubScreen.OnBackButton()) + if (((Drawable)onlineSubScreen).IsLoaded && onlineSubScreen.AllowUserExit && onlineSubScreen.OnBackButton()) return true; if (screenStack.CurrentScreen != null && !(screenStack.CurrentScreen is LoungeSubScreen)) diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 2c5c889154..ab66241a77 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -11,7 +11,6 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Overlays; @@ -38,7 +37,7 @@ namespace osu.Game.Screens public string Description => Title; - public virtual bool AllowBackButton => true; + public virtual bool AllowUserExit => true; public virtual bool ShowFooter => false; @@ -57,9 +56,14 @@ namespace osu.Game.Screens IBindable IOsuScreen.OverlayActivationMode => OverlayActivationMode; - public readonly Bindable BackButtonState = new Bindable(Visibility.Visible); + /// + /// The initial visibility state of the back button when this screen is entered for the first time. + /// + protected virtual bool InitialBackButtonVisibility => AllowUserExit; - IBindable IOsuScreen.BackButtonState => BackButtonState; + public readonly Bindable BackButtonVisibility; + + IBindable IOsuScreen.BackButtonVisibility => BackButtonVisibility; public virtual bool CursorVisible => true; @@ -159,6 +163,7 @@ namespace osu.Game.Screens Origin = Anchor.Centre; OverlayActivationMode = new Bindable(InitialOverlayActivationMode); + BackButtonVisibility = new Bindable(InitialBackButtonVisibility); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e9722350bd..f4e3e6f434 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Play /// public event Action OnGameplayStarted; - public override bool AllowBackButton => false; // handled by HoldForMenuButton + public override bool AllowUserExit => false; // handled by HoldForMenuButton protected override bool PlayExitSound => !isRestarting; diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index a6e171ba02..49db0f05bd 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -478,7 +478,7 @@ namespace osu.Game.Screens.Play if (quickRestart) { - BackButtonState.Value = Visibility.Hidden; + BackButtonVisibility.Value = false; // A quick restart starts by triggering a fade to black AddInternal(quickRestartBlackLayer = new Box @@ -499,7 +499,7 @@ namespace osu.Game.Screens.Play .ScaleTo(1) .FadeInFromZero(500, Easing.OutQuint); - this.Delay(quick_restart_initial_delay).Schedule(() => BackButtonState.Value = Visibility.Visible); + this.Delay(quick_restart_initial_delay).Schedule(() => BackButtonVisibility.Value = true); } else { diff --git a/osu.Game/Screens/StartupScreen.cs b/osu.Game/Screens/StartupScreen.cs index 9e04a238eb..0724327a9f 100644 --- a/osu.Game/Screens/StartupScreen.cs +++ b/osu.Game/Screens/StartupScreen.cs @@ -10,7 +10,7 @@ namespace osu.Game.Screens /// public abstract partial class StartupScreen : OsuScreen { - public override bool AllowBackButton => false; + public override bool AllowUserExit => false; public override bool HideOverlaysOnEnter => true; From 16d8b1138562d4350726ea0b3ed3053d73f805f0 Mon Sep 17 00:00:00 2001 From: HenintsoaSky Date: Wed, 27 Nov 2024 00:53:22 +0300 Subject: [PATCH 066/326] A toggle for star fountains --- changes.patch | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 changes.patch diff --git a/changes.patch b/changes.patch new file mode 100644 index 0000000000..e69de29bb2 From 9083daf3630ae230cccab5f469369906d9cc5d48 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Tue, 26 Nov 2024 20:04:35 -0500 Subject: [PATCH 067/326] Fix epic code failure I wasn't feeling well last night. --- .../OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs index b659a98802..22bab7eb93 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs @@ -20,8 +20,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { } - protected override APIRequest CreateScoreRequest() => Score == null - ? new ShowPlaylistScoreRequest(RoomId, PlaylistItem.ID, Score?.OnlineID ?? -1) + protected override APIRequest CreateScoreRequest() => Score != null + ? new ShowPlaylistScoreRequest(RoomId, PlaylistItem.ID, Score.OnlineID) : new ShowPlaylistUserScoreRequest(RoomId, PlaylistItem.ID, API.LocalUser.Value.Id); protected override ScoreInfo[] PerformSuccessCallback(Action> callback, List scores, MultiplayerScores? pivot = null) From a477bb7bfec1422b18c17c88f8012807838dc7e2 Mon Sep 17 00:00:00 2001 From: HenintsoaSky Date: Wed, 27 Nov 2024 07:38:33 +0300 Subject: [PATCH 068/326] Renaming of 'StarFountainEnabled' --- osu.Game/Screens/Play/KiaiGameplayFountains.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/KiaiGameplayFountains.cs b/osu.Game/Screens/Play/KiaiGameplayFountains.cs index a6b2cd6fdb..fd9596c838 100644 --- a/osu.Game/Screens/Play/KiaiGameplayFountains.cs +++ b/osu.Game/Screens/Play/KiaiGameplayFountains.cs @@ -20,12 +20,12 @@ namespace osu.Game.Screens.Play private StarFountain leftFountain = null!; private StarFountain rightFountain = null!; - private Bindable kiaiStarFountainsEnabled = null!; + private Bindable kiaiStarFountains = null!; [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - kiaiStarFountainsEnabled = config.GetBindable(OsuSetting.StarFountains); + kiaiStarFountains = config.GetBindable(OsuSetting.StarFountains); RelativeSizeAxes = Axes.Both; @@ -54,10 +54,7 @@ namespace osu.Game.Screens.Play { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - if (!kiaiStarFountainsEnabled.Value) - return; - - if (!kiaiStarFountainsEnabled.Value) + if (!kiaiStarFountains.Value) return; if (effectPoint.KiaiMode && !isTriggered) From aa3d3a6344dd9428840236119132aba023874d63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Nov 2024 14:24:57 +0900 Subject: [PATCH 069/326] Remove unnecessary local subscription in `BeatmapCarousel` Not sure why I left this around during the refactor. This is 100% handled by the `DetachedBeatmapStore`. Removing this subscription reduces overheads by a huge amount for users with large beatmap databases. My hypothesis is that subscriptions are more expensive based on **the number of results matching**. This one matches almost every beatmap so removing it is a large win. --- osu.Game/Screens/Select/BeatmapCarousel.cs | 46 ---------------------- 1 file changed, 46 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5e1e0ce615..fc7c7989e2 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -29,7 +29,6 @@ using osu.Game.Input.Bindings; using osu.Game.Screens.Select.Carousel; using osuTK; using osuTK.Input; -using Realms; namespace osu.Game.Screens.Select { @@ -207,8 +206,6 @@ namespace osu.Game.Screens.Select private CarouselRoot root; - private IDisposable? subscriptionBeatmaps; - private readonly DrawablePool setPool = new DrawablePool(100); private Sample? spinSample; @@ -258,13 +255,6 @@ namespace osu.Game.Screens.Select } } - protected override void LoadComplete() - { - base.LoadComplete(); - - subscriptionBeatmaps = realm.RegisterForNotifications(r => r.All().Where(b => !b.Hidden), beatmapsChanged); - } - private readonly HashSet setsRequiringUpdate = new HashSet(); private readonly HashSet setsRequiringRemoval = new HashSet(); @@ -366,35 +356,6 @@ namespace osu.Game.Screens.Select BeatmapSetInfo? fetchFromID(Guid id) => realm.Realm.Find(id); } - private void beatmapsChanged(IRealmCollection sender, ChangeSet? changes) - { - // we only care about actual changes in hidden status. - if (changes == null) - return; - - bool changed = false; - - foreach (int i in changes.InsertedIndices) - { - var beatmapInfo = sender[i]; - var beatmapSet = beatmapInfo.BeatmapSet; - - Debug.Assert(beatmapSet != null); - - // 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 existingSets) - && existingSets.SelectMany(s => s.Beatmaps).All(b => b.BeatmapInfo.ID != beatmapInfo.ID)) - { - updateBeatmapSet(beatmapSet.Detach()); - changed = true; - } - } - - if (changed) - invalidateAfterChange(); - } - public void RemoveBeatmapSet(BeatmapSetInfo beatmapSet) => Schedule(() => { removeBeatmapSet(beatmapSet.ID); @@ -1292,12 +1253,5 @@ namespace osu.Game.Screens.Select return ScrollableExtent * ((scrollbarPosition - top_padding) / (ScrollbarMovementExtent - (top_padding + bottom_padding))); } } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - subscriptionBeatmaps?.Dispose(); - } } } From dfbccc2144cfe509d1e49bdaaeaa0e3f4e62d334 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 00:55:02 -0500 Subject: [PATCH 070/326] Knock some sense into the playlists results screen implementation As we're moving towards using the `/playlist//scores/` endpoint, the existing playlists results screen classes needed some restructuring. --- .../TestScenePlaylistsResultsScreen.cs | 136 +++++++++++++----- .../DailyChallenge/DailyChallenge.cs | 2 +- .../Multiplayer/MultiplayerResultsScreen.cs | 2 +- .../Playlists/PlaylistItemResultsScreen.cs | 4 +- .../PlaylistItemScoreResultsScreen.cs | 14 +- .../PlaylistItemUserBestResultsScreen.cs | 41 ++++++ .../PlaylistItemUserResultsScreen.cs | 48 ------- .../OnlinePlay/Playlists/PlaylistsPlayer.cs | 2 +- .../Playlists/PlaylistsRoomSubScreen.cs | 6 +- 9 files changed, 161 insertions(+), 94 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserBestResultsScreen.cs delete mode 100644 osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 6ccbcd2859..c288b04da2 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Net; using Newtonsoft.Json.Linq; using NUnit.Framework; +using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Graphics.Containers; @@ -31,7 +32,7 @@ namespace osu.Game.Tests.Visual.Playlists private const int scores_per_result = 10; private const int real_user_position = 200; - private TestResultsScreen resultsScreen = null!; + private ResultsScreen resultsScreen = null!; private int lowestScoreId; // Score ID of the lowest score in the list. private int highestScoreId; // Score ID of the highest score in the list. @@ -68,11 +69,11 @@ namespace osu.Game.Tests.Visual.Playlists } [Test] - public void TestShowWithUserScore() + public void TestShowUserScore() { AddStep("bind user score info handler", () => bindHandler(userScore: userScore)); - createResults(() => userScore); + createResultsWithScore(() => userScore); waitForDisplay(); AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineID).State == PanelState.Expanded); @@ -81,11 +82,24 @@ namespace osu.Game.Tests.Visual.Playlists } [Test] - public void TestShowNullUserScore() + public void TestShowUserBest() + { + AddStep("bind user score info handler", () => bindHandler(userScore: userScore)); + + createUserBestResults(); + waitForDisplay(); + + AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.UserID == userScore.UserID).State == PanelState.Expanded); + AddAssert($"score panel position is {real_user_position}", + () => this.ChildrenOfType().Single(p => p.Score.UserID == userScore.UserID).ScorePosition.Value == real_user_position); + } + + [Test] + public void TestShowNonUserScores() { AddStep("bind user score info handler", () => bindHandler()); - createResults(); + createUserBestResults(); waitForDisplay(); AddAssert("top score selected", () => this.ChildrenOfType().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded); @@ -96,7 +110,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("bind user score info handler", () => bindHandler(true, userScore)); - createResults(() => userScore); + createResultsWithScore(() => userScore); waitForDisplay(); AddAssert("more than 1 panel displayed", () => this.ChildrenOfType().Count() > 1); @@ -104,11 +118,11 @@ namespace osu.Game.Tests.Visual.Playlists } [Test] - public void TestShowNullUserScoreWithDelay() + public void TestShowNonUserScoresWithDelay() { AddStep("bind delayed handler", () => bindHandler(true)); - createResults(); + createUserBestResults(); waitForDisplay(); AddAssert("top score selected", () => this.ChildrenOfType().OrderByDescending(p => p.Score.TotalScore).First().State == PanelState.Expanded); @@ -119,7 +133,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("bind delayed handler", () => bindHandler(true)); - createResults(); + createUserBestResults(); waitForDisplay(); for (int i = 0; i < 2; i++) @@ -127,13 +141,16 @@ namespace osu.Game.Tests.Visual.Playlists int beforePanelCount = 0; AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType().Count()); - AddStep("scroll to right", () => resultsScreen.ScorePanelList.ChildrenOfType().Single().ScrollToEnd(false)); + AddStep("scroll to right", () => resultsScreen.ChildrenOfType().Single().ChildrenOfType().Single().ScrollToEnd(false)); + + AddAssert("right loading spinner shown", () => + resultsScreen.ChildrenOfType().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Visible); - 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); + AddAssert("right loading spinner hidden", () => + resultsScreen.ChildrenOfType().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Hidden); } } @@ -142,29 +159,36 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("bind delayed handler with scores", () => bindHandler(delayed: true)); - createResults(); + createUserBestResults(); waitForDisplay(); int beforePanelCount = 0; AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType().Count()); - AddStep("scroll to right", () => resultsScreen.ScorePanelList.ChildrenOfType().Single().ScrollToEnd(false)); + AddStep("scroll to right", () => resultsScreen.ChildrenOfType().Single().ChildrenOfType().Single().ScrollToEnd(false)); + + AddAssert("right loading spinner shown", () => + resultsScreen.ChildrenOfType().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Visible); - 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); + AddAssert("right loading spinner hidden", () => + resultsScreen.ChildrenOfType().Single(l => l.Anchor == Anchor.CentreRight).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)); + AddStep("scroll to right", () => resultsScreen.ChildrenOfType().Single().ChildrenOfType().Single().ScrollToEnd(false)); + + AddAssert("right loading spinner shown", () => + resultsScreen.ChildrenOfType().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Visible); - 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("right loading spinner hidden", () => + resultsScreen.ChildrenOfType().Single(l => l.Anchor == Anchor.CentreRight).State.Value == Visibility.Hidden); + AddAssert("no placeholders shown", () => this.ChildrenOfType().Count(), () => Is.Zero); } @@ -173,7 +197,7 @@ namespace osu.Game.Tests.Visual.Playlists { AddStep("bind user score info handler", () => bindHandler(userScore: userScore)); - createResults(() => userScore); + createResultsWithScore(() => userScore); waitForDisplay(); AddStep("bind delayed handler", () => bindHandler(true)); @@ -183,30 +207,36 @@ namespace osu.Game.Tests.Visual.Playlists int beforePanelCount = 0; AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType().Count()); - AddStep("scroll to left", () => resultsScreen.ScorePanelList.ChildrenOfType().Single().ScrollToStart(false)); + AddStep("scroll to left", () => resultsScreen.ChildrenOfType().Single().ChildrenOfType().Single().ScrollToStart(false)); + + AddAssert("left loading spinner shown", () => + resultsScreen.ChildrenOfType().Single(l => l.Anchor == Anchor.CentreLeft).State.Value == Visibility.Visible); - AddAssert("left loading spinner shown", () => resultsScreen.LeftSpinner.State.Value == Visibility.Visible); waitForDisplay(); AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType().Count() == beforePanelCount + scores_per_result); - AddAssert("left loading spinner hidden", () => resultsScreen.LeftSpinner.State.Value == Visibility.Hidden); + AddAssert("left loading spinner hidden", () => + resultsScreen.ChildrenOfType().Single(l => l.Anchor == Anchor.CentreLeft).State.Value == Visibility.Hidden); } } + /// + /// Shows the with no scores provided by the API. + /// [Test] - public void TestShowWithNoScores() + public void TestShowUserBestWithNoScoresPresent() { AddStep("bind user score info handler", () => bindHandler(noScores: true)); - createResults(); - AddAssert("no scores visible", () => !resultsScreen.ScorePanelList.GetScorePanels().Any()); + createUserBestResults(); + AddAssert("no scores visible", () => !resultsScreen.ChildrenOfType().Single().GetScorePanels().Any()); AddAssert("placeholder shown", () => this.ChildrenOfType().Count(), () => Is.EqualTo(1)); } - private void createResults(Func? getScore = null) + private void createResultsWithScore(Func getScore) { AddStep("load results", () => { - LoadScreen(resultsScreen = new TestResultsScreen(getScore?.Invoke(), 1, new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + LoadScreen(resultsScreen = new TestScoreResultsScreen(getScore(), 1, new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { RulesetID = new OsuRuleset().RulesetInfo.OnlineID })); @@ -215,14 +245,27 @@ namespace osu.Game.Tests.Visual.Playlists AddUntilStep("wait for screen to load", () => resultsScreen.IsLoaded); } + private void createUserBestResults() + { + AddStep("load results", () => + { + LoadScreen(resultsScreen = new TestUserBestResultsScreen(1, new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + }, 2)); + }); + + AddUntilStep("wait for screen to load", () => resultsScreen.IsLoaded); + } + private void waitForDisplay() { AddUntilStep("wait for scores loaded", () => requestComplete // request handler may need to fire more than once to get scores. && totalCount > 0 - && resultsScreen.ScorePanelList.GetScorePanels().Count() == totalCount - && resultsScreen.ScorePanelList.AllPanelsVisible); + && resultsScreen.ChildrenOfType().Single().GetScorePanels().Count() == totalCount + && resultsScreen.ChildrenOfType().Single().AllPanelsVisible); AddWaitStep("wait for display", 5); } @@ -232,6 +275,7 @@ namespace osu.Game.Tests.Visual.Playlists switch (request) { case ShowPlaylistScoreRequest: + case ShowPlaylistUserScoreRequest: case IndexPlaylistScoresRequest: break; @@ -261,6 +305,14 @@ namespace osu.Game.Tests.Visual.Playlists break; + case ShowPlaylistUserScoreRequest u: + if (userScore == null) + triggerFail(u); + else + triggerSuccess(u, createUserResponse(userScore)); + + break; + case IndexPlaylistScoresRequest i: triggerSuccess(i, createIndexResponse(i, noScores)); break; @@ -314,7 +366,7 @@ namespace osu.Game.Tests.Visual.Playlists MaxCombo = userScore.MaxCombo, User = new APIUser { - Id = 2, + Id = 2 + i, Username = $"peppy{i}", CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }, @@ -329,7 +381,7 @@ namespace osu.Game.Tests.Visual.Playlists MaxCombo = userScore.MaxCombo, User = new APIUser { - Id = 2, + Id = 2 + i, Username = $"peppy{i}", CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }, @@ -363,7 +415,7 @@ namespace osu.Game.Tests.Visual.Playlists MaxCombo = 1000, User = new APIUser { - Id = 2, + Id = 2 + i, Username = $"peppy{i}", CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }, @@ -410,18 +462,32 @@ namespace osu.Game.Tests.Visual.Playlists }; } - private partial class TestResultsScreen : PlaylistItemUserResultsScreen + private partial class TestScoreResultsScreen : PlaylistItemScoreResultsScreen { public new LoadingSpinner LeftSpinner => base.LeftSpinner; public new LoadingSpinner CentreSpinner => base.CentreSpinner; public new LoadingSpinner RightSpinner => base.RightSpinner; public new ScorePanelList ScorePanelList => base.ScorePanelList; - public TestResultsScreen(ScoreInfo? score, int roomId, PlaylistItem playlistItem) + public TestScoreResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem) : base(score, roomId, playlistItem) { AllowRetry = true; } } + + private partial class TestUserBestResultsScreen : PlaylistItemUserBestResultsScreen + { + public new LoadingSpinner LeftSpinner => base.LeftSpinner; + public new LoadingSpinner CentreSpinner => base.CentreSpinner; + public new LoadingSpinner RightSpinner => base.RightSpinner; + public new ScorePanelList ScorePanelList => base.ScorePanelList; + + public TestUserBestResultsScreen(int roomId, PlaylistItem playlistItem, int userId) + : base(roomId, playlistItem, userId) + { + AllowRetry = true; + } + } } } diff --git a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs index 0dc7e7930a..13a282dd52 100644 --- a/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs +++ b/osu.Game/Screens/OnlinePlay/DailyChallenge/DailyChallenge.cs @@ -345,7 +345,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge private void presentScore(long id) { if (this.IsCurrentScreen()) - this.Push(new PlaylistItemScoreResultsScreen(room.RoomID!.Value, playlistItem, id)); + this.Push(new PlaylistItemScoreResultsScreen(id, room.RoomID!.Value, playlistItem)); } private void onRoomScoreSet(MultiplayerRoomScoreSetEvent e) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs index c439df82a6..6b3e8fea46 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerResultsScreen.cs @@ -7,7 +7,7 @@ using osu.Game.Screens.OnlinePlay.Playlists; namespace osu.Game.Screens.OnlinePlay.Multiplayer { - public partial class MultiplayerResultsScreen : PlaylistItemUserResultsScreen + public partial class MultiplayerResultsScreen : PlaylistItemScoreResultsScreen { public MultiplayerResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem) : base(score, roomId, playlistItem) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs index dc06b88823..81ae51bd1b 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemResultsScreen.cs @@ -191,8 +191,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { var scoreInfos = scores.Select(s => s.CreateScoreInfo(ScoreManager, Rulesets, PlaylistItem, Beatmap.Value.BeatmapInfo)).OrderByTotalScore().ToArray(); - // Invoke callback to add the scores. - callback.Invoke(scoreInfos); + // Invoke callback to add the scores. Exclude the score provided to this screen since it's added already. + callback.Invoke(scoreInfos.Where(s => s.OnlineID != Score?.OnlineID)); return scoreInfos; } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs index 32be7f21b0..05c03a4b28 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemScoreResultsScreen.cs @@ -11,13 +11,19 @@ using osu.Game.Scoring; namespace osu.Game.Screens.OnlinePlay.Playlists { /// - /// Shows a selected arbitrary score for a playlist item, with scores around included. + /// Shows a given score in a playlist item, with scores around included. /// public partial class PlaylistItemScoreResultsScreen : PlaylistItemResultsScreen { private readonly long scoreId; - public PlaylistItemScoreResultsScreen(long roomId, PlaylistItem playlistItem, long scoreId) + public PlaylistItemScoreResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem) + : base(score, roomId, playlistItem) + { + scoreId = score.OnlineID; + } + + public PlaylistItemScoreResultsScreen(long scoreId, long roomId, PlaylistItem playlistItem) : base(null, roomId, playlistItem) { this.scoreId = scoreId; @@ -28,9 +34,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected override ScoreInfo[] PerformSuccessCallback(Action> callback, List scores, MultiplayerScores? pivot = null) { var scoreInfos = base.PerformSuccessCallback(callback, scores, pivot); - - Schedule(() => SelectedScore.Value ??= scoreInfos.SingleOrDefault(score => score.OnlineID == scoreId)); - + Schedule(() => SelectedScore.Value ??= scoreInfos.SingleOrDefault(s => s.OnlineID == scoreId)); return scoreInfos; } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserBestResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserBestResultsScreen.cs new file mode 100644 index 0000000000..5b20496dba --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserBestResultsScreen.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 System; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Online.API; +using osu.Game.Online.Rooms; +using osu.Game.Scoring; + +namespace osu.Game.Screens.OnlinePlay.Playlists +{ + /// + /// Shows a user's best score in a playlist item, with scores around included. + /// + public partial class PlaylistItemUserBestResultsScreen : PlaylistItemResultsScreen + { + private readonly int userId; + + public PlaylistItemUserBestResultsScreen(long roomId, PlaylistItem playlistItem, int userId) + : base(null, roomId, playlistItem) + { + this.userId = userId; + } + + protected override APIRequest CreateScoreRequest() => new ShowPlaylistUserScoreRequest(RoomId, PlaylistItem.ID, userId); + + protected override ScoreInfo[] PerformSuccessCallback(Action> callback, List scores, MultiplayerScores? pivot = null) + { + var scoreInfos = base.PerformSuccessCallback(callback, scores, pivot); + + Schedule(() => + { + // Prefer selecting the local user's score, or otherwise default to the first visible score. + SelectedScore.Value ??= scoreInfos.FirstOrDefault(s => s.UserID == userId) ?? scoreInfos.FirstOrDefault(); + }); + + return scoreInfos; + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.cs deleted file mode 100644 index 22bab7eb93..0000000000 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistItemUserResultsScreen.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. - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Game.Online.API; -using osu.Game.Online.Rooms; -using osu.Game.Scoring; - -namespace osu.Game.Screens.OnlinePlay.Playlists -{ - /// - /// Shows the user's submitted score in a given playlist item, with scores around included. - /// - public partial class PlaylistItemUserResultsScreen : PlaylistItemResultsScreen - { - public PlaylistItemUserResultsScreen(ScoreInfo? score, long roomId, PlaylistItem playlistItem) - : base(score, roomId, playlistItem) - { - } - - protected override APIRequest CreateScoreRequest() => Score != null - ? new ShowPlaylistScoreRequest(RoomId, PlaylistItem.ID, Score.OnlineID) - : new ShowPlaylistUserScoreRequest(RoomId, PlaylistItem.ID, API.LocalUser.Value.Id); - - protected override ScoreInfo[] PerformSuccessCallback(Action> callback, List scores, MultiplayerScores? pivot = null) - { - var scoreInfos = scores.Select(s => s.CreateScoreInfo(ScoreManager, Rulesets, PlaylistItem, Beatmap.Value.BeatmapInfo)).OrderByTotalScore().ToArray(); - - // Select a score if we don't already have one selected. - // Note: This is done before the callback so that the panel list centres on the selected score before panels are added (eliminating initial scroll). - if (SelectedScore.Value == null) - { - Schedule(() => - { - // Prefer selecting the local user's score, or otherwise default to the first visible score. - SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.OnlineID == API.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); - }); - } - - // Invoke callback to add the scores. Exclude the user's current score which was added previously. - callback.Invoke(scoreInfos.Where(s => s.OnlineID != Score?.OnlineID)); - - return scoreInfos; - } - } -} diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index 7ca09b5563..b82c2404ab 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists protected override ResultsScreen CreateResults(ScoreInfo score) { Debug.Assert(Room.RoomID != null); - return new PlaylistItemUserResultsScreen(score, Room.RoomID.Value, PlaylistItem) + return new PlaylistItemScoreResultsScreen(score, Room.RoomID.Value, PlaylistItem) { AllowRetry = true, ShowUserStatistics = true, diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 44d1841fb8..1aaae60195 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -12,6 +12,7 @@ using osu.Framework.Logging; using osu.Framework.Screens; using osu.Game.Graphics.Cursor; using osu.Game.Input; +using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; @@ -32,6 +33,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private readonly IBindable isIdle = new BindableBool(); + [Resolved] + private IAPIProvider api { get; set; } = null!; + [Resolved(CanBeNull = true)] private IdleTracker? idleTracker { get; set; } @@ -143,7 +147,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists RequestResults = item => { Debug.Assert(Room.RoomID != null); - ParentScreen?.Push(new PlaylistItemUserResultsScreen(null, Room.RoomID.Value, item)); + ParentScreen?.Push(new PlaylistItemUserBestResultsScreen(Room.RoomID.Value, item, api.LocalUser.Value.Id)); } } }, From 5260a401d4a96241f4ea21cc88e7a8b840193c61 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Nov 2024 15:09:54 +0900 Subject: [PATCH 071/326] Use `RealmLive` in `SaveFailedScoreButton` This also optimises the manager classes to better support `Live` usage where the managed object is already in a good state (ie. doesn't require re-fetching). --- osu.Game/Beatmaps/BeatmapManager.cs | 6 +++++- osu.Game/Scoring/ScoreManager.cs | 12 +++++++++--- osu.Game/Screens/Play/SaveFailedScoreButton.cs | 10 +++++----- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4191771116..f1ce977d96 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -559,7 +559,11 @@ namespace osu.Game.Beatmaps // If we seem to be missing files, now is a good time to re-fetch. bool missingFiles = beatmapInfo.BeatmapSet?.Files.Count == 0; - if (refetch || beatmapInfo.IsManaged || missingFiles) + if (beatmapInfo.IsManaged) + { + beatmapInfo = beatmapInfo.Detach(); + } + else if (refetch || missingFiles) { Guid id = beatmapInfo.ID; beatmapInfo = Realm.Run(r => r.Find(id)?.Detach()) ?? beatmapInfo; diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index e3601fe91e..3177873182 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -78,7 +78,7 @@ namespace osu.Game.Scoring /// Perform a lookup query on available s. /// /// The query. - /// The first result for the provided query, or null if no results were found. + /// The first result for the provided query in its detached form, or null if no results were found. public ScoreInfo? Query(Expression> query) { return Realm.Run(r => r.All().FirstOrDefault(query)?.Detach()); @@ -88,8 +88,14 @@ namespace osu.Game.Scoring { ScoreInfo? databasedScoreInfo = null; - if (originalScoreInfo is ScoreInfo scoreInfo && !string.IsNullOrEmpty(scoreInfo.Hash)) - databasedScoreInfo = Query(s => s.Hash == scoreInfo.Hash); + if (originalScoreInfo is ScoreInfo scoreInfo) + { + if (scoreInfo.IsManaged) + return scoreInfo.Detach(); + + if (!string.IsNullOrEmpty(scoreInfo.Hash)) + databasedScoreInfo = Query(s => s.Hash == scoreInfo.Hash); + } if (originalScoreInfo.OnlineID > 0) databasedScoreInfo ??= Query(s => s.OnlineID == originalScoreInfo.OnlineID); diff --git a/osu.Game/Screens/Play/SaveFailedScoreButton.cs b/osu.Game/Screens/Play/SaveFailedScoreButton.cs index 4f665b87e8..e5c9e115d1 100644 --- a/osu.Game/Screens/Play/SaveFailedScoreButton.cs +++ b/osu.Game/Screens/Play/SaveFailedScoreButton.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Play private readonly Func>? importFailedScore; - private ScoreInfo? importedScore; + private Live? importedScore; private DownloadButton button = null!; @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Play switch (state.Value) { case DownloadState.LocallyAvailable: - game?.PresentScore(importedScore, ScorePresentType.Gameplay); + game?.PresentScore(importedScore?.Value, ScorePresentType.Gameplay); break; case DownloadState.NotDownloaded: @@ -65,7 +65,7 @@ namespace osu.Game.Screens.Play { Task.Run(importFailedScore).ContinueWith(t => { - importedScore = realm.Run(r => r.Find(t.GetResultSafely().ID)?.Detach()); + importedScore = realm.Run?>(r => r.Find(t.GetResultSafely().ID)?.ToLive(realm)); Schedule(() => state.Value = importedScore != null ? DownloadState.LocallyAvailable : DownloadState.NotDownloaded); }).FireAndForget(); } @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Play if (player != null) { - importedScore = realm.Run(r => r.Find(player.Score.ScoreInfo.ID)?.Detach()); + importedScore = realm.Run(r => r.Find(player.Score.ScoreInfo.ID)?.ToLive(realm)); state.Value = importedScore != null ? DownloadState.LocallyAvailable : DownloadState.NotDownloaded; } @@ -137,7 +137,7 @@ namespace osu.Game.Screens.Play { if (state.NewValue != DownloadState.LocallyAvailable) return; - if (importedScore != null) scoreManager.Export(importedScore); + if (importedScore != null) scoreManager.Export(importedScore.Value); this.state.ValueChanged -= exportWhenReady; } From 4fcc76270a276421c998f3e9b668b110bd69e207 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Nov 2024 15:46:55 +0900 Subject: [PATCH 072/326] Ensure events are unbound on disposal as a safety --- .../Overlays/Chat/ChannelList/ChannelList.cs | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index 3e8c71e645..f027888962 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -171,10 +171,12 @@ namespace osu.Game.Overlays.Chat.ChannelList public partial class ChannelGroup : FillFlowContainer { + private readonly bool sortByRecent; public readonly ChannelListItemFlow ItemFlow; public ChannelGroup(LocalisableString label, bool sortByRecent) { + this.sortByRecent = sortByRecent; Direction = FillDirection.Vertical; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -217,21 +219,39 @@ namespace osu.Game.Overlays.Chat.ChannelList { ItemFlow.Add(item); - item.Channel.NewMessagesArrived += newMessagesArrived; - item.Channel.PendingMessageResolved += pendingMessageResolved; + if (sortByRecent) + { + item.Channel.NewMessagesArrived += newMessagesArrived; + item.Channel.PendingMessageResolved += pendingMessageResolved; + } ItemFlow.Reflow(); } public void RemoveChannel(ChannelListItem item) { - item.Channel.NewMessagesArrived -= newMessagesArrived; - item.Channel.PendingMessageResolved -= pendingMessageResolved; + if (sortByRecent) + { + item.Channel.NewMessagesArrived -= newMessagesArrived; + item.Channel.PendingMessageResolved -= pendingMessageResolved; + } + ItemFlow.Remove(item, true); } private void pendingMessageResolved(LocalEchoMessage _, Message __) => ItemFlow.Reflow(); private void newMessagesArrived(IEnumerable _) => ItemFlow.Reflow(); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + foreach (var item in ItemFlow) + { + item.Channel.NewMessagesArrived -= newMessagesArrived; + item.Channel.PendingMessageResolved -= pendingMessageResolved; + } + } } private partial class ChannelSearchTextBox : BasicSearchTextBox From c3ac6d7fe5a89888e62ebe88a55ac5c21f0efa33 Mon Sep 17 00:00:00 2001 From: HenintsoaSky Date: Wed, 27 Nov 2024 10:22:30 +0300 Subject: [PATCH 073/326] Delete changes.patch oops --- changes.patch | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 changes.patch diff --git a/changes.patch b/changes.patch deleted file mode 100644 index e69de29bb2..0000000000 From 9c707ed3418b5bde88dd76d5d5f790fb69decb1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Nov 2024 16:47:54 +0900 Subject: [PATCH 074/326] Rename class and fix padding considerations --- ...ings.cs => ExpandingPlayerSettingsOverlay.cs} | 16 ++++++++++++---- .../Multiplayer/Spectate/MultiSpectatorScreen.cs | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) rename osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/{MultiSpectatorSettings.cs => ExpandingPlayerSettingsOverlay.cs} (75%) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ExpandingPlayerSettingsOverlay.cs similarity index 75% rename from osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs rename to osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ExpandingPlayerSettingsOverlay.cs index dfb26d104a..6ad53d4aeb 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorSettings.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ExpandingPlayerSettingsOverlay.cs @@ -8,15 +8,21 @@ using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play.HUD; +using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { - public partial class MultiSpectatorSettings : ExpandingContainer + public partial class ExpandingPlayerSettingsOverlay : ExpandingContainer { - public const float CONTRACTED_WIDTH = 30; - public const int EXPANDED_WIDTH = 300; + private const float padding = 10; - public MultiSpectatorSettings() + public const float CONTRACTED_WIDTH = button_size + padding * 2; + public const float EXPANDED_WIDTH = player_settings_width + button_size + padding * 3; + + private const float player_settings_width = 270; + private const float button_size = IconButton.DEFAULT_BUTTON_SIZE; + + public ExpandingPlayerSettingsOverlay() : base(CONTRACTED_WIDTH, EXPANDED_WIDTH) { Origin = Anchor.TopRight; @@ -30,6 +36,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Anchor = Anchor.TopLeft, Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, + Margin = new MarginPadding(padding), + Spacing = new Vector2(padding), Children = new Drawable[] { new IconButton diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 841aaf7a45..44d26a6dd0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -127,7 +127,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { ReadyToStart = performInitialSeek, }, - new MultiSpectatorSettings() + new ExpandingPlayerSettingsOverlay() }; for (int i = 0; i < Users.Count; i++) From 782ce24ca67c08525ff8c4f3d4382c5627b6af8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Nov 2024 17:09:13 +0900 Subject: [PATCH 075/326] Move player settings out of right flow --- osu.Game/Screens/Play/HUDOverlay.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index a6c2405eb6..62d9686aad 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -146,7 +146,6 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { ModDisplay = CreateModsContainer(), - PlayerSettingsOverlay = CreatePlayerSettingsOverlay(), } }, bottomRightElements = new FillFlowContainer @@ -164,6 +163,7 @@ namespace osu.Game.Screens.Play HoldToQuit = CreateHoldForMenuButton(), } }, + PlayerSettingsOverlay = new PlayerSettingsOverlay(), LeaderboardFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -173,7 +173,7 @@ namespace osu.Game.Screens.Play }, }; - hideTargets = new List { mainComponents, topRightElements }; + hideTargets = new List { mainComponents, topRightElements, PlayerSettingsOverlay }; if (rulesetComponents != null) hideTargets.Add(rulesetComponents); @@ -389,8 +389,6 @@ namespace osu.Game.Screens.Play Origin = Anchor.TopRight, }; - protected PlayerSettingsOverlay CreatePlayerSettingsOverlay() => new PlayerSettingsOverlay(); - public bool OnPressed(KeyBindingPressEvent e) { if (e.Repeat) From 7fdf13911b7500a4cc9b08259655aabc49142142 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Nov 2024 17:47:27 +0900 Subject: [PATCH 076/326] Adjust the colour of non-pinned settings groups' headers to be more legible --- osu.Game/Overlays/SettingsToolboxGroup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index 53849fa53c..f8cf218564 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -184,7 +184,7 @@ namespace osu.Game.Overlays content.ResizeHeightTo(0, animate ? transition_duration : 0, Easing.OutQuint); } - headerContent.FadeColour(Expanded.Value ? Color4.White : OsuColour.Gray(0.5f), 200, Easing.OutQuint); + headerContent.FadeColour(Expanded.Value ? Color4.White : OsuColour.Gray(0.7f), 200, Easing.OutQuint); } private void updateFadeState() From 0f739418084d99925a0e91691ed459122aec23d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Nov 2024 17:47:42 +0900 Subject: [PATCH 077/326] Combine new implementation back into the old one and use everywhere --- .../ExpandingPlayerSettingsOverlay.cs | 68 -------------- .../Spectate/MultiSpectatorScreen.cs | 2 +- .../Screens/Play/HUD/PlayerSettingsOverlay.cs | 88 +++++++++++++++---- 3 files changed, 74 insertions(+), 84 deletions(-) delete mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ExpandingPlayerSettingsOverlay.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ExpandingPlayerSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ExpandingPlayerSettingsOverlay.cs deleted file mode 100644 index 6ad53d4aeb..0000000000 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/ExpandingPlayerSettingsOverlay.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 osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Play.HUD; -using osuTK; - -namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate -{ - public partial class ExpandingPlayerSettingsOverlay : ExpandingContainer - { - private const float padding = 10; - - public const float CONTRACTED_WIDTH = button_size + padding * 2; - public const float EXPANDED_WIDTH = player_settings_width + button_size + padding * 3; - - private const float player_settings_width = 270; - private const float button_size = IconButton.DEFAULT_BUTTON_SIZE; - - public ExpandingPlayerSettingsOverlay() - : base(CONTRACTED_WIDTH, EXPANDED_WIDTH) - { - Origin = Anchor.TopRight; - Anchor = Anchor.TopRight; - - PlayerSettingsOverlay playerSettingsOverlay; - - InternalChild = new FillFlowContainer - { - Origin = Anchor.TopLeft, - Anchor = Anchor.TopLeft, - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding(padding), - Spacing = new Vector2(padding), - Children = new Drawable[] - { - new IconButton - { - Icon = FontAwesome.Solid.Cog, - Origin = Anchor.TopLeft, - Anchor = Anchor.TopLeft, - Action = () => Expanded.Toggle() - }, - playerSettingsOverlay = new PlayerSettingsOverlay - { - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft - } - } - }; - - playerSettingsOverlay.Show(); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - // Prevent unexpanding when hovering player settings - if (!Contains(e.ScreenSpaceMousePosition)) - base.OnHoverLost(e); - } - } -} diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 44d26a6dd0..33c3c60ed3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -127,7 +127,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { ReadyToStart = performInitialSeek, }, - new ExpandingPlayerSettingsOverlay() + new PlayerSettingsOverlay() }; for (int i = 0; i < Users.Count; i++) diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index a2b49f6302..e68ca4da7a 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -1,46 +1,104 @@ -// 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.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osuTK; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play.PlayerSettings; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD { - public partial class PlayerSettingsOverlay : VisibilityContainer + public partial class PlayerSettingsOverlay : ExpandingContainer { + public VisualSettings VisualSettings { get; private set; } + + private const float padding = 10; + + public const float CONTRACTED_WIDTH = button_size + padding * 2; + public const float EXPANDED_WIDTH = player_settings_width + padding * 2; + + private const float player_settings_width = 270; + private const float button_size = IconButton.DEFAULT_BUTTON_SIZE; + + public override void Show() => this.FadeIn(fade_duration); + public override void Hide() => this.FadeOut(fade_duration); + private const int fade_duration = 200; - public readonly VisualSettings VisualSettings; + // we'll handle this ourselves because we have slightly custom logic. + protected override bool ExpandOnHover => false; protected override Container Content => content; private readonly FillFlowContainer content; - public PlayerSettingsOverlay() - { - Anchor = Anchor.TopRight; - Origin = Anchor.TopRight; - AutoSizeAxes = Axes.Both; + private readonly IconButton button; - InternalChild = content = new FillFlowContainer + private InputManager inputManager = null!; + + public PlayerSettingsOverlay() + : base(0, EXPANDED_WIDTH) + { + Origin = Anchor.TopRight; + Anchor = Anchor.TopRight; + + base.Content.Add(content = new FillFlowContainer { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, Direction = FillDirection.Vertical, Spacing = new Vector2(0, 20), + Margin = new MarginPadding(padding), Children = new PlayerSettingsGroup[] { VisualSettings = new VisualSettings { Expanded = { Value = false } }, new AudioSettings { Expanded = { Value = false } } } - }; + }); + + AddInternal(button = new IconButton + { + Icon = FontAwesome.Solid.Cog, + Origin = Anchor.TopRight, + Anchor = Anchor.TopLeft, + Margin = new MarginPadding(5), + Action = () => Expanded.Toggle() + }); + + AddInternal(new Box + { + Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0), Color4.Black.Opacity(0.8f)), + Depth = float.MaxValue, + RelativeSizeAxes = Axes.Both, + }); } - protected override void PopIn() => this.FadeIn(fade_duration); - protected override void PopOut() => this.FadeOut(fade_duration); + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager()!; + } + + protected override void Update() + { + base.Update(); + + Expanded.Value = inputManager.CurrentState.Mouse.Position.X >= button.ScreenSpaceDrawQuad.TopLeft.X; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + // handle un-expanding manually because our children do weird hover blocking stuff. + } public void AddAtStart(PlayerSettingsGroup drawable) => content.Insert(-1, drawable); } From b70fb4b0fe747977871c04a49feaad1a9b175654 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 05:52:43 -0500 Subject: [PATCH 078/326] Add `FormBeatmapFileSelector` for intermediate user-choice step --- .../UserInterfaceV2/FormFileSelector.cs | 30 +++- .../Edit/Setup/FormBeatmapFileSelector.cs | 161 ++++++++++++++++++ 2 files changed, 186 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs diff --git a/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs index 81023417a5..5fdf453fc4 100644 --- a/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/FormFileSelector.cs @@ -242,20 +242,26 @@ namespace osu.Game.Graphics.UserInterfaceV2 Task ICanAcceptFiles.Import(ImportTask[] tasks, ImportParameters parameters) => throw new NotImplementedException(); + protected virtual FileChooserPopover CreatePopover(string[] handledExtensions, Bindable current, string? chooserPath) => new FileChooserPopover(handledExtensions, current, chooserPath); + public Popover GetPopover() { - var popover = new FileChooserPopover(handledExtensions, Current, initialChooserPath); + var popover = CreatePopover(handledExtensions, Current, initialChooserPath); popoverState.UnbindBindings(); popoverState.BindTo(popover.State); return popover; } - private partial class FileChooserPopover : OsuPopover + protected partial class FileChooserPopover : OsuPopover { protected override string PopInSampleName => "UI/overlay-big-pop-in"; protected override string PopOutSampleName => "UI/overlay-big-pop-out"; - public FileChooserPopover(string[] handledExtensions, Bindable currentFile, string? chooserPath) + private readonly Bindable current = new Bindable(); + + protected OsuFileSelector FileSelector; + + public FileChooserPopover(string[] handledExtensions, Bindable current, string? chooserPath) : base(false) { Child = new Container @@ -264,12 +270,13 @@ namespace osu.Game.Graphics.UserInterfaceV2 // simplest solution to avoid underlying text to bleed through the bottom border // https://github.com/ppy/osu/pull/30005#issuecomment-2378884430 Padding = new MarginPadding { Bottom = 1 }, - Child = new OsuFileSelector(chooserPath, handledExtensions) + Child = FileSelector = new OsuFileSelector(chooserPath, handledExtensions) { RelativeSizeAxes = Axes.Both, - CurrentFile = { BindTarget = currentFile } }, }; + + this.current.BindTo(current); } [BackgroundDependencyLoader] @@ -292,6 +299,19 @@ namespace osu.Game.Graphics.UserInterfaceV2 } }); } + + protected override void LoadComplete() + { + base.LoadComplete(); + + FileSelector.CurrentFile.ValueChanged += f => + { + if (f.NewValue != null) + OnFileSelected(f.NewValue); + }; + } + + protected virtual void OnFileSelected(FileInfo file) => current.Value = file; } } } diff --git a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs new file mode 100644 index 0000000000..317ed1b903 --- /dev/null +++ b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs @@ -0,0 +1,161 @@ +// 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.IO; +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.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Screens.Edit.Setup +{ + /// + /// A type of dedicated to beatmap resources. + /// + /// + /// This expands on by adding an intermediate step before finalisation + /// to choose whether the selected file should be applied to the current difficulty or all difficulties in the set, + /// the user's choice is saved in before the file selection is finalised and propagated to . + /// + public partial class FormBeatmapFileSelector : FormFileSelector + { + private readonly bool multipleDifficulties; + + public readonly Bindable ApplyToAllDifficulties = new Bindable(true); + + public FormBeatmapFileSelector(bool multipleDifficulties, params string[] handledExtensions) + : base(handledExtensions) + { + this.multipleDifficulties = multipleDifficulties; + } + + protected override FileChooserPopover CreatePopover(string[] handledExtensions, Bindable current, string? chooserPath) + { + var popover = new BeatmapFileChooserPopover(handledExtensions, current, chooserPath, multipleDifficulties); + + popover.ApplyToAllDifficulties.ValueChanged += v => + { + Debug.Assert(v.NewValue != null); + ApplyToAllDifficulties.Value = v.NewValue.Value; + }; + + return popover; + } + + private partial class BeatmapFileChooserPopover : FileChooserPopover + { + private readonly bool multipleDifficulties; + + public readonly Bindable ApplyToAllDifficulties = new Bindable(); + + private Container changeScopeContainer = null!; + + public BeatmapFileChooserPopover(string[] handledExtensions, Bindable current, string? chooserPath, bool multipleDifficulties) + : base(handledExtensions, current, chooserPath) + { + this.multipleDifficulties = multipleDifficulties; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider, OsuColour colours) + { + Add(changeScopeContainer = new InputBlockingContainer + { + Alpha = 0f, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background6.Opacity(0.9f), + RelativeSizeAxes = Axes.Both, + }, + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + CornerRadius = 10f, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = colourProvider.Background5, + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 10f), + Margin = new MarginPadding(30), + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Apply this change to all difficulties?", + Margin = new MarginPadding { Bottom = 20f }, + }, + new RoundedButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 300f, + Text = "Apply to all difficulties", + Action = () => ApplyToAllDifficulties.Value = true, + BackgroundColour = colours.Red2, + }, + new RoundedButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 300f, + Text = "Only apply to this difficulty", + Action = () => ApplyToAllDifficulties.Value = false, + }, + } + } + } + }, + } + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + ApplyToAllDifficulties.ValueChanged += onChangeScopeSelected; + } + + protected override void OnFileSelected(FileInfo file) + { + if (multipleDifficulties) + changeScopeContainer.FadeIn(200, Easing.InQuint); + else + base.OnFileSelected(file); + } + + private void onChangeScopeSelected(ValueChangedEvent c) + { + if (c.NewValue == null) + return; + + Debug.Assert(FileSelector.CurrentFile.Value != null); + base.OnFileSelected(FileSelector.CurrentFile.Value); + } + } + } +} From efb68e423268a289898c1a5967d20fe73a58b78d Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 05:53:22 -0500 Subject: [PATCH 079/326] Refactor `ResourcesSection` to support new form of selection --- .../Screens/Edit/Setup/ResourcesSection.cs | 188 ++++++++---------- 1 file changed, 88 insertions(+), 100 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 90603a6366..70282878e0 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -9,7 +9,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Beatmaps; -using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osu.Game.Localisation; using osu.Game.Models; @@ -19,8 +18,8 @@ namespace osu.Game.Screens.Edit.Setup { public partial class ResourcesSection : SetupSection { - private FormFileSelector audioTrackChooser = null!; - private FormFileSelector backgroundChooser = null!; + private FormBeatmapFileSelector audioTrackChooser = null!; + private FormBeatmapFileSelector backgroundChooser = null!; public override LocalisableString Title => EditorSetupStrings.ResourcesHeader; @@ -40,7 +39,6 @@ namespace osu.Game.Screens.Edit.Setup private Editor? editor { get; set; } private SetupScreenHeaderBackground headerBackground = null!; - private RoundedButton syncResourcesButton = null!; [BackgroundDependencyLoader] private void load() @@ -51,25 +49,20 @@ namespace osu.Game.Screens.Edit.Setup Height = 110, }; + bool multipleDifficulties = working.Value.BeatmapSetInfo.Beatmaps.Count > 1; + Children = new Drawable[] { - backgroundChooser = new FormFileSelector(".jpg", ".jpeg", ".png") + backgroundChooser = new FormBeatmapFileSelector(multipleDifficulties, ".jpg", ".jpeg", ".png") { Caption = GameplaySettingsStrings.BackgroundHeader, PlaceholderText = EditorSetupStrings.ClickToSelectBackground, }, - audioTrackChooser = new FormFileSelector(".mp3", ".ogg") + audioTrackChooser = new FormBeatmapFileSelector(multipleDifficulties, ".mp3", ".ogg") { Caption = EditorSetupStrings.AudioTrack, PlaceholderText = EditorSetupStrings.ClickToSelectTrack, }, - syncResourcesButton = new RoundedButton - { - RelativeSizeAxes = Axes.X, - Text = EditorSetupStrings.ResourcesUpdateAllDifficulties, - Action = syncResources, - Enabled = { Value = false }, - } }; backgroundChooser.PreviewContainer.Add(headerBackground); @@ -84,39 +77,56 @@ namespace osu.Game.Screens.Edit.Setup audioTrackChooser.Current.BindValueChanged(audioTrackChanged); } - private string? newBackgroundFile; - private string? newAudioFile; - - public bool ChangeBackgroundImage(FileInfo source) + public bool ChangeBackgroundImage(FileInfo source, bool applyToAllDifficulties) { if (!source.Exists) return false; - var beatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; - string[] filenames = set.Files.Select(f => f.Filename).Where(f => - f.StartsWith(@"bg", StringComparison.OrdinalIgnoreCase) && - f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - - string currentFilename = working.Value.Metadata.BackgroundFile; - string? newFilename = null; - - var oldFile = set.GetFile(currentFilename); - - if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.BackgroundFile != currentFilename)) + if (applyToAllDifficulties) { - beatmaps.DeleteFile(set, oldFile); - newFilename = currentFilename; + string newFilename = $@"bg{source.Extension}"; + + foreach (var beatmapInSet in set.Beatmaps) + { + if (set.GetFile(beatmapInSet.Metadata.BackgroundFile) is RealmNamedFileUsage existingFile) + beatmaps.DeleteFile(set, existingFile); + + if (beatmapInSet.Metadata.BackgroundFile != newFilename) + { + beatmapInSet.Metadata.BackgroundFile = newFilename; + + if (!beatmapInSet.Equals(working.Value.BeatmapInfo)) + beatmaps.Save(beatmapInSet, beatmaps.GetWorkingBeatmap(beatmapInSet).Beatmap); + } + } + } + else + { + var beatmap = working.Value.BeatmapInfo; + + string[] filenames = set.Files.Select(f => f.Filename).Where(f => + f.StartsWith(@"bg", StringComparison.OrdinalIgnoreCase) && + f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); + + string currentFilename = working.Value.Metadata.BackgroundFile; + + var oldFile = set.GetFile(currentFilename); + string? newFilename = null; + + if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.BackgroundFile != currentFilename)) + { + beatmaps.DeleteFile(set, oldFile); + newFilename = currentFilename; + } + + newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"bg{source.Extension}"); + working.Value.Metadata.BackgroundFile = newFilename; } - newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"bg{source.Extension}"); - using (var stream = source.OpenRead()) - beatmaps.AddFile(set, stream, newFilename); - - working.Value.Metadata.BackgroundFile = newBackgroundFile = newFilename; - syncResourcesButton.Enabled.Value = set.Beatmaps.Count > 1; + beatmaps.AddFile(set, stream, working.Value.Metadata.BackgroundFile); editorBeatmap.SaveState(); @@ -126,36 +136,56 @@ namespace osu.Game.Screens.Edit.Setup return true; } - public bool ChangeAudioTrack(FileInfo source) + public bool ChangeAudioTrack(FileInfo source, bool applyToAllDifficulties) { if (!source.Exists) return false; - var beatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; - string[] filenames = set.Files.Select(f => f.Filename).Where(f => - f.StartsWith(@"audio", StringComparison.OrdinalIgnoreCase) && - f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - - string currentFilename = working.Value.Metadata.AudioFile; - string? newFilename = null; - - var oldFile = set.GetFile(currentFilename); - - if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.AudioFile != currentFilename)) + if (applyToAllDifficulties) { - beatmaps.DeleteFile(set, oldFile); - newFilename = currentFilename; + string newFilename = $@"audio{source.Extension}"; + + foreach (var beatmapInSet in set.Beatmaps) + { + if (set.GetFile(beatmapInSet.Metadata.AudioFile) is RealmNamedFileUsage existingFile) + beatmaps.DeleteFile(set, existingFile); + + if (beatmapInSet.Metadata.AudioFile != newFilename) + { + beatmapInSet.Metadata.AudioFile = newFilename; + + if (!beatmapInSet.Equals(working.Value.BeatmapInfo)) + beatmaps.Save(beatmapInSet, beatmaps.GetWorkingBeatmap(beatmapInSet).Beatmap); + } + } + } + else + { + var beatmap = working.Value.BeatmapInfo; + + string[] filenames = set.Files.Select(f => f.Filename).Where(f => + f.StartsWith(@"audio", StringComparison.OrdinalIgnoreCase) && + f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); + + string currentFilename = working.Value.Metadata.AudioFile; + + var oldFile = set.GetFile(currentFilename); + string? newFilename = null; + + if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.AudioFile != currentFilename)) + { + beatmaps.DeleteFile(set, oldFile); + newFilename = currentFilename; + } + + newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"audio{source.Extension}"); + working.Value.Metadata.AudioFile = newFilename; } - newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"audio{source.Extension}"); - using (var stream = source.OpenRead()) - beatmaps.AddFile(set, stream, newFilename); - - working.Value.Metadata.AudioFile = newAudioFile = newFilename; - updateSyncResourcesButton(); + beatmaps.AddFile(set, stream, working.Value.Metadata.AudioFile); editorBeatmap.SaveState(); music.ReloadCurrentTrack(); @@ -163,57 +193,15 @@ namespace osu.Game.Screens.Edit.Setup return true; } - private void updateSyncResourcesButton() - { - var set = working.Value.BeatmapSetInfo; - - syncResourcesButton.Enabled.Value = - (newBackgroundFile != null && set.Beatmaps.DistinctBy(b => b.Metadata.BackgroundFile, StringComparer.OrdinalIgnoreCase).Count() > 1) || - (newAudioFile != null && set.Beatmaps.DistinctBy(b => b.Metadata.AudioFile, StringComparer.OrdinalIgnoreCase).Count() > 1); - } - - private void syncResources() - { - var beatmap = working.Value.BeatmapInfo; - var set = working.Value.BeatmapSetInfo; - - foreach (var otherBeatmap in set.Beatmaps.Where(b => !b.Equals(beatmap))) - { - var otherWorking = beatmaps.GetWorkingBeatmap(otherBeatmap); - - if (newBackgroundFile != null && !string.Equals(otherBeatmap.Metadata.BackgroundFile, newBackgroundFile, StringComparison.OrdinalIgnoreCase)) - { - if (set.GetFile(otherBeatmap.Metadata.BackgroundFile) is RealmNamedFileUsage file) - beatmaps.DeleteFile(set, file); - - otherBeatmap.Metadata.BackgroundFile = newBackgroundFile; - } - - if (newAudioFile != null && !string.Equals(otherBeatmap.Metadata.AudioFile, newAudioFile, StringComparison.OrdinalIgnoreCase)) - { - if (set.GetFile(otherBeatmap.Metadata.AudioFile) is RealmNamedFileUsage file) - beatmaps.DeleteFile(set, file); - - otherBeatmap.Metadata.AudioFile = newAudioFile; - } - - beatmaps.Save(otherBeatmap, otherWorking.Beatmap); - } - - newAudioFile = null; - newBackgroundFile = null; - syncResourcesButton.Enabled.Value = false; - } - private void backgroundChanged(ValueChangedEvent file) { - if (file.NewValue == null || !ChangeBackgroundImage(file.NewValue)) + if (file.NewValue == null || !ChangeBackgroundImage(file.NewValue, backgroundChooser.ApplyToAllDifficulties.Value)) backgroundChooser.Current.Value = file.OldValue; } private void audioTrackChanged(ValueChangedEvent file) { - if (file.NewValue == null || !ChangeAudioTrack(file.NewValue)) + if (file.NewValue == null || !ChangeAudioTrack(file.NewValue, audioTrackChooser.ApplyToAllDifficulties.Value)) audioTrackChooser.Current.Value = file.OldValue; } } From 4b8094d0dbc5fd4d9e63511b0f59d62d7e7257d9 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 05:53:30 -0500 Subject: [PATCH 080/326] Update test coverage --- .../Editing/TestSceneEditorBeatmapCreation.cs | 208 ++++++++++-------- .../UserInterface/TestSceneFormControls.cs | 10 +- 2 files changed, 129 insertions(+), 89 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 9fabed346b..2817225f2b 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -15,8 +15,6 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Collections; using osu.Game.Database; -using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Localisation; using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; @@ -102,17 +100,7 @@ namespace osu.Game.Tests.Visual.Editing 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(); - - return setFile(TestResources.GetTestBeatmapForImport(), extractedFolder => - { - bool success = setup.ChildrenOfType().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"))); - Assert.That(Beatmap.Value.Metadata.AudioFile == "audio.mp3"); - return success; - }); - }); + AddAssert("switch track to real track", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3")); AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual); AddUntilStep("track length changed", () => Beatmap.Value.Track.Length > 60000); @@ -517,11 +505,105 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestSingleBackgroundFile() + { + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddAssert("set background", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg")); + + AddStep("save", () => Editor.Save()); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty"; + }); + + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty (1)"; + }); + + AddStep("switch to second difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(1))); + AddUntilStep("wait for editor load", () => Editor.IsLoaded); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("set background on second diff only", () => setBackground(applyToAllDifficulties: false, expected: "bg (1).jpg")); + AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); + AddStep("save", () => Editor.Save()); + + AddStep("switch to first difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + AddUntilStep("wait for editor load", () => Editor.IsLoaded); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("set background on first diff only", () => setBackground(applyToAllDifficulties: false, expected: "bg (2).jpg")); + AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (2).jpg")); + AddStep("save", () => Editor.Save()); + + AddAssert("set background on all diff", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg")); + AddAssert("all diff uses one background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.BackgroundFile == "bg.jpg")); + AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg.jpg")); + AddAssert("other files removed", () => !Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg" || f.Filename == "bg (2).jpg")); + } + + [Test] + public void TestSingleAudioFile() + { + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddAssert("set audio", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3")); + + AddStep("save", () => Editor.Save()); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty"; + }); + + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName == "New Difficulty (1)"; + }); + + AddStep("switch to second difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(1))); + AddUntilStep("wait for editor load", () => Editor.IsLoaded); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("set audio on second diff only", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3")); + AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); + AddStep("save", () => Editor.Save()); + + AddStep("switch to first difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + AddUntilStep("wait for editor load", () => Editor.IsLoaded); + AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("set audio on first diff only", () => setAudio(applyToAllDifficulties: false, expected: "audio (2).mp3")); + AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (2).mp3")); + AddStep("save", () => Editor.Save()); + + AddAssert("set audio on all diff", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3")); + AddAssert("all diff uses one audio", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.AudioFile == "audio.mp3")); + AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio.mp3")); + AddAssert("other files removed", () => !Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3" || f.Filename == "audio (2).mp3")); + } + [Test] public void TestMultipleBackgroundFiles() { AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddAssert("set background", () => setBackground(expected: "bg.jpg")); + AddAssert("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg.jpg")); AddStep("save", () => Editor.Save()); AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); @@ -536,7 +618,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("new difficulty uses same background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); AddStep("enter setup mode", () => InputManager.Key(Key.F4)); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddAssert("set background", () => setBackground(expected: "bg (1).jpg")); + AddAssert("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg (1).jpg")); AddAssert("new difficulty uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); AddStep("save", () => Editor.Save()); @@ -546,27 +628,15 @@ namespace osu.Game.Tests.Visual.Editing AddStep("enter setup mode", () => InputManager.Key(Key.F4)); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddStep("set background", () => setBackground(expected: "bg.jpg")); + AddStep("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg.jpg")); AddAssert("other background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); - - bool setBackground(string expected) - { - var setup = Editor.ChildrenOfType().First(); - - return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder => - { - bool success = setup.ChildrenOfType().First().ChangeBackgroundImage(new FileInfo(Path.Combine(extractedFolder, "machinetop_background.jpg"))); - Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected)); - return success; - }); - } } [Test] public void TestMultipleAudioFiles() { AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddAssert("set audio", () => setAudio(expected: "audio.mp3")); + AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3")); AddStep("save", () => Editor.Save()); AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); @@ -581,7 +651,7 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("new difficulty uses same audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); AddStep("enter setup mode", () => InputManager.Key(Key.F4)); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddAssert("set audio", () => setAudio(expected: "audio (1).mp3")); + AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3")); AddAssert("new difficulty uses new audio", () => Beatmap.Value.Metadata.AudioFile == "audio (1).mp3"); AddStep("save", () => Editor.Save()); @@ -591,74 +661,38 @@ namespace osu.Game.Tests.Visual.Editing AddStep("enter setup mode", () => InputManager.Key(Key.F4)); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddStep("set audio", () => setAudio(expected: "audio.mp3")); + AddStep("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3")); AddAssert("other audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); - - bool setAudio(string expected) - { - var setup = Editor.ChildrenOfType().First(); - - return setFile(TestResources.GetTestBeatmapForImport(), extractedFolder => - { - bool success = setup.ChildrenOfType().First().ChangeAudioTrack(new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3"))); - Assert.That(Beatmap.Value.Metadata.AudioFile, Is.EqualTo(expected)); - return success; - }); - } } - [Test] - public void TestUpdateBackgroundOnAllDifficulties() + private bool setBackground(bool applyToAllDifficulties, string expected) { - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddAssert("button disabled", () => !getButton().Enabled.Value); - AddAssert("set background", () => setBackground(expected: "bg.jpg")); + var setup = Editor.ChildrenOfType().First(); - // there is only one diff so this should still be disabled. - AddAssert("button still disabled", () => !getButton().Enabled.Value); - - AddStep("save", () => Editor.Save()); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); - AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); - AddUntilStep("wait for created", () => + return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder => { - string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty"; + bool success = setup.ChildrenOfType().First().ChangeBackgroundImage( + new FileInfo(Path.Combine(extractedFolder, @"machinetop_background.jpg")), + applyToAllDifficulties); + + Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected)); + return success; }); + } - AddAssert("new difficulty uses same background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddAssert("button disabled", () => !getButton().Enabled.Value); - AddAssert("set background", () => setBackground(expected: "bg (1).jpg")); - AddAssert("new background added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); - AddAssert("new difficulty uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); + private bool setAudio(bool applyToAllDifficulties, string expected) + { + var setup = Editor.ChildrenOfType().First(); - AddAssert("button enabled", () => getButton().Enabled.Value); - AddStep("press button", () => getButton().TriggerClick()); - - AddAssert("new difficulty still uses new background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps[1].Metadata.BackgroundFile == "bg (1).jpg"); - AddAssert("old difficulty uses new background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps[0].Metadata.BackgroundFile == "bg (1).jpg"); - AddAssert("old background removed", () => Beatmap.Value.BeatmapSetInfo.Files.All(f => f.Filename != "bg.jpg")); - - AddStep("save", () => Editor.Save()); - AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); - AddAssert("old difficulty still uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); - - bool setBackground(string expected) + return setFile(TestResources.GetTestBeatmapForImport(), extractedFolder => { - var setup = Editor.ChildrenOfType().First(); + bool success = setup.ChildrenOfType().First().ChangeAudioTrack( + new FileInfo(Path.Combine(extractedFolder, "03. Renatus - Soleily 192kbps.mp3")), + applyToAllDifficulties); - return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder => - { - bool success = setup.ChildrenOfType().First().ChangeBackgroundImage(new FileInfo(Path.Combine(extractedFolder, "machinetop_background.jpg"))); - Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected)); - return success; - }); - } - - RoundedButton getButton() => Editor.ChildrenOfType().Single(b => b.Text == EditorSetupStrings.ResourcesUpdateAllDifficulties); + Assert.That(Beatmap.Value.Metadata.AudioFile, Is.EqualTo(expected)); + return success; + }); } private bool setFile(string archivePath, Func func) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs index c6fd65b973..b9ff78b49f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFormControls.cs @@ -9,6 +9,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; +using osu.Game.Screens.Edit.Setup; using osuTK; namespace osu.Game.Tests.Visual.UserInterface @@ -89,8 +90,13 @@ namespace osu.Game.Tests.Visual.UserInterface }, new FormFileSelector { - Caption = "Audio file", - PlaceholderText = "Select an audio file", + Caption = "File selector", + PlaceholderText = "Select a file", + }, + new FormBeatmapFileSelector(true) + { + Caption = "File selector with intermediate choice dialog", + PlaceholderText = "Select a file", }, new FormColourPalette { From 4ae3ccfe480bb40ffa3b44bb1fc0879bfb129f98 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 06:05:02 -0500 Subject: [PATCH 081/326] Make `BackButtonVisibility` in game class private --- osu.Game/OsuGame.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 514209524e..c52755197b 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -178,7 +178,7 @@ namespace osu.Game /// /// Whether the back button is currently displayed. /// - public readonly IBindable BackButtonVisibility = new Bindable(); + private readonly IBindable backButtonVisibility = new Bindable(); IBindable ILocalUserPlayInfo.PlayingState => playingState; @@ -1194,7 +1194,7 @@ namespace osu.Game if (mode.NewValue != OverlayActivation.All) CloseAllOverlays(); }; - BackButtonVisibility.ValueChanged += visible => + backButtonVisibility.ValueChanged += visible => { if (visible.NewValue) BackButton.Show(); @@ -1594,14 +1594,14 @@ namespace osu.Game if (current is IOsuScreen currentOsuScreen) { - BackButtonVisibility.UnbindFrom(currentOsuScreen.BackButtonVisibility); + backButtonVisibility.UnbindFrom(currentOsuScreen.BackButtonVisibility); OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode); API.Activity.UnbindFrom(currentOsuScreen.Activity); } if (newScreen is IOsuScreen newOsuScreen) { - BackButtonVisibility.BindTo(newOsuScreen.BackButtonVisibility); + backButtonVisibility.BindTo(newOsuScreen.BackButtonVisibility); OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode); API.Activity.BindTo(newOsuScreen.Activity); From f792b6de002f1e82e004ba2c4c7b3d8de5360a27 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 06:07:10 -0500 Subject: [PATCH 082/326] Fix comment --- osu.Game/Screens/IOsuScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index 46dfbfb1ac..9e474ed0c6 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -35,7 +35,8 @@ namespace osu.Game.Screens /// Whether a footer (and a back button) should be displayed underneath the screen. /// /// - /// Temporarily, the back button is shown regardless of whether is true. + /// Temporarily, the footer's own back button is shown regardless of whether is set to hidden. + /// This will be corrected as the footer becomes used more commonly. /// bool ShowFooter { get; } From 238a1ce284ce0b77fda9823c2255f3525fe6c8e9 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 15:27:18 -0500 Subject: [PATCH 083/326] Fix tests reliability and improve code Shaved off lots of copypasta so the test actually shows what it's testing. --- .../Editing/TestSceneEditorBeatmapCreation.cs | 141 +++++++----------- 1 file changed, 54 insertions(+), 87 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 2817225f2b..c7d745b6e0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Editing 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)); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual); AddAssert("switch track to real track", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3")); @@ -508,43 +508,21 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestSingleBackgroundFile() { - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddAssert("set background", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg")); - AddStep("save", () => Editor.Save()); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); - AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); - AddUntilStep("wait for created", () => - { - string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty"; - }); + createNewDifficulty(); + createNewDifficulty(); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); - AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); - AddUntilStep("wait for created", () => - { - string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty (1)"; - }); + switchToDifficulty(1); - AddStep("switch to second difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(1))); - AddUntilStep("wait for editor load", () => Editor.IsLoaded); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); AddAssert("set background on second diff only", () => setBackground(applyToAllDifficulties: false, expected: "bg (1).jpg")); AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); - AddStep("save", () => Editor.Save()); - AddStep("switch to first difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); - AddUntilStep("wait for editor load", () => Editor.IsLoaded); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + switchToDifficulty(0); + AddAssert("set background on first diff only", () => setBackground(applyToAllDifficulties: false, expected: "bg (2).jpg")); AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (2).jpg")); - AddStep("save", () => Editor.Save()); AddAssert("set background on all diff", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg")); AddAssert("all diff uses one background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.BackgroundFile == "bg.jpg")); @@ -555,43 +533,21 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestSingleAudioFile() { - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddAssert("set audio", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3")); - AddStep("save", () => Editor.Save()); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); - AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); - AddUntilStep("wait for created", () => - { - string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty"; - }); + createNewDifficulty(); + createNewDifficulty(); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); - AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); - AddUntilStep("wait for created", () => - { - string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty (1)"; - }); + switchToDifficulty(1); - AddStep("switch to second difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(1))); - AddUntilStep("wait for editor load", () => Editor.IsLoaded); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); AddAssert("set audio on second diff only", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3")); AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); - AddStep("save", () => Editor.Save()); - AddStep("switch to first difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); - AddUntilStep("wait for editor load", () => Editor.IsLoaded); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + switchToDifficulty(0); + AddAssert("set audio on first diff only", () => setAudio(applyToAllDifficulties: false, expected: "audio (2).mp3")); AddAssert("file added", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (2).mp3")); - AddStep("save", () => Editor.Save()); AddAssert("set audio on all diff", () => setAudio(applyToAllDifficulties: true, expected: "audio.mp3")); AddAssert("all diff uses one audio", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.AudioFile == "audio.mp3")); @@ -602,32 +558,19 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestMultipleBackgroundFiles() { - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddAssert("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg.jpg")); - AddStep("save", () => Editor.Save()); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); - AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); - AddUntilStep("wait for created", () => - { - string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty"; - }); + createNewDifficulty(); AddAssert("new difficulty uses same background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); AddAssert("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg (1).jpg")); AddAssert("new difficulty uses new background", () => Beatmap.Value.Metadata.BackgroundFile == "bg (1).jpg"); - AddStep("save", () => Editor.Save()); - AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); + switchToDifficulty(0); + AddAssert("old difficulty uses old background", () => Beatmap.Value.Metadata.BackgroundFile == "bg.jpg"); AddAssert("old background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg.jpg")); - - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); - AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); AddStep("set background", () => setBackground(applyToAllDifficulties: false, expected: "bg.jpg")); AddAssert("other background not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg")); } @@ -635,34 +578,58 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestMultipleAudioFiles() { - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3")); + createNewDifficulty(); + + AddAssert("new difficulty uses same audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); + AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); + AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3")); + AddAssert("new difficulty uses new audio", () => Beatmap.Value.Metadata.AudioFile == "audio (1).mp3"); + + switchToDifficulty(0); + + AddAssert("old difficulty uses old audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); + AddAssert("old audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio.mp3")); + AddStep("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3")); + AddAssert("other audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); + } + + private void createNewDifficulty() + { + string? currentDifficulty = null; + AddStep("save", () => Editor.Save()); - AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo)); + AddStep("create new difficulty", () => + { + currentDifficulty = EditorBeatmap.BeatmapInfo.DifficultyName; + Editor.CreateNewDifficulty(new OsuRuleset().RulesetInfo); + }); + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); AddUntilStep("wait for created", () => { string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName == "New Difficulty"; + return difficultyName != null && difficultyName != currentDifficulty; }); - AddAssert("new difficulty uses same audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for editor load", () => Editor.IsLoaded); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddAssert("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio (1).mp3")); - AddAssert("new difficulty uses new audio", () => Beatmap.Value.Metadata.AudioFile == "audio (1).mp3"); + } + private void switchToDifficulty(int index) + { AddStep("save", () => Editor.Save()); - AddStep("switch to previous difficulty", () => Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.First())); - AddAssert("old difficulty uses old audio", () => Beatmap.Value.Metadata.AudioFile == "audio.mp3"); - AddAssert("old audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio.mp3")); + AddStep($"switch to difficulty #{index + 1}", () => + Editor.SwitchToDifficulty(Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(index))); - AddStep("enter setup mode", () => InputManager.Key(Key.F4)); + AddUntilStep("wait for editor load", () => Editor.IsLoaded); + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); AddUntilStep("wait for load", () => Editor.ChildrenOfType().Any()); - AddStep("set audio", () => setAudio(applyToAllDifficulties: false, expected: "audio.mp3")); - AddAssert("other audio not removed", () => Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "audio (1).mp3")); } private bool setBackground(bool applyToAllDifficulties, string expected) From 24c0799680c30223bdc1326bc3735807da950178 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 16:54:51 -0500 Subject: [PATCH 084/326] Move beatmap ID lookup to `UesrActivity` --- osu.Desktop/DiscordRichPresence.cs | 18 +----------------- osu.Game/Users/UserActivity.cs | 12 ++++++++++++ 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index ba61f4be34..1fa964d8bc 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -167,9 +167,7 @@ namespace osu.Desktop 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 - && !(activity.Value is UserActivity.EditingBeatmap && hideIdentifiableInformation)) + if (activity.Value.GetBeatmapID(hideIdentifiableInformation) is int beatmapId && beatmapId > 0) { presence.Buttons = new[] { @@ -329,20 +327,6 @@ namespace osu.Desktop return true; } - private static int? getBeatmapID(UserActivity activity) - { - switch (activity) - { - case UserActivity.InGame game: - return game.BeatmapID; - - case UserActivity.EditingBeatmap edit: - return edit.BeatmapID; - } - - return null; - } - protected override void Dispose(bool isDisposing) { if (multiplayerClient.IsNotNull()) diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs index 93812e3f6b..a8e0fc9030 100644 --- a/osu.Game/Users/UserActivity.cs +++ b/osu.Game/Users/UserActivity.cs @@ -41,6 +41,12 @@ namespace osu.Game.Users public virtual Color4 GetAppropriateColour(OsuColour colours) => colours.GreenDarker; + /// + /// Returns the ID of the beatmap involved in this activity, if applicable and/or available. + /// + /// + public virtual int? GetBeatmapID(bool hideIdentifiableInformation = false) => null; + [MessagePackObject] public class ChoosingBeatmap : UserActivity { @@ -76,6 +82,7 @@ namespace osu.Game.Users public override string GetStatus(bool hideIdentifiableInformation = false) => RulesetPlayingVerb; public override string GetDetails(bool hideIdentifiableInformation = false) => BeatmapDisplayTitle; + public override int? GetBeatmapID(bool hideIdentifiableInformation = false) => BeatmapID; } [MessagePackObject] @@ -156,6 +163,11 @@ namespace osu.Game.Users // For now let's assume that showing the beatmap a user is editing could reveal unwanted information. ? string.Empty : BeatmapDisplayTitle; + + public override int? GetBeatmapID(bool hideIdentifiableInformation = false) => hideIdentifiableInformation + // For now let's assume that showing the beatmap a user is editing could reveal unwanted information. + ? null + : BeatmapID; } [MessagePackObject] From 19e396f87886836f41a14b0e739a625e6e663e80 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 27 Nov 2024 23:46:19 -0500 Subject: [PATCH 085/326] Fix android workflow not installing .NET 8 version --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d75f09f184..d8645d728e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,7 +114,10 @@ jobs: dotnet-version: "8.0.x" - name: Install .NET workloads - run: dotnet workload install android + # since windows image 20241113.3.0, not specifying a version here + # installs the .NET 7 version of android workload for very unknown reasons. + # revisit once we upgrade to .NET 9, it's probably fixed there. + run: dotnet workload install android --version (dotnet --version) - name: Compile run: dotnet build -c Debug osu.Android.slnf From 4d9d5adbf441ecc8286ca4dc512f96063ddf19bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Nov 2024 15:13:32 +0900 Subject: [PATCH 086/326] Rename parameter to be more clear --- .../Edit/Setup/FormBeatmapFileSelector.cs | 16 ++++++++-------- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 6 +++--- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs index 317ed1b903..ae368a7b7e 100644 --- a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs +++ b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs @@ -27,19 +27,19 @@ namespace osu.Game.Screens.Edit.Setup /// public partial class FormBeatmapFileSelector : FormFileSelector { - private readonly bool multipleDifficulties; + private readonly bool beatmapHasMultipleDifficulties; public readonly Bindable ApplyToAllDifficulties = new Bindable(true); - public FormBeatmapFileSelector(bool multipleDifficulties, params string[] handledExtensions) + public FormBeatmapFileSelector(bool beatmapHasMultipleDifficulties, params string[] handledExtensions) : base(handledExtensions) { - this.multipleDifficulties = multipleDifficulties; + this.beatmapHasMultipleDifficulties = beatmapHasMultipleDifficulties; } protected override FileChooserPopover CreatePopover(string[] handledExtensions, Bindable current, string? chooserPath) { - var popover = new BeatmapFileChooserPopover(handledExtensions, current, chooserPath, multipleDifficulties); + var popover = new BeatmapFileChooserPopover(handledExtensions, current, chooserPath, beatmapHasMultipleDifficulties); popover.ApplyToAllDifficulties.ValueChanged += v => { @@ -52,16 +52,16 @@ namespace osu.Game.Screens.Edit.Setup private partial class BeatmapFileChooserPopover : FileChooserPopover { - private readonly bool multipleDifficulties; + private readonly bool beatmapHasMultipleDifficulties; public readonly Bindable ApplyToAllDifficulties = new Bindable(); private Container changeScopeContainer = null!; - public BeatmapFileChooserPopover(string[] handledExtensions, Bindable current, string? chooserPath, bool multipleDifficulties) + public BeatmapFileChooserPopover(string[] handledExtensions, Bindable current, string? chooserPath, bool beatmapHasMultipleDifficulties) : base(handledExtensions, current, chooserPath) { - this.multipleDifficulties = multipleDifficulties; + this.beatmapHasMultipleDifficulties = beatmapHasMultipleDifficulties; } [BackgroundDependencyLoader] @@ -142,7 +142,7 @@ namespace osu.Game.Screens.Edit.Setup protected override void OnFileSelected(FileInfo file) { - if (multipleDifficulties) + if (beatmapHasMultipleDifficulties) changeScopeContainer.FadeIn(200, Easing.InQuint); else base.OnFileSelected(file); diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 70282878e0..1ce944b5a4 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -49,16 +49,16 @@ namespace osu.Game.Screens.Edit.Setup Height = 110, }; - bool multipleDifficulties = working.Value.BeatmapSetInfo.Beatmaps.Count > 1; + bool beatmapHasMultipleDifficulties = working.Value.BeatmapSetInfo.Beatmaps.Count > 1; Children = new Drawable[] { - backgroundChooser = new FormBeatmapFileSelector(multipleDifficulties, ".jpg", ".jpeg", ".png") + backgroundChooser = new FormBeatmapFileSelector(beatmapHasMultipleDifficulties, ".jpg", ".jpeg", ".png") { Caption = GameplaySettingsStrings.BackgroundHeader, PlaceholderText = EditorSetupStrings.ClickToSelectBackground, }, - audioTrackChooser = new FormBeatmapFileSelector(multipleDifficulties, ".mp3", ".ogg") + audioTrackChooser = new FormBeatmapFileSelector(beatmapHasMultipleDifficulties, ".mp3", ".ogg") { Caption = EditorSetupStrings.AudioTrack, PlaceholderText = EditorSetupStrings.ClickToSelectTrack, From 32b34c1967172bab39c5b2f05975e23dee76cdcd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Nov 2024 15:20:51 +0900 Subject: [PATCH 087/326] Rename container to make more sense --- osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs index ae368a7b7e..6af78f24f8 100644 --- a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs +++ b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Edit.Setup public readonly Bindable ApplyToAllDifficulties = new Bindable(); - private Container changeScopeContainer = null!; + private Container selectApplicationScopeContainer = null!; public BeatmapFileChooserPopover(string[] handledExtensions, Bindable current, string? chooserPath, bool beatmapHasMultipleDifficulties) : base(handledExtensions, current, chooserPath) @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Edit.Setup [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuColour colours) { - Add(changeScopeContainer = new InputBlockingContainer + Add(selectApplicationScopeContainer = new InputBlockingContainer { Alpha = 0f, RelativeSizeAxes = Axes.Both, @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Edit.Setup protected override void OnFileSelected(FileInfo file) { if (beatmapHasMultipleDifficulties) - changeScopeContainer.FadeIn(200, Easing.InQuint); + selectApplicationScopeContainer.FadeIn(200, Easing.InQuint); else base.OnFileSelected(file); } From 70eee8882a0ed4df045c0ea8f30764cd93cee88c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Nov 2024 15:42:37 +0900 Subject: [PATCH 088/326] Remove unnecessary null check --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs index 7838bd2fc8..c8e5f0859d 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void hideCloseButton() { - closeButton?.ResizeWidthTo(0, 100, Easing.OutQuint) + closeButton.ResizeWidthTo(0, 100, Easing.OutQuint) .Then().FadeOut().Expire(); } From 4a1401a33df7c3b489c6c0db05b68f6f1fe31079 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 02:37:27 -0500 Subject: [PATCH 089/326] Rewrite bindable flow to make more sense --- .../Edit/Setup/FormBeatmapFileSelector.cs | 33 ++++++++----------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs index 6af78f24f8..3e5f0f4306 100644 --- a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs +++ b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs @@ -40,13 +40,7 @@ namespace osu.Game.Screens.Edit.Setup protected override FileChooserPopover CreatePopover(string[] handledExtensions, Bindable current, string? chooserPath) { var popover = new BeatmapFileChooserPopover(handledExtensions, current, chooserPath, beatmapHasMultipleDifficulties); - - popover.ApplyToAllDifficulties.ValueChanged += v => - { - Debug.Assert(v.NewValue != null); - ApplyToAllDifficulties.Value = v.NewValue.Value; - }; - + popover.ApplyToAllDifficulties.BindTo(ApplyToAllDifficulties); return popover; } @@ -54,7 +48,7 @@ namespace osu.Game.Screens.Edit.Setup { private readonly bool beatmapHasMultipleDifficulties; - public readonly Bindable ApplyToAllDifficulties = new Bindable(); + public readonly Bindable ApplyToAllDifficulties = new Bindable(true); private Container selectApplicationScopeContainer = null!; @@ -115,7 +109,11 @@ namespace osu.Game.Screens.Edit.Setup Origin = Anchor.Centre, Width = 300f, Text = "Apply to all difficulties", - Action = () => ApplyToAllDifficulties.Value = true, + Action = () => + { + ApplyToAllDifficulties.Value = true; + updateFileSelection(); + }, BackgroundColour = colours.Red2, }, new RoundedButton @@ -124,7 +122,11 @@ namespace osu.Game.Screens.Edit.Setup Origin = Anchor.Centre, Width = 300f, Text = "Only apply to this difficulty", - Action = () => ApplyToAllDifficulties.Value = false, + Action = () => + { + ApplyToAllDifficulties.Value = false; + updateFileSelection(); + }, }, } } @@ -134,12 +136,6 @@ namespace osu.Game.Screens.Edit.Setup }); } - protected override void LoadComplete() - { - base.LoadComplete(); - ApplyToAllDifficulties.ValueChanged += onChangeScopeSelected; - } - protected override void OnFileSelected(FileInfo file) { if (beatmapHasMultipleDifficulties) @@ -148,11 +144,8 @@ namespace osu.Game.Screens.Edit.Setup base.OnFileSelected(file); } - private void onChangeScopeSelected(ValueChangedEvent c) + private void updateFileSelection() { - if (c.NewValue == null) - return; - Debug.Assert(FileSelector.CurrentFile.Value != null); base.OnFileSelected(FileSelector.CurrentFile.Value); } From b1d0939142f62ea2d43401bb7bd4bc0d32191479 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 02:37:31 -0500 Subject: [PATCH 090/326] Add localisation support --- osu.Game/Localisation/EditorSetupStrings.cs | 20 ++++++++++++++----- .../Edit/Setup/FormBeatmapFileSelector.cs | 7 ++++--- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/osu.Game/Localisation/EditorSetupStrings.cs b/osu.Game/Localisation/EditorSetupStrings.cs index 60e677757e..8597b7d9a1 100644 --- a/osu.Game/Localisation/EditorSetupStrings.cs +++ b/osu.Game/Localisation/EditorSetupStrings.cs @@ -188,11 +188,6 @@ namespace osu.Game.Localisation /// public static LocalisableString AudioTrack => new TranslatableString(getKey(@"audio_track"), @"Audio Track"); - /// - /// "Update all difficulties" - /// - public static LocalisableString ResourcesUpdateAllDifficulties => new TranslatableString(getKey(@"resources_update_all_difficulties"), @"Update all difficulties"); - /// /// "Click to select a track" /// @@ -203,6 +198,21 @@ namespace osu.Game.Localisation /// public static LocalisableString ClickToSelectBackground => new TranslatableString(getKey(@"click_to_select_background"), @"Click to select a background image"); + /// + /// "Apply this change to all difficulties?" + /// + public static LocalisableString ApplicationScopeSelectionTitle => new TranslatableString(getKey(@"application_scope_selection_title"), @"Apply this change to all difficulties?"); + + /// + /// "Apply to all difficulties" + /// + public static LocalisableString ApplyToAllDifficulties => new TranslatableString(getKey(@"apply_to_all_difficulties"), @"Apply to all difficulties"); + + /// + /// "Only apply to this difficulty" + /// + public static LocalisableString ApplyToThisDifficulty => new TranslatableString(getKey(@"apply_to_this_difficulty"), @"Only apply to this difficulty"); + /// /// "Ruleset ({0})" /// diff --git a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs index 3e5f0f4306..53287383ec 100644 --- a/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs +++ b/osu.Game/Screens/Edit/Setup/FormBeatmapFileSelector.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osuTK; +using osu.Game.Localisation; namespace osu.Game.Screens.Edit.Setup { @@ -100,7 +101,7 @@ namespace osu.Game.Screens.Edit.Setup { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = "Apply this change to all difficulties?", + Text = EditorSetupStrings.ApplicationScopeSelectionTitle, Margin = new MarginPadding { Bottom = 20f }, }, new RoundedButton @@ -108,7 +109,7 @@ namespace osu.Game.Screens.Edit.Setup Anchor = Anchor.Centre, Origin = Anchor.Centre, Width = 300f, - Text = "Apply to all difficulties", + Text = EditorSetupStrings.ApplyToAllDifficulties, Action = () => { ApplyToAllDifficulties.Value = true; @@ -121,7 +122,7 @@ namespace osu.Game.Screens.Edit.Setup Anchor = Anchor.Centre, Origin = Anchor.Centre, Width = 300f, - Text = "Only apply to this difficulty", + Text = EditorSetupStrings.ApplyToThisDifficulty, Action = () => { ApplyToAllDifficulties.Value = false; From 4314f9c0a92ca15635cc317d38b3a56c1bcce9d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Nov 2024 09:22:08 +0100 Subject: [PATCH 091/326] Remove unused accessors --- .../Playlists/TestScenePlaylistsResultsScreen.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index c288b04da2..33bd573617 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -464,11 +464,6 @@ namespace osu.Game.Tests.Visual.Playlists private partial class TestScoreResultsScreen : PlaylistItemScoreResultsScreen { - public new LoadingSpinner LeftSpinner => base.LeftSpinner; - public new LoadingSpinner CentreSpinner => base.CentreSpinner; - public new LoadingSpinner RightSpinner => base.RightSpinner; - public new ScorePanelList ScorePanelList => base.ScorePanelList; - public TestScoreResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem) : base(score, roomId, playlistItem) { @@ -478,11 +473,6 @@ namespace osu.Game.Tests.Visual.Playlists private partial class TestUserBestResultsScreen : PlaylistItemUserBestResultsScreen { - public new LoadingSpinner LeftSpinner => base.LeftSpinner; - public new LoadingSpinner CentreSpinner => base.CentreSpinner; - public new LoadingSpinner RightSpinner => base.RightSpinner; - public new ScorePanelList ScorePanelList => base.ScorePanelList; - public TestUserBestResultsScreen(int roomId, PlaylistItem playlistItem, int userId) : base(roomId, playlistItem, userId) { From ced8dda1a29da0697bf5e47c7ab0734f473b6892 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Nov 2024 17:30:50 +0900 Subject: [PATCH 092/326] Clear previous `LastLocalUserScore` when returning to song select This seems like the lowest friction way of fixing https://github.com/ppy/osu/issues/30885. We could also only null this on application, but this feels worse because - It would require local handling (potentially complex) in `BeatmapOffsetControl` if we want to continue displaying the graph and button after clicking it. - It would make the session static very specific in usage and potentially make future usage not possible due to being nulled in only a very specific scenario. One might argue that it would be nice to have this non-null until the next play, but if such a usage comes up I'd propose we rename this session static and add a new one with that purpose. --- osu.Game/Configuration/SessionStatics.cs | 4 +++- osu.Game/Screens/Play/PlayerLoader.cs | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 225f209380..18631f5d00 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -10,6 +10,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Scoring; +using osu.Game.Screens.Play; namespace osu.Game.Configuration { @@ -77,7 +78,8 @@ namespace osu.Game.Configuration TouchInputActive, /// - /// Stores the local user's last score (can be completed or aborted). + /// Contains the local user's last score (can be completed or aborted) after exiting . + /// Will be cleared to null when leaving . /// LastLocalUserScore, diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 3e36c630db..0db96b71ad 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -28,6 +28,7 @@ using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Performance; +using osu.Game.Scoring; using osu.Game.Screens.Menu; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Skinning; @@ -78,6 +79,8 @@ namespace osu.Game.Screens.Play private FillFlowContainer disclaimers = null!; private OsuScrollContainer settingsScroll = null!; + private Bindable lastScore = null!; + private Bindable showStoryboards = null!; private bool backgroundBrightnessReduction; @@ -179,6 +182,8 @@ namespace osu.Game.Screens.Play { muteWarningShownOnce = sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce); batteryWarningShownOnce = sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce); + lastScore = sessionStatics.GetBindable(Static.LastLocalUserScore); + showStoryboards = config.GetBindable(OsuSetting.ShowStoryboard); const float padding = 25; @@ -347,6 +352,8 @@ namespace osu.Game.Screens.Play highPerformanceSession?.Dispose(); highPerformanceSession = null; + lastScore.Value = null; + return base.OnExiting(e); } From c26c84ba4519ade44fb3196a0e8187dde35605ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Nov 2024 18:03:19 +0900 Subject: [PATCH 093/326] Add test coverage governing new behaviour --- .../Navigation/TestSceneScreenNavigation.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index eda7ce925a..5646649d33 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -354,6 +354,23 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("retry count is 1", () => player.RestartCount == 1); } + [Test] + public void TestLastScoreNullAfterExitingPlayer() + { + AddUntilStep("wait for last play null", getLastPlay, () => Is.Null); + + var getOriginalPlayer = playToCompletion(); + + AddStep("attempt to retry", () => getOriginalPlayer().ChildrenOfType().First().Action()); + AddUntilStep("wait for last play matches player", getLastPlay, () => Is.EqualTo(getOriginalPlayer().Score.ScoreInfo)); + + AddUntilStep("wait for player", () => Game.ScreenStack.CurrentScreen != getOriginalPlayer() && Game.ScreenStack.CurrentScreen is Player); + AddStep("exit player", () => (Game.ScreenStack.CurrentScreen as Player)?.Exit()); + AddUntilStep("wait for last play null", getLastPlay, () => Is.Null); + + ScoreInfo getLastPlay() => Game.Dependencies.Get().Get(Static.LastLocalUserScore); + } + [Test] public void TestRetryImmediatelyAfterCompletion() { From b0958c8d418db28022fe2d12dd0ca2722ddad14c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Nov 2024 10:24:56 +0100 Subject: [PATCH 094/326] Attempt to fix test failures --- 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 512cb697dd..e102feb3e2 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -144,10 +144,12 @@ namespace osu.Game.Overlays protected override void Dispose(bool isDisposing) { - base.Dispose(isDisposing); - + // this event subscription fires async loads, which hard-fail if `CompositeDrawable.disposalCancellationSource` is canceled, which happens in the base call. + // therefore, unsubscribe from this event early to reduce the chances of a stray event firing at an inconvenient spot. if (api.IsNotNull()) api.NotificationsClient.MessageReceived -= handleMedalMessages; + + base.Dispose(isDisposing); } } } From c14fe21219acc24741b7d6b31106763fdd488796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Nov 2024 11:19:00 +0100 Subject: [PATCH 095/326] Fix LCA call crashing in actual usage It's not allowed to call `LoadComponentsAsync()` on a background thread: https://github.com/ppy/osu-framework/blob/fd64f2f0d47f0ee1aaa596bde1e83e527d610340/osu.Framework/Graphics/Containers/CompositeDrawable.cs#L147 and in this case the event that causes the LCA call is dispatched from a websocket client, which is not on the update thread, so scheduling is required. --- osu.Game/Overlays/MedalOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/MedalOverlay.cs b/osu.Game/Overlays/MedalOverlay.cs index e102feb3e2..25e22ffbda 100644 --- a/osu.Game/Overlays/MedalOverlay.cs +++ b/osu.Game/Overlays/MedalOverlay.cs @@ -85,11 +85,11 @@ namespace osu.Game.Overlays Logger.Log($"Queueing medal unlock for \"{medal.Name}\" ({queuedMedals.Count} to display)"); - LoadComponentAsync(medalAnimation, m => + Schedule(() => LoadComponentAsync(medalAnimation, m => { queuedMedals.Enqueue(m); showNextMedal(); - }); + })); } protected override bool OnClick(ClickEvent e) From 8f6e5c475465d826204f5308f4b431c4a52db253 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2024 19:14:18 +0800 Subject: [PATCH 096/326] Convert legacy ruleset to globalconfig --- .globalconfig | 8 +++ CodeAnalysis/osu.ruleset | 58 ------------------- Directory.Build.props | 1 - .../CodeAnalysis.tests.globalconfig | 7 +++ osu.Game.Tests/osu.Game.Tests.csproj | 6 +- osu.Game.Tests/tests.ruleset | 6 -- osu.sln | 2 +- 7 files changed, 19 insertions(+), 69 deletions(-) delete mode 100644 CodeAnalysis/osu.ruleset create mode 100644 osu.Game.Tests/CodeAnalysis.tests.globalconfig delete mode 100644 osu.Game.Tests/tests.ruleset diff --git a/.globalconfig b/.globalconfig index a4d4707f9b..c5723daed6 100644 --- a/.globalconfig +++ b/.globalconfig @@ -46,6 +46,14 @@ dotnet_diagnostic.IDE0130.severity = warning # IDE1006: Naming style dotnet_diagnostic.IDE1006.severity = warning +dotnet_diagnostic.CA1309.severity = warning + +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = warning + +# CA2201: Do not raise reserved exception types +dotnet_diagnostic.CA2201.severity = warning + #Disable operator overloads requiring alternate named methods dotnet_diagnostic.CA2225.severity = none diff --git a/CodeAnalysis/osu.ruleset b/CodeAnalysis/osu.ruleset deleted file mode 100644 index 6a99e230d1..0000000000 --- a/CodeAnalysis/osu.ruleset +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props index 5ba12b845b..e7e5e4e831 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -20,7 +20,6 @@ - $(MSBuildThisFileDirectory)CodeAnalysis\osu.ruleset true diff --git a/osu.Game.Tests/CodeAnalysis.tests.globalconfig b/osu.Game.Tests/CodeAnalysis.tests.globalconfig new file mode 100644 index 0000000000..3f039736ec --- /dev/null +++ b/osu.Game.Tests/CodeAnalysis.tests.globalconfig @@ -0,0 +1,7 @@ +# Higher global_level has higher priority, the default global_level +# is 100 for root .globalconfig and 0 for others +# https://learn.microsoft.com/dotnet/fundamentals/code-analysis/configuration-files#precedence +is_global = true +global_level = 101 + +dotnet_diagnostic.CA2007.severity = none diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 28a1d4d021..01d2241650 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -12,9 +12,9 @@ WinExe net8.0 - - tests.ruleset - + + + diff --git a/osu.Game.Tests/tests.ruleset b/osu.Game.Tests/tests.ruleset deleted file mode 100644 index a0abb781d3..0000000000 --- a/osu.Game.Tests/tests.ruleset +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/osu.sln b/osu.sln index 829e43fc65..2d9a4e86d0 100644 --- a/osu.sln +++ b/osu.sln @@ -60,7 +60,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Directory.Build.props = Directory.Build.props osu.Android.props = osu.Android.props osu.iOS.props = osu.iOS.props - CodeAnalysis\osu.ruleset = CodeAnalysis\osu.ruleset + global.json = global.json osu.sln.DotSettings = osu.sln.DotSettings osu.TestProject.props = osu.TestProject.props EndProjectSection From 66093872e85d8d08dba3860393fabbe87cf9a660 Mon Sep 17 00:00:00 2001 From: Hiviexd Date: Thu, 28 Nov 2024 12:49:30 +0100 Subject: [PATCH 097/326] Adjust daily challenge tier thresholds to match expectations --- .../Components/DailyChallengeStatsTooltip.cs | 22 +++++++++---------- 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs index 24e531bd87..ea49f9d784 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs @@ -138,34 +138,32 @@ namespace osu.Game.Overlays.Profile.Header.Components topFifty.ValueColour = colourProvider.Content2; } - // reference: https://github.com/ppy/osu-web/blob/adf1e94754ba9625b85eba795f4a310caf169eec/resources/js/profile-page/daily-challenge.tsx#L13-L47 + // reference: https://github.com/ppy/osu-web/blob/a97f156014e00ea1aa315140da60542e798a9f06/resources/js/profile-page/daily-challenge.tsx#L13-L47 - // Rounding up is needed here to ensure the overlay shows the same colour as osu-web for the play count. - // This is because, for example, 31 / 3 > 10 in JavaScript because floats are used, while here it would - // get truncated to 10 with an integer division and show a lower tier. - public static RankingTier TierForPlayCount(int playCount) => TierForDaily((int)Math.Ceiling(playCount / 3.0d)); + // Rounding down is needed here to ensure the overlay shows the same colour as osu-web for the play count. + public static RankingTier TierForPlayCount(int playCount) => TierForDaily((int)Math.Floor(playCount / 3.0d)); public static RankingTier TierForDaily(int daily) { - if (daily > 360) + if (daily >= 360) return RankingTier.Lustrous; - if (daily > 240) + if (daily >= 240) return RankingTier.Radiant; - if (daily > 120) + if (daily >= 120) return RankingTier.Rhodium; - if (daily > 60) + if (daily >= 60) return RankingTier.Platinum; - if (daily > 30) + if (daily >= 30) return RankingTier.Gold; - if (daily > 10) + if (daily >= 10) return RankingTier.Silver; - if (daily > 5) + if (daily >= 5) return RankingTier.Bronze; return RankingTier.Iron; From 6ed21229b7c5c95f7125a1c2f534cbce3b1931b3 Mon Sep 17 00:00:00 2001 From: Hiviexd Date: Thu, 28 Nov 2024 12:49:48 +0100 Subject: [PATCH 098/326] update test --- .../Visual/Online/TestSceneUserProfileDailyChallenge.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs index 9db30380f6..3222e16412 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Online AddSliderStep("weekly best", 0, 250, 1, v => update(s => s.WeeklyStreakBest = v)); AddSliderStep("top 10%", 0, 999, 0, v => update(s => s.Top10PercentPlacements = v)); AddSliderStep("top 50%", 0, 999, 0, v => update(s => s.Top50PercentPlacements = v)); - AddSliderStep("playcount", 0, 999, 0, v => update(s => s.PlayCount = v)); + AddSliderStep("playcount", 0, 1500, 0, v => update(s => s.PlayCount = v)); AddStep("create", () => { Clear(); @@ -66,8 +66,8 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestPlayCountRankingTier() { - AddAssert("1 before silver", () => DailyChallengeStatsTooltip.TierForPlayCount(30) == RankingTier.Bronze); - AddAssert("first silver", () => DailyChallengeStatsTooltip.TierForPlayCount(31) == RankingTier.Silver); + AddAssert("1 before silver", () => DailyChallengeStatsTooltip.TierForPlayCount(29) == RankingTier.Bronze); + AddAssert("first silver", () => DailyChallengeStatsTooltip.TierForPlayCount(30) == RankingTier.Silver); } } } From c57ace0b5f184491890ab566084e4a5b9ec9d790 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2024 19:40:09 +0800 Subject: [PATCH 099/326] Enable recommended rules for documentation --- Directory.Build.props | 3 +++ osu.Game/Database/RealmObjectExtensions.cs | 22 ++++++++++++---------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index e7e5e4e831..60e0334f97 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -20,6 +20,9 @@ + Default + Default + Recommended true diff --git a/osu.Game/Database/RealmObjectExtensions.cs b/osu.Game/Database/RealmObjectExtensions.cs index 2fa3b8a880..df725505fc 100644 --- a/osu.Game/Database/RealmObjectExtensions.cs +++ b/osu.Game/Database/RealmObjectExtensions.cs @@ -248,29 +248,30 @@ namespace osu.Game.Database return new RealmLive(realmObject, realm); } +#pragma warning disable RS0030 // mentioning banned symbols in documentation /// - /// Register a callback to be invoked each time this changes. + /// Register a callback to be invoked each time this changes. /// /// /// /// This adds osu! specific thread and managed state safety checks on top of . /// /// - /// The first callback will be invoked with the initial after the asynchronous query completes, + /// The first callback will be invoked with the initial after the asynchronous query completes, /// and then called again after each write transaction which changes either any of the objects in the collection, or /// which objects are in the collection. The changes parameter will /// be null the first time the callback is invoked with the initial results. For each call after that, /// it will contain information about which rows in the results were added, removed or modified. /// /// - /// If a write transaction did not modify any objects in this , the callback is not invoked at all. + /// If a write transaction did not modify any objects in this , the callback is not invoked at all. /// If an error occurs the callback will be invoked with null for the sender parameter and a non-null error. - /// Currently the only errors that can occur are when opening the on the background worker thread. + /// Currently the only errors that can occur are when opening the on the background worker thread. /// /// - /// At the time when the block is called, the object will be fully evaluated + /// At the time when the block is called, the object will be fully evaluated /// and up-to-date, and as long as you do not perform a write transaction on the same thread - /// or explicitly call , accessing it will never perform blocking work. + /// or explicitly call , accessing it will never perform blocking work. /// /// /// Notifications are delivered via the standard event loop, and so can't be delivered while the event loop is blocked by other activity. @@ -279,13 +280,14 @@ namespace osu.Game.Database /// /// /// The to observe for changes. - /// The callback to be invoked with the updated . + /// The callback to be invoked with the updated . /// /// A subscription token. It must be kept alive for as long as you want to receive change notifications. - /// To stop receiving notifications, call . + /// To stop receiving notifications, call . /// - /// - /// + /// + /// +#pragma warning restore RS0030 public static IDisposable QueryAsyncWithNotifications(this IRealmCollection collection, NotificationCallbackDelegate callback) where T : RealmObjectBase { From cd9b5927eba9b19b4b66382aaedd25298a91a4df Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2024 19:46:25 +0800 Subject: [PATCH 100/326] Enable recommended rules for globalization --- .globalconfig | 8 +++++++- CodeAnalysis/BannedSymbols.txt | 4 ---- Directory.Build.props | 2 ++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.globalconfig b/.globalconfig index c5723daed6..d6c657bad0 100644 --- a/.globalconfig +++ b/.globalconfig @@ -46,11 +46,17 @@ dotnet_diagnostic.IDE0130.severity = warning # IDE1006: Naming style dotnet_diagnostic.IDE1006.severity = warning -dotnet_diagnostic.CA1309.severity = warning +# CA1305: Specify IFormatProvider +# Too many noisy warnings for parsing/formatting numbers +dotnet_diagnostic.CA1305.severity = none # CA2007: Consider calling ConfigureAwait on the awaited task dotnet_diagnostic.CA2007.severity = warning +# CA2101: Specify marshaling for P/Invoke string arguments +# Reports warning for all non-UTF16 usages on DllImport; consider migrating to LibraryImport +dotnet_diagnostic.CA2101.severity = none + # CA2201: Do not raise reserved exception types dotnet_diagnostic.CA2201.severity = warning diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index 3c60b28765..550f7c8e11 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -14,10 +14,6 @@ M:Realms.CollectionExtensions.SubscribeForNotifications`1(System.Collections.Gen M:System.Threading.Tasks.Task.Wait();Don't use Task.Wait. Use Task.WaitSafely() to ensure we avoid deadlocks. P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResultSafely() to ensure we avoid deadlocks. M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever. -M:System.Char.ToLower(System.Char);char.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture. -M:System.Char.ToUpper(System.Char);char.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use char.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture. -M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString. -M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString. M:Humanizer.InflectorExtensions.Pascalize(System.String);Humanizer's .Pascalize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToPascalCase() instead. M:Humanizer.InflectorExtensions.Camelize(System.String);Humanizer's .Camelize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToCamelCase() instead. M:Humanizer.InflectorExtensions.Underscore(System.String);Humanizer's .Underscore() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToSnakeCase() instead. diff --git a/Directory.Build.props b/Directory.Build.props index 60e0334f97..f89696d867 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -23,6 +23,8 @@ Default Default Recommended + Recommended + Recommended true From 13d7c6a2d863b9bae4ae024e4fd59ac2f895c292 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2024 19:55:03 +0800 Subject: [PATCH 101/326] Enable recommended rules for maintainability --- Directory.Build.props | 2 ++ osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs | 2 ++ .../API/Requests/Responses/APIUserMostPlayedBeatmap.cs | 2 ++ .../Notifications/WebSocket/Events/NewChatMessageData.cs | 2 ++ osu.Game/Utils/SpecialFunctions.cs | 5 +---- 5 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index f89696d867..7cd02a72d4 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -25,6 +25,8 @@ Recommended Recommended Recommended + Recommended + Default true diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs index 583def8eda..8e4cc387ed 100644 --- a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -45,7 +45,9 @@ namespace osu.Game.Online.API.Requests.Responses public KudosuAction Action; +#pragma warning disable CA1507 // Happens to name the same because of casing preference [JsonProperty("action")] +#pragma warning restore CA1507 private string action { set diff --git a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs index 6d5fd59f9c..38ad2bd02d 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs @@ -15,7 +15,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("count")] public int PlayCount { get; set; } +#pragma warning disable CA1507 // Happens to name the same because of casing preference [JsonProperty("beatmap")] +#pragma warning restore CA1507 private APIBeatmap beatmap { get; set; } public APIBeatmap BeatmapInfo diff --git a/osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs b/osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs index ff9f5ee9f7..677286bb8a 100644 --- a/osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs +++ b/osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs @@ -19,7 +19,9 @@ namespace osu.Game.Online.Notifications.WebSocket.Events [JsonProperty(@"messages")] public List Messages { get; set; } = null!; +#pragma warning disable CA1507 // Happens to name the same because of casing preference [JsonProperty(@"users")] +#pragma warning restore CA1507 private List users { get; set; } = null!; [OnDeserialized] diff --git a/osu.Game/Utils/SpecialFunctions.cs b/osu.Game/Utils/SpecialFunctions.cs index 0b0f0598bb..795a84a973 100644 --- a/osu.Game/Utils/SpecialFunctions.cs +++ b/osu.Game/Utils/SpecialFunctions.cs @@ -666,10 +666,7 @@ namespace osu.Game.Utils { // 2020-10-07 jbialogrodzki #730 Since this is public API we should probably // handle null arguments? It doesn't seem to have been done consistently in this class though. - if (coefficients == null) - { - throw new ArgumentNullException(nameof(coefficients)); - } + ArgumentNullException.ThrowIfNull(coefficients); // 2020-10-07 jbialogrodzki #730 Zero polynomials need explicit handling. // Without this check, we attempted to peek coefficients at negative indices! From f5a77165096fd395a2df7d8e3656bf794a7db2aa Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2024 20:37:53 +0800 Subject: [PATCH 102/326] Apply minor performance rules --- .globalconfig | 16 ++++++++++++++++ Directory.Build.props | 1 + .../TestSceneMultiplayerParticipantsList.cs | 2 +- .../UserInterface/TestSceneDeleteLocalScore.cs | 2 +- .../Extensions/StringDehumanizeExtensions.cs | 2 +- .../Overlays/Chat/ChannelList/ChannelList.cs | 11 ++--------- osu.Game/Overlays/ChatOverlay.cs | 3 +-- osu.Game/Overlays/Comments/CommentsContainer.cs | 2 +- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 4 ++-- .../Objects/Legacy/ConvertHitObjectParser.cs | 2 +- .../Ranking/Statistics/User/OverallRanking.cs | 4 ++-- osu.Game/Skinning/SkinImporter.cs | 2 +- osu.Game/Storyboards/Storyboard.cs | 3 ++- .../Visual/Spectator/TestSpectatorClient.cs | 4 ++-- 14 files changed, 34 insertions(+), 24 deletions(-) diff --git a/.globalconfig b/.globalconfig index d6c657bad0..e218f46cd2 100644 --- a/.globalconfig +++ b/.globalconfig @@ -50,6 +50,22 @@ dotnet_diagnostic.IDE1006.severity = warning # Too many noisy warnings for parsing/formatting numbers dotnet_diagnostic.CA1305.severity = none +# CA1806: Do not ignore method results +# The usages for numeric parsing are explicitly optional +dotnet_diagnostic.CA1806.severity = suggestion + +# CA1822: Mark members as static +# Potential false positive around reflection/too much noise +dotnet_diagnostic.CA1822.severity = none + +# CA1859: Use concrete types when possible for improved performance +# Involves design considerations +dotnet_diagnostic.CA1859.severity = none + +# CA1861: Avoid constant arrays as arguments +# Outdated with collection expressions +dotnet_diagnostic.CA1861.severity = none + # CA2007: Consider calling ConfigureAwait on the awaited task dotnet_diagnostic.CA2007.severity = warning diff --git a/Directory.Build.props b/Directory.Build.props index 7cd02a72d4..0f982d496e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -27,6 +27,7 @@ Recommended Recommended Default + Minimum true diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 95ae4c5e80..d88741ec0c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -198,7 +198,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("make second user host", () => MultiplayerClient.TransferHost(3)); - AddUntilStep("kick buttons not visible", () => this.ChildrenOfType().Count(d => d.IsPresent) == 0); + AddUntilStep("kick buttons not visible", () => !this.ChildrenOfType().Any(d => d.IsPresent)); AddStep("make local user host again", () => MultiplayerClient.TransferHost(API.LocalUser.Value.Id)); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index e2fe10fa74..f7bdda6b57 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -151,7 +151,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("click delete option", () => { - InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType().First(i => i.Item.Text.Value.ToString().ToLowerInvariant() == "delete")); + InputManager.MoveMouseTo(contextMenuContainer.ChildrenOfType().First(i => string.Equals(i.Item.Text.Value.ToString(), "delete", System.StringComparison.OrdinalIgnoreCase))); InputManager.Click(MouseButton.Left); }); diff --git a/osu.Game/Extensions/StringDehumanizeExtensions.cs b/osu.Game/Extensions/StringDehumanizeExtensions.cs index 6f0d7622d3..5993f83b55 100644 --- a/osu.Game/Extensions/StringDehumanizeExtensions.cs +++ b/osu.Game/Extensions/StringDehumanizeExtensions.cs @@ -60,7 +60,7 @@ namespace osu.Game.Extensions public static string ToCamelCase(this string input) { string word = input.ToPascalCase(); - return word.Length > 0 ? word.Substring(0, 1).ToLowerInvariant() + word.Substring(1) : word; + return word.Length > 0 ? char.ToLowerInvariant(word[0]) + word.Substring(1) : word; } /// diff --git a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs index fc0060d86a..39860b5e03 100644 --- a/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs +++ b/osu.Game/Overlays/Chat/ChannelList/ChannelList.cs @@ -120,10 +120,9 @@ namespace osu.Game.Overlays.Chat.ChannelList public void RemoveChannel(Channel channel) { - if (!channelMap.ContainsKey(channel)) + if (!channelMap.TryGetValue(channel, out var item)) return; - ChannelListItem item = channelMap[channel]; FillFlowContainer flow = getFlowForChannel(channel); channelMap.Remove(channel); @@ -132,13 +131,7 @@ namespace osu.Game.Overlays.Chat.ChannelList updateVisibility(); } - public ChannelListItem GetItem(Channel channel) - { - if (!channelMap.ContainsKey(channel)) - throw new ArgumentOutOfRangeException(); - - return channelMap[channel]; - } + public ChannelListItem GetItem(Channel channel) => channelMap[channel]; public void ScrollChannelIntoView(Channel channel) => scroll.ScrollIntoView(GetItem(channel)); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index b11483e678..a00414522d 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -386,9 +386,8 @@ namespace osu.Game.Overlays { channelList.RemoveChannel(channel); - if (loadedChannels.ContainsKey(channel)) + if (loadedChannels.TryGetValue(channel, out var loaded)) { - DrawableChannel loaded = loadedChannels[channel]; loadedChannels.Remove(channel); // DrawableChannel removed from cache must be manually disposed loaded.Dispose(); diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 921c1682f5..5e277357a9 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -244,7 +244,7 @@ namespace osu.Game.Overlays.Comments protected void OnSuccess(CommentBundle response) { commentCounter.Current.Value = response.Total; - newCommentEditor.CommentableMeta.Value = response.CommentableMeta.SingleOrDefault(m => m.Id == id.Value && m.Type == type.Value.ToString().ToSnakeCase().ToLowerInvariant()); + newCommentEditor.CommentableMeta.Value = response.CommentableMeta.SingleOrDefault(m => m.Id == id.Value && string.Equals(m.Type, type.Value.ToString().ToSnakeCase(), StringComparison.OrdinalIgnoreCase)); if (!response.Comments.Any()) { diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index 4ca937bf86..fb056b457b 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -173,10 +173,10 @@ namespace osu.Game.Rulesets.Mods }; drawable.OnRevertResult += (_, result) => { - if (!ratesForRewinding.ContainsKey(result.HitObject)) return; + if (!ratesForRewinding.TryGetValue(result.HitObject, out double rewindValue)) return; if (!shouldProcessResult(result)) return; - recentRates.Insert(0, ratesForRewinding[result.HitObject]); + recentRates.Insert(0, rewindValue); ratesForRewinding.Remove(result.HitObject); recentRates.RemoveAt(recentRates.Count - 1); diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index f8bc0ce112..0162c8017b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -243,7 +243,7 @@ namespace osu.Game.Rulesets.Objects.Legacy return PathType.CATMULL; case 'B': - if (input.Length > 1 && int.TryParse(input.Substring(1), out int degree) && degree > 0) + if (input.Length > 1 && int.TryParse(input.AsSpan(1), out int degree) && degree > 0) return PathType.BSpline(degree); return PathType.BEZIER; diff --git a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs index 171a3f0f65..9f5afea6f0 100644 --- a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs +++ b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs @@ -59,14 +59,14 @@ namespace osu.Game.Screens.Ranking.Statistics.User new SimpleStatisticTable.Spacer(), new PerformancePointsChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, }, - new Drawable[] { }, + [], new Drawable[] { new MaximumComboChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, new SimpleStatisticTable.Spacer(), new AccuracyChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, }, - new Drawable[] { }, + [], new Drawable[] { new RankedScoreChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, diff --git a/osu.Game/Skinning/SkinImporter.cs b/osu.Game/Skinning/SkinImporter.cs index 59c7f0ba26..70d3195ecd 100644 --- a/osu.Game/Skinning/SkinImporter.cs +++ b/osu.Game/Skinning/SkinImporter.cs @@ -37,7 +37,7 @@ namespace osu.Game.Skinning protected override string[] HashableFileTypes => new[] { ".ini", ".json" }; - protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == @".osk"; + protected override bool ShouldDeleteArchive(string path) => string.Equals(Path.GetExtension(path), @".osk", StringComparison.OrdinalIgnoreCase); protected override SkinInfo CreateModel(ArchiveReader archive, ImportParameters parameters) => new SkinInfo { Name = archive.Name ?? @"No name" }; diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 8c43b99702..5120757f3d 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.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.IO; using System.Linq; @@ -89,7 +90,7 @@ namespace osu.Game.Storyboards // Importantly, do this after the NullOrEmpty because EF may have stored the non-nullable value as null to the database, bypassing compile-time constraints. backgroundPath = backgroundPath.ToLowerInvariant(); - return GetLayer("Background").Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); + return GetLayer("Background").Elements.Any(e => string.Equals(e.Path, backgroundPath, StringComparison.OrdinalIgnoreCase)); } } diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index c27e7f15ca..5d33afd288 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -78,12 +78,12 @@ namespace osu.Game.Tests.Visual.Spectator /// The spectator state to end play with. public void SendEndPlay(int userId, SpectatedUserState state = SpectatedUserState.Quit) { - if (!userBeatmapDictionary.ContainsKey(userId)) + if (!userBeatmapDictionary.TryGetValue(userId, out int beatmapId)) return; ((ISpectatorClient)this).UserFinishedPlaying(userId, new SpectatorState { - BeatmapID = userBeatmapDictionary[userId], + BeatmapID = beatmapId, RulesetID = 0, Mods = userModsDictionary[userId], State = state From ac2c4e81c77fcee81462adf5b2c8d60dd21036a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Nov 2024 14:04:39 +0100 Subject: [PATCH 103/326] Use switch --- .../OnlinePlay/Playlists/PlaylistsRoomFooter.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs index c8e5f0859d..9ccc8f3cab 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs @@ -83,8 +83,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void onRoomChanged(object? sender, PropertyChangedEventArgs e) { - if (e.PropertyName == nameof(Room.Status) || e.PropertyName == nameof(Room.Host) || e.PropertyName == nameof(Room.StartDate)) - updateState(); + switch (e.PropertyName) + { + case nameof(Room.Status): + case nameof(Room.Host): + case nameof(Room.StartDate): + updateState(); + break; + } } private void updateState() From 9926ffd32627b6d50442dabcea30bb58c8f2ca6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Nov 2024 14:06:12 +0100 Subject: [PATCH 104/326] Make button a little narrower --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs index 9ccc8f3cab..6089b4734e 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs @@ -59,7 +59,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Action = () => OnClose?.Invoke(), Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(200, 1), + Size = new Vector2(120, 1), Alpha = 0, RelativeSizeAxes = Axes.Y, } From c93c549b054a7e4ec5935a05e9bb71be807a5182 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Nov 2024 14:17:31 +0100 Subject: [PATCH 105/326] Fix ready button not disabling on playlist close --- .../Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 61df331c48..9573155f5a 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.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.ComponentModel; using System.Diagnostics; using System.Linq; @@ -285,7 +286,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists DialogOverlay?.Push(new ClosePlaylistDialog(Room, () => { var request = new ClosePlaylistRequest(Room.RoomID!.Value); - request.Success += () => Room.Status = new RoomStatusEnded(); + request.Success += () => + { + Room.Status = new RoomStatusEnded(); + Room.EndDate = DateTimeOffset.UtcNow; + }; API.Queue(request); })); } From 5b63d725c507b7e12cf111e3b3db9faf20cd0725 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2024 22:13:59 +0800 Subject: [PATCH 106/326] Mark CA1826/CA1860 as suggestion --- .globalconfig | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.globalconfig b/.globalconfig index e218f46cd2..7673e5afb0 100644 --- a/.globalconfig +++ b/.globalconfig @@ -58,13 +58,19 @@ dotnet_diagnostic.CA1806.severity = suggestion # Potential false positive around reflection/too much noise dotnet_diagnostic.CA1822.severity = none +# CA1826: Do not use Enumerable method on indexable collections +dotnet_diagnostic.CA1826.severity = suggestion + # CA1859: Use concrete types when possible for improved performance # Involves design considerations -dotnet_diagnostic.CA1859.severity = none +dotnet_diagnostic.CA1859.severity = suggestion + +# CA1860: Avoid using 'Enumerable.Any()' extension method +dotnet_diagnostic.CA1860.severity = suggestion # CA1861: Avoid constant arrays as arguments # Outdated with collection expressions -dotnet_diagnostic.CA1861.severity = none +dotnet_diagnostic.CA1861.severity = suggestion # CA2007: Consider calling ConfigureAwait on the awaited task dotnet_diagnostic.CA2007.severity = warning From 0a8ec4db2b6fc80cdcf6c9d5dc190b4e31a140d1 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2024 22:20:36 +0800 Subject: [PATCH 107/326] Enable recommended rules for reliability --- .globalconfig | 8 ++++++++ Directory.Build.props | 1 + 2 files changed, 9 insertions(+) diff --git a/.globalconfig b/.globalconfig index 7673e5afb0..7eec612775 100644 --- a/.globalconfig +++ b/.globalconfig @@ -75,6 +75,14 @@ dotnet_diagnostic.CA1861.severity = suggestion # CA2007: Consider calling ConfigureAwait on the awaited task dotnet_diagnostic.CA2007.severity = warning +# CA2016: Forward the 'CancellationToken' parameter to methods +# Some overloads are having special handling for debugger +dotnet_diagnostic.CA2016.severity = suggestion + +# CA2021: Do not call Enumerable.Cast or Enumerable.OfType with incompatible types +# Causing a lot of false positives with generics +dotnet_diagnostic.CA2021.severity = none + # CA2101: Specify marshaling for P/Invoke string arguments # Reports warning for all non-UTF16 usages on DllImport; consider migrating to LibraryImport dotnet_diagnostic.CA2101.severity = none diff --git a/Directory.Build.props b/Directory.Build.props index 0f982d496e..0dcbffd0dc 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -28,6 +28,7 @@ Recommended Default Minimum + Recommended true From fced2545944f401fe6cf7a8429eb97bc774824fc Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 28 Nov 2024 22:32:55 +0800 Subject: [PATCH 108/326] Enable selected rules for usage --- .globalconfig | 7 +++++-- Directory.Build.props | 2 ++ .../Argon/ManiaArgonSkinTransformer.cs | 20 +++++++++---------- osu.Game/Online/Leaderboards/Leaderboard.cs | 2 +- osu.Game/Online/OnlineViewContainer.cs | 2 +- osu.Game/Overlays/AccountCreationOverlay.cs | 2 +- .../Overlays/Toolbar/ToolbarUserButton.cs | 2 +- 7 files changed, 21 insertions(+), 16 deletions(-) diff --git a/.globalconfig b/.globalconfig index 7eec612775..e2cd1926f0 100644 --- a/.globalconfig +++ b/.globalconfig @@ -90,8 +90,11 @@ dotnet_diagnostic.CA2101.severity = none # CA2201: Do not raise reserved exception types dotnet_diagnostic.CA2201.severity = warning -#Disable operator overloads requiring alternate named methods -dotnet_diagnostic.CA2225.severity = none +# CA2208: Instantiate argument exceptions correctly +dotnet_diagnostic.CA2208.severity = suggestion + +# CA2242: Test for NaN correctly +dotnet_diagnostic.CA2242.severity = warning # Banned APIs dotnet_diagnostic.RS0030.severity = error diff --git a/Directory.Build.props b/Directory.Build.props index 0dcbffd0dc..0ab41d27a0 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -29,6 +29,8 @@ Default Minimum Recommended + Default + Default true diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index afccb2e568..c37c18081a 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -164,7 +164,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 1: return colour_cyan; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } case 3: @@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 2: return colour_cyan; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } case 4: @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 3: return colour_purple; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } case 5: @@ -206,7 +206,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 4: return colour_cyan; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } case 6: @@ -224,7 +224,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 5: return colour_pink; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } case 7: @@ -244,7 +244,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 6: return colour_pink; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } case 8: @@ -266,7 +266,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 7: return colour_purple; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } case 9: @@ -290,7 +290,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 8: return colour_purple; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } case 10: @@ -316,7 +316,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 9: return colour_purple; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } } @@ -339,7 +339,7 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon case 5: return colour_green; - default: throw new ArgumentOutOfRangeException(); + default: throw new ArgumentOutOfRangeException(nameof(columnIndex)); } } } diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 0fd9597ac0..d76da54adf 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -363,7 +363,7 @@ namespace osu.Game.Online.Leaderboards return null; default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(state)); } } diff --git a/osu.Game/Online/OnlineViewContainer.cs b/osu.Game/Online/OnlineViewContainer.cs index 824da152b2..ce55b50d94 100644 --- a/osu.Game/Online/OnlineViewContainer.cs +++ b/osu.Game/Online/OnlineViewContainer.cs @@ -87,7 +87,7 @@ namespace osu.Game.Online break; default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(state.NewValue)); } }); diff --git a/osu.Game/Overlays/AccountCreationOverlay.cs b/osu.Game/Overlays/AccountCreationOverlay.cs index 82fc5508f1..55cba33153 100644 --- a/osu.Game/Overlays/AccountCreationOverlay.cs +++ b/osu.Game/Overlays/AccountCreationOverlay.cs @@ -126,7 +126,7 @@ namespace osu.Game.Overlays break; default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(state.NewValue)); } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs index 96c0b15c44..787c525566 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserButton.cs @@ -130,7 +130,7 @@ namespace osu.Game.Overlays.Toolbar break; default: - throw new ArgumentOutOfRangeException(); + throw new ArgumentOutOfRangeException(nameof(state.NewValue)); } }); } From 58cf1c11e4721c85b554ccd08bf3b64ca62b2395 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 28 Nov 2024 06:41:43 +0900 Subject: [PATCH 109/326] Improve menu/context-menu sample playback --- .../UserInterface/TestSceneContextMenu.cs | 11 ++- .../Cursor/OsuContextMenuContainer.cs | 8 --- .../Graphics/UserInterface/HoverSampleSet.cs | 5 +- .../Graphics/UserInterface/OsuContextMenu.cs | 15 ++-- .../UserInterface/OsuContextMenuSamples.cs | 37 ---------- osu.Game/Graphics/UserInterface/OsuMenu.cs | 19 ++--- .../Graphics/UserInterface/OsuMenuSamples.cs | 70 +++++++++++++++++++ osu.Game/OsuGameBase.cs | 5 ++ .../Edit/Components/Menus/EditorMenuBar.cs | 2 +- 9 files changed, 103 insertions(+), 69 deletions(-) delete mode 100644 osu.Game/Graphics/UserInterface/OsuContextMenuSamples.cs create mode 100644 osu.Game/Graphics/UserInterface/OsuMenuSamples.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs index 2a2f267fc8..118e37dab4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneContextMenu.cs @@ -101,7 +101,16 @@ namespace osu.Game.Tests.Visual.UserInterface } } } - } + }, + } + }, + new OsuMenuItem(@"Another nested option") + { + Items = new MenuItem[] + { + new OsuMenuItem(@"Sub-One"), + new OsuMenuItem(@"Sub-Two"), + new OsuMenuItem(@"Sub-Three"), } }, new OsuMenuItem(@"Choose me please"), diff --git a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs index 7b21a413f7..3180661b0c 100644 --- a/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuContextMenuContainer.cs @@ -11,16 +11,8 @@ namespace osu.Game.Graphics.Cursor [Cached(typeof(OsuContextMenuContainer))] public partial class OsuContextMenuContainer : ContextMenuContainer { - [Cached] - private OsuContextMenuSamples samples = new OsuContextMenuSamples(); - private OsuContextMenu menu = null!; - public OsuContextMenuContainer() - { - AddInternal(samples); - } - protected override Menu CreateMenu() => menu = new OsuContextMenu(true); public void CloseMenu() diff --git a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs index 5b0fbc693e..62eb765cc8 100644 --- a/osu.Game/Graphics/UserInterface/HoverSampleSet.cs +++ b/osu.Game/Graphics/UserInterface/HoverSampleSet.cs @@ -23,6 +23,9 @@ namespace osu.Game.Graphics.UserInterface DialogCancel, [Description("dialog-ok")] - DialogOk + DialogOk, + + [Description("menu-open")] + MenuOpen, } } diff --git a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs index 96797e5d01..7a5d2c369b 100644 --- a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs @@ -15,7 +15,7 @@ namespace osu.Game.Graphics.UserInterface private const int fade_duration = 250; [Resolved] - private OsuContextMenuSamples samples { get; set; } = null!; + private OsuMenuSamples menuSamples { get; set; } = null!; // todo: this shouldn't be required after https://github.com/ppy/osu-framework/issues/4519 is fixed. private bool wasOpened; @@ -47,15 +47,14 @@ namespace osu.Game.Graphics.UserInterface protected override void AnimateOpen() { + wasOpened = true; this.FadeIn(fade_duration, Easing.OutQuint); - if (playClickSample) - samples.PlayClickSample(); + if (!playClickSample) + return; - if (!wasOpened) - samples.PlayOpenSample(); - - wasOpened = true; + menuSamples?.PlayClickSample(); + menuSamples?.PlayOpenSample(); } protected override void AnimateClose() @@ -63,7 +62,7 @@ namespace osu.Game.Graphics.UserInterface this.FadeOut(fade_duration, Easing.OutQuint); if (wasOpened) - samples.PlayCloseSample(); + menuSamples?.PlayCloseSample(); wasOpened = false; } diff --git a/osu.Game/Graphics/UserInterface/OsuContextMenuSamples.cs b/osu.Game/Graphics/UserInterface/OsuContextMenuSamples.cs deleted file mode 100644 index 6d7543c472..0000000000 --- a/osu.Game/Graphics/UserInterface/OsuContextMenuSamples.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. - -#nullable disable - -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Extensions; -using osu.Framework.Graphics; - -namespace osu.Game.Graphics.UserInterface -{ - public partial class OsuContextMenuSamples : Component - { - private Sample sampleClick; - private Sample sampleOpen; - private Sample sampleClose; - - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - sampleClick = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select"); - sampleOpen = audio.Samples.Get(@"UI/dropdown-open"); - sampleClose = audio.Samples.Get(@"UI/dropdown-close"); - } - - public void PlayClickSample() => Scheduler.AddOnce(playClickSample); - private void playClickSample() => sampleClick.Play(); - - public void PlayOpenSample() => Scheduler.AddOnce(playOpenSample); - private void playOpenSample() => sampleOpen.Play(); - - public void PlayCloseSample() => Scheduler.AddOnce(playCloseSample); - private void playCloseSample() => sampleClose.Play(); - } -} diff --git a/osu.Game/Graphics/UserInterface/OsuMenu.cs b/osu.Game/Graphics/UserInterface/OsuMenu.cs index 6e7dad2b5f..7cc1bab25f 100644 --- a/osu.Game/Graphics/UserInterface/OsuMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuMenu.cs @@ -4,8 +4,6 @@ #nullable disable 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.Containers; @@ -20,12 +18,12 @@ namespace osu.Game.Graphics.UserInterface { public partial class OsuMenu : Menu { - private Sample sampleOpen; - private Sample sampleClose; - // todo: this shouldn't be required after https://github.com/ppy/osu-framework/issues/4519 is fixed. private bool wasOpened; + [Resolved] + private OsuMenuSamples menuSamples { get; set; } = null!; + public OsuMenu(Direction direction, bool topLevelMenu = false) : base(direction, topLevelMenu) { @@ -33,13 +31,8 @@ namespace osu.Game.Graphics.UserInterface MaskingContainer.CornerRadius = 4; ItemsContainer.Padding = new MarginPadding(5); - } - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - sampleOpen = audio.Samples.Get(@"UI/dropdown-open"); - sampleClose = audio.Samples.Get(@"UI/dropdown-close"); + OnSubmenuOpen += _ => { menuSamples?.PlaySubOpenSample(); }; } protected override void Update() @@ -64,7 +57,7 @@ namespace osu.Game.Graphics.UserInterface protected override void AnimateOpen() { if (!TopLevelMenu && !wasOpened) - sampleOpen?.Play(); + menuSamples?.PlayOpenSample(); this.FadeIn(300, Easing.OutQuint); wasOpened = true; @@ -73,7 +66,7 @@ namespace osu.Game.Graphics.UserInterface protected override void AnimateClose() { if (!TopLevelMenu && wasOpened) - sampleClose?.Play(); + menuSamples?.PlayCloseSample(); this.FadeOut(300, Easing.OutQuint); wasOpened = false; diff --git a/osu.Game/Graphics/UserInterface/OsuMenuSamples.cs b/osu.Game/Graphics/UserInterface/OsuMenuSamples.cs new file mode 100644 index 0000000000..779671b6ad --- /dev/null +++ b/osu.Game/Graphics/UserInterface/OsuMenuSamples.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. + +#nullable disable + +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; + +namespace osu.Game.Graphics.UserInterface +{ + public partial class OsuMenuSamples : Component + { + private Sample sampleClick; + private Sample sampleOpen; + private Sample sampleSubOpen; + private Sample sampleClose; + + private bool triggerOpen; + private bool triggerSubOpen; + private bool triggerClose; + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + sampleClick = audio.Samples.Get(@"UI/menu-open-select"); + sampleOpen = audio.Samples.Get(@"UI/menu-open"); + sampleSubOpen = audio.Samples.Get(@"UI/menu-sub-open"); + sampleClose = audio.Samples.Get(@"UI/menu-close"); + } + + public void PlayClickSample() + { + Scheduler.AddOnce(playClickSample); + } + + public void PlayOpenSample() + { + triggerOpen = true; + Scheduler.AddOnce(resolvePlayback); + } + + public void PlaySubOpenSample() + { + triggerSubOpen = true; + Scheduler.AddOnce(resolvePlayback); + } + + public void PlayCloseSample() + { + triggerClose = true; + Scheduler.AddOnce(resolvePlayback); + } + + private void playClickSample() => sampleClick.Play(); + + private void resolvePlayback() + { + if (triggerSubOpen) + sampleSubOpen?.Play(); + else if (triggerOpen) + sampleOpen?.Play(); + else if (triggerClose) + sampleClose?.Play(); + + triggerOpen = triggerSubOpen = triggerClose = false; + } + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index dc13924b4f..0f9848cacc 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -41,6 +41,7 @@ using osu.Game.Database; using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.UserInterface; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.IO; @@ -385,6 +386,10 @@ namespace osu.Game GlobalActionContainer globalBindings; + OsuMenuSamples menuSamples; + dependencies.Cache(menuSamples = new OsuMenuSamples()); + base.Content.Add(menuSamples); + base.Content.Add(SafeAreaContainer = new SafeAreaContainer { SafeAreaOverrideEdges = SafeAreaOverrideEdges, diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index 47a13dcfba..76b8811b89 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -99,7 +99,7 @@ namespace osu.Game.Screens.Edit.Components.Menus ForegroundColourHover = colourProvider.Content1; BackgroundColourHover = colourProvider.Background1; - AddInternal(hoverClickSounds = new HoverClickSounds()); + AddInternal(hoverClickSounds = new HoverClickSounds(HoverSampleSet.MenuOpen)); } protected override void LoadComplete() From 932afcde01469543084467b4699d9774123b8363 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 17:43:32 -0500 Subject: [PATCH 110/326] Make editor make sense --- osu.Game/Screens/Edit/Editor.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 13e5791605..0e4807dc78 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -80,8 +80,6 @@ namespace osu.Game.Screens.Edit public override float BackgroundParallaxAmount => 0.1f; - public override bool AllowUserExit => false; - public override bool HideOverlaysOnEnter => true; public override bool DisallowExternalBeatmapRulesetChanges => true; @@ -194,6 +192,8 @@ namespace osu.Game.Screens.Edit } } + protected override bool InitialBackButtonVisibility => false; + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -760,11 +760,6 @@ namespace osu.Game.Screens.Edit switch (e.Action) { - case GlobalAction.Back: - // as we don't want to display the back button, manual handling of exit action is required. - this.Exit(); - return true; - case GlobalAction.EditorCloneSelection: Clone(); return true; From 078d62fe093fc1ba9587cb6f3cdd4e4fec02e1f7 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 17:54:03 -0500 Subject: [PATCH 111/326] Fix weird default in test scene --- .../Visual/Online/TestSceneUserProfileDailyChallenge.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs index 3222e16412..0477d39193 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileDailyChallenge.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Online AddSliderStep("weekly best", 0, 250, 1, v => update(s => s.WeeklyStreakBest = v)); AddSliderStep("top 10%", 0, 999, 0, v => update(s => s.Top10PercentPlacements = v)); AddSliderStep("top 50%", 0, 999, 0, v => update(s => s.Top50PercentPlacements = v)); - AddSliderStep("playcount", 0, 1500, 0, v => update(s => s.PlayCount = v)); + AddSliderStep("playcount", 0, 1500, 1, v => update(s => s.PlayCount = v)); AddStep("create", () => { Clear(); From 51bcde67aae51cf23250109b4256a931dcf074f3 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 17:55:15 -0500 Subject: [PATCH 112/326] Remove no longer required comment --- .../Profile/Header/Components/DailyChallengeStatsTooltip.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs index ea49f9d784..826b40d70c 100644 --- a/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs +++ b/osu.Game/Overlays/Profile/Header/Components/DailyChallengeStatsTooltip.cs @@ -140,7 +140,6 @@ namespace osu.Game.Overlays.Profile.Header.Components // reference: https://github.com/ppy/osu-web/blob/a97f156014e00ea1aa315140da60542e798a9f06/resources/js/profile-page/daily-challenge.tsx#L13-L47 - // Rounding down is needed here to ensure the overlay shows the same colour as osu-web for the play count. public static RankingTier TierForPlayCount(int playCount) => TierForDaily((int)Math.Floor(playCount / 3.0d)); public static RankingTier TierForDaily(int daily) From 311f0947e41b44aaf5a08397138a8b3d57bc59d7 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 17:57:47 -0500 Subject: [PATCH 113/326] Abstractify resource change logic and share between background and audio --- .../Screens/Edit/Setup/ResourcesSection.cs | 91 ++++++------------- 1 file changed, 29 insertions(+), 62 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 1ce944b5a4..a02900a204 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -82,57 +82,12 @@ namespace osu.Game.Screens.Edit.Setup if (!source.Exists) return false; - var set = working.Value.BeatmapSetInfo; - - if (applyToAllDifficulties) - { - string newFilename = $@"bg{source.Extension}"; - - foreach (var beatmapInSet in set.Beatmaps) - { - if (set.GetFile(beatmapInSet.Metadata.BackgroundFile) is RealmNamedFileUsage existingFile) - beatmaps.DeleteFile(set, existingFile); - - if (beatmapInSet.Metadata.BackgroundFile != newFilename) - { - beatmapInSet.Metadata.BackgroundFile = newFilename; - - if (!beatmapInSet.Equals(working.Value.BeatmapInfo)) - beatmaps.Save(beatmapInSet, beatmaps.GetWorkingBeatmap(beatmapInSet).Beatmap); - } - } - } - else - { - var beatmap = working.Value.BeatmapInfo; - - string[] filenames = set.Files.Select(f => f.Filename).Where(f => - f.StartsWith(@"bg", StringComparison.OrdinalIgnoreCase) && - f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - - string currentFilename = working.Value.Metadata.BackgroundFile; - - var oldFile = set.GetFile(currentFilename); - string? newFilename = null; - - if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.BackgroundFile != currentFilename)) - { - beatmaps.DeleteFile(set, oldFile); - newFilename = currentFilename; - } - - newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"bg{source.Extension}"); - working.Value.Metadata.BackgroundFile = newFilename; - } - - using (var stream = source.OpenRead()) - beatmaps.AddFile(set, stream, working.Value.Metadata.BackgroundFile); - - editorBeatmap.SaveState(); + changeResource(source, applyToAllDifficulties, @"bg", + metadata => metadata.BackgroundFile, + (metadata, name) => metadata.BackgroundFile = name); headerBackground.UpdateBackground(); editor?.ApplyToBackground(bg => bg.RefreshBackground()); - return true; } @@ -141,20 +96,34 @@ namespace osu.Game.Screens.Edit.Setup if (!source.Exists) return false; + changeResource(source, applyToAllDifficulties, @"audio", + metadata => metadata.AudioFile, + (metadata, name) => metadata.AudioFile = name); + + music.ReloadCurrentTrack(); + return true; + } + + private void changeResource(FileInfo source, bool applyToAllDifficulties, string baseFilename, Func readFilename, Action writeFilename) + { var set = working.Value.BeatmapSetInfo; + string newFilename = string.Empty; + if (applyToAllDifficulties) { - string newFilename = $@"audio{source.Extension}"; + newFilename = $"{baseFilename}{source.Extension}"; foreach (var beatmapInSet in set.Beatmaps) { - if (set.GetFile(beatmapInSet.Metadata.AudioFile) is RealmNamedFileUsage existingFile) + string filenameInBeatmap = readFilename(beatmapInSet.Metadata); + + if (set.GetFile(filenameInBeatmap) is RealmNamedFileUsage existingFile) beatmaps.DeleteFile(set, existingFile); - if (beatmapInSet.Metadata.AudioFile != newFilename) + if (filenameInBeatmap != newFilename) { - beatmapInSet.Metadata.AudioFile = newFilename; + writeFilename(beatmapInSet.Metadata, newFilename); if (!beatmapInSet.Equals(working.Value.BeatmapInfo)) beatmaps.Save(beatmapInSet, beatmaps.GetWorkingBeatmap(beatmapInSet).Beatmap); @@ -166,31 +135,29 @@ namespace osu.Game.Screens.Edit.Setup var beatmap = working.Value.BeatmapInfo; string[] filenames = set.Files.Select(f => f.Filename).Where(f => - f.StartsWith(@"audio", StringComparison.OrdinalIgnoreCase) && + f.StartsWith(baseFilename, StringComparison.OrdinalIgnoreCase) && f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - string currentFilename = working.Value.Metadata.AudioFile; + string currentFilename = readFilename(working.Value.Metadata); var oldFile = set.GetFile(currentFilename); - string? newFilename = null; - if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => b.Metadata.AudioFile != currentFilename)) + if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => readFilename(b.Metadata) != currentFilename)) { beatmaps.DeleteFile(set, oldFile); newFilename = currentFilename; } - newFilename ??= NamingUtils.GetNextBestFilename(filenames, $@"audio{source.Extension}"); - working.Value.Metadata.AudioFile = newFilename; + if (string.IsNullOrEmpty(newFilename)) + newFilename = NamingUtils.GetNextBestFilename(filenames, $@"{baseFilename}{source.Extension}"); + + writeFilename(working.Value.Metadata, newFilename); } using (var stream = source.OpenRead()) - beatmaps.AddFile(set, stream, working.Value.Metadata.AudioFile); + beatmaps.AddFile(set, stream, newFilename); editorBeatmap.SaveState(); - music.ReloadCurrentTrack(); - - return true; } private void backgroundChanged(ValueChangedEvent file) From 489d7a30ec093152cd838cfba9b64c6f235bfe66 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 28 Nov 2024 18:32:03 -0500 Subject: [PATCH 114/326] Perform a single `Save` call rather than doing it in each difficulty --- .../Editing/TestSceneEditorBeatmapCreation.cs | 3 --- .../Screens/Edit/Setup/ResourcesSection.cs | 27 +++++++------------ 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index c7d745b6e0..7a390ac131 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -107,9 +107,6 @@ namespace osu.Game.Tests.Visual.Editing AddStep("test play", () => Editor.TestGameplay()); - AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog != null); - AddStep("confirm save", () => InputManager.Key(Key.Number1)); - AddUntilStep("wait for return to editor", () => Editor.IsCurrentScreen()); AddAssert("track is still not virtual", () => Beatmap.Value.Track is not TrackVirtual); diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index a02900a204..4d2bbb035e 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -32,9 +32,6 @@ namespace osu.Game.Screens.Edit.Setup [Resolved] private IBindable working { get; set; } = null!; - [Resolved] - private EditorBeatmap editorBeatmap { get; set; } = null!; - [Resolved] private Editor? editor { get; set; } @@ -114,25 +111,17 @@ namespace osu.Game.Screens.Edit.Setup { newFilename = $"{baseFilename}{source.Extension}"; - foreach (var beatmapInSet in set.Beatmaps) + foreach (var beatmap in set.Beatmaps) { - string filenameInBeatmap = readFilename(beatmapInSet.Metadata); + if (set.GetFile(readFilename(beatmap.Metadata)) is RealmNamedFileUsage otherExistingFile) + beatmaps.DeleteFile(set, otherExistingFile); - if (set.GetFile(filenameInBeatmap) is RealmNamedFileUsage existingFile) - beatmaps.DeleteFile(set, existingFile); - - if (filenameInBeatmap != newFilename) - { - writeFilename(beatmapInSet.Metadata, newFilename); - - if (!beatmapInSet.Equals(working.Value.BeatmapInfo)) - beatmaps.Save(beatmapInSet, beatmaps.GetWorkingBeatmap(beatmapInSet).Beatmap); - } + writeFilename(beatmap.Metadata, newFilename); } } else { - var beatmap = working.Value.BeatmapInfo; + var thisBeatmap = working.Value.BeatmapInfo; string[] filenames = set.Files.Select(f => f.Filename).Where(f => f.StartsWith(baseFilename, StringComparison.OrdinalIgnoreCase) && @@ -142,7 +131,7 @@ namespace osu.Game.Screens.Edit.Setup var oldFile = set.GetFile(currentFilename); - if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(beatmap)).All(b => readFilename(b.Metadata) != currentFilename)) + if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(thisBeatmap)).All(b => readFilename(b.Metadata) != currentFilename)) { beatmaps.DeleteFile(set, oldFile); newFilename = currentFilename; @@ -157,7 +146,9 @@ namespace osu.Game.Screens.Edit.Setup using (var stream = source.OpenRead()) beatmaps.AddFile(set, stream, newFilename); - editorBeatmap.SaveState(); + // editor change handler cannot be aware of any file changes or other difficulties having their metadata modified. + // for simplicity's sake, trigger a save when changing any resource to ensure the change is correctly saved. + editor?.Save(); } private void backgroundChanged(ValueChangedEvent file) From 276c37bcf77b19762c84b9d4c89251597a492ea8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2024 13:47:56 +0900 Subject: [PATCH 115/326] 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 4699beeac0..02898623a9 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 ccae4a15ee..80e695e5d1 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From b697ddc6db70de3ff8a7f07a9f734de66ea7f694 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2024 15:32:35 +0900 Subject: [PATCH 116/326] Simplify the dev footer display --- osu.Game/OsuGame.cs | 14 ++--- osu.Game/Overlays/DevBuildBanner.cs | 58 ++++++++++++++++++ osu.Game/Overlays/VersionManager.cs | 95 ----------------------------- osu.Game/Screens/Menu/MainMenu.cs | 13 ---- 4 files changed, 64 insertions(+), 116 deletions(-) create mode 100644 osu.Game/Overlays/DevBuildBanner.cs delete mode 100644 osu.Game/Overlays/VersionManager.cs diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a92b1f4d36..2e3b989c4e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -195,7 +195,8 @@ namespace osu.Game private MainMenu menuScreen; - private VersionManager versionManager; + [CanBeNull] + private DevBuildBanner devBuildBanner; [CanBeNull] private IntroScreen introScreen; @@ -1055,10 +1056,7 @@ namespace osu.Game }, topMostOverlayContent.Add); if (!IsDeployedBuild) - { - dependencies.Cache(versionManager = new VersionManager()); - loadComponentSingleFile(versionManager, ScreenContainer.Add); - } + loadComponentSingleFile(devBuildBanner = new DevBuildBanner(), ScreenContainer.Add); loadComponentSingleFile(osuLogo, _ => { @@ -1564,12 +1562,12 @@ namespace osu.Game { case IntroScreen intro: introScreen = intro; - versionManager?.Show(); + devBuildBanner?.Show(); break; case MainMenu menu: menuScreen = menu; - versionManager?.Show(); + devBuildBanner?.Show(); break; case Player player: @@ -1577,7 +1575,7 @@ namespace osu.Game break; default: - versionManager?.Hide(); + devBuildBanner?.Hide(); break; } diff --git a/osu.Game/Overlays/DevBuildBanner.cs b/osu.Game/Overlays/DevBuildBanner.cs new file mode 100644 index 0000000000..f514483e76 --- /dev/null +++ b/osu.Game/Overlays/DevBuildBanner.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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Overlays +{ + public partial class DevBuildBanner : VisibilityContainer + { + [BackgroundDependencyLoader] + private void load(OsuColour colours, TextureStore textures, OsuGameBase game) + { + AutoSizeAxes = Axes.Both; + + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + + Alpha = 0; + + AddRange(new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Font = OsuFont.Numeric.With(weight: FontWeight.Bold, size: 12), + Colour = colours.YellowDark, + Text = @"DEVELOPER BUILD", + }, + new Sprite + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Texture = textures.Get(@"Menu/dev-build-footer"), + Scale = new Vector2(0.4f, 1), + Y = 2, + }, + }); + } + + protected override void PopIn() + { + this.FadeIn(1400, Easing.OutQuint); + } + + protected override void PopOut() + { + this.FadeOut(500, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Overlays/VersionManager.cs b/osu.Game/Overlays/VersionManager.cs deleted file mode 100644 index 71f8fc05aa..0000000000 --- a/osu.Game/Overlays/VersionManager.cs +++ /dev/null @@ -1,95 +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.Development; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Overlays -{ - public partial class VersionManager : VisibilityContainer - { - [BackgroundDependencyLoader] - private void load(OsuColour colours, TextureStore textures, OsuGameBase game) - { - AutoSizeAxes = Axes.Both; - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; - - Alpha = 0; - - FillFlowContainer mainFill; - - Children = new Drawable[] - { - mainFill = new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5), - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Children = new Drawable[] - { - new OsuSpriteText - { - Font = OsuFont.GetFont(weight: FontWeight.Bold), - Text = game.Name - }, - new OsuSpriteText - { - Colour = DebugUtils.IsDebugBuild ? colours.Red : Color4.White, - Text = game.Version - }, - } - }, - } - } - }; - - if (!game.IsDeployedBuild) - { - mainFill.AddRange(new Drawable[] - { - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Font = OsuFont.Numeric.With(size: 12), - Colour = colours.Yellow, - Text = @"Development Build" - }, - new Sprite - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Texture = textures.Get(@"Menu/dev-build-footer"), - }, - }); - } - } - - protected override void PopIn() - { - this.FadeIn(1400, Easing.OutQuint); - } - - protected override void PopOut() - { - this.FadeOut(500, Easing.OutQuint); - } - } -} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 35c6bab81b..c753a52657 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -79,9 +79,6 @@ 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; @@ -294,16 +291,6 @@ namespace osu.Game.Screens.Menu } } - protected override void Update() - { - base.Update(); - - bottomElementsFlow.Margin = new MarginPadding - { - Bottom = (versionManager?.DrawHeight + 5) ?? 0 - }; - } - protected override void LogoSuspending(OsuLogo logo) { var seq = logo.FadeOut(300, Easing.InSine) From 110e4fbb30503114779e18348e098d062a9ea378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 28 Nov 2024 15:37:27 +0100 Subject: [PATCH 117/326] Centralise supported file extensions to one helper class As proposed in https://github.com/ppy/osu-server-beatmap-submission/pull/5#discussion_r1861680837. --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 3 ++- .../Formats/LegacyStoryboardDecoder.cs | 3 ++- .../UserInterfaceV2/OsuFileSelector.cs | 23 ++++++++----------- osu.Game/OsuGameBase.cs | 2 -- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 3 ++- .../Edit/Checks/Components/AudioCheckUtils.cs | 5 ++-- .../Screens/Edit/Setup/ResourcesSection.cs | 7 ++++-- osu.Game/Skinning/SkinnableSprite.cs | 11 +++++---- osu.Game/Storyboards/Storyboard.cs | 5 ++-- osu.Game/Utils/SupportedExtensions.cs | 19 +++++++++++++++ 11 files changed, 50 insertions(+), 33 deletions(-) create mode 100644 osu.Game/Utils/SupportedExtensions.cs diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f1ce977d96..07fcdb9d62 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -408,7 +408,7 @@ namespace osu.Game.Beatmaps // user requested abort return; - var video = b.Files.FirstOrDefault(f => OsuGameBase.VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.OrdinalIgnoreCase))); + var video = b.Files.FirstOrDefault(f => SupportedExtensions.VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.OrdinalIgnoreCase))); if (video != null) { diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 4d7ac355e0..d6c658f824 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Types; using osu.Game.Screens.Edit; +using osu.Game.Utils; namespace osu.Game.Beatmaps.Formats { @@ -446,7 +447,7 @@ namespace osu.Game.Beatmaps.Formats // 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())) + if (!SupportedExtensions.VIDEO_EXTENSIONS.Contains(Path.GetExtension(filename).ToLowerInvariant())) { beatmap.BeatmapInfo.Metadata.BackgroundFile = filename; lineSupportedByEncoder = true; diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 2f9a256d31..fe9a852faf 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -10,6 +10,7 @@ using osu.Game.Beatmaps.Legacy; using osu.Game.IO; using osu.Game.Storyboards; using osu.Game.Storyboards.Commands; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -112,7 +113,7 @@ namespace osu.Game.Beatmaps.Formats // // This avoids potential weird crashes when ffmpeg attempts to parse an image file as a video // (see https://github.com/ppy/osu/issues/22829#issuecomment-1465552451). - if (!OsuGameBase.VIDEO_EXTENSIONS.Contains(Path.GetExtension(path).ToLowerInvariant())) + if (!SupportedExtensions.VIDEO_EXTENSIONS.Contains(Path.GetExtension(path).ToLowerInvariant())) break; storyboard.GetLayer("Video").Add(storyboardSprite = new StoryboardVideo(path, offset)); diff --git a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs index c7b559d9ed..addea5c4a9 100644 --- a/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs +++ b/osu.Game/Graphics/UserInterfaceV2/OsuFileSelector.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterfaceV2.FileSelection; using osu.Game.Overlays; +using osu.Game.Utils; namespace osu.Game.Graphics.UserInterfaceV2 { @@ -96,24 +97,18 @@ namespace osu.Game.Graphics.UserInterfaceV2 { get { - if (OsuGameBase.VIDEO_EXTENSIONS.Contains(File.Extension.ToLowerInvariant())) + string extension = File.Extension.ToLowerInvariant(); + + if (SupportedExtensions.VIDEO_EXTENSIONS.Contains(extension)) return FontAwesome.Regular.FileVideo; - switch (File.Extension) - { - case @".ogg": - case @".mp3": - case @".wav": - return FontAwesome.Regular.FileAudio; + if (SupportedExtensions.AUDIO_EXTENSIONS.Contains(extension)) + return FontAwesome.Regular.FileAudio; - case @".jpg": - case @".jpeg": - case @".png": - return FontAwesome.Regular.FileImage; + if (SupportedExtensions.IMAGE_EXTENSIONS.Contains(extension)) + return FontAwesome.Regular.FileImage; - default: - return FontAwesome.Regular.File; - } + return FontAwesome.Regular.File; } } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index dc13924b4f..b028280774 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -73,8 +73,6 @@ namespace osu.Game [Cached(typeof(OsuGameBase))] public partial class OsuGameBase : Framework.Game, ICanAcceptFiles, IBeatSyncProvider { - public static readonly string[] VIDEO_EXTENSIONS = { ".mp4", ".mov", ".avi", ".flv", ".mpg", ".wmv", ".m4v" }; - #if DEBUG public const string GAME_NAME = "osu! (development)"; #else diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index d0ee2ccd71..18e01e2490 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -35,6 +35,7 @@ using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Skinning; using osu.Framework.Graphics.Cursor; using osu.Game.Input.Bindings; +using osu.Game.Utils; namespace osu.Game.Overlays.SkinEditor { @@ -709,7 +710,7 @@ namespace osu.Game.Overlays.SkinEditor Task ICanAcceptFiles.Import(ImportTask[] tasks, ImportParameters parameters) => throw new NotImplementedException(); - public IEnumerable HandledExtensions => new[] { ".jpg", ".jpeg", ".png" }; + public IEnumerable HandledExtensions => SupportedExtensions.IMAGE_EXTENSIONS; #endregion diff --git a/osu.Game/Rulesets/Edit/Checks/Components/AudioCheckUtils.cs b/osu.Game/Rulesets/Edit/Checks/Components/AudioCheckUtils.cs index b8cbe63c1e..8a35b84170 100644 --- a/osu.Game/Rulesets/Edit/Checks/Components/AudioCheckUtils.cs +++ b/osu.Game/Rulesets/Edit/Checks/Components/AudioCheckUtils.cs @@ -3,13 +3,12 @@ using System.IO; using System.Linq; +using osu.Game.Utils; namespace osu.Game.Rulesets.Edit.Checks.Components { public static class AudioCheckUtils { - public static readonly string[] AUDIO_EXTENSIONS = { "mp3", "ogg", "wav" }; - - public static bool HasAudioExtension(string filename) => AUDIO_EXTENSIONS.Any(Path.GetExtension(filename).ToLowerInvariant().EndsWith); + public static bool HasAudioExtension(string filename) => SupportedExtensions.AUDIO_EXTENSIONS.Contains(Path.GetExtension(filename).ToLowerInvariant()); } } diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 845c21b598..daed658e3b 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -10,6 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osu.Game.Localisation; +using osu.Game.Utils; namespace osu.Game.Screens.Edit.Setup { @@ -48,12 +49,14 @@ namespace osu.Game.Screens.Edit.Setup Children = new Drawable[] { - backgroundChooser = new FormFileSelector(".jpg", ".jpeg", ".png") + backgroundChooser = new FormFileSelector(SupportedExtensions.IMAGE_EXTENSIONS) { Caption = GameplaySettingsStrings.BackgroundHeader, PlaceholderText = EditorSetupStrings.ClickToSelectBackground, }, - audioTrackChooser = new FormFileSelector(".mp3", ".ogg") + // `SupportedExtensions.AUDIO_EXTENSIONS` not used here specifically it includes `.wav` for samples, which is strictly disallowed by ranking criteria + // (https://osu.ppy.sh/wiki/en/Ranking_criteria#audio) + audioTrackChooser = new FormFileSelector(@".mp3", @".ogg") { Caption = EditorSetupStrings.AudioTrack, PlaceholderText = EditorSetupStrings.ClickToSelectTrack, diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index 9effb483c4..47618f6296 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.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; using System.Collections.Generic; +using System.IO; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -14,6 +14,7 @@ using osu.Game.Configuration; using osu.Game.Graphics.Sprites; using osu.Game.Localisation.SkinComponents; using osu.Game.Overlays.Settings; +using osu.Game.Utils; using osuTK; namespace osu.Game.Skinning @@ -93,10 +94,10 @@ namespace osu.Game.Skinning // but that requires further thought. var highestPrioritySkin = getHighestPriorityUserSkin(((SkinnableSprite)SettingSourceObject).source.AllSources) as Skin; - string[]? availableFiles = highestPrioritySkin?.SkinInfo.PerformRead(s => s.Files - .Where(f => f.Filename.EndsWith(".png", StringComparison.Ordinal) - || f.Filename.EndsWith(".jpg", StringComparison.Ordinal)) - .Select(f => f.Filename).Distinct()).ToArray(); + string[]? availableFiles = highestPrioritySkin?.SkinInfo.PerformRead( + s => s.Files + .Where(f => SupportedExtensions.IMAGE_EXTENSIONS.Contains(Path.GetExtension(f.Filename).ToLowerInvariant())) + .Select(f => f.Filename).Distinct()).ToArray(); if (availableFiles?.Length > 0) Items = availableFiles; diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 8c43b99702..4d456f7360 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Storyboards.Drawables; +using osu.Game.Utils; namespace osu.Game.Storyboards { @@ -96,8 +97,6 @@ namespace osu.Game.Storyboards public virtual DrawableStoryboard CreateDrawable(IReadOnlyList? mods = null) => new DrawableStoryboard(this, mods); - private static readonly string[] image_extensions = { @".png", @".jpg" }; - public virtual string? GetStoragePathFromStoryboardPath(string path) { string? resolvedPath = null; @@ -109,7 +108,7 @@ namespace osu.Game.Storyboards else { // Some old storyboards don't include a file extension, so let's best guess at one. - foreach (string ext in image_extensions) + foreach (string ext in SupportedExtensions.IMAGE_EXTENSIONS) { if ((resolvedPath = BeatmapInfo.BeatmapSet?.GetPathForFile($"{path}{ext}")) != null) break; diff --git a/osu.Game/Utils/SupportedExtensions.cs b/osu.Game/Utils/SupportedExtensions.cs new file mode 100644 index 0000000000..ec1538a041 --- /dev/null +++ b/osu.Game/Utils/SupportedExtensions.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. + +namespace osu.Game.Utils +{ + public static class SupportedExtensions + { + public static readonly string[] VIDEO_EXTENSIONS = [@".mp4", @".mov", @".avi", @".flv", @".mpg", @".wmv", @".m4v"]; + public static readonly string[] AUDIO_EXTENSIONS = [@".mp3", @".ogg", @".wav"]; + public static readonly string[] IMAGE_EXTENSIONS = [@".jpg", @".jpeg", @".png"]; + + public static readonly string[] ALL_EXTENSIONS = + [ + ..VIDEO_EXTENSIONS, + ..AUDIO_EXTENSIONS, + ..IMAGE_EXTENSIONS + ]; + } +} From 5a9127dfc6568d537e453259bac841b251c448de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 29 Nov 2024 08:46:08 +0100 Subject: [PATCH 118/326] Accidentally a word --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index daed658e3b..f02e4bbb28 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Edit.Setup Caption = GameplaySettingsStrings.BackgroundHeader, PlaceholderText = EditorSetupStrings.ClickToSelectBackground, }, - // `SupportedExtensions.AUDIO_EXTENSIONS` not used here specifically it includes `.wav` for samples, which is strictly disallowed by ranking criteria + // `SupportedExtensions.AUDIO_EXTENSIONS` not used here specifically because it includes `.wav` for samples, which is strictly disallowed by ranking criteria // (https://osu.ppy.sh/wiki/en/Ranking_criteria#audio) audioTrackChooser = new FormFileSelector(@".mp3", @".ogg") { From 5f092811cb4d984a84d2bcc5cc1a7a7d43765d55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 29 Nov 2024 09:22:29 +0100 Subject: [PATCH 119/326] Use helper in one more place --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index f02e4bbb28..59a0520a52 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -54,9 +54,7 @@ namespace osu.Game.Screens.Edit.Setup Caption = GameplaySettingsStrings.BackgroundHeader, PlaceholderText = EditorSetupStrings.ClickToSelectBackground, }, - // `SupportedExtensions.AUDIO_EXTENSIONS` not used here specifically because it includes `.wav` for samples, which is strictly disallowed by ranking criteria - // (https://osu.ppy.sh/wiki/en/Ranking_criteria#audio) - audioTrackChooser = new FormFileSelector(@".mp3", @".ogg") + audioTrackChooser = new FormFileSelector(SupportedExtensions.AUDIO_EXTENSIONS) { Caption = EditorSetupStrings.AudioTrack, PlaceholderText = EditorSetupStrings.ClickToSelectTrack, From 3cfa455369c432b69f11a8a7f121abb0d8fac476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 29 Nov 2024 10:54:32 +0100 Subject: [PATCH 120/326] Fix strong drum rolls being counted for double the combo in legacy scoring attributes --- .../Difficulty/TaikoLegacyScoreSimulator.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs index 9839d94277..416a11c2a8 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoLegacyScoreSimulator.cs @@ -144,6 +144,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty foreach (var nested in hitObject.NestedHitObjects) simulateHit(nested, ref attributes); return; + + case StrongNestedHitObject: + // we never need to deal with these directly. + // the only thing strong hits do in terms of scoring is double their object's score increase, + // which is already handled at the parent object level via the `strongable.IsStrong` check lower down in this method. + // not handling these here can lead to them falsely being counted as combo-increasing when handling strong drum rolls! + return; } if (hitObject is DrumRollTick tick) From 0e1b62ef8521d3cec72b0c60fbc557d25e94762a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2024 21:10:27 +0900 Subject: [PATCH 121/326] Expose more migration helper methods For use in https://github.com/ppy/osu-queue-score-statistics/pull/305. Some of these might look a bit odd, but I personally still prefer having them all in one central location. --- .../StandardisedScoreMigrationTools.cs | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index db44731bed..8181c56876 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -245,7 +245,7 @@ namespace osu.Game.Database var scoreProcessor = ruleset.CreateScoreProcessor(); // warning: ordering is important here - both total score and ranks are dependent on accuracy! - score.Accuracy = computeAccuracy(score, scoreProcessor); + score.Accuracy = ComputeAccuracy(score, scoreProcessor); score.Rank = computeRank(score, scoreProcessor); (score.TotalScoreWithoutMods, score.TotalScore) = convertFromLegacyTotalScore(score, ruleset, beatmap); } @@ -269,7 +269,7 @@ namespace osu.Game.Database var scoreProcessor = ruleset.CreateScoreProcessor(); // warning: ordering is important here - both total score and ranks are dependent on accuracy! - score.Accuracy = computeAccuracy(score, scoreProcessor); + score.Accuracy = ComputeAccuracy(score, scoreProcessor); score.Rank = computeRank(score, scoreProcessor); (score.TotalScoreWithoutMods, score.TotalScore) = convertFromLegacyTotalScore(score, ruleset, difficulty, attributes); } @@ -313,7 +313,8 @@ 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 withoutMods, long withMods) 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.TotalScoreWithoutMods, score.TotalScore); @@ -620,24 +621,28 @@ namespace osu.Game.Database } } - private static double computeAccuracy(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor) + public static double ComputeAccuracy(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor) + => ComputeAccuracy(scoreInfo.Statistics, scoreInfo.MaximumStatistics, scoreProcessor); + + public static double ComputeAccuracy(IReadOnlyDictionary statistics, IReadOnlyDictionary maximumStatistics, 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()) - .Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key)); + int baseScore = statistics.Where(kvp => kvp.Key.AffectsAccuracy()) + .Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key)); + int maxBaseScore = maximumStatistics.Where(kvp => kvp.Key.AffectsAccuracy()) + .Sum(kvp => kvp.Value * scoreProcessor.GetBaseScoreForResult(kvp.Key)); return maxBaseScore == 0 ? 1 : baseScore / (double)maxBaseScore; } - public static ScoreRank ComputeRank(ScoreInfo scoreInfo) => computeRank(scoreInfo, scoreInfo.Ruleset.CreateInstance().CreateScoreProcessor()); + public static ScoreRank ComputeRank(ScoreInfo scoreInfo) => + ComputeRank(scoreInfo.Accuracy, scoreInfo.Statistics, scoreInfo.Mods, scoreInfo.Ruleset.CreateInstance().CreateScoreProcessor()); - private static ScoreRank computeRank(ScoreInfo scoreInfo, ScoreProcessor scoreProcessor) + public static ScoreRank ComputeRank(double accuracy, IReadOnlyDictionary statistics, IList mods, ScoreProcessor scoreProcessor) { - var rank = scoreProcessor.RankFromScore(scoreInfo.Accuracy, scoreInfo.Statistics); + var rank = scoreProcessor.RankFromScore(accuracy, statistics); - foreach (var mod in scoreInfo.Mods.OfType()) - rank = mod.AdjustRank(rank, scoreInfo.Accuracy); + foreach (var mod in mods.OfType()) + rank = mod.AdjustRank(rank, accuracy); return rank; } From a719693d10cb72cb2a098b9d40f968e6578985aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Nov 2024 21:21:05 +0900 Subject: [PATCH 122/326] Fix one remaining method call --- osu.Game/Database/StandardisedScoreMigrationTools.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/StandardisedScoreMigrationTools.cs b/osu.Game/Database/StandardisedScoreMigrationTools.cs index 8181c56876..15e3da3c19 100644 --- a/osu.Game/Database/StandardisedScoreMigrationTools.cs +++ b/osu.Game/Database/StandardisedScoreMigrationTools.cs @@ -246,7 +246,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.Rank = ComputeRank(score, scoreProcessor); (score.TotalScoreWithoutMods, score.TotalScore) = convertFromLegacyTotalScore(score, ruleset, beatmap); } @@ -270,7 +270,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.Rank = ComputeRank(score, scoreProcessor); (score.TotalScoreWithoutMods, score.TotalScore) = convertFromLegacyTotalScore(score, ruleset, difficulty, attributes); } @@ -637,6 +637,9 @@ namespace osu.Game.Database public static ScoreRank ComputeRank(ScoreInfo scoreInfo) => ComputeRank(scoreInfo.Accuracy, scoreInfo.Statistics, scoreInfo.Mods, scoreInfo.Ruleset.CreateInstance().CreateScoreProcessor()); + public static ScoreRank ComputeRank(ScoreInfo scoreInfo, ScoreProcessor processor) => + ComputeRank(scoreInfo.Accuracy, scoreInfo.Statistics, scoreInfo.Mods, processor); + public static ScoreRank ComputeRank(double accuracy, IReadOnlyDictionary statistics, IList mods, ScoreProcessor scoreProcessor) { var rank = scoreProcessor.RankFromScore(accuracy, statistics); From 68f21709a8c314488d5c50e1502cc122ac8c756f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Sat, 30 Nov 2024 02:32:09 +0800 Subject: [PATCH 123/326] Fix CA1865 --- .../Visual/Gameplay/TestScenePlayerLocalScoreImport.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 1660f93384..046ae6d953 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -221,7 +221,7 @@ namespace osu.Game.Tests.Visual.Gameplay string? filePath = null; // 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); + AddUntilStep("wait for export file", () => filePath = LocalStorage.GetFiles("exports").SingleOrDefault(f => !Path.GetFileName(f).StartsWith('_')), () => Is.Not.Null); AddUntilStep("filesize is non-zero", () => { try From 1e2e364cd3d74281ffb921c7d9542ba82f02d6b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 30 Nov 2024 21:01:22 +0900 Subject: [PATCH 124/326] Stop loudly logging backwards seek bug to sentry Several users have reported stutters when this happens. It's potentially from the error report overhead. We now know that this is a BASS level issue anyway, so having this logging is not helpful. --- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index c4feb249f4..92258f3fc9 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -3,19 +3,15 @@ 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; -using osu.Game.Utils; namespace osu.Game.Rulesets.UI { @@ -168,13 +164,7 @@ namespace osu.Game.Rulesets.UI if (lastBackwardsSeekLogTime == null || Math.Abs(Clock.CurrentTime - lastBackwardsSeekLogTime.Value) > 1000) { lastBackwardsSeekLogTime = Clock.CurrentTime; - - 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); + Logger.Log($"Denying backwards seek during gameplay (reference: {referenceClock.CurrentTime:N2} stable: {proposedTime:N2})"); } state = PlaybackState.NotValid; From f4e155bfa6a6f8158a578540e9e01621e5b46553 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Sat, 30 Nov 2024 15:19:35 +0100 Subject: [PATCH 125/326] Account for rate changing mods when disabling the "Ready" button --- .../Playlists/PlaylistsReadyButton.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs index a460779ea6..3c7808356c 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.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.ComponentModel; using System.Linq; using osu.Framework.Allocation; @@ -10,7 +11,9 @@ using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.Rooms; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.OnlinePlay.Components; +using osu.Game.Utils; namespace osu.Game.Screens.OnlinePlay.Playlists { @@ -19,6 +22,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [Resolved] private IBindable gameBeatmap { get; set; } = null!; + [Resolved] + private IBindable> mods { get; set; } = null!; + private readonly Room room; public PlaylistsReadyButton(Room room) @@ -63,14 +69,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { base.Update(); - Enabled.Value = hasRemainingAttempts && enoughTimeLeft; + Enabled.Value = hasRemainingAttempts && enoughTimeLeft(); } public override LocalisableString TooltipText { get { - if (!enoughTimeLeft) + if (!enoughTimeLeft()) return "No time left!"; if (!hasRemainingAttempts) @@ -80,9 +86,16 @@ namespace osu.Game.Screens.OnlinePlay.Playlists } } - private bool enoughTimeLeft => + private bool enoughTimeLeft() + { + // this doesn't consider mods which apply variable rates, yet. + double rate = ModUtils.CalculateRateWithMods(mods.Value); + + double hitLength = Math.Round(gameBeatmap.Value.Track.Length / rate); + // This should probably consider the length of the currently selected item, rather than a constant 30 seconds. - room.EndDate != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(gameBeatmap.Value.Track.Length) < room.EndDate; + return room.EndDate != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(hitLength) < room.EndDate; + } protected override void Dispose(bool isDisposing) { From 164b809c8911262c5f8f775b5c8c4c3ce843afc7 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Sat, 30 Nov 2024 23:02:22 +0100 Subject: [PATCH 126/326] Document ready button enable state with some comments --- .../OnlinePlay/Playlists/PlaylistsReadyButton.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs index 3c7808356c..0a4b504749 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs @@ -88,13 +88,15 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private bool enoughTimeLeft() { - // this doesn't consider mods which apply variable rates, yet. + // TODO: This doesn't consider mods which apply variable rates, yet. double rate = ModUtils.CalculateRateWithMods(mods.Value); - double hitLength = Math.Round(gameBeatmap.Value.Track.Length / rate); + // We want to avoid users not being able to submit scores if they chose to not skip, + // so track length is chosen over playable length. + double trackLength = Math.Round(gameBeatmap.Value.Track.Length / rate); - // This should probably consider the length of the currently selected item, rather than a constant 30 seconds. - return room.EndDate != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(hitLength) < room.EndDate; + // Additional 30 second delay added to account for load and/or submit time. + return room.EndDate != null && DateTimeOffset.UtcNow.AddSeconds(30).AddMilliseconds(trackLength) < room.EndDate; } protected override void Dispose(bool isDisposing) From 9140893249037130c9a2bae7bd67be20f8be098a Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sat, 30 Nov 2024 23:36:02 -0500 Subject: [PATCH 127/326] Fix score no longer being saved when quick-restarting after pass --- .../Visual/Mods/TestSceneModFailCondition.cs | 2 +- osu.Game/Screens/Play/Player.cs | 10 +++------- osu.Game/Screens/Play/PlayerLoader.cs | 6 ++---- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs b/osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs index f4732234a7..a7447a92cd 100644 --- a/osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs +++ b/osu.Game.Tests/Visual/Mods/TestSceneModFailCondition.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Mods protected override TestPlayer CreateModPlayer(Ruleset ruleset) { var player = base.CreateModPlayer(ruleset); - player.RestartRequested = _ => restartRequested = true; + player.PrepareLoaderForRestart = _ => restartRequested = true; return player; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 2d1f602832..cb24b99ce2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -83,7 +83,7 @@ namespace osu.Game.Screens.Play /// protected virtual bool PauseOnFocusLost => true; - public Action RestartRequested; + public Action PrepareLoaderForRestart; private bool isRestarting; private bool skipExitTransition; @@ -719,12 +719,8 @@ namespace osu.Game.Screens.Play // stopping here is to ensure music doesn't become audible after exiting back to PlayerLoader. musicController.Stop(); - if (RestartRequested != null) - { - skipExitTransition = quickRestart; - RestartRequested?.Invoke(quickRestart); - return true; - } + skipExitTransition = quickRestart; + PrepareLoaderForRestart?.Invoke(quickRestart); return PerformExit(quickRestart); } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 0db96b71ad..d2ba5398e4 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -457,7 +457,7 @@ namespace osu.Game.Screens.Play CurrentPlayer = createPlayer(); CurrentPlayer.Configuration.AutomaticallySkipIntro |= quickRestart; CurrentPlayer.RestartCount = restartCount++; - CurrentPlayer.RestartRequested = restartRequested; + CurrentPlayer.PrepareLoaderForRestart = prepareForRestart; LoadTask = LoadComponentAsync(CurrentPlayer, _ => { @@ -470,13 +470,11 @@ namespace osu.Game.Screens.Play { } - private void restartRequested(bool quickRestartRequested) + private void prepareForRestart(bool quickRestartRequested) { quickRestart = quickRestartRequested; hideOverlays = true; ValidForResume = true; - - this.MakeCurrent(); } private void contentIn(double delayBeforeSideDisplays = 0) From 53dce83b56b9c67657c5a8033016150e20c8e939 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 1 Dec 2024 02:11:53 -0500 Subject: [PATCH 128/326] Fix restarting no longer working from results screen Thanks to tests for pointing that out :blobsweat: --- osu.Game/Screens/Play/Player.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index cb24b99ce2..3a0a0613f3 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -722,6 +722,16 @@ namespace osu.Game.Screens.Play skipExitTransition = quickRestart; PrepareLoaderForRestart?.Invoke(quickRestart); + if (!this.IsCurrentScreen()) + { + // if we're called externally (i.e. from results screen), + // use MakeCurrent to exit results screen as well as this player screen + // since ValidForResume = false in here + Debug.Assert(!ValidForResume); + this.MakeCurrent(); + return true; + } + return PerformExit(quickRestart); } From 6afe083ec96f218fbad7638630a6069a761404c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 1 Dec 2024 18:44:26 +0900 Subject: [PATCH 129/326] Fix settings showing up during gameplay --- osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs | 6 ++---- osu.Game/Screens/Play/HUDOverlay.cs | 12 ++++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index e68ca4da7a..18d7f6a503 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -23,17 +23,15 @@ namespace osu.Game.Screens.Play.HUD private const float padding = 10; - public const float CONTRACTED_WIDTH = button_size + padding * 2; public const float EXPANDED_WIDTH = player_settings_width + padding * 2; private const float player_settings_width = 270; - private const float button_size = IconButton.DEFAULT_BUTTON_SIZE; + + private const int fade_duration = 200; public override void Show() => this.FadeIn(fade_duration); public override void Hide() => this.FadeOut(fade_duration); - private const int fade_duration = 200; - // we'll handle this ourselves because we have slightly custom logic. protected override bool ExpandOnHover => false; diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 62d9686aad..1c5277a8d9 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -88,6 +88,7 @@ namespace osu.Game.Screens.Play private readonly FillFlowContainer bottomRightElements; private readonly FillFlowContainer topRightElements; + private readonly Container rightSettings; internal readonly IBindable IsPlaying = new Bindable(); @@ -163,7 +164,14 @@ namespace osu.Game.Screens.Play HoldToQuit = CreateHoldForMenuButton(), } }, - PlayerSettingsOverlay = new PlayerSettingsOverlay(), + rightSettings = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + PlayerSettingsOverlay = new PlayerSettingsOverlay(), + } + }, LeaderboardFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -173,7 +181,7 @@ namespace osu.Game.Screens.Play }, }; - hideTargets = new List { mainComponents, topRightElements, PlayerSettingsOverlay }; + hideTargets = new List { mainComponents, topRightElements, rightSettings }; if (rulesetComponents != null) hideTargets.Add(rulesetComponents); From 23522b02d899a1a73b2ad56452dd932d7ff6ee3e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 1 Dec 2024 19:53:57 +0900 Subject: [PATCH 130/326] Use local instead of field for local only usage --- osu.Game/Screens/Play/HUDOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 1c5277a8d9..fca871e42f 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -88,7 +88,6 @@ namespace osu.Game.Screens.Play private readonly FillFlowContainer bottomRightElements; private readonly FillFlowContainer topRightElements; - private readonly Container rightSettings; internal readonly IBindable IsPlaying = new Bindable(); @@ -116,6 +115,8 @@ namespace osu.Game.Screens.Play public HUDOverlay([CanBeNull] DrawableRuleset drawableRuleset, IReadOnlyList mods, bool alwaysShowLeaderboard = true) { + Container rightSettings; + this.drawableRuleset = drawableRuleset; this.mods = mods; From b14dde937ddc8132cb47cdfca6c3c2ce8426c002 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 2 Dec 2024 13:51:41 +0100 Subject: [PATCH 131/326] Add failing test case --- .../Editor/TestSceneOsuComposerSelection.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs index b97fe5c5a8..345965b912 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs @@ -231,6 +231,36 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("slider still has 2 anchors", () => secondSlider.Path.ControlPoints.Count, () => Is.EqualTo(2)); } + [Test] + public void TestControlClickDoesNotDiscardExistingSelectionEvenIfNothingHit() + { + var firstSlider = new Slider + { + StartTime = 0, + Position = new Vector2(0, 0), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(100)) + } + } + }; + + AddStep("add object", () => EditorBeatmap.AddRange([firstSlider])); + AddStep("select first slider", () => EditorBeatmap.SelectedHitObjects.AddRange([firstSlider])); + + AddStep("move mouse to middle of playfield", () => InputManager.MoveMouseTo(blueprintContainer.ScreenSpaceDrawQuad.Centre)); + 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)); + } + private ComposeBlueprintContainer blueprintContainer => Editor.ChildrenOfType().First(); From b505ecc7ba8e44afd38e0ba3cb77edf277d3593c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 2 Dec 2024 13:51:43 +0100 Subject: [PATCH 132/326] Do not deselect objects when control-clicking without hitting anything As per feedback in https://discord.com/channels/90072389919997952/1259818301517725707/1310270647187935284. --- .../Edit/Compose/Components/BlueprintContainer.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index e12574f7ee..4a321f4a81 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -433,7 +433,10 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Finishes the current blueprint selection. /// /// The mouse event which triggered end of selection. - /// Whether a click selection was active. + /// + /// Whether the mouse event is considered to be fully handled. + /// If the return value is , the standard click / mouse up action will follow. + /// private bool endClickSelection(MouseButtonEvent e) { // If already handled a selection, double-click, or drag, we don't want to perform a mouse up / click action. @@ -443,14 +446,16 @@ namespace osu.Game.Screens.Edit.Compose.Components if (e.ControlPressed) { - // if a selection didn't occur, we may want to trigger a deselection. - // Iterate from the top of the input stack (blueprints closest to the front of the screen first). // Priority is given to already-selected blueprints. foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren.Where(b => b.IsHovered).OrderByDescending(b => b.IsSelected)) return clickSelectionHandled = SelectionHandler.MouseUpSelectionRequested(blueprint, e); - return false; + // can only be reached if there are no hovered blueprints. + // in that case, we still want to suppress mouse up / click handling, because when control is pressed, + // it is presumed we want to add to existing selection, not remove from it + // (unless explicitly control-clicking a selected object, which is handled above). + return true; } if (selectedBlueprintAlreadySelectedOnMouseDown && SelectedItems.Count == 1) From 68f4fa5a5781d1f6d2344cf6041d39c51ef9bf05 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 3 Dec 2024 00:00:31 +0800 Subject: [PATCH 133/326] Turn off CA1507 --- .globalconfig | 4 ++++ osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs | 2 -- .../Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs | 2 -- .../Notifications/WebSocket/Events/NewChatMessageData.cs | 2 -- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.globalconfig b/.globalconfig index e2cd1926f0..ca7b86c778 100644 --- a/.globalconfig +++ b/.globalconfig @@ -50,6 +50,10 @@ dotnet_diagnostic.IDE1006.severity = warning # Too many noisy warnings for parsing/formatting numbers dotnet_diagnostic.CA1305.severity = none +# CA1507: Use nameof to express symbol names +# Flaggs serialization name attributes +dotnet_diagnostic.CA1507.severity = suggestion + # CA1806: Do not ignore method results # The usages for numeric parsing are explicitly optional dotnet_diagnostic.CA1806.severity = suggestion diff --git a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs index 8e4cc387ed..583def8eda 100644 --- a/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs +++ b/osu.Game/Online/API/Requests/Responses/APIKudosuHistory.cs @@ -45,9 +45,7 @@ namespace osu.Game.Online.API.Requests.Responses public KudosuAction Action; -#pragma warning disable CA1507 // Happens to name the same because of casing preference [JsonProperty("action")] -#pragma warning restore CA1507 private string action { set diff --git a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs index 38ad2bd02d..6d5fd59f9c 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs @@ -15,9 +15,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("count")] public int PlayCount { get; set; } -#pragma warning disable CA1507 // Happens to name the same because of casing preference [JsonProperty("beatmap")] -#pragma warning restore CA1507 private APIBeatmap beatmap { get; set; } public APIBeatmap BeatmapInfo diff --git a/osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs b/osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs index 677286bb8a..ff9f5ee9f7 100644 --- a/osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs +++ b/osu.Game/Online/Notifications/WebSocket/Events/NewChatMessageData.cs @@ -19,9 +19,7 @@ namespace osu.Game.Online.Notifications.WebSocket.Events [JsonProperty(@"messages")] public List Messages { get; set; } = null!; -#pragma warning disable CA1507 // Happens to name the same because of casing preference [JsonProperty(@"users")] -#pragma warning restore CA1507 private List users { get; set; } = null!; [OnDeserialized] From 7ece8ec1dc466a5f9676b888746c4c33e3140e0f Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Tue, 3 Dec 2024 00:03:59 +0800 Subject: [PATCH 134/326] Update for suggestions --- osu.Game/Overlays/ChatOverlay.cs | 3 +-- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index a00414522d..c49afa3a66 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -386,9 +386,8 @@ namespace osu.Game.Overlays { channelList.RemoveChannel(channel); - if (loadedChannels.TryGetValue(channel, out var loaded)) + if (loadedChannels.Remove(channel, out var loaded)) { - loadedChannels.Remove(channel); // DrawableChannel removed from cache must be manually disposed loaded.Dispose(); } diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index fb056b457b..83a48599ca 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -173,10 +173,10 @@ namespace osu.Game.Rulesets.Mods }; drawable.OnRevertResult += (_, result) => { - if (!ratesForRewinding.TryGetValue(result.HitObject, out double rewindValue)) return; + if (!ratesForRewinding.TryGetValue(result.HitObject, out double rate)) return; if (!shouldProcessResult(result)) return; - recentRates.Insert(0, rewindValue); + recentRates.Insert(0, rate); ratesForRewinding.Remove(result.HitObject); recentRates.RemoveAt(recentRates.Count - 1); From e920cfa1872d233f90df27f4db76ffd0e75da6a8 Mon Sep 17 00:00:00 2001 From: Tim Schumacher Date: Mon, 2 Dec 2024 23:49:26 +0100 Subject: [PATCH 135/326] Move rate-changing TODO to a common place in CalculateRateWithMods --- osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs | 1 - osu.Game/Screens/Select/BeatmapInfoWedge.cs | 1 - osu.Game/Utils/ModUtils.cs | 1 + 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs index 0a4b504749..e72f8be50a 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsReadyButton.cs @@ -88,7 +88,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private bool enoughTimeLeft() { - // TODO: This doesn't consider mods which apply variable rates, yet. double rate = ModUtils.CalculateRateWithMods(mods.Value); // We want to avoid users not being able to submit scores if they chose to not skip, diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 3b0fdc3e47..fd1c944689 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -401,7 +401,6 @@ namespace osu.Game.Screens.Select if (beatmap == null || bpmLabelContainer == null) return; - // this doesn't consider mods which apply variable rates, yet. double rate = ModUtils.CalculateRateWithMods(mods.Value); int bpmMax = FormatUtils.RoundBPM(beatmap.ControlPointInfo.BPMMaximum, rate); diff --git a/osu.Game/Utils/ModUtils.cs b/osu.Game/Utils/ModUtils.cs index f901f15388..15fc34b468 100644 --- a/osu.Game/Utils/ModUtils.cs +++ b/osu.Game/Utils/ModUtils.cs @@ -286,6 +286,7 @@ namespace osu.Game.Utils { double rate = 1; + // TODO: This doesn't consider mods which apply variable rates, yet. foreach (var mod in mods.OfType()) rate = mod.ApplyToRate(0, rate); From 2ceb3f6f85e2d592e7b10794b7949de61ff84d6c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Dec 2024 13:43:20 +0900 Subject: [PATCH 136/326] Show an ongoing operation when checking for updates Addresses https://github.com/ppy/osu/discussions/30950. --- osu.Game/Localisation/GeneralSettingsStrings.cs | 5 +++++ .../Overlays/Settings/Sections/General/UpdateSettings.cs | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/osu.Game/Localisation/GeneralSettingsStrings.cs b/osu.Game/Localisation/GeneralSettingsStrings.cs index 42623f4632..83a3af574c 100644 --- a/osu.Game/Localisation/GeneralSettingsStrings.cs +++ b/osu.Game/Localisation/GeneralSettingsStrings.cs @@ -44,6 +44,11 @@ namespace osu.Game.Localisation /// public static LocalisableString CheckUpdate => new TranslatableString(getKey(@"check_update"), @"Check for updates"); + /// + /// "Checking for updates" + /// + public static LocalisableString CheckingForUpdates => new TranslatableString(getKey(@"checking_for_updates"), @"Checking for updates"); + /// /// "Open osu! folder" /// diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 82cc952e53..53567109e3 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -53,8 +53,16 @@ namespace osu.Game.Overlays.Settings.Sections.General Action = () => { checkForUpdatesButton.Enabled.Value = false; + + var checkingNotification = new ProgressNotification { Text = GeneralSettingsStrings.CheckingForUpdates, }; + notifications?.Post(checkingNotification); + Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(task => Schedule(() => { + // This sequence allows the notification to be immediately dismissed. + checkingNotification.State = ProgressNotificationState.Cancelled; + checkingNotification.Close(false); + if (!task.GetResultSafely()) { notifications?.Post(new SimpleNotification From 457957d3b8d9a68b359e15953d4f151a3cc5b44b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Dec 2024 14:20:39 +0900 Subject: [PATCH 137/326] Refactor check-update flow to better handle unobserved exceptions --- .../Sections/General/UpdateSettings.cs | 71 ++++++++++++------- 1 file changed, 44 insertions(+), 27 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 53567109e3..261103173e 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Logging; @@ -13,6 +12,7 @@ using osu.Framework.Screens; using osu.Framework.Statistics; using osu.Game.Configuration; using osu.Game.Localisation; +using osu.Game.Online.Multiplayer; using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Settings.Sections.Maintenance; using osu.Game.Updater; @@ -36,8 +36,11 @@ namespace osu.Game.Overlays.Settings.Sections.General [Resolved] private Storage storage { get; set; } = null!; + [Resolved] + private OsuGame? game { get; set; } + [BackgroundDependencyLoader] - private void load(OsuConfigManager config, OsuGame? game) + private void load(OsuConfigManager config) { Add(new SettingsEnumDropdown { @@ -50,31 +53,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Add(checkForUpdatesButton = new SettingsButton { Text = GeneralSettingsStrings.CheckUpdate, - Action = () => - { - checkForUpdatesButton.Enabled.Value = false; - - var checkingNotification = new ProgressNotification { Text = GeneralSettingsStrings.CheckingForUpdates, }; - notifications?.Post(checkingNotification); - - Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(task => Schedule(() => - { - // This sequence allows the notification to be immediately dismissed. - checkingNotification.State = ProgressNotificationState.Cancelled; - checkingNotification.Close(false); - - if (!task.GetResultSafely()) - { - notifications?.Post(new SimpleNotification - { - Text = GeneralSettingsStrings.RunningLatestRelease(game!.Version), - Icon = FontAwesome.Solid.CheckCircle, - }); - } - - checkForUpdatesButton.Enabled.Value = true; - })); - } + Action = () => checkForUpdates().FireAndForget() }); } @@ -102,6 +81,44 @@ namespace osu.Game.Overlays.Settings.Sections.General } } + private async Task checkForUpdates() + { + if (updateManager == null || game == null) + return; + + checkForUpdatesButton.Enabled.Value = false; + + var checkingNotification = new ProgressNotification + { + Text = GeneralSettingsStrings.CheckingForUpdates, + }; + notifications?.Post(checkingNotification); + + try + { + bool foundUpdate = await updateManager.CheckForUpdateAsync().ConfigureAwait(true); + + if (!foundUpdate) + { + notifications?.Post(new SimpleNotification + { + Text = GeneralSettingsStrings.RunningLatestRelease(game.Version), + Icon = FontAwesome.Solid.CheckCircle, + }); + } + } + catch + { + } + finally + { + // This sequence allows the notification to be immediately dismissed. + checkingNotification.State = ProgressNotificationState.Cancelled; + checkingNotification.Close(false); + checkForUpdatesButton.Enabled.Value = true; + } + } + private void exportLogs() { ProgressNotification notification = new ProgressNotification From 6ff1dec7b2b9fa2eebcd96620c316ddfc7a67c6e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 3 Dec 2024 15:45:58 +0900 Subject: [PATCH 138/326] Add tests --- .../TestScenePlaylistsRoomSubScreen.cs | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index 5f9e06fda5..de84ca680d 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -5,25 +5,64 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Extensions; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Playlists; +using osu.Game.Tests.Resources; using osu.Game.Tests.Visual.OnlinePlay; namespace osu.Game.Tests.Visual.Playlists { public partial class TestScenePlaylistsRoomSubScreen : OnlinePlayTestScene { + private const double track_length = 10000; + [Resolved] private IAPIProvider api { get; set; } = null!; protected new TestRoomManager RoomManager => (TestRoomManager)base.RoomManager; + private BeatmapManager beatmaps = null!; + private RulesetStore rulesets = null!; + private BeatmapSetInfo? importedSet; + + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, API)); + Dependencies.Cache(Realm); + + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); + + Realm.Write(r => + { + foreach (var set in r.All()) + { + foreach (var b in set.Beatmaps) + { + // These will all have a virtual track length of 1000, see WorkingBeatmap.GetVirtualTrack(). + b.Length = track_length - 1000; + } + } + }); + + importedSet = beatmaps.GetAllUsableBeatmapSets().First(); + } + [Test] public void TestStatusUpdateOnEnter() { @@ -69,5 +108,42 @@ namespace osu.Game.Tests.Visual.Playlists AddAssert("close button present", () => roomScreen.ChildrenOfType().Any()); AddUntilStep("wait for close button to disappear", () => !roomScreen.ChildrenOfType().Any()); } + + [TestCase(120_000, true)] // Definitely enough time. + [TestCase(45_000, true)] // Enough time. + [TestCase(35_000, false)] // Not enough time to complete beatmap after lenience. + [TestCase(20_000, false)] // Not enough time. + [TestCase(5_000, false)] // Not enough time to complete beatmap before lenience. + [TestCase(37_500, true, 2)] // Enough time to complete beatmap after mods are applied. + public void TestReadyButtonEnablementPeriod(int offsetMs, bool enabled, double rate = 1) + { + Room room = null!; + PlaylistsRoomSubScreen roomScreen = null!; + + AddStep("create room", () => + { + RoomManager.AddRoom(room = new Room + { + Name = @"Test Room", + Host = api.LocalUser.Value, + Category = RoomCategory.Normal, + StartDate = DateTimeOffset.Now, + EndDate = DateTimeOffset.Now.AddMilliseconds(offsetMs), + Playlist = + [ + new PlaylistItem(importedSet!.Beatmaps[0]) + { + RequiredMods = rate == 1 + ? [] + : [new APIMod(new OsuModDoubleTime { SpeedChange = { Value = rate } })] + } + ] + }); + }); + + AddStep("push screen", () => LoadScreen(roomScreen = new PlaylistsRoomSubScreen(room))); + AddUntilStep("wait for screen load", () => roomScreen.IsCurrentScreen()); + AddUntilStep("ready button enabled", () => roomScreen.ChildrenOfType().SingleOrDefault()?.Enabled.Value, () => Is.EqualTo(enabled)); + } } } From 75781e3436adcde31e915da4b3ffa0da01574f47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Dec 2024 16:34:23 +0900 Subject: [PATCH 139/326] Update usages of IPC in line with framework changes --- osu.Desktop/Program.cs | 2 +- .../Visual/Navigation/TestSceneInterProcessCommunication.cs | 2 +- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Tests/CleanRunHeadlessGameHost.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index ebc7509af6..df872ae6c6 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -99,7 +99,7 @@ namespace osu.Desktop var hostOptions = new HostOptions { - IPCPort = !tournamentClient ? OsuGame.IPC_PORT : null, + IPCPipeName = !tournamentClient ? OsuGame.IPC_PIPE_NAME : null, FriendlyGameName = OsuGameBase.GAME_NAME, }; diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs b/osu.Game.Tests/Visual/Navigation/TestSceneInterProcessCommunication.cs index 83430b5665..be9dc387f2 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 { IPCPort = OsuGame.IPC_PORT }); + ipcSenderHost = new HeadlessGameHost(gameHost.Name, new HostOptions { IPCPipeName = OsuGame.IPC_PIPE_NAME }); osuSchemeLinkIPCSender = new OsuSchemeLinkIPCChannel(ipcSenderHost); archiveImportIPCSender = new ArchiveImportIPCChannel(ipcSenderHost); }); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 08960e3ebb..279530a579 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -87,9 +87,9 @@ namespace osu.Game { #if DEBUG // Different port allows running release and debug builds alongside each other. - public const int IPC_PORT = 44824; + public const string IPC_PIPE_NAME = "osu-lazer-debug"; #else - public const int IPC_PORT = 44823; + public const string IPC_PORT = "osu-lazer"; #endif /// diff --git a/osu.Game/Tests/CleanRunHeadlessGameHost.cs b/osu.Game/Tests/CleanRunHeadlessGameHost.cs index 00e5b38b1a..df4a91c4e2 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 { - IPCPort = bindIPC ? OsuGame.IPC_PORT : null, + IPCPipeName = bindIPC ? OsuGame.IPC_PIPE_NAME : null, }, bypassCleanup: bypassCleanupOnDispose, realtime: realtime) { this.bypassCleanupOnSetup = bypassCleanupOnSetup; From 808934581fa0956e111941efef55fb99f69c54bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 3 Dec 2024 14:17:14 +0100 Subject: [PATCH 140/326] Move bookmarks out of `BeatmapInfo` Not sure why I didn't do that in https://github.com/ppy/osu/pull/28473... --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 4 ++-- osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs | 4 ++-- .../Visual/Editing/TestSceneEditorSummaryTimeline.cs | 2 +- osu.Game/Beatmaps/Beatmap.cs | 2 ++ osu.Game/Beatmaps/BeatmapConverter.cs | 1 + osu.Game/Beatmaps/BeatmapInfo.cs | 3 --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 4 ++-- osu.Game/Beatmaps/IBeatmap.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 ++++++ .../Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs | 2 +- osu.Game/Screens/Edit/EditorBeatmap.cs | 6 ++++++ 12 files changed, 26 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index be411128f7..adb1755c11 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -109,9 +109,9 @@ namespace osu.Game.Tests.Beatmaps.Formats 95901, 106450, 116999, 119637, 130186, 140735, 151285, 161834, 164471, 175020, 185570, 196119, 206669, 209306 }; - Assert.AreEqual(expectedBookmarks.Length, beatmap.BeatmapInfo.Bookmarks.Length); + Assert.AreEqual(expectedBookmarks.Length, beatmap.Bookmarks.Length); for (int i = 0; i < expectedBookmarks.Length; i++) - Assert.AreEqual(expectedBookmarks[i], beatmap.BeatmapInfo.Bookmarks[i]); + Assert.AreEqual(expectedBookmarks[i], beatmap.Bookmarks[i]); Assert.AreEqual(1.8, beatmap.DistanceSpacing); Assert.AreEqual(4, beatmap.BeatmapInfo.BeatDivisor); Assert.AreEqual(4, beatmap.GridSize); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index e57a4fff62..c20cf7befd 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -73,9 +73,9 @@ namespace osu.Game.Tests.Beatmaps.Formats 95901, 106450, 116999, 119637, 130186, 140735, 151285, 161834, 164471, 175020, 185570, 196119, 206669, 209306 }; - Assert.AreEqual(expectedBookmarks.Length, beatmapInfo.Bookmarks.Length); + Assert.AreEqual(expectedBookmarks.Length, beatmap.Bookmarks.Length); for (int i = 0; i < expectedBookmarks.Length; i++) - Assert.AreEqual(expectedBookmarks[i], beatmapInfo.Bookmarks[i]); + Assert.AreEqual(expectedBookmarks[i], beatmap.Bookmarks[i]); Assert.AreEqual(1.8, beatmap.DistanceSpacing); Assert.AreEqual(4, beatmapInfo.BeatDivisor); Assert.AreEqual(4, beatmap.GridSize); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs index 677d3135ba..e584f1b9d7 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Editing beatmap.ControlPointInfo.Add(50000, new DifficultyControlPoint { SliderVelocity = 2 }); beatmap.ControlPointInfo.Add(80000, new EffectControlPoint { KiaiMode = true }); beatmap.ControlPointInfo.Add(110000, new EffectControlPoint { KiaiMode = false }); - beatmap.BeatmapInfo.Bookmarks = new[] { 75000, 125000 }; + beatmap.Bookmarks = new[] { 75000, 125000 }; beatmap.Breaks.Add(new ManualBreakPeriod(90000, 120000)); editorBeatmap = new EditorBeatmap(beatmap); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index d8effc2f22..8ea6fa1f51 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -139,6 +139,8 @@ namespace osu.Game.Beatmaps public int CountdownOffset { get; set; } + public int[] Bookmarks { get; set; } = Array.Empty(); + IBeatmap IBeatmap.Clone() => Clone(); public Beatmap Clone() => (Beatmap)MemberwiseClone(); diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index 82b40c0318..0cf10c659b 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -85,6 +85,7 @@ namespace osu.Game.Beatmaps beatmap.TimelineZoom = original.TimelineZoom; beatmap.Countdown = original.Countdown; beatmap.CountdownOffset = original.CountdownOffset; + beatmap.Bookmarks = original.Bookmarks; return beatmap; } diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 2df262eba3..333ec89eab 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -231,9 +231,6 @@ namespace osu.Game.Beatmaps [Obsolete("Use ScoreManager.GetMaximumAchievableComboAsync instead.")] public int? MaxCombo { get; set; } - [Ignored] - public int[] Bookmarks { get; set; } = Array.Empty(); - public int BeatmapVersion; public BeatmapInfo Clone() => (BeatmapInfo)this.Detach().MemberwiseClone(); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 0b5450e5ac..153db6d6b9 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -305,7 +305,7 @@ namespace osu.Game.Beatmaps.Formats switch (pair.Key) { case @"Bookmarks": - beatmap.BeatmapInfo.Bookmarks = pair.Value.Split(',').Select(v => + beatmap.Bookmarks = pair.Value.Split(',').Select(v => { bool result = int.TryParse(v, out int val); return new { result, val }; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 093e76a535..6c855e1346 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -110,8 +110,8 @@ namespace osu.Game.Beatmaps.Formats { writer.WriteLine("[Editor]"); - if (beatmap.BeatmapInfo.Bookmarks.Length > 0) - writer.WriteLine(FormattableString.Invariant($"Bookmarks: {string.Join(',', beatmap.BeatmapInfo.Bookmarks)}")); + if (beatmap.Bookmarks.Length > 0) + writer.WriteLine(FormattableString.Invariant($"Bookmarks: {string.Join(',', beatmap.Bookmarks)}")); writer.WriteLine(FormattableString.Invariant($"DistanceSpacing: {beatmap.DistanceSpacing}")); writer.WriteLine(FormattableString.Invariant($"BeatDivisor: {beatmap.BeatmapInfo.BeatDivisor}")); writer.WriteLine(FormattableString.Invariant($"GridSize: {beatmap.GridSize}")); diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 6a41b8ee6c..826d4e19a7 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -107,6 +107,8 @@ namespace osu.Game.Beatmaps /// int CountdownOffset { get; internal set; } + int[] Bookmarks { get; internal set; } + /// /// Creates a shallow-clone of this beatmap and returns it. /// diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 59b1ac22bc..14acc9b908 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -413,6 +413,12 @@ namespace osu.Game.Rulesets.Difficulty set => baseBeatmap.CountdownOffset = value; } + public int[] Bookmarks + { + get => baseBeatmap.Bookmarks; + set => baseBeatmap.Bookmarks = value; + } + #endregion } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs index 189cb4ba4a..04d5a5d618 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts protected override void LoadBeatmap(EditorBeatmap beatmap) { base.LoadBeatmap(beatmap); - foreach (int bookmark in beatmap.BeatmapInfo.Bookmarks) + foreach (int bookmark in beatmap.Bookmarks) Add(new BookmarkVisualisation(bookmark)); } diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index b5e18fd38c..66fb5d07fe 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -270,6 +270,12 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.CountdownOffset = value; } + public int[] Bookmarks + { + get => PlayableBeatmap.Bookmarks; + set => PlayableBeatmap.Bookmarks = value; + } + public IBeatmap Clone() => (EditorBeatmap)MemberwiseClone(); private IList mutableHitObjects => (IList)PlayableBeatmap.HitObjects; From d60b7f479801f7a08ea588f17241abaff937ece2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 3 Dec 2024 15:14:22 +0100 Subject: [PATCH 141/326] Implement basic bookmark support in editor --- .../Input/Bindings/GlobalActionContainer.cs | 16 +++++ osu.Game/Localisation/EditorStrings.cs | 32 +++++++++- .../GlobalActionKeyBindingStrings.cs | 20 ++++++ .../Timelines/Summary/Parts/BookmarkPart.cs | 63 ++++++++++++++++--- osu.Game/Screens/Edit/Editor.cs | 62 ++++++++++++++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 12 +++- .../Edit/LegacyEditorBeatmapPatcher.cs | 22 +++++++ 7 files changed, 218 insertions(+), 9 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 02ede0a2f8..42028c044f 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -152,6 +152,10 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Right }, GlobalAction.EditorSeekToNextHitObject), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.Left }, GlobalAction.EditorSeekToPreviousSamplePoint), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.Right }, GlobalAction.EditorSeekToNextSamplePoint), + new KeyBinding(new[] { InputKey.Control, InputKey.B }, GlobalAction.EditorAddBookmark), + new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.B }, GlobalAction.EditorRemoveClosestBookmark), + new KeyBinding(InputKey.None, GlobalAction.EditorSeekToPreviousBookmark), + new KeyBinding(InputKey.None, GlobalAction.EditorSeekToNextBookmark), }; private static IEnumerable editorTestPlayKeyBindings => new[] @@ -476,6 +480,18 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridType))] EditorCycleGridType, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorAddBookmark))] + EditorAddBookmark, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorRemoveClosestBookmark))] + EditorRemoveClosestBookmark, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToPreviousBookmark))] + EditorSeekToPreviousBookmark, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorSeekToNextBookmark))] + EditorSeekToNextBookmark, } public enum GlobalActionCategory diff --git a/osu.Game/Localisation/EditorStrings.cs b/osu.Game/Localisation/EditorStrings.cs index 127bdd8355..3b4026be11 100644 --- a/osu.Game/Localisation/EditorStrings.cs +++ b/osu.Game/Localisation/EditorStrings.cs @@ -154,6 +154,36 @@ namespace osu.Game.Localisation /// public static LocalisableString TimelineShowTicks => new TranslatableString(getKey(@"timeline_show_ticks"), @"Show ticks"); + /// + /// "Bookmarks" + /// + public static LocalisableString Bookmarks => new TranslatableString(getKey(@"bookmarks"), @"Bookmarks"); + + /// + /// "Add bookmark" + /// + public static LocalisableString AddBookmark => new TranslatableString(getKey(@"add_bookmark"), @"Add bookmark"); + + /// + /// "Remove closest bookmark" + /// + public static LocalisableString RemoveClosestBookmark => new TranslatableString(getKey(@"remove_closest_bookmark"), @"Remove closest bookmark"); + + /// + /// "Seek to previous bookmark" + /// + public static LocalisableString SeekToPreviousBookmark => new TranslatableString(getKey(@"seek_to_previous_bookmark"), @"Seek to previous bookmark"); + + /// + /// "Seek to next bookmark" + /// + public static LocalisableString SeekToNextBookmark => new TranslatableString(getKey(@"seek_to_next_bookmark"), @"Seek to next bookmark"); + + /// + /// "Reset bookmarks" + /// + public static LocalisableString ResetBookmarks => new TranslatableString(getKey(@"reset_bookmarks"), @"Reset bookmarks"); + private static string getKey(string key) => $@"{prefix}:{key}"; } -} +} \ No newline at end of file diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index ed80704a0a..f9db0461ce 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -429,6 +429,26 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorSeekToNextSamplePoint => new TranslatableString(getKey(@"editor_seek_to_next_sample_point"), @"Seek to next sample point"); + /// + /// "Add bookmark" + /// + public static LocalisableString EditorAddBookmark => new TranslatableString(getKey(@"editor_add_bookmark"), @"Add bookmark"); + + /// + /// "Remove closest bookmark" + /// + public static LocalisableString EditorRemoveClosestBookmark => new TranslatableString(getKey(@"editor_remove_closest_bookmark"), @"Remove closest bookmark"); + + /// + /// "Seek to previous bookmark" + /// + public static LocalisableString EditorSeekToPreviousBookmark => new TranslatableString(getKey(@"editor_seek_to_previous_bookmark"), @"Seek to previous bookmark"); + + /// + /// "Seek to next bookmark" + /// + public static LocalisableString EditorSeekToNextBookmark => new TranslatableString(getKey(@"editor_seek_to_next_bookmark"), @"Seek to next bookmark"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs index 04d5a5d618..4b178dd831 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs @@ -2,7 +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.Cursor; +using osu.Framework.Graphics.Pooling; +using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Extensions; using osu.Game.Graphics; @@ -15,24 +19,69 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// public partial class BookmarkPart : TimelinePart { + private readonly BindableList bookmarks = new BindableList(); + + private DrawablePool pool = null!; + + [BackgroundDependencyLoader] + private void load() + { + AddInternal(pool = new DrawablePool(10)); + } + protected override void LoadBeatmap(EditorBeatmap beatmap) { base.LoadBeatmap(beatmap); - foreach (int bookmark in beatmap.Bookmarks) - Add(new BookmarkVisualisation(bookmark)); + + bookmarks.UnbindAll(); + bookmarks.BindTo(beatmap.Bookmarks); } - private partial class BookmarkVisualisation : PointVisualisation, IHasTooltip + protected override void LoadComplete() { - public BookmarkVisualisation(double startTime) - : base(startTime) + base.LoadComplete(); + bookmarks.BindCollectionChanged((_, _) => { + Clear(disposeChildren: false); + foreach (int bookmark in bookmarks) + Add(pool.Get(v => v.StartTime = bookmark)); + }, true); + } + + private partial class BookmarkVisualisation : PoolableDrawable, IHasTooltip + { + private int startTime; + + public int StartTime + { + get => startTime; + set + { + if (startTime == value) + return; + + startTime = value; + X = startTime; + } } [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.Blue; + private void load(OsuColour colours) + { + RelativePositionAxes = Axes.Both; + RelativeSizeAxes = Axes.Y; - public LocalisableString TooltipText => $"{StartTime.ToEditorFormattedString()} bookmark"; + Anchor = Anchor.CentreLeft; + Origin = Anchor.Centre; + + Width = PointVisualisation.MAX_WIDTH; + Height = 0.4f; + + Colour = colours.Blue; + InternalChild = new FastCircle { RelativeSizeAxes = Axes.Both }; + } + + public LocalisableString TooltipText => $"{((double)StartTime).ToEditorFormattedString()} bookmark"; } } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0e4807dc78..a022ca5435 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -422,6 +422,29 @@ namespace osu.Game.Screens.Edit Items = new MenuItem[] { new EditorMenuItem(EditorStrings.SetPreviewPointToCurrent, MenuItemType.Standard, SetPreviewPointToCurrentTime), + new EditorMenuItem(EditorStrings.Bookmarks) + { + Items = new MenuItem[] + { + new EditorMenuItem(EditorStrings.AddBookmark, MenuItemType.Standard, addBookmarkAtCurrentTime) + { + Hotkey = new Hotkey(GlobalAction.EditorAddBookmark), + }, + new EditorMenuItem(EditorStrings.RemoveClosestBookmark, MenuItemType.Destructive, removeBookmarksInProximityToCurrentTime) + { + Hotkey = new Hotkey(GlobalAction.EditorRemoveClosestBookmark) + }, + new EditorMenuItem(EditorStrings.SeekToPreviousBookmark, MenuItemType.Standard, () => seekBookmark(-1)) + { + Hotkey = new Hotkey(GlobalAction.EditorSeekToPreviousBookmark) + }, + new EditorMenuItem(EditorStrings.SeekToNextBookmark, MenuItemType.Standard, () => seekBookmark(1)) + { + Hotkey = new Hotkey(GlobalAction.EditorSeekToNextBookmark) + }, + new EditorMenuItem(EditorStrings.ResetBookmarks, MenuItemType.Destructive, () => editorBeatmap.Bookmarks.Clear()) + } + } } } } @@ -753,6 +776,14 @@ namespace osu.Game.Screens.Edit case GlobalAction.EditorSeekToNextSamplePoint: seekSamplePoint(1); return true; + + case GlobalAction.EditorSeekToPreviousBookmark: + seekBookmark(-1); + return true; + + case GlobalAction.EditorSeekToNextBookmark: + seekBookmark(1); + return true; } if (e.Repeat) @@ -760,6 +791,14 @@ namespace osu.Game.Screens.Edit switch (e.Action) { + case GlobalAction.EditorAddBookmark: + addBookmarkAtCurrentTime(); + return true; + + case GlobalAction.EditorRemoveClosestBookmark: + removeBookmarksInProximityToCurrentTime(); + return true; + case GlobalAction.EditorCloneSelection: Clone(); return true; @@ -792,6 +831,19 @@ namespace osu.Game.Screens.Edit return false; } + private void addBookmarkAtCurrentTime() + { + int bookmark = (int)clock.CurrentTimeAccurate; + int idx = editorBeatmap.Bookmarks.BinarySearch(bookmark); + if (idx < 0) + editorBeatmap.Bookmarks.Insert(~idx, bookmark); + } + + private void removeBookmarksInProximityToCurrentTime() + { + editorBeatmap.Bookmarks.RemoveAll(b => Math.Abs(b - clock.CurrentTimeAccurate) < 2000); + } + public void OnReleased(KeyBindingReleaseEvent e) { } @@ -1127,6 +1179,16 @@ namespace osu.Game.Screens.Edit clock.SeekSmoothlyTo(found.StartTime); } + private void seekBookmark(int direction) + { + int? targetBookmark = direction < 1 + ? editorBeatmap.Bookmarks.Cast().LastOrDefault(b => b < clock.CurrentTimeAccurate) + : editorBeatmap.Bookmarks.Cast().FirstOrDefault(b => b > clock.CurrentTimeAccurate); + + if (targetBookmark != null) + clock.SeekSmoothlyTo(targetBookmark.Value); + } + private void seekSamplePoint(int direction) { double currentTime = clock.CurrentTimeAccurate; diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 66fb5d07fe..44f9646889 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -118,6 +118,14 @@ namespace osu.Game.Screens.Edit playableBeatmap.Breaks.AddRange(Breaks); }); + Bookmarks = new BindableList(playableBeatmap.Bookmarks); + Bookmarks.BindCollectionChanged((_, _) => + { + BeginChange(); + playableBeatmap.Bookmarks = Bookmarks.OrderBy(x => x).Distinct().ToArray(); + EndChange(); + }); + PreviewTime = new BindableInt(BeatmapInfo.Metadata.PreviewTime); PreviewTime.BindValueChanged(s => { @@ -270,7 +278,9 @@ namespace osu.Game.Screens.Edit set => PlayableBeatmap.CountdownOffset = value; } - public int[] Bookmarks + public readonly BindableList Bookmarks; + + int[] IBeatmap.Bookmarks { get => PlayableBeatmap.Bookmarks; set => PlayableBeatmap.Bookmarks = value; diff --git a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs index a1ee41fc48..f3d58a3c3c 100644 --- a/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs +++ b/osu.Game/Screens/Edit/LegacyEditorBeatmapPatcher.cs @@ -46,6 +46,7 @@ namespace osu.Game.Screens.Edit processHitObjects(result, () => newBeatmap ??= readBeatmap(newState)); processTimingPoints(() => newBeatmap ??= readBeatmap(newState)); processBreaks(() => newBeatmap ??= readBeatmap(newState)); + processBookmarks(() => newBeatmap ??= readBeatmap(newState)); processHitObjectLocalData(() => newBeatmap ??= readBeatmap(newState)); editorBeatmap.EndChange(); } @@ -97,6 +98,27 @@ namespace osu.Game.Screens.Edit } } + private void processBookmarks(Func getNewBeatmap) + { + var newBookmarks = getNewBeatmap().Bookmarks.ToHashSet(); + + foreach (int oldBookmark in editorBeatmap.Bookmarks.ToArray()) + { + if (newBookmarks.Contains(oldBookmark)) + continue; + + editorBeatmap.Bookmarks.Remove(oldBookmark); + } + + foreach (int newBookmark in newBookmarks) + { + if (editorBeatmap.Bookmarks.Contains(newBookmark)) + continue; + + editorBeatmap.Bookmarks.Add(newBookmark); + } + } + private void processHitObjects(DiffResult result, Func getNewBeatmap) { findChangedIndices(result, LegacyDecoder.Section.HitObjects, out var removedIndices, out var addedIndices); From 837744b0aa9ffbd2587f332721eef868ff80878d Mon Sep 17 00:00:00 2001 From: Susko3 Date: Tue, 3 Dec 2024 23:26:33 +0000 Subject: [PATCH 142/326] Use LocalisationManager.GetLocalisedString() instead of bindable hack Made possible by https://github.com/ppy/osu-framework/pull/6377. --- osu.Desktop/Windows/WindowsAssociationManager.cs | 10 +--------- .../Overlays/Settings/Sections/Input/TabletSettings.cs | 4 ++-- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/osu.Desktop/Windows/WindowsAssociationManager.cs b/osu.Desktop/Windows/WindowsAssociationManager.cs index c8066cabda..6f53c65ca9 100644 --- a/osu.Desktop/Windows/WindowsAssociationManager.cs +++ b/osu.Desktop/Windows/WindowsAssociationManager.cs @@ -148,15 +148,7 @@ namespace osu.Desktop.Windows foreach (var association in uri_associations) association.UpdateDescription(getLocalisedString(association.Description)); - string getLocalisedString(LocalisableString s) - { - if (localisation == null) - return s.ToString(); - - var b = localisation.GetLocalisedBindableString(s); - b.UnbindAll(); - return b.Value; - } + string getLocalisedString(LocalisableString s) => localisation?.GetLocalisedString(s) ?? s.ToString(); } #region Native interop diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index 4c9320c2a6..00ffbc1120 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -114,10 +114,10 @@ namespace osu.Game.Overlays.Settings.Sections.Input if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux) { t.NewLine(); - var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedBindableString(TabletSettingsStrings.NoTabletDetectedDescription( + var formattedSource = MessageFormatter.FormatText(localisation.GetLocalisedString(TabletSettingsStrings.NoTabletDetectedDescription( RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? @"https://opentabletdriver.net/Wiki/FAQ/Windows" - : @"https://opentabletdriver.net/Wiki/FAQ/Linux")).Value); + : @"https://opentabletdriver.net/Wiki/FAQ/Linux"))); t.AddLinks(formattedSource.Text, formattedSource.Links); } }), From 296fa69edd24c658d7525e8fe903923abb874bfc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Dec 2024 14:30:59 +0900 Subject: [PATCH 143/326] Add "buttons" as a search term for key bindings --- osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs index a93e6c37af..704fa6e907 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/BindingSettings.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input { protected override LocalisableString Header => BindingSettingsStrings.ShortcutAndGameplayBindings; - public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { @"keybindings", @"controls", @"keyboard", @"keys" }); + public override IEnumerable FilterTerms => base.FilterTerms.Concat(new LocalisableString[] { @"keybindings", @"controls", @"keyboard", @"keys", @"buttons" }); public BindingSettings(KeyBindingPanel keyConfig) { From a4d58648e23e19ef9cda687dbb5127ba17becc14 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Dec 2024 14:31:39 +0900 Subject: [PATCH 144/326] Fix quick retry transition from results screen --- osu.Game/Screens/Ranking/ResultsScreen.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 0209fbd39c..507d138d90 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -55,6 +55,8 @@ namespace osu.Game.Screens.Ranking [Resolved] private Player? player { get; set; } + private bool skipExitTransition; + [Resolved] private IAPIProvider api { get; set; } = null!; @@ -203,6 +205,7 @@ namespace osu.Game.Screens.Ranking { if (!this.IsCurrentScreen()) return; + skipExitTransition = true; player?.Restart(true); }, }); @@ -313,7 +316,8 @@ namespace osu.Game.Screens.Ranking // HitObject references from HitEvent. Score?.HitEvents.Clear(); - this.FadeOut(100); + if (!skipExitTransition) + this.FadeOut(100); return false; } From ad4df82593e334b9b9c0522326655479077717f8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 4 Dec 2024 16:26:36 +0900 Subject: [PATCH 145/326] Improve multiplayer listing search by making it fuzzy --- .../Lounge/Components/RoomsContainer.cs | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs index 17aed021b2..6eda993f94 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomsContainer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; +using System.Globalization; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -80,19 +81,34 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components bool matchingFilter = true; matchingFilter &= criteria.Ruleset == null || r.Room.PlaylistItemStats?.RulesetIDs.Any(id => id == criteria.Ruleset.OnlineID) != false; - - if (!string.IsNullOrEmpty(criteria.SearchString)) - { - // Room name isn't translatable, so ToString() is used here for simplicity. - matchingFilter &= r.FilterTerms.Any(term => term.ToString().Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase)); - } - matchingFilter &= matchPermissions(r, criteria.Permissions); + // Room name isn't translatable, so ToString() is used here for simplicity. + string[] filterTerms = r.FilterTerms.Select(t => t.ToString()).ToArray(); + string[] searchTerms = criteria.SearchString.Split(' ', StringSplitOptions.RemoveEmptyEntries); + matchingFilter &= searchTerms.All(searchTerm => filterTerms.Any(filterTerm => checkTerm(filterTerm, searchTerm))); + r.MatchingFilter = matchingFilter; } }); + // Lifted from SearchContainer. + static bool checkTerm(string haystack, string needle) + { + int index = 0; + + for (int i = 0; i < needle.Length; i++) + { + int found = CultureInfo.InvariantCulture.CompareInfo.IndexOf(haystack, needle[i], index, CompareOptions.OrdinalIgnoreCase); + if (found < 0) + return false; + + index = found + 1; + } + + return true; + } + static bool matchPermissions(DrawableLoungeRoom room, RoomPermissionsFilter accessType) { switch (accessType) From 2a6fbb59ffec80028e1b313a2a331c2d16386adb Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 4 Dec 2024 02:12:05 -0500 Subject: [PATCH 146/326] Add failing test case --- .../Editing/TestSceneEditorBeatmapCreation.cs | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index db87987815..ddf6502899 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -19,6 +19,7 @@ using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; @@ -154,6 +155,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName); AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 })); + AddStep("add effect point", () => EditorBeatmap.ControlPointInfo.Add(500, new EffectControlPoint { KiaiMode = true })); AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { new HitCircle @@ -200,6 +202,11 @@ namespace osu.Game.Tests.Visual.Editing var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single(); return timingPoint.Time == 0 && timingPoint.BeatLength == 1000; }); + AddAssert("created difficulty has effect points", () => + { + var effectPoint = EditorBeatmap.ControlPointInfo.EffectPoints.Single(); + return effectPoint.Time == 500 && effectPoint.KiaiMode && effectPoint.ScrollSpeedBindable.IsDefault; + }); AddAssert("created difficulty has no objects", () => EditorBeatmap.HitObjects.Count == 0); AddAssert("status is modified", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.LocallyModified); @@ -219,6 +226,104 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestCreateNewDifficultyWithScrollSpeed_SameRuleset() + { + string firstDifficultyName = Guid.NewGuid().ToString(); + + AddStep("save beatmap", () => Editor.Save()); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new ManiaRuleset().RulesetInfo)); + + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName); + AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 })); + AddStep("add effect points", () => + { + EditorBeatmap.ControlPointInfo.Add(250, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.05 }); + EditorBeatmap.ControlPointInfo.Add(500, new EffectControlPoint { KiaiMode = true, ScrollSpeed = 0.1 }); + EditorBeatmap.ControlPointInfo.Add(750, new EffectControlPoint { KiaiMode = true, ScrollSpeed = 0.15 }); + EditorBeatmap.ControlPointInfo.Add(1000, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.2 }); + EditorBeatmap.ControlPointInfo.Add(1500, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.3 }); + }); + + AddStep("save beatmap", () => Editor.Save()); + + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new ManiaRuleset().RulesetInfo)); + + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog is CreateNewDifficultyDialog); + AddStep("confirm creation with no objects", () => DialogOverlay.CurrentDialog!.PerformOkAction()); + + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName != firstDifficultyName; + }); + + AddAssert("created difficulty has timing point", () => + { + var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single(); + return timingPoint.Time == 0 && timingPoint.BeatLength == 1000; + }); + + AddAssert("created difficulty has effect points", () => + { + return EditorBeatmap.ControlPointInfo.EffectPoints.SequenceEqual(new[] + { + new EffectControlPoint { Time = 250, KiaiMode = false, ScrollSpeed = 0.05 }, + new EffectControlPoint { Time = 500, KiaiMode = true, ScrollSpeed = 0.1 }, + new EffectControlPoint { Time = 750, KiaiMode = true, ScrollSpeed = 0.15 }, + new EffectControlPoint { Time = 1000, KiaiMode = false, ScrollSpeed = 0.2 }, + new EffectControlPoint { Time = 1500, KiaiMode = false, ScrollSpeed = 0.3 }, + }); + }); + } + + [Test] + public void TestCreateNewDifficultyWithScrollSpeed_DifferentRuleset() + { + string firstDifficultyName = Guid.NewGuid().ToString(); + + AddStep("save beatmap", () => Editor.Save()); + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new ManiaRuleset().RulesetInfo)); + + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName); + AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 })); + AddStep("add effect points", () => + { + EditorBeatmap.ControlPointInfo.Add(250, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.05 }); + EditorBeatmap.ControlPointInfo.Add(500, new EffectControlPoint { KiaiMode = true, ScrollSpeed = 0.1 }); + EditorBeatmap.ControlPointInfo.Add(750, new EffectControlPoint { KiaiMode = true, ScrollSpeed = 0.15 }); + EditorBeatmap.ControlPointInfo.Add(1000, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.2 }); + EditorBeatmap.ControlPointInfo.Add(1500, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.3 }); + }); + + AddStep("save beatmap", () => Editor.Save()); + + AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new TaikoRuleset().RulesetInfo)); + + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName != firstDifficultyName; + }); + + AddAssert("created difficulty has timing point", () => + { + var timingPoint = EditorBeatmap.ControlPointInfo.TimingPoints.Single(); + return timingPoint.Time == 0 && timingPoint.BeatLength == 1000; + }); + + AddAssert("created difficulty has effect points", () => + { + // since this difficulty is on another ruleset, scroll speed specifications are completely reset, + // therefore discarding some effect points in the process due to being redundant. + return EditorBeatmap.ControlPointInfo.EffectPoints.SequenceEqual(new[] + { + new EffectControlPoint { Time = 500, KiaiMode = true }, + new EffectControlPoint { Time = 1000, KiaiMode = false }, + }); + }); + } + [Test] public void TestCopyDifficulty() { From e3abbf1177418ced2251ebbd7720a27bfd1691dc Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 4 Dec 2024 02:12:21 -0500 Subject: [PATCH 147/326] Copy effect points across on blank difficulty creation --- osu.Game/Beatmaps/BeatmapManager.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 07fcdb9d62..da556316cd 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -154,9 +154,18 @@ namespace osu.Game.Beatmaps DifficultyName = NamingUtils.GetNextBestName(targetBeatmapSet.Beatmaps.Select(b => b.DifficultyName), "New Difficulty") }; var newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo }; + foreach (var timingPoint in referenceWorkingBeatmap.Beatmap.ControlPointInfo.TimingPoints) newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone()); + foreach (var effectPoint in referenceWorkingBeatmap.Beatmap.ControlPointInfo.EffectPoints) + { + if (!rulesetInfo.Equals(referenceWorkingBeatmap.BeatmapInfo.Ruleset)) + effectPoint.ScrollSpeedBindable.SetDefault(); + + newBeatmap.ControlPointInfo.Add(effectPoint.Time, effectPoint.DeepClone()); + } + return addDifficultyToSet(targetBeatmapSet, newBeatmap, referenceWorkingBeatmap.Skin); } From 06824c1658c714849276f13e614edc084db05536 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 4 Dec 2024 04:20:09 -0500 Subject: [PATCH 148/326] Add failing test case --- .../Editing/TestSceneEditorBeatmapCreation.cs | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 7a390ac131..75759edaea 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -4,15 +4,20 @@ using System; using System.IO; using System.Linq; +using System.Text; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; +using osu.Framework.Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; +using osu.Framework.IO.Stores; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Formats; using osu.Game.Collections; using osu.Game.Database; using osu.Game.Overlays.Dialog; @@ -27,6 +32,7 @@ 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.Skinning; using osu.Game.Storyboards; using osu.Game.Tests.Resources; using osuTK; @@ -527,6 +533,32 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("other files removed", () => !Beatmap.Value.BeatmapSetInfo.Files.Any(f => f.Filename == "bg (1).jpg" || f.Filename == "bg (2).jpg")); } + [Test] + public void TestBackgroundFileChangesPreserveOnEncode() + { + AddStep("enter setup mode", () => Editor.Mode.Value = EditorScreenMode.SongSetup); + AddAssert("set background", () => setBackground(applyToAllDifficulties: true, expected: "bg.jpg")); + + createNewDifficulty(); + createNewDifficulty(); + + switchToDifficulty(0); + + AddAssert("set different background on all diff", () => setBackgroundDifferentExtension(applyToAllDifficulties: true, expected: "bg.jpeg")); + AddAssert("all diff uses one background", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => b.Metadata.BackgroundFile == "bg.jpeg")); + AddAssert("all diff encode same background", () => + { + return Beatmap.Value.BeatmapSetInfo.Beatmaps.All(b => + { + var files = new RealmFileStore(Realm, Dependencies.Get().Storage); + using var store = new RealmBackedResourceStore(b.BeatmapSet!.ToLive(Realm), files.Store, Realm); + string[] osu = Encoding.UTF8.GetString(store.Get(b.File!.Filename)).Split(Environment.NewLine); + Assert.That(osu, Does.Contain("0,0,\"bg.jpeg\",0,0")); + return true; + }); + }); + } + [Test] public void TestSingleAudioFile() { @@ -644,6 +676,25 @@ namespace osu.Game.Tests.Visual.Editing }); } + private bool setBackgroundDifferentExtension(bool applyToAllDifficulties, string expected) + { + var setup = Editor.ChildrenOfType().First(); + + return setFile(TestResources.GetQuickTestBeatmapForImport(), extractedFolder => + { + File.Move( + Path.Combine(extractedFolder, @"machinetop_background.jpg"), + Path.Combine(extractedFolder, @"machinetop_background.jpeg")); + + bool success = setup.ChildrenOfType().First().ChangeBackgroundImage( + new FileInfo(Path.Combine(extractedFolder, @"machinetop_background.jpeg")), + applyToAllDifficulties); + + Assert.That(Beatmap.Value.Metadata.BackgroundFile, Is.EqualTo(expected)); + return success; + }); + } + private bool setAudio(bool applyToAllDifficulties, string expected) { var setup = Editor.ChildrenOfType().First(); From 8e0f6fc12dc04a224a9aefb0121f990a8b007af2 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 4 Dec 2024 04:36:00 -0500 Subject: [PATCH 149/326] Re-encode difficulties on resource change --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 3 --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 10 ++++++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 75759edaea..157deef80a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -8,16 +8,13 @@ using System.Text; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; -using osu.Framework.Extensions; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; -using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Formats; using osu.Game.Collections; using osu.Game.Database; using osu.Game.Overlays.Dialog; diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 098877ebe7..84107a57e9 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -103,6 +103,7 @@ namespace osu.Game.Screens.Edit.Setup private void changeResource(FileInfo source, bool applyToAllDifficulties, string baseFilename, Func readFilename, Action writeFilename) { + var thisBeatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; string newFilename = string.Empty; @@ -117,12 +118,17 @@ namespace osu.Game.Screens.Edit.Setup beatmaps.DeleteFile(set, otherExistingFile); writeFilename(beatmap.Metadata, newFilename); + + if (!beatmap.Equals(thisBeatmap)) + { + // save the difficulty to re-encode the .osu file, updating any reference of the old filename. + var beatmapWorking = beatmaps.GetWorkingBeatmap(beatmap); + beatmaps.Save(beatmap, beatmapWorking.Beatmap, beatmapWorking.GetSkin()); + } } } else { - var thisBeatmap = working.Value.BeatmapInfo; - string[] filenames = set.Files.Select(f => f.Filename).Where(f => f.StartsWith(baseFilename, StringComparison.OrdinalIgnoreCase) && f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); From fa87df6c6af7879f1bc2e60b276724dddd3c2136 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 4 Dec 2024 04:55:40 -0500 Subject: [PATCH 150/326] Move non-current handling to `PerformExit` Co-authored-by: Dean Herbert --- osu.Game/Screens/Play/Player.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3a0a0613f3..1866ed26ce 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -646,7 +646,6 @@ namespace osu.Game.Screens.Play // import current score if possible. prepareAndImportScoreAsync(); - // Screen may not be current if a restart has been performed. if (this.IsCurrentScreen()) { skipExitTransition = skipTransition; @@ -657,6 +656,12 @@ namespace osu.Game.Screens.Play // - the pause / fail dialog was requested but couldn't be displayed due to the type or state of this Player instance. this.Exit(); } + else + { + // May be restarting from results screen. + if (this.GetChildScreen() != null) + this.MakeCurrent(); + } return true; } @@ -722,16 +727,6 @@ namespace osu.Game.Screens.Play skipExitTransition = quickRestart; PrepareLoaderForRestart?.Invoke(quickRestart); - if (!this.IsCurrentScreen()) - { - // if we're called externally (i.e. from results screen), - // use MakeCurrent to exit results screen as well as this player screen - // since ValidForResume = false in here - Debug.Assert(!ValidForResume); - this.MakeCurrent(); - return true; - } - return PerformExit(quickRestart); } From f83ec721fb31f9f3ac9840c246d6bf3f176fdd96 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 5 Dec 2024 02:41:00 -0500 Subject: [PATCH 151/326] Move latency certifier and import files button outside debug section --- .../Sections/DebugSettings/GeneralSettings.cs | 16 +-------- .../Sections/Maintenance/GeneralSettings.cs | 36 +++++++++++++++++++ .../Settings/Sections/MaintenanceSection.cs | 1 + 3 files changed, 38 insertions(+), 15 deletions(-) create mode 100644 osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs index df46e38491..57f36e2875 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs @@ -5,11 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Localisation; -using osu.Framework.Screens; using osu.Game.Localisation; -using osu.Game.Screens; -using osu.Game.Screens.Import; -using osu.Game.Screens.Utility; namespace osu.Game.Overlays.Settings.Sections.DebugSettings { @@ -18,7 +14,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings protected override LocalisableString Header => CommonStrings.General; [BackgroundDependencyLoader] - private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig, IPerformFromScreenRunner? performer) + private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig) { Children = new Drawable[] { @@ -32,16 +28,6 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings LabelText = DebugSettingsStrings.BypassFrontToBackPass, Current = config.GetBindable(DebugSetting.BypassFrontToBackPass) }, - new SettingsButton - { - Text = DebugSettingsStrings.ImportFiles, - Action = () => performer?.PerformFromScreen(menu => menu.Push(new FileImportScreen())) - }, - new SettingsButton - { - Text = DebugSettingsStrings.RunLatencyCertifier, - Action = () => performer?.PerformFromScreen(menu => menu.Push(new LatencyCertifierScreen())) - } }; } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs new file mode 100644 index 0000000000..f75fc2c8bc --- /dev/null +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -0,0 +1,36 @@ +// 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.Localisation; +using osu.Framework.Screens; +using osu.Game.Localisation; +using osu.Game.Screens; +using osu.Game.Screens.Import; +using osu.Game.Screens.Utility; + +namespace osu.Game.Overlays.Settings.Sections.Maintenance +{ + public partial class GeneralSettings : SettingsSubsection + { + protected override LocalisableString Header => CommonStrings.General; + + [BackgroundDependencyLoader] + private void load(IPerformFromScreenRunner? performer) + { + Children = new[] + { + new SettingsButton + { + Text = DebugSettingsStrings.ImportFiles, + Action = () => performer?.PerformFromScreen(menu => menu.Push(new FileImportScreen())) + }, + new SettingsButton + { + Text = DebugSettingsStrings.RunLatencyCertifier, + Action = () => performer?.PerformFromScreen(menu => menu.Push(new LatencyCertifierScreen())) + } + }; + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs index bd90e4c35d..f1b1511df8 100644 --- a/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs +++ b/osu.Game/Overlays/Settings/Sections/MaintenanceSection.cs @@ -23,6 +23,7 @@ namespace osu.Game.Overlays.Settings.Sections { Children = new Drawable[] { + new GeneralSettings(), new BeatmapSettings(), new SkinSettings(), new CollectionsSettings(), From 7ab16a55e561f37a4ffe07b2076f6917a46ea414 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 5 Dec 2024 02:41:17 -0500 Subject: [PATCH 152/326] Make debug section only visible on debug builds --- .../Overlays/FirstRunSetup/ScreenBehaviour.cs | 5 ++- osu.Game/Overlays/SettingsOverlay.cs | 36 +++++++++++-------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs b/osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs index 31a56c9748..d31ce7ea18 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenBehaviour.cs @@ -5,6 +5,7 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; @@ -90,11 +91,13 @@ namespace osu.Game.Overlays.FirstRunSetup new GraphicsSection(), new OnlineSection(), new MaintenanceSection(), - new DebugSection(), }, SearchTerm = SettingsItem.CLASSIC_DEFAULT_SEARCH_TERM, } }; + + if (DebugUtils.IsDebugBuild) + searchContainer.Add(new DebugSection()); } private void applyClassic() diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 9076dadf93..1157860e03 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -27,21 +28,28 @@ namespace osu.Game.Overlays public LocalisableString Title => SettingsStrings.HeaderTitle; public LocalisableString Description => SettingsStrings.HeaderDescription; - protected override IEnumerable CreateSections() => new SettingsSection[] + protected override IEnumerable CreateSections() { - // This list should be kept in sync with ScreenBehaviour. - new GeneralSection(), - new SkinSection(), - new InputSection(createSubPanel(new KeyBindingPanel())), - new UserInterfaceSection(), - new GameplaySection(), - new RulesetSection(), - new AudioSection(), - new GraphicsSection(), - new OnlineSection(), - new MaintenanceSection(), - new DebugSection(), - }; + var sections = new List + { + // This list should be kept in sync with ScreenBehaviour. + new GeneralSection(), + new SkinSection(), + new InputSection(createSubPanel(new KeyBindingPanel())), + new UserInterfaceSection(), + new GameplaySection(), + new RulesetSection(), + new AudioSection(), + new GraphicsSection(), + new OnlineSection(), + new MaintenanceSection(), + }; + + if (DebugUtils.IsDebugBuild) + sections.Add(new DebugSection()); + + return sections; + } private readonly List subPanels = new List(); From 7c1be5eca25cdf1749329b8a9c0972711673dbe5 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 5 Dec 2024 02:45:43 -0500 Subject: [PATCH 153/326] Remove unnecessary localisations --- osu.Game/Localisation/DebugSettingsStrings.cs | 25 ------------------- .../Settings/Sections/DebugSection.cs | 3 +-- .../Sections/DebugSettings/GeneralSettings.cs | 7 +++--- .../Sections/DebugSettings/MemorySettings.cs | 5 ++-- 4 files changed, 6 insertions(+), 34 deletions(-) diff --git a/osu.Game/Localisation/DebugSettingsStrings.cs b/osu.Game/Localisation/DebugSettingsStrings.cs index 066c07858c..bdb0348981 100644 --- a/osu.Game/Localisation/DebugSettingsStrings.cs +++ b/osu.Game/Localisation/DebugSettingsStrings.cs @@ -9,21 +9,6 @@ namespace osu.Game.Localisation { private const string prefix = @"osu.Game.Resources.Localisation.DebugSettings"; - /// - /// "Debug" - /// - public static LocalisableString DebugSectionHeader => new TranslatableString(getKey(@"debug_section_header"), @"Debug"); - - /// - /// "Show log overlay" - /// - public static LocalisableString ShowLogOverlay => new TranslatableString(getKey(@"show_log_overlay"), @"Show log overlay"); - - /// - /// "Bypass front-to-back render pass" - /// - public static LocalisableString BypassFrontToBackPass => new TranslatableString(getKey(@"bypass_front_to_back_pass"), @"Bypass front-to-back render pass"); - /// /// "Import files" /// @@ -34,16 +19,6 @@ namespace osu.Game.Localisation /// public static LocalisableString RunLatencyCertifier => new TranslatableString(getKey(@"run_latency_certifier"), @"Run latency certifier"); - /// - /// "Memory" - /// - public static LocalisableString MemoryHeader => new TranslatableString(getKey(@"memory_header"), @"Memory"); - - /// - /// "Clear all caches" - /// - public static LocalisableString ClearAllCaches => new TranslatableString(getKey(@"clear_all_caches"), @"Clear all caches"); - private static string getKey(string key) => $"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/Settings/Sections/DebugSection.cs b/osu.Game/Overlays/Settings/Sections/DebugSection.cs index b84c441057..15951d462b 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSection.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSection.cs @@ -6,14 +6,13 @@ 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; namespace osu.Game.Overlays.Settings.Sections { public partial class DebugSection : SettingsSection { - public override LocalisableString Header => DebugSettingsStrings.DebugSectionHeader; + public override LocalisableString Header => "Debug"; public override Drawable CreateIcon() => new SpriteIcon { diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs index 57f36e2875..280aa685a9 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs @@ -5,13 +5,12 @@ using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Localisation; -using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.DebugSettings { public partial class GeneralSettings : SettingsSubsection { - protected override LocalisableString Header => CommonStrings.General; + protected override LocalisableString Header => "General"; [BackgroundDependencyLoader] private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig) @@ -20,12 +19,12 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { new SettingsCheckbox { - LabelText = DebugSettingsStrings.ShowLogOverlay, + LabelText = "Show log overlay", Current = frameworkConfig.GetBindable(FrameworkSetting.ShowLogOverlay) }, new SettingsCheckbox { - LabelText = DebugSettingsStrings.BypassFrontToBackPass, + LabelText = @"Bypass front-to-back render pass", Current = config.GetBindable(DebugSetting.BypassFrontToBackPass) }, }; diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index d5de7ae2db..d43abd58a8 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -11,13 +11,12 @@ using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Database; -using osu.Game.Localisation; namespace osu.Game.Overlays.Settings.Sections.DebugSettings { public partial class MemorySettings : SettingsSubsection { - protected override LocalisableString Header => DebugSettingsStrings.MemoryHeader; + protected override LocalisableString Header => "Memory"; [BackgroundDependencyLoader] private void load(GameHost host, RealmAccess realm) @@ -29,7 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { new SettingsButton { - Text = DebugSettingsStrings.ClearAllCaches, + Text = "Clear all caches", Action = host.Collect }, new SettingsButton From 1b1e7b63e96717903f71e73ec775ae77bbee407f Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Thu, 5 Dec 2024 02:48:46 -0500 Subject: [PATCH 154/326] Clean up code slightly --- .../Overlays/Settings/Sections/DebugSection.cs | 15 +++++++-------- .../Sections/DebugSettings/GeneralSettings.cs | 4 ++-- .../Sections/DebugSettings/MemorySettings.cs | 16 ++++++++-------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/DebugSection.cs b/osu.Game/Overlays/Settings/Sections/DebugSection.cs index 15951d462b..1d2129413c 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSection.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSection.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.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; @@ -12,7 +11,7 @@ namespace osu.Game.Overlays.Settings.Sections { public partial class DebugSection : SettingsSection { - public override LocalisableString Header => "Debug"; + public override LocalisableString Header => @"Debug"; public override Drawable CreateIcon() => new SpriteIcon { @@ -21,12 +20,12 @@ namespace osu.Game.Overlays.Settings.Sections public DebugSection() { - Add(new GeneralSettings()); - - if (DebugUtils.IsDebugBuild) - Add(new BatchImportSettings()); - - Add(new MemorySettings()); + Children = new Drawable[] + { + new GeneralSettings(), + new BatchImportSettings(), + new MemorySettings(), + }; } } } diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs index 280aa685a9..bd6ada4ca7 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/GeneralSettings.cs @@ -10,7 +10,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { public partial class GeneralSettings : SettingsSubsection { - protected override LocalisableString Header => "General"; + protected override LocalisableString Header => @"General"; [BackgroundDependencyLoader] private void load(FrameworkDebugConfigManager config, FrameworkConfigManager frameworkConfig) @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { new SettingsCheckbox { - LabelText = "Show log overlay", + LabelText = @"Show log overlay", Current = frameworkConfig.GetBindable(FrameworkSetting.ShowLogOverlay) }, new SettingsCheckbox diff --git a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs index d43abd58a8..b693822838 100644 --- a/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs +++ b/osu.Game/Overlays/Settings/Sections/DebugSettings/MemorySettings.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { public partial class MemorySettings : SettingsSubsection { - protected override LocalisableString Header => "Memory"; + protected override LocalisableString Header => @"Memory"; [BackgroundDependencyLoader] private void load(GameHost host, RealmAccess realm) @@ -28,27 +28,27 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { new SettingsButton { - Text = "Clear all caches", + Text = @"Clear all caches", Action = host.Collect }, new SettingsButton { - Text = "Compact realm", + Text = @"Compact realm", Action = () => { // Blocking operations implicitly causes a Compact(). - using (realm.BlockAllOperations("compact")) + using (realm.BlockAllOperations(@"compact")) { } } }, blockAction = new SettingsButton { - Text = "Block realm", + Text = @"Block realm", }, unblockAction = new SettingsButton { - Text = "Unblock realm", + Text = @"Unblock realm", }, }; @@ -56,7 +56,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings { try { - IDisposable? token = realm.BlockAllOperations("maintenance"); + IDisposable? token = realm.BlockAllOperations(@"maintenance"); blockAction.Enabled.Value = false; @@ -88,7 +88,7 @@ namespace osu.Game.Overlays.Settings.Sections.DebugSettings } catch (Exception e) { - Logger.Error(e, "Blocking realm failed"); + Logger.Error(e, @"Blocking realm failed"); } }; } From 68e400dd0c8d4839bab9a2265f9c699fcc8d1b27 Mon Sep 17 00:00:00 2001 From: Huo Yaoyuan Date: Thu, 5 Dec 2024 18:00:42 +0800 Subject: [PATCH 155/326] Put globalconfig into seperated folder and reference explicitly --- .globalconfig => CodeAnalysis/osu.globalconfig | 1 + Directory.Build.props | 2 ++ osu.sln | 7 ++++++- 3 files changed, 9 insertions(+), 1 deletion(-) rename .globalconfig => CodeAnalysis/osu.globalconfig (99%) diff --git a/.globalconfig b/CodeAnalysis/osu.globalconfig similarity index 99% rename from .globalconfig rename to CodeAnalysis/osu.globalconfig index ca7b86c778..247a825033 100644 --- a/.globalconfig +++ b/CodeAnalysis/osu.globalconfig @@ -1,5 +1,6 @@ # .NET Code Style # IDE styles reference: https://docs.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ +is_global = true # IDE0001: Simplify names dotnet_diagnostic.IDE0001.severity = warning diff --git a/Directory.Build.props b/Directory.Build.props index 0ab41d27a0..3acb86ee0c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,6 +18,8 @@ + + Default diff --git a/osu.sln b/osu.sln index 2d9a4e86d0..63da18c23e 100644 --- a/osu.sln +++ b/osu.sln @@ -56,7 +56,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{10DF8F12-50FD-45D8-8A38-17BA764BF54D}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig - .globalconfig = .globalconfig Directory.Build.props = Directory.Build.props osu.Android.props = osu.Android.props osu.iOS.props = osu.iOS.props @@ -95,6 +94,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Pippidon" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "osu.Game.Rulesets.Pippidon.Tests", "Templates\Rulesets\ruleset-scrolling-example\osu.Game.Rulesets.Pippidon.Tests\osu.Game.Rulesets.Pippidon.Tests.csproj", "{1743BF7C-E6AE-4A06-BAD9-166D62894303}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CodeAnalysis", "CodeAnalysis", "{FB156649-D457-4D1A-969C-D3A23FD31513}" + ProjectSection(SolutionItems) = preProject + CodeAnalysis\BannedSymbols.txt = CodeAnalysis\BannedSymbols.txt + CodeAnalysis\osu.globalconfig = CodeAnalysis\osu.globalconfig + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU From 84a85000afab3f37985ef59e3efe03b0bdd69d36 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2024 21:07:08 +0900 Subject: [PATCH 156/326] 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 02898623a9..ebfb136150 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 80e695e5d1..252c7a14c4 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 49f4b0e6eff6423758382a5c7d79aed21e9c7077 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 5 Dec 2024 21:07:13 +0900 Subject: [PATCH 157/326] 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 43353370b7..7b4453b015 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 69014550b5499a1bc60bcd6144a43180b94ff5ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Dec 2024 12:48:06 +0900 Subject: [PATCH 158/326] Remove unnecessary null checks --- osu.Game/Graphics/UserInterface/OsuContextMenu.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs index 7a5d2c369b..433d37834f 100644 --- a/osu.Game/Graphics/UserInterface/OsuContextMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuContextMenu.cs @@ -53,8 +53,8 @@ namespace osu.Game.Graphics.UserInterface if (!playClickSample) return; - menuSamples?.PlayClickSample(); - menuSamples?.PlayOpenSample(); + menuSamples.PlayClickSample(); + menuSamples.PlayOpenSample(); } protected override void AnimateClose() @@ -62,7 +62,7 @@ namespace osu.Game.Graphics.UserInterface this.FadeOut(fade_duration, Easing.OutQuint); if (wasOpened) - menuSamples?.PlayCloseSample(); + menuSamples.PlayCloseSample(); wasOpened = false; } From 62ea4e09709eb51906e732e30c449103ff5ac2e1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 6 Dec 2024 16:37:00 +0900 Subject: [PATCH 159/326] Add failing test --- .../ManiaBeatmapConversionTest.cs | 1 + ...-specific-spinner-expected-conversion.json | 60 +++++++++++++++++++ .../Beatmaps/mania-specific-spinner.osu | 27 +++++++++ 3 files changed, 88 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/mania-specific-spinner-expected-conversion.json create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/mania-specific-spinner.osu diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index 609c2e8953..b167ea3ab1 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Mania.Tests [TestCase("basic")] [TestCase("zero-length-slider")] + [TestCase("mania-specific-spinner")] [TestCase("20544")] [TestCase("100374")] [TestCase("1450162")] diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/mania-specific-spinner-expected-conversion.json b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/mania-specific-spinner-expected-conversion.json new file mode 100644 index 0000000000..aa1fa7f16d --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/mania-specific-spinner-expected-conversion.json @@ -0,0 +1,60 @@ +{ + "Mappings": [ + { + "RandomW": 273071671, + "RandomX": 842502087, + "RandomY": 3579807591, + "RandomZ": 273326509, + "StartTime": 11783.0, + "Objects": [ + { + "StartTime": 11783.0, + "EndTime": 15116.0, + "Column": 0 + } + ] + }, + { + "RandomW": 2659271247, + "RandomX": 3579807591, + "RandomY": 273326509, + "RandomZ": 273071671, + "StartTime": 91545.0, + "Objects": [ + { + "StartTime": 91545.0, + "EndTime": 92735.0, + "Column": 0 + } + ] + }, + { + "RandomW": 3083635271, + "RandomX": 273326509, + "RandomY": 273071671, + "RandomZ": 2659271247, + "StartTime": 152497.0, + "Objects": [ + { + "StartTime": 152497.0, + "EndTime": 153687.0, + "Column": 1 + } + ] + }, + { + "RandomW": 4073591514, + "RandomX": 273071671, + "RandomY": 2659271247, + "RandomZ": 3083635271, + "StartTime": 231545.0, + "Objects": [ + { + "StartTime": 231545.0, + "EndTime": 232974.0, + "Column": 3 + } + ] + } + ] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/mania-specific-spinner.osu b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/mania-specific-spinner.osu new file mode 100644 index 0000000000..fb709744d7 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/mania-specific-spinner.osu @@ -0,0 +1,27 @@ +osu file format v14 + +[General] +Mode: 3 + +[Difficulty] +HPDrainRate:5 +CircleSize:4 +OverallDifficulty:5 +ApproachRate:0 +SliderMultiplier:2.6 +SliderTickRate:1 + +[TimingPoints] +355,476.190476190476,4,2,1,60,1,0 +60652,-100,4,2,1,60,0,1 +92735,-100,4,2,1,60,0,0 +121485,-100,4,2,1,60,0,1 +153688,-100,4,2,1,60,0,0 +182497,-100,4,2,1,60,0,1 +213688,-100,4,2,1,60,0,0 + +[HitObjects] +256,192,11783,12,0,15116,0:0:0:0: +256,192,91545,12,0,92735,0:0:0:0: +256,192,152497,12,0,153687,0:0:0:0: +256,192,231545,12,0,232974,0:0:0:0: From 8b456e13794adaf471791aa70b14a83bdeedf96a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 6 Dec 2024 16:01:21 +0900 Subject: [PATCH 160/326] Always convert mania spinners A big part of these changes is refactoring, which is somewhat necessary because it was previously implemented as two separate pathways which in-fact need to be joined at the hip when handling spinners. I've chosen to use `IHasLegacyHitObjectType` here because there's no other flag that allows us to tell `ConvertHold` apart from `ConvertSpinner`. --- .../Beatmaps/ManiaBeatmapConverter.cs | 163 ++++++++---------- .../Beatmaps/Legacy/LegacyHitObjectType.cs | 4 +- 2 files changed, 79 insertions(+), 88 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 970d68759f..79e4c6020d 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -7,11 +7,13 @@ using System.Linq; using System.Collections.Generic; using System.Threading; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Legacy; 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.Objects.Legacy; using osu.Game.Rulesets.Scoring.Legacy; using osu.Game.Utils; using osuTK; @@ -124,16 +126,85 @@ namespace osu.Game.Rulesets.Mania.Beatmaps protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) { - if (original is ManiaHitObject maniaOriginal) + if (original is ManiaHitObject maniaObj) { - yield return maniaOriginal; + yield return maniaObj; yield break; } - var objects = IsForCurrentRuleset ? generateSpecific(original, beatmap) : generateConverted(original, beatmap); - foreach (ManiaHitObject obj in objects) - yield return obj; + if (original is not IHasLegacyHitObjectType legacy) + yield break; + + double startTime = original.StartTime; + double endTime = (original as IHasDuration)?.EndTime ?? startTime; + Vector2 position = (original as IHasPosition)?.Position ?? Vector2.Zero; + + Patterns.PatternGenerator conversion; + + switch (legacy.LegacyType & LegacyHitObjectType.ObjectTypes) + { + case LegacyHitObjectType.Circle: + if (IsForCurrentRuleset) + { + conversion = new SpecificBeatmapPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern); + recordNote(startTime, position); + } + else + { + computeDensity(startTime); + conversion = new HitObjectPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern, lastTime, lastPosition, density, lastStair); + recordNote(startTime, position); + } + + break; + + case LegacyHitObjectType.Slider: + if (IsForCurrentRuleset) + { + conversion = new SpecificBeatmapPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern); + recordNote(original.StartTime, position); + } + else + { + var generator = new PathObjectPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern); + conversion = generator; + + for (int i = 0; i <= generator.SpanCount; i++) + { + double time = original.StartTime + generator.SegmentDuration * i; + + recordNote(time, position); + computeDensity(time); + } + } + + break; + + case LegacyHitObjectType.Spinner: + conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern); + recordNote(endTime, new Vector2(256, 192)); + computeDensity(endTime); + break; + + case LegacyHitObjectType.Hold: + conversion = new SpecificBeatmapPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern); + recordNote(endTime, position); + computeDensity(endTime); + break; + + default: + throw new ArgumentException($"Invalid legacy object type: {legacy.LegacyType}", nameof(original)); + } + + foreach (var newPattern in conversion.Generate()) + { + lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern; + lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair; + + foreach (var obj in newPattern.HitObjects) + yield return obj; + } } private readonly LimitedCapacityQueue prevNoteTimes = new LimitedCapacityQueue(max_notes_for_density); @@ -157,88 +228,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps lastPosition = position; } - /// - /// Method that generates hit objects for osu!mania specific beatmaps. - /// - /// The original hit object. - /// The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap. - /// The hit objects generated. - private IEnumerable generateSpecific(HitObject original, IBeatmap originalBeatmap) - { - var generator = new SpecificBeatmapPatternGenerator(Random, original, originalBeatmap, TotalColumns, lastPattern); - - foreach (var newPattern in generator.Generate()) - { - lastPattern = newPattern; - - foreach (var obj in newPattern.HitObjects) - yield return obj; - } - } - - /// - /// Method that generates hit objects for non-osu!mania beatmaps. - /// - /// The original hit object. - /// The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap. - /// The hit objects generated. - private IEnumerable generateConverted(HitObject original, IBeatmap originalBeatmap) - { - Patterns.PatternGenerator? conversion = null; - - switch (original) - { - case IHasPath: - { - var generator = new PathObjectPatternGenerator(Random, original, originalBeatmap, TotalColumns, lastPattern); - conversion = generator; - - var positionData = original as IHasPosition; - - for (int i = 0; i <= generator.SpanCount; i++) - { - double time = original.StartTime + generator.SegmentDuration * i; - - recordNote(time, positionData?.Position ?? Vector2.Zero); - computeDensity(time); - } - - break; - } - - case IHasDuration endTimeData: - { - conversion = new EndTimeObjectPatternGenerator(Random, original, originalBeatmap, TotalColumns, lastPattern); - - recordNote(endTimeData.EndTime, new Vector2(256, 192)); - computeDensity(endTimeData.EndTime); - break; - } - - case IHasPosition positionData: - { - computeDensity(original.StartTime); - - conversion = new HitObjectPatternGenerator(Random, original, originalBeatmap, TotalColumns, lastPattern, lastTime, lastPosition, density, lastStair); - - recordNote(original.StartTime, positionData.Position); - break; - } - } - - if (conversion == null) - yield break; - - foreach (var newPattern in conversion.Generate()) - { - lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern; - lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair; - - foreach (var obj in newPattern.HitObjects) - yield return obj; - } - } - /// /// A pattern generator for osu!mania-specific beatmaps. /// diff --git a/osu.Game/Beatmaps/Legacy/LegacyHitObjectType.cs b/osu.Game/Beatmaps/Legacy/LegacyHitObjectType.cs index 6fab66bf70..ca3f7cc354 100644 --- a/osu.Game/Beatmaps/Legacy/LegacyHitObjectType.cs +++ b/osu.Game/Beatmaps/Legacy/LegacyHitObjectType.cs @@ -13,6 +13,8 @@ namespace osu.Game.Beatmaps.Legacy NewCombo = 1 << 2, Spinner = 1 << 3, ComboOffset = (1 << 4) | (1 << 5) | (1 << 6), - Hold = 1 << 7 + Hold = 1 << 7, + + ObjectTypes = Circle | Slider | Spinner | Hold } } From 8e1bd98386647a440ca3a6f9303accdb9c813565 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 6 Dec 2024 16:05:51 +0900 Subject: [PATCH 161/326] Split out + rename `PassThroughPatternGenerator` Better symbolises the intent of this generator which is to convert hitobjects in their most simple forms - anything with an end time converts to a hold or otherwise converts to a normal note. --- .../Beatmaps/ManiaBeatmapConverter.cs | 54 +--------------- .../Legacy/PassThroughPatternGenerator.cs | 61 +++++++++++++++++++ 2 files changed, 64 insertions(+), 51 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PassThroughPatternGenerator.cs diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 79e4c6020d..c469f4e4e9 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -147,7 +147,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps case LegacyHitObjectType.Circle: if (IsForCurrentRuleset) { - conversion = new SpecificBeatmapPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern); + conversion = new PassThroughPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern); recordNote(startTime, position); } else @@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps case LegacyHitObjectType.Slider: if (IsForCurrentRuleset) { - conversion = new SpecificBeatmapPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern); + conversion = new PassThroughPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern); recordNote(original.StartTime, position); } else @@ -188,7 +188,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps break; case LegacyHitObjectType.Hold: - conversion = new SpecificBeatmapPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern); + conversion = new PassThroughPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern); recordNote(endTime, position); computeDensity(endTime); break; @@ -227,53 +227,5 @@ namespace osu.Game.Rulesets.Mania.Beatmaps lastTime = time; lastPosition = position; } - - /// - /// A pattern generator for osu!mania-specific beatmaps. - /// - private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator - { - public SpecificBeatmapPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern) - : base(random, hitObject, beatmap, previousPattern, totalColumns) - { - } - - public override IEnumerable Generate() - { - yield return generate(); - } - - private Pattern generate() - { - var positionData = HitObject as IHasXPosition; - - int column = GetColumn(positionData?.X ?? 0); - - var pattern = new Pattern(); - - if (HitObject is IHasDuration endTimeData) - { - pattern.Add(new HoldNote - { - StartTime = HitObject.StartTime, - Duration = endTimeData.Duration, - Column = column, - Samples = HitObject.Samples, - NodeSamples = (HitObject as IHasRepeats)?.NodeSamples ?? HoldNote.CreateDefaultNodeSamples(HitObject) - }); - } - else if (HitObject is IHasXPosition) - { - pattern.Add(new Note - { - StartTime = HitObject.StartTime, - Samples = HitObject.Samples, - Column = column - }); - } - - return pattern; - } - } } } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PassThroughPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PassThroughPatternGenerator.cs new file mode 100644 index 0000000000..a8d2dc5ae6 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PassThroughPatternGenerator.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.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Utils; + +namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy +{ + /// + /// A simple generator which, for any object, if the hitobject has an end time + /// it becomes a or otherwise a . + /// + internal class PassThroughPatternGenerator : PatternGenerator + { + public PassThroughPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern) + : base(random, hitObject, beatmap, previousPattern, totalColumns) + { + } + + public override IEnumerable Generate() + { + yield return generate(); + } + + private Pattern generate() + { + var positionData = HitObject as IHasXPosition; + + int column = GetColumn(positionData?.X ?? 0); + + var pattern = new Pattern(); + + if (HitObject is IHasDuration endTimeData) + { + pattern.Add(new HoldNote + { + StartTime = HitObject.StartTime, + Duration = endTimeData.Duration, + Column = column, + Samples = HitObject.Samples, + NodeSamples = (HitObject as IHasRepeats)?.NodeSamples ?? HoldNote.CreateDefaultNodeSamples(HitObject) + }); + } + else if (HitObject is IHasXPosition) + { + pattern.Add(new Note + { + StartTime = HitObject.StartTime, + Samples = HitObject.Samples, + Column = column + }); + } + + return pattern; + } + } +} From e65f8ba7a079e0ee55f3b2c1504b3653e5a8d9ed Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 6 Dec 2024 16:11:57 +0900 Subject: [PATCH 162/326] Simplify implementation --- .../Patterns/Legacy/PassThroughPatternGenerator.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PassThroughPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PassThroughPatternGenerator.cs index a8d2dc5ae6..6c22854d68 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PassThroughPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PassThroughPatternGenerator.cs @@ -22,14 +22,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } public override IEnumerable Generate() - { - yield return generate(); - } - - private Pattern generate() { var positionData = HitObject as IHasXPosition; - int column = GetColumn(positionData?.X ?? 0); var pattern = new Pattern(); @@ -45,7 +39,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy NodeSamples = (HitObject as IHasRepeats)?.NodeSamples ?? HoldNote.CreateDefaultNodeSamples(HitObject) }); } - else if (HitObject is IHasXPosition) + else { pattern.Add(new Note { @@ -55,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy }); } - return pattern; + yield return pattern; } } } From e8728abc00a84f1b93eb2522049b54376e0d455f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 6 Dec 2024 16:21:59 +0900 Subject: [PATCH 163/326] Rename `LegacyPatternGenerator` to stop naming conflicts --- osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 2 +- .../Patterns/Legacy/EndTimeObjectPatternGenerator.cs | 2 +- .../Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs | 2 +- .../{PatternGenerator.cs => LegacyPatternGenerator.cs} | 8 ++++---- .../Patterns/Legacy/PassThroughPatternGenerator.cs | 2 +- .../Patterns/Legacy/PathObjectPatternGenerator.cs | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) rename osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/{PatternGenerator.cs => LegacyPatternGenerator.cs} (96%) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index c469f4e4e9..aefe60a3c9 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps double endTime = (original as IHasDuration)?.EndTime ?? startTime; Vector2 position = (original as IHasPosition)?.Position ?? Vector2.Zero; - Patterns.PatternGenerator conversion; + PatternGenerator conversion; switch (legacy.LegacyType & LegacyHitObjectType.ObjectTypes) { diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index 52bb87ae19..12aba3a483 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -12,7 +12,7 @@ using osu.Game.Utils; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { - internal class EndTimeObjectPatternGenerator : PatternGenerator + internal class EndTimeObjectPatternGenerator : LegacyPatternGenerator { private readonly int endTime; private readonly PatternType convertType; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs index 9880369dfb..5af26d61f4 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs @@ -16,7 +16,7 @@ using osu.Game.Utils; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { - internal class HitObjectPatternGenerator : PatternGenerator + internal class HitObjectPatternGenerator : LegacyPatternGenerator { public PatternType StairType { get; private set; } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/LegacyPatternGenerator.cs similarity index 96% rename from osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs rename to osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/LegacyPatternGenerator.cs index 48b8778501..7a3033e68b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/LegacyPatternGenerator.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// A pattern generator for legacy hit objects. /// - internal abstract class PatternGenerator : Patterns.PatternGenerator + internal abstract class LegacyPatternGenerator : PatternGenerator { /// /// The column index at which to start generating random notes. @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// protected readonly LegacyRandom Random; - protected PatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, Pattern previousPattern, int totalColumns) + protected LegacyPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, Pattern previousPattern, int totalColumns) : base(hitObject, beatmap, totalColumns, previousPattern) { ArgumentNullException.ThrowIfNull(random); @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// A function to retrieve the next column. If null, a randomisation scheme will be used. /// A function to perform additional validation checks to determine if a column is a valid candidate for a . /// The minimum column index. If null, is used. - /// The maximum column index. If null, TotalColumns is used. + /// The maximum column index. If null, TotalColumns is used. /// A list of patterns for which the validity of a column should be checked against. /// A column is not a valid candidate if a occupies the same column in any of the patterns. /// A column which has passed the check and for which there are no @@ -189,7 +189,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// Returns a random column index in the range [, ). /// /// The minimum column index. If null, is used. - /// The maximum column index. If null, is used. + /// The maximum column index. If null, is used. protected int GetRandomColumn(int? lowerBound = null, int? upperBound = null) => Random.Next(lowerBound ?? RandomStart, upperBound ?? TotalColumns); /// diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PassThroughPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PassThroughPatternGenerator.cs index 6c22854d68..efeb99e8b4 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PassThroughPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PassThroughPatternGenerator.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// A simple generator which, for any object, if the hitobject has an end time /// it becomes a or otherwise a . /// - internal class PassThroughPatternGenerator : PatternGenerator + internal class PassThroughPatternGenerator : LegacyPatternGenerator { public PassThroughPatternGenerator(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/PathObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PathObjectPatternGenerator.cs index c54da74424..cd608161ee 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PathObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PathObjectPatternGenerator.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// A pattern generator for IHasDistance hit objects. /// - internal class PathObjectPatternGenerator : PatternGenerator + internal class PathObjectPatternGenerator : LegacyPatternGenerator { public readonly int StartTime; public readonly int EndTime; From 1bbf32d56768cffba66fbbc3a7776647d5956fe3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 6 Dec 2024 16:27:31 +0900 Subject: [PATCH 164/326] Add some explanatory comments In particular, the spinner one is the most relevant to this batch of changes. --- osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index aefe60a3c9..b91aa5f6e1 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -152,6 +152,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps } else { + // Note: The density is used during the pattern generator constructor, and intentionally computed first. computeDensity(startTime); conversion = new HitObjectPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern, lastTime, lastPosition, density, lastStair); recordNote(startTime, position); @@ -182,6 +183,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps break; case LegacyHitObjectType.Spinner: + // Note: Some older mania-specific beatmaps can have spinners that are converted rather than passed through. + // Newer beatmaps will usually use the "hold" hitobject type below. conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern); recordNote(endTime, new Vector2(256, 192)); computeDensity(endTime); From e703d9e814df82b30c76ffb44b0afa42f1228f6d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 6 Dec 2024 17:16:04 +0900 Subject: [PATCH 165/326] NRT refactorings + rename generators to match usage In particular, "EndTimeObject" is no longer correct - it's strictly used for spinners and not holds. --- .../Beatmaps/ManiaBeatmapConverter.cs | 10 +++++----- ...nGenerator.cs => HitCirclePatternGenerator.cs} | 15 +++++++++------ .../Patterns/Legacy/LegacyPatternGenerator.cs | 8 +++----- ...ternGenerator.cs => SliderPatternGenerator.cs} | 12 +++++------- ...ernGenerator.cs => SpinnerPatternGenerator.cs} | 7 +++++-- .../Beatmaps/Patterns/Pattern.cs | 8 ++++---- 6 files changed, 31 insertions(+), 29 deletions(-) rename osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/{HitObjectPatternGenerator.cs => HitCirclePatternGenerator.cs} (96%) rename osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/{PathObjectPatternGenerator.cs => SliderPatternGenerator.cs} (97%) rename osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/{EndTimeObjectPatternGenerator.cs => SpinnerPatternGenerator.cs} (91%) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index b91aa5f6e1..0792c75e54 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps { // Note: The density is used during the pattern generator constructor, and intentionally computed first. computeDensity(startTime); - conversion = new HitObjectPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern, lastTime, lastPosition, density, lastStair); + conversion = new HitCirclePatternGenerator(Random, original, beatmap, TotalColumns, lastPattern, lastTime, lastPosition, density, lastStair); recordNote(startTime, position); } @@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps } else { - var generator = new PathObjectPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern); + var generator = new SliderPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern); conversion = generator; for (int i = 0; i <= generator.SpanCount; i++) @@ -185,7 +185,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps case LegacyHitObjectType.Spinner: // Note: Some older mania-specific beatmaps can have spinners that are converted rather than passed through. // Newer beatmaps will usually use the "hold" hitobject type below. - conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern); + conversion = new SpinnerPatternGenerator(Random, original, beatmap, TotalColumns, lastPattern); recordNote(endTime, new Vector2(256, 192)); computeDensity(endTime); break; @@ -202,8 +202,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps foreach (var newPattern in conversion.Generate()) { - lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern; - lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair; + lastPattern = conversion is SpinnerPatternGenerator ? lastPattern : newPattern; + lastStair = (conversion as HitCirclePatternGenerator)?.StairType ?? lastStair; foreach (var obj in newPattern.HitObjects) yield return obj; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitCirclePatternGenerator.cs similarity index 96% rename from osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs rename to osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitCirclePatternGenerator.cs index 5af26d61f4..28499f3edc 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/HitCirclePatternGenerator.cs @@ -16,13 +16,16 @@ using osu.Game.Utils; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { - internal class HitObjectPatternGenerator : LegacyPatternGenerator + /// + /// Converter for legacy "HitCircle" hit objects. + /// + internal class HitCirclePatternGenerator : LegacyPatternGenerator { public PatternType StairType { get; private set; } private readonly PatternType convertType; - public HitObjectPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern, double previousTime, Vector2 previousPosition, + public HitCirclePatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair) : base(random, hitObject, beatmap, previousPattern, totalColumns) { @@ -114,10 +117,10 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy } if (convertType.HasFlag(PatternType.Cycle) && PreviousPattern.HitObjects.Count() == 1 - // If we convert to 7K + 1, let's not overload the special key - && (TotalColumns != 8 || lastColumn != 0) - // Make sure the last column was not the centre column - && (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2)) + // If we convert to 7K + 1, let's not overload the special key + && (TotalColumns != 8 || lastColumn != 0) + // Make sure the last column was not the centre column + && (TotalColumns % 2 == 0 || lastColumn != TotalColumns / 2)) { // Generate a new pattern by cycling backwards (similar to Reverse but for only one hit object) int column = RandomStart + TotalColumns - lastColumn - 1; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/LegacyPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/LegacyPatternGenerator.cs index 7a3033e68b..a7ced095b3 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/LegacyPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/LegacyPatternGenerator.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 JetBrains.Annotations; @@ -96,8 +94,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (conversionDifficulty != null) return conversionDifficulty.Value; - HitObject lastObject = Beatmap.HitObjects.LastOrDefault(); - HitObject firstObject = Beatmap.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) - Beatmap.TotalBreakTime) / 1000); @@ -138,7 +136,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// A column which has passed the check and for which there are no /// s in any of occupying the same column. /// If there are no valid candidate columns. - protected int FindAvailableColumn(int initialColumn, int? lowerBound = null, int? upperBound = null, Func nextColumn = null, [InstantHandle] Func validation = null, + protected int FindAvailableColumn(int initialColumn, int? lowerBound = null, int? upperBound = null, Func? nextColumn = null, [InstantHandle] Func? validation = null, params Pattern[] patterns) { lowerBound ??= RandomStart; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PathObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/SliderPatternGenerator.cs similarity index 97% rename from osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PathObjectPatternGenerator.cs rename to osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/SliderPatternGenerator.cs index cd608161ee..e539baa94a 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PathObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/SliderPatternGenerator.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; @@ -19,9 +17,9 @@ using osu.Game.Utils; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { /// - /// A pattern generator for IHasDistance hit objects. + /// Converter for legacy "Slider" hit objects. /// - internal class PathObjectPatternGenerator : LegacyPatternGenerator + internal class SliderPatternGenerator : LegacyPatternGenerator { public readonly int StartTime; public readonly int EndTime; @@ -30,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy private PatternType convertType; - public PathObjectPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern) + public SliderPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern) : base(random, hitObject, beatmap, previousPattern, totalColumns) { convertType = PatternType.None; @@ -484,9 +482,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// Retrieves the list of node samples that occur at time greater than or equal to . /// /// The time to retrieve node samples at. - private IList> nodeSamplesAt(int time) + private IList>? nodeSamplesAt(int time) { - if (!(HitObject is IHasPathWithRepeats curveData)) + if (HitObject is not IHasPathWithRepeats curveData) return null; int index = SegmentDuration == 0 ? 0 : (time - StartTime) / SegmentDuration; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/SpinnerPatternGenerator.cs similarity index 91% rename from osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs rename to osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/SpinnerPatternGenerator.cs index 12aba3a483..39896d3e13 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/SpinnerPatternGenerator.cs @@ -12,12 +12,15 @@ using osu.Game.Utils; namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy { - internal class EndTimeObjectPatternGenerator : LegacyPatternGenerator + /// + /// Converter for legacy "Spinner" hit objects. + /// + internal class SpinnerPatternGenerator : LegacyPatternGenerator { private readonly int endTime; private readonly PatternType convertType; - public EndTimeObjectPatternGenerator(LegacyRandom random, HitObject hitObject, IBeatmap beatmap, int totalColumns, Pattern previousPattern) + public SpinnerPatternGenerator(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/Pattern.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs index 4b3902657f..9e4d8b599e 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Pattern.cs @@ -1,9 +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.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using osu.Game.Rulesets.Mania.Objects; @@ -14,8 +13,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns /// internal class Pattern { - private List hitObjects; - private HashSet containedColumns; + private List? hitObjects; + private HashSet? containedColumns; /// /// All the hit objects contained in this pattern. @@ -72,6 +71,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns containedColumns?.Clear(); } + [MemberNotNull(nameof(hitObjects), nameof(containedColumns))] private void prepareStorage() { hitObjects ??= new List(); From 8dda5aada88523eda32272a4095dac5a085b577a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 6 Dec 2024 17:29:17 +0900 Subject: [PATCH 166/326] Populate default `LegacyType` value on convert hitobjects Normally not an issue, but some tests create their own hitobjects deriving from `ConvertHitObject`. --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs | 2 +- osu.Game/Rulesets/Objects/Legacy/ConvertHold.cs | 6 ++++++ osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs | 6 ++++++ osu.Game/Rulesets/Objects/Legacy/ConvertSpinner.cs | 6 ++++++ 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs index 28683583ee..ced9b24ebf 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObject.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public Vector2 Position { get; set; } - public LegacyHitObjectType LegacyType { get; set; } + public LegacyHitObjectType LegacyType { get; set; } = LegacyHitObjectType.Circle; public override Judgement CreateJudgement() => new IgnoreJudgement(); diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHold.cs index d74224892b..939e4a495f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHold.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHold.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Objects.Legacy @@ -16,5 +17,10 @@ namespace osu.Game.Rulesets.Objects.Legacy public double Duration { get; set; } public double EndTime => StartTime + Duration; + + public ConvertHold() + { + LegacyType = LegacyHitObjectType.Hold; + } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index fee68f2f11..dbbe142944 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Legacy; namespace osu.Game.Rulesets.Objects.Legacy { @@ -56,6 +57,11 @@ namespace osu.Game.Rulesets.Objects.Legacy public bool GenerateTicks { get; set; } = true; + public ConvertSlider() + { + LegacyType = LegacyHitObjectType.Slider; + } + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSpinner.cs index 59551cd37a..c2b4a9e16b 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSpinner.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.Beatmaps.Legacy; using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Objects.Legacy @@ -16,5 +17,10 @@ namespace osu.Game.Rulesets.Objects.Legacy public double Duration { get; set; } public double EndTime => StartTime + Duration; + + public ConvertSpinner() + { + LegacyType = LegacyHitObjectType.Spinner; + } } } From ec8b320e21ddb366c5527ba80d00c0414ca8a38d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 6 Dec 2024 17:45:19 +0900 Subject: [PATCH 167/326] Handle non-legacy types Also used in some tests (e.g. beatmaps containing `HitCircle`s). --- .../Beatmaps/ManiaBeatmapConverter.cs | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 0792c75e54..79234a3ba2 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -126,23 +126,41 @@ namespace osu.Game.Rulesets.Mania.Beatmaps protected override IEnumerable ConvertHitObject(HitObject original, IBeatmap beatmap, CancellationToken cancellationToken) { - if (original is ManiaHitObject maniaObj) + LegacyHitObjectType legacyType; + + switch (original) { - yield return maniaObj; + case ManiaHitObject maniaObj: + { + yield return maniaObj; - yield break; + yield break; + } + + case IHasLegacyHitObjectType legacy: + legacyType = legacy.LegacyType & LegacyHitObjectType.ObjectTypes; + break; + + case IHasPath: + legacyType = LegacyHitObjectType.Slider; + break; + + case IHasDuration: + legacyType = LegacyHitObjectType.Hold; + break; + + default: + legacyType = LegacyHitObjectType.Circle; + break; } - if (original is not IHasLegacyHitObjectType legacy) - yield break; - double startTime = original.StartTime; double endTime = (original as IHasDuration)?.EndTime ?? startTime; Vector2 position = (original as IHasPosition)?.Position ?? Vector2.Zero; PatternGenerator conversion; - switch (legacy.LegacyType & LegacyHitObjectType.ObjectTypes) + switch (legacyType) { case LegacyHitObjectType.Circle: if (IsForCurrentRuleset) @@ -197,7 +215,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps break; default: - throw new ArgumentException($"Invalid legacy object type: {legacy.LegacyType}", nameof(original)); + throw new ArgumentException($"Invalid legacy object type: {legacyType}", nameof(original)); } foreach (var newPattern in conversion.Generate()) From 5cb6b86b1cd0814b76fcd11cfcf29a04680d4022 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Fri, 6 Dec 2024 05:47:41 -0500 Subject: [PATCH 168/326] Fix reference effect point getting mutated --- osu.Game/Beatmaps/BeatmapManager.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index da556316cd..148bd90f28 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -15,6 +15,7 @@ using osu.Framework.Audio.Track; using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Framework.Platform; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; using osu.Game.Database; using osu.Game.Extensions; @@ -160,10 +161,12 @@ namespace osu.Game.Beatmaps foreach (var effectPoint in referenceWorkingBeatmap.Beatmap.ControlPointInfo.EffectPoints) { - if (!rulesetInfo.Equals(referenceWorkingBeatmap.BeatmapInfo.Ruleset)) - effectPoint.ScrollSpeedBindable.SetDefault(); + var clonedEffectPoint = (EffectControlPoint)effectPoint.DeepClone(); - newBeatmap.ControlPointInfo.Add(effectPoint.Time, effectPoint.DeepClone()); + if (!rulesetInfo.Equals(referenceWorkingBeatmap.BeatmapInfo.Ruleset)) + clonedEffectPoint.ScrollSpeedBindable.SetDefault(); + + newBeatmap.ControlPointInfo.Add(clonedEffectPoint.Time, clonedEffectPoint); } return addDifficultyToSet(targetBeatmapSet, newBeatmap, referenceWorkingBeatmap.Skin); From b9f1fef2501ea0cce933b7522d658d3ae9f3d577 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 7 Dec 2024 10:46:03 +0900 Subject: [PATCH 169/326] 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 ebfb136150..632325725a 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 252c7a14c4..62a65f291d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 0a00f7a7c21b8db0d0d5ea73814a64767d7ea59e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sat, 7 Dec 2024 11:11:43 +0900 Subject: [PATCH 170/326] Implement skinnable mod display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also makes the mod display initialization sequence (start expanded, then unexpand) controlled by HUDOverlay rather than mod display itself. This enabled different treatment depending on whether the mod display is viewed in the skin editor or in the player. Co-authored-by: Bartłomiej Dach --- .../Visual/Gameplay/TestSceneSkinEditor.cs | 6 ++ osu.Game/Rulesets/UI/ModIcon.cs | 13 +++- osu.Game/Screens/Play/HUD/ModDisplay.cs | 75 +++++++++++++------ .../Screens/Play/HUD/SkinnableModDisplay.cs | 51 +++++++++++++ osu.Game/Screens/Play/HUDOverlay.cs | 11 ++- 5 files changed, 133 insertions(+), 23 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 91188f5bac..49a8a65cd0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -20,6 +20,7 @@ using osu.Game.Overlays.Settings; using osu.Game.Overlays.SkinEditor; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Edit; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; @@ -53,6 +54,11 @@ namespace osu.Game.Tests.Visual.Gameplay { base.SetUpSteps(); + AddStep("Add DT and HD", () => + { + LoadPlayer([new OsuModDoubleTime { SpeedChange = { Value = 1.337 } }, new OsuModHidden()]); + }); + AddStep("reset skin", () => skins.CurrentSkinInfo.SetDefault()); AddUntilStep("wait for hud load", () => targetContainer.ComponentsLoaded); diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 5237425075..6abc7355d5 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -39,7 +39,18 @@ namespace osu.Game.Rulesets.UI private IMod mod; private readonly bool showTooltip; - private readonly bool showExtendedInformation; + + private bool showExtendedInformation; + + public bool ShowExtendedInformation + { + get => showExtendedInformation; + set + { + showExtendedInformation = value; + updateExtendedInformation(); + } + } public IMod Mod { diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index b37d41e7a2..9f42175a70 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -20,9 +20,27 @@ namespace osu.Game.Screens.Play.HUD /// public partial class ModDisplay : CompositeDrawable, IHasCurrentValue> { - private const int fade_duration = 1000; + private ExpansionMode expansionMode = ExpansionMode.ExpandOnHover; - public ExpansionMode ExpansionMode = ExpansionMode.ExpandOnHover; + public ExpansionMode ExpansionMode + { + get => expansionMode; + set + { + if (expansionMode == value) + return; + + expansionMode = value; + + if (IsLoaded) + { + if (expansionMode == ExpansionMode.AlwaysExpanded || (expansionMode == ExpansionMode.ExpandOnHover && IsHovered)) + expand(); + else if (expansionMode == ExpansionMode.AlwaysContracted || (expansionMode == ExpansionMode.ExpandOnHover && !IsHovered)) + contract(); + } + } + } private readonly BindableWithCurrent> current = new BindableWithCurrent>(Array.Empty()); @@ -37,7 +55,19 @@ namespace osu.Game.Screens.Play.HUD } } - private readonly bool showExtendedInformation; + private bool showExtendedInformation; + + public bool ShowExtendedInformation + { + get => showExtendedInformation; + set + { + showExtendedInformation = value; + foreach (var icon in iconsContainer) + icon.ShowExtendedInformation = value; + } + } + private readonly FillFlowContainer iconsContainer; public ModDisplay(bool showExtendedInformation = true) @@ -59,10 +89,23 @@ namespace osu.Game.Screens.Play.HUD Current.BindValueChanged(updateDisplay, true); - iconsContainer.FadeInFromZero(fade_duration, Easing.OutQuint); + switch (expansionMode) + { + case ExpansionMode.AlwaysExpanded: + expand(0); + break; - if (ExpansionMode == ExpansionMode.AlwaysExpanded || ExpansionMode == ExpansionMode.AlwaysContracted) - FinishTransforms(true); + case ExpansionMode.AlwaysContracted: + contract(0); + break; + + case ExpansionMode.ExpandOnHover: + if (IsHovered) + expand(0); + else + contract(0); + break; + } } private void updateDisplay(ValueChangedEvent> mods) @@ -71,28 +114,18 @@ namespace osu.Game.Screens.Play.HUD foreach (Mod mod in mods.NewValue.AsOrdered()) iconsContainer.Add(new ModIcon(mod, showExtendedInformation: showExtendedInformation) { Scale = new Vector2(0.6f) }); - - appearTransform(); } - private void appearTransform() - { - expand(); - - using (iconsContainer.BeginDelayedSequence(1200)) - contract(); - } - - private void expand() + private void expand(double duration = 500) { if (ExpansionMode != ExpansionMode.AlwaysContracted) - iconsContainer.TransformSpacingTo(new Vector2(5, 0), 500, Easing.OutQuint); + iconsContainer.TransformSpacingTo(new Vector2(5, 0), duration, Easing.OutQuint); } - private void contract() + private void contract(double duration = 500) { if (ExpansionMode != ExpansionMode.AlwaysExpanded) - iconsContainer.TransformSpacingTo(new Vector2(-25, 0), 500, Easing.OutQuint); + iconsContainer.TransformSpacingTo(new Vector2(-25, 0), duration, Easing.OutQuint); } protected override bool OnHover(HoverEvent e) @@ -123,6 +156,6 @@ namespace osu.Game.Screens.Play.HUD /// /// The will always be contracted. /// - AlwaysContracted + AlwaysContracted, } } diff --git a/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs b/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs new file mode 100644 index 0000000000..ce4a4e978e --- /dev/null +++ b/osu.Game/Screens/Play/HUD/SkinnableModDisplay.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.Collections.Generic; +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.Mods; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Play.HUD +{ + /// + /// Displays a single-line horizontal auto-sized flow of mods. For cases where wrapping is required, use instead. + /// + public partial class SkinnableModDisplay : CompositeDrawable, ISerialisableDrawable + { + private ModDisplay modDisplay = null!; + + [Resolved] + private Bindable> mods { get; set; } = null!; + + [SettingSource("Show extended info", "Whether to show extended information for each mod.")] + public Bindable ShowExtendedInformation { get; } = new Bindable(true); + + [SettingSource("Expansion mode", "How the mod display expands when interacted with.")] + public Bindable ExpansionModeSetting { get; } = new Bindable(ExpansionMode.ExpandOnHover); + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = modDisplay = new ModDisplay(); + modDisplay.Current = mods; + AutoSizeAxes = Axes.Both; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ShowExtendedInformation.BindValueChanged(_ => modDisplay.ShowExtendedInformation = ShowExtendedInformation.Value, true); + ExpansionModeSetting.BindValueChanged(_ => modDisplay.ExpansionMode = ExpansionModeSetting.Value, true); + + FinishTransforms(true); + } + + public bool UsesFixedAnchor { get; set; } + } +} diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index fca871e42f..5d92fee841 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -35,6 +35,8 @@ namespace osu.Game.Screens.Play { public const float FADE_DURATION = 300; + private const float mods_fade_duration = 1000; + public const Easing FADE_EASING = Easing.OutQuint; /// @@ -85,7 +87,6 @@ namespace osu.Game.Screens.Play private readonly BindableBool replayLoaded = new BindableBool(); private static bool hasShownNotificationOnce; - private readonly FillFlowContainer bottomRightElements; private readonly FillFlowContainer topRightElements; @@ -248,6 +249,14 @@ namespace osu.Game.Screens.Play updateVisibility(); }, true); + + ModDisplay.ExpansionMode = ExpansionMode.AlwaysExpanded; + Scheduler.AddDelayed(() => + { + ModDisplay.ExpansionMode = ExpansionMode.ExpandOnHover; + }, 1200); + + ModDisplay.FadeInFromZero(mods_fade_duration, FADE_EASING); } protected override void Update() From db18492fbc36064ca11ab4d5c485111201906e97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sat, 7 Dec 2024 13:12:09 +0900 Subject: [PATCH 171/326] Update default osk for skinnable mod display --- .../Archives/modified-default-20241207.osk | Bin 0 -> 1661 bytes osu.Game.Tests/Skins/SkinDeserialisationTest.cs | 4 +++- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Resources/Archives/modified-default-20241207.osk diff --git a/osu.Game.Tests/Resources/Archives/modified-default-20241207.osk b/osu.Game.Tests/Resources/Archives/modified-default-20241207.osk new file mode 100644 index 0000000000000000000000000000000000000000..8ed25fa8f43000764ac2949525b600ba6dd7d051 GIT binary patch literal 1661 zcmWIWW@Zs#U|`^2*p|^Ad3F9}u|+`ML9hq|LveOyo?d2NrfQEN-ys75mg@aU3%P{6 z0;HCdgp`ChG;8R6WYu|N{p3bhg*2=GBW{&Nw=ecR`~5FOw6EN4{k_*Op0xR?ZdB

s$CWvpaJhzM6zc;gYTHDJI$M-hetZt_6y6!A~DVl+qdevfm zPu4#(kUVR@Wl2SFX6)L5e`J3;LEqAN!x{ZX2*?!ulod;WZ~>DZATSLm4( z=b4t|nN~M1Jub@bN{ns!0@&a8Y{)u*8CU?a;qV~J3>E(5CbmgKxTS;@@6 zATGziAO>`vZ(?SiN2rT)er`d2UTR)RG1#4NX9niqG7va^|F>qqH?BYrg>3b&3m2#Q zTsGOV?X`2LtD2<{f3ouJg&S7guids#m$6(Zm?QV<2ZvAFi_hJ(zx#92?&heoKUrfE z+~)dkO^c16UsTe~#ib z1J`!{3wPee&fBGKllp~glE^QKq!riYc3sOp{jlHm`kz-@{rpnSJ)GgBXQ}ix%3fYT zroQKHRe0vgZx1$P>|wV!yCmz!3-K?)Qt3U~e=}VF94zd5Ijcg`q2wda zq~prfh`zQkRG-aUuWcoIDg37A+wyg<;`W~G z;8-SblIOTGm(%y`wLiCc=tgLlue(%~k%NbHjg8I~MZa&~?vn?rYwRP_8L#Md;oM$ge-6_20SFW3K#%}jt z9Ofsv4~I{^)_kEY&haka-n%X0pR=6mm;5|8X;t`d-Zyvoa=T;B&DIunNKk83o8U5I zR#2`btMo>uG+~#oUMpp$ub8lD!_HX6%zyb}d*>*g-+1Nxm;doEt#WJ>{{kgPY>+7PT#*2-1yZ>{eX4X@jNoUP~nbZb|`G7beC$TauGc_j# zQJ8Rf>uQ}lb3S-ekiiwkkFi}sr-dgM1=)VS^r^Aws)ONzjHtlOG*80`^0M3_E1s@m z1r`U4Od<@p%Uz%~5YPysV5Km+F7#3ks)vE0@dQ*Cyv#$_ie3UCw5BuSDv!|3Ko1y% a86vFc!4%-l$_A2W0m6?!x(uj-fdK$KrKltT literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index 7372557161..962a9b2a7a 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -68,7 +68,9 @@ namespace osu.Game.Tests.Skins // Covers legacy rank display "Archives/modified-classic-20230809.osk", // Covers legacy key counter - "Archives/modified-classic-20240724.osk" + "Archives/modified-classic-20240724.osk", + // Covers skinnable mod display + "Archives/modified-default-20241207.osk", }; ///

From 13759f5aa034c70c2df34805b74c4b1da5dc839a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 Dec 2024 13:47:09 +0900 Subject: [PATCH 172/326] Back out test change It was mostly a demonstrative thing to use in the heat in the moment for the skinnable mod display and it breaks all other tests. So let's just not. --- osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index 49a8a65cd0..61ccc8b82c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -54,11 +54,6 @@ namespace osu.Game.Tests.Visual.Gameplay { base.SetUpSteps(); - AddStep("Add DT and HD", () => - { - LoadPlayer([new OsuModDoubleTime { SpeedChange = { Value = 1.337 } }, new OsuModHidden()]); - }); - AddStep("reset skin", () => skins.CurrentSkinInfo.SetDefault()); AddUntilStep("wait for hud load", () => targetContainer.ComponentsLoaded); From 2713ae601a2bbbf4b7c389968c23e76c392b9a42 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 7 Dec 2024 14:41:30 +0900 Subject: [PATCH 173/326] Remove unused using --- 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 61ccc8b82c..91188f5bac 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -20,7 +20,6 @@ using osu.Game.Overlays.Settings; using osu.Game.Overlays.SkinEditor; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Edit; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; From b04f5ab6e4dbd0a486015dbc513f35d8d84d48c5 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sat, 7 Dec 2024 14:13:14 +0200 Subject: [PATCH 174/326] Fix IPC not working in release --- 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 279530a579..d8145c8246 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -89,7 +89,7 @@ namespace osu.Game // Different port allows running release and debug builds alongside each other. public const string IPC_PIPE_NAME = "osu-lazer-debug"; #else - public const string IPC_PORT = "osu-lazer"; + public const string IPC_PIPE_NAME = "osu-lazer"; #endif /// From a46070be30588410675e618bba79c51f852175a3 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 9 Dec 2024 07:02:40 -0500 Subject: [PATCH 175/326] Add description to osu! file associations in iOS --- osu.iOS/Info.plist | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index 1330e29bc1..ae36d00910 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -68,6 +68,8 @@ sh.ppy.osu.items + UTTypeDescription + osu! replay UTTypeIdentifier sh.ppy.osu.osr UTTypeTagSpecification @@ -81,6 +83,8 @@ sh.ppy.osu.items + UTTypeDescription + osu! skin UTTypeIdentifier sh.ppy.osu.osk UTTypeTagSpecification @@ -94,6 +98,8 @@ sh.ppy.osu.items + UTTypeDescription + osu! beatmap UTTypeIdentifier sh.ppy.osu.osz UTTypeTagSpecification @@ -107,6 +113,8 @@ sh.ppy.osu.items + UTTypeDescription + osu! beatmap UTTypeIdentifier sh.ppy.osu.olz UTTypeTagSpecification From e9868c631851a1f8f41e5296db787648ece29597 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 9 Dec 2024 07:47:28 -0500 Subject: [PATCH 176/326] Enable exporting beatmaps in iOS --- osu.Game/Screens/Edit/Editor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0e4807dc78..47ccc8f72e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1215,12 +1215,15 @@ namespace osu.Game.Screens.Edit saveRelatedMenuItems.Add(save); yield return save; - if (RuntimeInfo.IsDesktop) + if (RuntimeInfo.OS != RuntimeInfo.Platform.Android) { var export = createExportMenu(); saveRelatedMenuItems.AddRange(export.Items); yield return export; + } + if (RuntimeInfo.IsDesktop) + { var externalEdit = new EditorMenuItem("Edit externally", MenuItemType.Standard, editExternally); saveRelatedMenuItems.Add(externalEdit); yield return externalEdit; From 0c0dcb1e1545febd175212fcdff25625052b161d Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 9 Dec 2024 08:16:37 -0500 Subject: [PATCH 177/326] Use temporary storage for exported files on iOS --- osu.Game/Database/LegacyExporter.cs | 10 ++++++++-- osu.Game/IO/OsuStorage.cs | 5 +++++ osu.iOS/OsuGameIOS.cs | 4 ++++ osu.iOS/OsuStorageIOS.cs | 23 +++++++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 osu.iOS/OsuStorageIOS.cs diff --git a/osu.Game/Database/LegacyExporter.cs b/osu.Game/Database/LegacyExporter.cs index f9164e34cd..193887765d 100644 --- a/osu.Game/Database/LegacyExporter.cs +++ b/osu.Game/Database/LegacyExporter.cs @@ -3,12 +3,14 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Platform; using osu.Game.Extensions; +using osu.Game.IO; using osu.Game.Overlays.Notifications; using osu.Game.Utils; using Realms; @@ -40,13 +42,15 @@ namespace osu.Game.Database protected abstract string FileExtension { get; } protected readonly Storage UserFileStorage; - private readonly Storage exportStorage; + private readonly Storage? exportStorage; public Action? PostNotification { get; set; } protected LegacyExporter(Storage storage) { - exportStorage = storage.GetStorageForDirectory(@"exports"); + if (storage is OsuStorage osuStorage) + exportStorage = osuStorage.GetExportStorage(); + UserFileStorage = storage.GetStorageForDirectory(@"files"); } @@ -68,6 +72,8 @@ namespace osu.Game.Database /// A cancellation token. public async Task ExportAsync(Live model, CancellationToken cancellationToken = default) { + Debug.Assert(exportStorage != null); + string itemFilename = model.PerformRead(s => GetFilename(s).GetValidFilename()); if (itemFilename.Length > MAX_FILENAME_LENGTH - FileExtension.Length) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index a936fa74da..27e1889c6a 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -61,6 +61,11 @@ namespace osu.Game.IO TryChangeToCustomStorage(out Error); } + /// + /// Returns the used for storing exported files. + /// + public virtual Storage GetExportStorage() => GetStorageForDirectory(@"exports"); + /// /// Resets the custom storage path, changing the target storage to the default location. /// diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index 2a4f9b87ac..c0bd77366e 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -5,6 +5,8 @@ using System; using Foundation; using Microsoft.Maui.Devices; using osu.Framework.Graphics; +using osu.Framework.iOS; +using osu.Framework.Platform; using osu.Game; using osu.Game.Updater; using osu.Game.Utils; @@ -19,6 +21,8 @@ namespace osu.iOS protected override BatteryInfo CreateBatteryInfo() => new IOSBatteryInfo(); + protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorageIOS((IOSGameHost)host, defaultStorage); + protected override Edges SafeAreaOverrideEdges => // iOS shows a home indicator at the bottom, and adds a safe area to account for this. // Because we have the home indicator (mostly) hidden we don't really care about drawing in this region. diff --git a/osu.iOS/OsuStorageIOS.cs b/osu.iOS/OsuStorageIOS.cs new file mode 100644 index 0000000000..f3a5eec737 --- /dev/null +++ b/osu.iOS/OsuStorageIOS.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 System.IO; +using osu.Framework.iOS; +using osu.Framework.Platform; +using osu.Game.IO; + +namespace osu.iOS +{ + public class OsuStorageIOS : OsuStorage + { + private readonly IOSGameHost host; + + public OsuStorageIOS(IOSGameHost host, Storage defaultStorage) + : base(host, defaultStorage) + { + this.host = host; + } + + public override Storage GetExportStorage() => new IOSStorage(Path.GetTempPath(), host); + } +} From f0f3c5357164334ea788ec928292b6b59768e55f Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 9 Dec 2024 08:26:18 -0500 Subject: [PATCH 178/326] Update exporter test to use `OsuStorage` --- osu.Game.Tests/Database/LegacyModelExporterTest.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Database/LegacyModelExporterTest.cs b/osu.Game.Tests/Database/LegacyModelExporterTest.cs index 0c4b0cc9c4..d261c49517 100644 --- a/osu.Game.Tests/Database/LegacyModelExporterTest.cs +++ b/osu.Game.Tests/Database/LegacyModelExporterTest.cs @@ -12,6 +12,7 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Database; +using osu.Game.IO; using osu.Game.Overlays.Notifications; using Realms; @@ -21,7 +22,9 @@ namespace osu.Game.Tests.Database public class LegacyModelExporterTest { private TestLegacyModelExporter legacyExporter = null!; - private TemporaryNativeStorage storage = null!; + + private OsuStorage storage = null!; + private TemporaryNativeStorage underlyingStorage = null!; private const string short_filename = "normal file name"; @@ -31,7 +34,7 @@ namespace osu.Game.Tests.Database [SetUp] public void SetUp() { - storage = new TemporaryNativeStorage("export-storage"); + storage = new OsuStorage(new HeadlessGameHost(), underlyingStorage = new TemporaryNativeStorage("export-storage")); legacyExporter = new TestLegacyModelExporter(storage); } @@ -102,8 +105,8 @@ namespace osu.Game.Tests.Database [TearDown] public void TearDown() { - if (storage.IsNotNull()) - storage.Dispose(); + if (underlyingStorage.IsNotNull()) + underlyingStorage.Dispose(); } private class TestLegacyModelExporter : LegacyExporter From 1febed66cfabc03c2930c55c8aeecd9cd288e1d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 9 Dec 2024 23:51:57 +0900 Subject: [PATCH 179/326] Fix top score statistics section total score display being terminally broken Closes https://github.com/ppy/osu/issues/31038. If you don't realise why this does anything, realise this: the drawable creation callback runs for every created sprite text in the text flow. ANd the created sprite texts are split by whitespace. And Russian / Ukrainian / Polish etc. use spaces as thousands separators. So on those languages the first encountered part of the score would duplicate itself to the remaining parts. I'm actively convinced it was _more difficult_ to produce what was in place in `master` than to do it properly. Why did `TextColumn` even have `LocalisableString Text` and `Bindable Current` next to each other????? --- .../Scores/TopScoreStatisticsSection.cs | 41 +++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index e8833fa0a3..8e342b49c9 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -12,7 +12,6 @@ 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.Game.Beatmaps; using osu.Game.Graphics; @@ -35,7 +34,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly FontUsage smallFont = OsuFont.GetFont(size: 16); private readonly FontUsage largeFont = OsuFont.GetFont(size: 22, weight: FontWeight.Light); - private readonly TextColumn totalScoreColumn; + private readonly TotalScoreColumn totalScoreColumn; private readonly TextColumn accuracyColumn; private readonly TextColumn maxComboColumn; private readonly TextColumn ppColumn; @@ -67,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Spacing = new Vector2(margin, 0), Children = new Drawable[] { - totalScoreColumn = new TextColumn(BeatmapsetsStrings.ShowScoreboardHeadersScoreTotal, largeFont, top_columns_min_width), + totalScoreColumn = new TotalScoreColumn(BeatmapsetsStrings.ShowScoreboardHeadersScoreTotal, largeFont, top_columns_min_width), accuracyColumn = new TextColumn(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy, largeFont, top_columns_min_width), maxComboColumn = new TextColumn(BeatmapsetsStrings.ShowScoreboardHeadersCombo, largeFont, top_columns_min_width) } @@ -226,7 +225,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } - private partial class TextColumn : InfoColumn, IHasCurrentValue + private partial class TextColumn : InfoColumn { private readonly OsuTextFlowContainer text; @@ -249,18 +248,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } - private Bindable current; - - public Bindable Current - { - get => current; - set - { - text.Clear(); - text.AddText(value.Value, t => t.Current = current = value); - } - } - public TextColumn(LocalisableString title, FontUsage font, float? minWidth = null) : this(title, new OsuTextFlowContainer(t => t.Font = font) { @@ -276,6 +263,28 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } + private partial class TotalScoreColumn : TextColumn + { + private readonly BindableWithCurrent current = new BindableWithCurrent(); + + public TotalScoreColumn(LocalisableString title, FontUsage font, float? minWidth = null) + : base(title, font, minWidth) + { + } + + public Bindable Current + { + get => current; + set => current.Current = value; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Current.BindValueChanged(_ => Text = current.Value, true); + } + } + private partial class ModsInfoColumn : InfoColumn { private readonly FillFlowContainer modsContainer; From 92dfcae6eba4a545c6f2bdab0fdb6ddb6536ff0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2024 14:35:09 +0900 Subject: [PATCH 180/326] Adjust bad grammar --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 0b5450e5ac..975f962f7f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -51,7 +51,7 @@ namespace osu.Game.Beatmaps.Formats } /// - /// Whether or not beatmap or runtime offsets should be applied. Defaults on; only disable for testing purposes. + /// Whether beatmap or runtime offsets should be applied. Defaults on; only disable for testing purposes. /// public bool ApplyOffsets = true; From d69f5fd4cfd9bdc5720c12a4ccca9400e78784bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2024 14:45:12 +0900 Subject: [PATCH 181/326] Avoid beatmap lookup per bar in logo visualisation Just noticed in passing. --- osu.Game/Screens/Menu/LogoVisualisation.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 6d9d2f69b7..53d153ab31 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -106,9 +106,12 @@ namespace osu.Game.Screens.Menu foreach (var source in amplitudeSources) addAmplitudesFromSource(source); + float kiaiMultiplier = beatSyncProvider.CheckIsKiaiTime() ? 1 : 0.5f; + for (int i = 0; i < bars_per_visualiser; i++) { - float targetAmplitude = (temporalAmplitudes[(i + indexOffset) % bars_per_visualiser]) * (beatSyncProvider.CheckIsKiaiTime() ? 1 : 0.5f); + float targetAmplitude = (temporalAmplitudes[(i + indexOffset) % bars_per_visualiser]) * kiaiMultiplier; + if (targetAmplitude > frequencyAmplitudes[i]) frequencyAmplitudes[i] = targetAmplitude; } From 7fcfebf4b43b5eee6e3f981d4df291d348641835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 7 Dec 2024 22:51:09 +0900 Subject: [PATCH 182/326] Use Alt-{Left,Right} as default bindings for bookmark navigation --- 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 42028c044f..170d247023 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -154,8 +154,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.Right }, GlobalAction.EditorSeekToNextSamplePoint), new KeyBinding(new[] { InputKey.Control, InputKey.B }, GlobalAction.EditorAddBookmark), new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.B }, GlobalAction.EditorRemoveClosestBookmark), - new KeyBinding(InputKey.None, GlobalAction.EditorSeekToPreviousBookmark), - new KeyBinding(InputKey.None, GlobalAction.EditorSeekToNextBookmark), + new KeyBinding(new[] { InputKey.Alt, InputKey.Left }, GlobalAction.EditorSeekToPreviousBookmark), + new KeyBinding(new[] { InputKey.Alt, InputKey.Right }, GlobalAction.EditorSeekToNextBookmark), }; private static IEnumerable editorTestPlayKeyBindings => new[] From 3cac5837547f5ca08baa9f615948d4a4166a4505 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2024 16:40:47 +0900 Subject: [PATCH 183/326] Rewrite resource changing code to be more legible (to my eye) --- .../Screens/Edit/Setup/ResourcesSection.cs | 71 +++++++++++-------- 1 file changed, 40 insertions(+), 31 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 84107a57e9..6cde0e6792 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -103,55 +103,64 @@ namespace osu.Game.Screens.Edit.Setup private void changeResource(FileInfo source, bool applyToAllDifficulties, string baseFilename, Func readFilename, Action writeFilename) { - var thisBeatmap = working.Value.BeatmapInfo; var set = working.Value.BeatmapSetInfo; + var beatmap = working.Value.BeatmapInfo; - string newFilename = string.Empty; + var otherBeatmaps = set.Beatmaps.Where(b => !b.Equals(beatmap)); + // First, clean up files which will no longer be used. if (applyToAllDifficulties) { - newFilename = $"{baseFilename}{source.Extension}"; - - foreach (var beatmap in set.Beatmaps) + foreach (var b in set.Beatmaps) { - if (set.GetFile(readFilename(beatmap.Metadata)) is RealmNamedFileUsage otherExistingFile) + if (set.GetFile(readFilename(b.Metadata)) is RealmNamedFileUsage otherExistingFile) beatmaps.DeleteFile(set, otherExistingFile); - - writeFilename(beatmap.Metadata, newFilename); - - if (!beatmap.Equals(thisBeatmap)) - { - // save the difficulty to re-encode the .osu file, updating any reference of the old filename. - var beatmapWorking = beatmaps.GetWorkingBeatmap(beatmap); - beatmaps.Save(beatmap, beatmapWorking.Beatmap, beatmapWorking.GetSkin()); - } } } else { - string[] filenames = set.Files.Select(f => f.Filename).Where(f => + RealmNamedFileUsage? oldFile = set.GetFile(readFilename(working.Value.Metadata)); + + if (oldFile != null) + { + bool oldFileUsedInOtherDiff = otherBeatmaps + .Any(b => readFilename(b.Metadata) == oldFile.Filename); + if (!oldFileUsedInOtherDiff) + beatmaps.DeleteFile(set, oldFile); + } + } + + // Choose a new filename that doesn't clash with any other existing files. + string newFilename = $"{baseFilename}{source.Extension}"; + + if (set.GetFile(newFilename) != null) + { + string[] existingFilenames = set.Files.Select(f => f.Filename).Where(f => f.StartsWith(baseFilename, StringComparison.OrdinalIgnoreCase) && f.EndsWith(source.Extension, StringComparison.OrdinalIgnoreCase)).ToArray(); - - string currentFilename = readFilename(working.Value.Metadata); - - var oldFile = set.GetFile(currentFilename); - - if (oldFile != null && set.Beatmaps.Where(b => !b.Equals(thisBeatmap)).All(b => readFilename(b.Metadata) != currentFilename)) - { - beatmaps.DeleteFile(set, oldFile); - newFilename = currentFilename; - } - - if (string.IsNullOrEmpty(newFilename)) - newFilename = NamingUtils.GetNextBestFilename(filenames, $@"{baseFilename}{source.Extension}"); - - writeFilename(working.Value.Metadata, newFilename); + newFilename = NamingUtils.GetNextBestFilename(existingFilenames, $@"{baseFilename}{source.Extension}"); } using (var stream = source.OpenRead()) beatmaps.AddFile(set, stream, newFilename); + if (applyToAllDifficulties) + { + foreach (var b in otherBeatmaps) + { + if (readFilename(b.Metadata) != newFilename) + { + writeFilename(b.Metadata, newFilename); + + // save the difficulty to re-encode the .osu file, updating any reference of the old filename. + var beatmapWorking = beatmaps.GetWorkingBeatmap(b); + beatmaps.Save(b, beatmapWorking.Beatmap, beatmapWorking.GetSkin()); + } + } + } + + writeFilename(beatmap.Metadata, newFilename); + // editor change handler cannot be aware of any file changes or other difficulties having their metadata modified. // for simplicity's sake, trigger a save when changing any resource to ensure the change is correctly saved. editor?.Save(); From bbaa542d4a376e490c7e58d794ff49ca7e1bdddb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2024 16:44:35 +0900 Subject: [PATCH 184/326] Add note about expensive operation --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 6cde0e6792..7fcd09d7e7 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -148,14 +148,17 @@ namespace osu.Game.Screens.Edit.Setup { foreach (var b in otherBeatmaps) { - if (readFilename(b.Metadata) != newFilename) - { - writeFilename(b.Metadata, newFilename); + // This operation is quite expensive, so only perform it if required. + if (readFilename(b.Metadata) == newFilename) continue; - // save the difficulty to re-encode the .osu file, updating any reference of the old filename. - var beatmapWorking = beatmaps.GetWorkingBeatmap(b); - beatmaps.Save(b, beatmapWorking.Beatmap, beatmapWorking.GetSkin()); - } + writeFilename(b.Metadata, newFilename); + + // save the difficulty to re-encode the .osu file, updating any reference of the old filename. + // + // note that this triggers a full save flow, including triggering a difficulty calculation. + // this is not a cheap operation and should be reconsidered in the future. + var beatmapWorking = beatmaps.GetWorkingBeatmap(b); + beatmaps.Save(b, beatmapWorking.Beatmap, beatmapWorking.GetSkin()); } } From dae380b7fa927c351e2e413c5b23834f717908d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2024 22:03:07 +0900 Subject: [PATCH 185/326] Fix lookups of hit circle slider pieces potentially using wrong source skin Addresses https://github.com/ppy/osu/discussions/30927. --- .../Skinning/Legacy/LegacyMainCirclePiece.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index ef616ae964..0dc0f065d4 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -57,11 +57,22 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy [BackgroundDependencyLoader] private void load() { + const string base_lookup = @"hitcircle"; + var drawableOsuObject = (DrawableOsuHitObject?)drawableObject; + // As a precondition, ensure that any prefix lookups are run against the skin which is providing "hitcircle". + // This is to correctly handle a case such as: + // + // - Beatmap provides `hitcircle` + // - User skin provides `sliderstartcircle` + // + // In such a case, the `hitcircle` should be used for slider start circles rather than the user's skin override. + var provider = skin.FindProvider(s => s.GetTexture(base_lookup) != null) ?? skin; + // if a base texture for the specified prefix exists, continue using it for subsequent lookups. // otherwise fall back to the default prefix "hitcircle". - string circleName = (priorityLookupPrefix != null && skin.GetTexture(priorityLookupPrefix) != null) ? priorityLookupPrefix : @"hitcircle"; + string circleName = (priorityLookupPrefix != null && provider.GetTexture(priorityLookupPrefix) != null) ? priorityLookupPrefix : base_lookup; Vector2 maxSize = OsuHitObject.OBJECT_DIMENSIONS * 2; @@ -70,7 +81,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(maxSize) }) + CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = provider.GetTexture(circleName)?.WithMaximumSize(maxSize) }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -79,7 +90,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(@$"{circleName}overlay")?.WithMaximumSize(maxSize) }) + Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = provider.GetTexture(@$"{circleName}overlay")?.WithMaximumSize(maxSize) }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 315a9dba9bd0d9f3a994a7e50d7a8acf0c6a024d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 11 Dec 2024 09:59:18 +0900 Subject: [PATCH 186/326] Allow tsunyoku and stanriders to trigger diffcalc spreadsheet generator --- .github/workflows/diffcalc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index 4297a88e89..8461208a2e 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -115,7 +115,7 @@ jobs: steps: - name: Check permissions run: | - ALLOWED_USERS=(smoogipoo peppy bdach frenzibyte) + ALLOWED_USERS=(smoogipoo peppy bdach frenzibyte tsunyoku stanriders) for i in "${ALLOWED_USERS[@]}"; do if [[ "${{ github.actor }}" == "$i" ]]; then exit 0 From 637fe07b31066087361f7ed305383021f581c647 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 Dec 2024 12:36:42 +0900 Subject: [PATCH 187/326] Rename `Room{Status -> Mode}Filter` I need the "status" term free for an upcoming change. And web calls this parameter "mode" as well: https://github.com/ppy/osu-web/blob/642e973f916f315fb505aa79d4376675d0a2ec95/app/Models/Multiplayer/Room.php#L184-L199 so it works in my head. --- osu.Game/Online/Rooms/GetRoomsRequest.cs | 10 +++++----- .../OnlinePlay/Components/ListingPollingComponent.cs | 2 +- .../OnlinePlay/Lounge/Components/FilterCriteria.cs | 2 +- .../{RoomStatusFilter.cs => RoomModeFilter.cs} | 2 +- osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) rename osu.Game/Screens/OnlinePlay/Lounge/Components/{RoomStatusFilter.cs => RoomModeFilter.cs} (91%) diff --git a/osu.Game/Online/Rooms/GetRoomsRequest.cs b/osu.Game/Online/Rooms/GetRoomsRequest.cs index 7feb709acb..b36e6fc088 100644 --- a/osu.Game/Online/Rooms/GetRoomsRequest.cs +++ b/osu.Game/Online/Rooms/GetRoomsRequest.cs @@ -11,12 +11,12 @@ namespace osu.Game.Online.Rooms { public class GetRoomsRequest : APIRequest> { - private readonly RoomStatusFilter status; + private readonly RoomModeFilter mode; private readonly string category; - public GetRoomsRequest(RoomStatusFilter status, string category) + public GetRoomsRequest(RoomModeFilter mode, string category) { - this.status = status; + this.mode = mode; this.category = category; } @@ -24,8 +24,8 @@ namespace osu.Game.Online.Rooms { var req = base.CreateWebRequest(); - if (status != RoomStatusFilter.Open) - req.AddParameter("mode", status.ToString().ToSnakeCase().ToLowerInvariant()); + if (mode != RoomModeFilter.Open) + req.AddParameter("mode", mode.ToString().ToSnakeCase().ToLowerInvariant()); if (!string.IsNullOrEmpty(category)) req.AddParameter("category", category); diff --git a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs index b213d424df..88bd595202 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs @@ -47,7 +47,7 @@ namespace osu.Game.Screens.OnlinePlay.Components lastPollRequest?.Cancel(); - var req = new GetRoomsRequest(Filter.Value.Status, Filter.Value.Category); + var req = new GetRoomsRequest(Filter.Value.Mode, Filter.Value.Category); req.Success += result => { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs index 0f63718355..cc8b0247f6 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs @@ -8,7 +8,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components public class FilterCriteria { public string SearchString = string.Empty; - public RoomStatusFilter Status; + public RoomModeFilter Mode; public string Category = string.Empty; public RulesetInfo? Ruleset; public RoomPermissionsFilter Permissions; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusFilter.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomModeFilter.cs similarity index 91% rename from osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusFilter.cs rename to osu.Game/Screens/OnlinePlay/Lounge/Components/RoomModeFilter.cs index 53fbf670e1..0c07233bff 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusFilter.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomModeFilter.cs @@ -5,7 +5,7 @@ using System.ComponentModel; namespace osu.Game.Screens.OnlinePlay.Lounge.Components { - public enum RoomStatusFilter + public enum RoomModeFilter { Open, diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index 5d0983f09c..9a02e4bec8 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -83,7 +83,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge private LoadingLayer loadingLayer = null!; private RoomsContainer roomsContainer = null!; private SearchTextBox searchTextBox = null!; - private Dropdown statusDropdown = null!; + private Dropdown statusDropdown = null!; [BackgroundDependencyLoader(true)] private void load() @@ -223,12 +223,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { SearchString = searchTextBox.Current.Value, Ruleset = ruleset.Value, - Status = statusDropdown.Current.Value + Mode = statusDropdown.Current.Value }; protected virtual IEnumerable CreateFilterControls() { - statusDropdown = new SlimEnumDropdown + statusDropdown = new SlimEnumDropdown { RelativeSizeAxes = Axes.None, Width = 160, From 3352571f2aa3378bdf9dbb0068bac21dbb823890 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 Dec 2024 13:01:11 +0900 Subject: [PATCH 188/326] Add ability to filter out currently playing rooms Addresses https://osu.ppy.sh/community/forums/topics/2013293?n=1. --- osu.Game/Online/Rooms/GetRoomsRequest.cs | 17 +++++++++++------ .../Components/ListingPollingComponent.cs | 2 +- .../Lounge/Components/FilterCriteria.cs | 1 + .../Lounge/Components/RoomStatusFilter.cs | 11 +++++++++++ .../OnlinePlay/Lounge/LoungeSubScreen.cs | 11 ++++++----- .../Multiplayer/MultiplayerLoungeSubScreen.cs | 13 ++++++++++++- 6 files changed, 42 insertions(+), 13 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusFilter.cs diff --git a/osu.Game/Online/Rooms/GetRoomsRequest.cs b/osu.Game/Online/Rooms/GetRoomsRequest.cs index b36e6fc088..2d0d572e84 100644 --- a/osu.Game/Online/Rooms/GetRoomsRequest.cs +++ b/osu.Game/Online/Rooms/GetRoomsRequest.cs @@ -12,12 +12,14 @@ namespace osu.Game.Online.Rooms public class GetRoomsRequest : APIRequest> { private readonly RoomModeFilter mode; + private readonly RoomStatusFilter? status; private readonly string category; - public GetRoomsRequest(RoomModeFilter mode, string category) + public GetRoomsRequest(FilterCriteria filterCriteria) { - this.mode = mode; - this.category = category; + mode = filterCriteria.Mode; + category = filterCriteria.Category; + status = filterCriteria.Status; } protected override WebRequest CreateWebRequest() @@ -25,14 +27,17 @@ namespace osu.Game.Online.Rooms var req = base.CreateWebRequest(); if (mode != RoomModeFilter.Open) - req.AddParameter("mode", mode.ToString().ToSnakeCase().ToLowerInvariant()); + req.AddParameter(@"mode", mode.ToString().ToSnakeCase().ToLowerInvariant()); + + if (status != null) + req.AddParameter(@"status", status.Value.ToString().ToSnakeCase().ToLowerInvariant()); if (!string.IsNullOrEmpty(category)) - req.AddParameter("category", category); + req.AddParameter(@"category", category); return req; } - protected override string Target => "rooms"; + protected override string Target => @"rooms"; } } diff --git a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs index 88bd595202..21452727b8 100644 --- a/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs +++ b/osu.Game/Screens/OnlinePlay/Components/ListingPollingComponent.cs @@ -47,7 +47,7 @@ namespace osu.Game.Screens.OnlinePlay.Components lastPollRequest?.Cancel(); - var req = new GetRoomsRequest(Filter.Value.Mode, Filter.Value.Category); + var req = new GetRoomsRequest(Filter.Value); req.Success += result => { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs index cc8b0247f6..121dffde1f 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/FilterCriteria.cs @@ -9,6 +9,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components { public string SearchString = string.Empty; public RoomModeFilter Mode; + public RoomStatusFilter? Status; public string Category = string.Empty; public RulesetInfo? Ruleset; public RoomPermissionsFilter Permissions; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusFilter.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusFilter.cs new file mode 100644 index 0000000000..a4d5043ff5 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusFilter.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.Screens.OnlinePlay.Lounge.Components +{ + public enum RoomStatusFilter + { + Idle, + Playing, + } +} diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs index 9a02e4bec8..f00cf7427c 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeSubScreen.cs @@ -83,7 +83,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge private LoadingLayer loadingLayer = null!; private RoomsContainer roomsContainer = null!; private SearchTextBox searchTextBox = null!; - private Dropdown statusDropdown = null!; + + protected Dropdown StatusDropdown { get; private set; } = null!; [BackgroundDependencyLoader(true)] private void load() @@ -223,20 +224,20 @@ namespace osu.Game.Screens.OnlinePlay.Lounge { SearchString = searchTextBox.Current.Value, Ruleset = ruleset.Value, - Mode = statusDropdown.Current.Value + Mode = StatusDropdown.Current.Value }; protected virtual IEnumerable CreateFilterControls() { - statusDropdown = new SlimEnumDropdown + StatusDropdown = new SlimEnumDropdown { RelativeSizeAxes = Axes.None, Width = 160, }; - statusDropdown.Current.BindValueChanged(_ => UpdateFilter()); + StatusDropdown.Current.BindValueChanged(_ => UpdateFilter()); - yield return statusDropdown; + yield return StatusDropdown; } #endregion diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index 50358ea9d3..23216c86b2 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -31,6 +31,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer private MultiplayerClient client { get; set; } = null!; private Dropdown roomAccessTypeDropdown = null!; + private OsuCheckbox showInProgress = null!; public override void OnResuming(ScreenTransitionEvent e) { @@ -56,7 +57,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer roomAccessTypeDropdown.Current.BindValueChanged(_ => UpdateFilter()); - return base.CreateFilterControls().Append(roomAccessTypeDropdown); + showInProgress = new OsuCheckbox + { + LabelText = "Show playing rooms", + RelativeSizeAxes = Axes.None, + Width = 200, + Current = { Value = true } + }; + showInProgress.Current.BindValueChanged(_ => UpdateFilter()); + + return base.CreateFilterControls().Concat([roomAccessTypeDropdown, showInProgress]); } protected override FilterCriteria CreateFilterCriteria() @@ -64,6 +74,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer var criteria = base.CreateFilterCriteria(); criteria.Category = @"realtime"; criteria.Permissions = roomAccessTypeDropdown.Current.Value; + criteria.Status = showInProgress.Current.Value ? null : RoomStatusFilter.Idle; return criteria; } From b37a06c0fe0ce05b6b82292219faeafd024c86e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 Dec 2024 13:24:54 +0900 Subject: [PATCH 189/326] Hide "show playing rooms" toggle when in filter mode it doesn't make sense with --- .../Multiplayer/MultiplayerLoungeSubScreen.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index 23216c86b2..303ba60875 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.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 System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -48,7 +47,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer protected override IEnumerable CreateFilterControls() { - roomAccessTypeDropdown = new SlimEnumDropdown + foreach (var control in base.CreateFilterControls()) + yield return control; + + yield return roomAccessTypeDropdown = new SlimEnumDropdown { RelativeSizeAxes = Axes.None, Current = Config.GetBindable(OsuSetting.MultiplayerRoomFilter), @@ -57,16 +59,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer roomAccessTypeDropdown.Current.BindValueChanged(_ => UpdateFilter()); - showInProgress = new OsuCheckbox + yield return showInProgress = new OsuCheckbox { LabelText = "Show playing rooms", RelativeSizeAxes = Axes.None, Width = 200, + Padding = new MarginPadding { Vertical = 5, }, Current = { Value = true } }; - showInProgress.Current.BindValueChanged(_ => UpdateFilter()); - return base.CreateFilterControls().Concat([roomAccessTypeDropdown, showInProgress]); + showInProgress.Current.BindValueChanged(_ => UpdateFilter()); + StatusDropdown.Current.BindValueChanged(_ => showInProgress.Alpha = StatusDropdown.Current.Value == RoomModeFilter.Open ? 1 : 0, true); } protected override FilterCriteria CreateFilterCriteria() @@ -74,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer var criteria = base.CreateFilterCriteria(); criteria.Category = @"realtime"; criteria.Permissions = roomAccessTypeDropdown.Current.Value; - criteria.Status = showInProgress.Current.Value ? null : RoomStatusFilter.Idle; + criteria.Status = showInProgress.Current.Value && criteria.Mode == RoomModeFilter.Open ? null : RoomStatusFilter.Idle; return criteria; } From 723883e1f06dc7277f1441c8ab73130b0437adbc Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 11 Dec 2024 01:03:20 -0500 Subject: [PATCH 190/326] Revert "Update exporter test to use `OsuStorage`" This reverts commit f0f3c5357164334ea788ec928292b6b59768e55f. --- osu.Game.Tests/Database/LegacyModelExporterTest.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Database/LegacyModelExporterTest.cs b/osu.Game.Tests/Database/LegacyModelExporterTest.cs index d261c49517..0c4b0cc9c4 100644 --- a/osu.Game.Tests/Database/LegacyModelExporterTest.cs +++ b/osu.Game.Tests/Database/LegacyModelExporterTest.cs @@ -12,7 +12,6 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Database; -using osu.Game.IO; using osu.Game.Overlays.Notifications; using Realms; @@ -22,9 +21,7 @@ namespace osu.Game.Tests.Database public class LegacyModelExporterTest { private TestLegacyModelExporter legacyExporter = null!; - - private OsuStorage storage = null!; - private TemporaryNativeStorage underlyingStorage = null!; + private TemporaryNativeStorage storage = null!; private const string short_filename = "normal file name"; @@ -34,7 +31,7 @@ namespace osu.Game.Tests.Database [SetUp] public void SetUp() { - storage = new OsuStorage(new HeadlessGameHost(), underlyingStorage = new TemporaryNativeStorage("export-storage")); + storage = new TemporaryNativeStorage("export-storage"); legacyExporter = new TestLegacyModelExporter(storage); } @@ -105,8 +102,8 @@ namespace osu.Game.Tests.Database [TearDown] public void TearDown() { - if (underlyingStorage.IsNotNull()) - underlyingStorage.Dispose(); + if (storage.IsNotNull()) + storage.Dispose(); } private class TestLegacyModelExporter : LegacyExporter From e0aec6f907f81f04cf6c802eea618b7f2c0c062a Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 11 Dec 2024 01:03:55 -0500 Subject: [PATCH 191/326] Revert unnecessary complexity --- osu.Game/Database/LegacyExporter.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Database/LegacyExporter.cs b/osu.Game/Database/LegacyExporter.cs index 193887765d..80393c27f7 100644 --- a/osu.Game/Database/LegacyExporter.cs +++ b/osu.Game/Database/LegacyExporter.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; @@ -42,15 +41,13 @@ namespace osu.Game.Database protected abstract string FileExtension { get; } protected readonly Storage UserFileStorage; - private readonly Storage? exportStorage; + private readonly Storage exportStorage; public Action? PostNotification { get; set; } protected LegacyExporter(Storage storage) { - if (storage is OsuStorage osuStorage) - exportStorage = osuStorage.GetExportStorage(); - + exportStorage = (storage as OsuStorage)?.GetExportStorage() ?? storage.GetStorageForDirectory(@"exports"); UserFileStorage = storage.GetStorageForDirectory(@"files"); } @@ -72,8 +69,6 @@ namespace osu.Game.Database /// A cancellation token. public async Task ExportAsync(Live model, CancellationToken cancellationToken = default) { - Debug.Assert(exportStorage != null); - string itemFilename = model.PerformRead(s => GetFilename(s).GetValidFilename()); if (itemFilename.Length > MAX_FILENAME_LENGTH - FileExtension.Length) From 5a0b732ee32e66e24118e390706795a419cd3954 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 11 Dec 2024 16:26:11 +0900 Subject: [PATCH 192/326] Add comments backreferences to copies of duplicated code for future use --- .../Edit/Blueprints/JuiceStreamSelectionBlueprint.cs | 2 ++ .../Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs index a61478f5d5..6a0ce35a07 100644 --- a/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch/Edit/Blueprints/JuiceStreamSelectionBlueprint.cs @@ -190,6 +190,8 @@ namespace osu.Game.Rulesets.Catch.Edit.Blueprints lastSliderPathVersion = HitObject.Path.Version.Value; } + // duplicated in `SliderSelectionBlueprint.convertToStream()` + // consider extracting common helper when applying changes here private void convertToStream() { if (editorBeatmap == null || beatDivisor == null) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 34de81f1ba..02f76b51b0 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -551,6 +551,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders HitObject.Position += first; } + // duplicated in `JuiceStreamSelectionBlueprint.convertToStream()` + // consider extracting common helper when applying changes here private void convertToStream() { if (editorBeatmap == null || beatDivisor == null) From de31a48beb3d1f2ec47b9421a12492a70c054667 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Dec 2024 23:29:50 +0900 Subject: [PATCH 193/326] Some `Carousel` classes can be abstract --- .../Screens/Select/Carousel/CarouselGroup.cs | 48 +++++++++---------- .../Carousel/CarouselGroupEagerSelect.cs | 4 +- 2 files changed, 26 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 62d694976f..c0fb5fa397 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -10,8 +10,31 @@ namespace osu.Game.Screens.Select.Carousel /// /// A group which ensures only one item is selected. /// - public class CarouselGroup : CarouselItem + public abstract class CarouselGroup : CarouselItem { + protected CarouselGroup(List? items = null) + { + if (items != null) this.items = items; + + State.ValueChanged += state => + { + switch (state.NewValue) + { + case CarouselItemState.Collapsed: + case CarouselItemState.NotSelected: + this.items.ForEach(c => c.State.Value = CarouselItemState.Collapsed); + break; + + case CarouselItemState.Selected: + this.items.ForEach(c => + { + if (c.State.Value == CarouselItemState.Collapsed) c.State.Value = CarouselItemState.NotSelected; + }); + break; + } + }; + } + public override DrawableCarouselItem? CreateDrawableRepresentation() => null; public SlimReadOnlyListWrapper Items => items.AsSlimReadOnly(); @@ -67,29 +90,6 @@ namespace osu.Game.Screens.Select.Carousel TotalItemsNotFiltered++; } - public CarouselGroup(List? items = null) - { - if (items != null) this.items = items; - - State.ValueChanged += state => - { - switch (state.NewValue) - { - case CarouselItemState.Collapsed: - case CarouselItemState.NotSelected: - this.items.ForEach(c => c.State.Value = CarouselItemState.Collapsed); - break; - - case CarouselItemState.Selected: - this.items.ForEach(c => - { - if (c.State.Value == CarouselItemState.Collapsed) c.State.Value = CarouselItemState.NotSelected; - }); - break; - } - }; - } - public override void Filter(FilterCriteria criteria) { base.Filter(criteria); diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index cf4ba5924f..8cc1ea258a 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -10,9 +10,9 @@ namespace osu.Game.Screens.Select.Carousel /// /// A group which ensures at least one item is selected (if the group itself is selected). /// - public class CarouselGroupEagerSelect : CarouselGroup + public abstract class CarouselGroupEagerSelect : CarouselGroup { - public CarouselGroupEagerSelect() + protected CarouselGroupEagerSelect() { State.ValueChanged += state => { From bab9b9c937748bc283febc553b8b0b8e2510599b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 10 Dec 2024 23:52:37 +0900 Subject: [PATCH 194/326] Remove no-longer-correct comment --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index fc7c7989e2..f0c3b1f477 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -246,9 +246,6 @@ namespace osu.Game.Screens.Select if (detachedBeatmapStore != null && detachedBeatmapSets == null) { - // This is performing an unnecessary second lookup on realm (in addition to the subscription), but for performance reasons - // we require it to be separate: the subscription's initial callback (with `ChangeSet` of `null`) will run on the update - // thread. If we attempt to detach beatmaps in this callback the game will fall over (it takes time). detachedBeatmapSets = detachedBeatmapStore.GetDetachedBeatmaps(cancellationToken); detachedBeatmapSets.BindCollectionChanged(beatmapSetsChanged); loadNewRoot(); From c94b393e309cd4a9f0e5d4f0b5f3e53a6d2e5b30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2024 00:18:33 +0900 Subject: [PATCH 195/326] Access beatmap store via abstract base class The intention here is to make things more testable going forward. Specifically, to remove the "back-door" entrance into `BeatmapCarousel` where `BeatmapSets` can be set by tests and bypas/block realm retrieval. --- .../Background/TestSceneUserDimBackgrounds.cs | 6 ++-- .../Visual/Multiplayer/QueueModeTestScene.cs | 6 ++-- .../Multiplayer/TestSceneMultiplayer.cs | 6 ++-- .../TestSceneMultiplayerMatchSongSelect.cs | 6 ++-- .../TestScenePlaylistsSongSelect.cs | 6 ++-- .../SongSelect/TestScenePlaySongSelect.cs | 6 ++-- osu.Game/Database/BeatmapStore.cs | 35 +++++++++++++++++++ ...pStore.cs => RealmDetachedBeatmapStore.cs} | 5 ++- osu.Game/OsuGame.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 ++-- 10 files changed, 59 insertions(+), 25 deletions(-) create mode 100644 osu.Game/Database/BeatmapStore.cs rename osu.Game/Database/{DetachedBeatmapStore.cs => RealmDetachedBeatmapStore.cs} (96%) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index d8be57382f..5bbbfb0284 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -49,17 +49,17 @@ namespace osu.Game.Tests.Visual.Background [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - DetachedBeatmapStore detachedBeatmapStore; + BeatmapStore beatmapStore; Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); - Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); + Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); - Add(detachedBeatmapStore); + Add(beatmapStore); Beatmap.SetDefault(); } diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 2b738743ea..ab0a4e8e03 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -44,14 +44,14 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - DetachedBeatmapStore detachedBeatmapStore; + BeatmapStore beatmapStore; Dependencies.Cache(new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); + Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); - Add(detachedBeatmapStore); + Add(beatmapStore); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 9213a52c0e..0f3fa7511d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -66,14 +66,14 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - DetachedBeatmapStore detachedBeatmapStore; + BeatmapStore beatmapStore; Dependencies.Cache(new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); + Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); - Add(detachedBeatmapStore); + Add(beatmapStore); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 2a5f16d091..3ea96bae84 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -46,16 +46,16 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - DetachedBeatmapStore detachedBeatmapStore; + BeatmapStore beatmapStore; Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); + Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); importedBeatmapSet = manager.Import(TestResources.CreateTestBeatmapSetInfo(8, rulesets.AvailableRulesets.ToArray()))!; - Add(detachedBeatmapStore); + Add(beatmapStore); } private void setUp() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index fa1909254a..24b67bc4a1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -31,18 +31,18 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - DetachedBeatmapStore detachedBeatmapStore; + BeatmapStore beatmapStore; Dependencies.Cache(new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); + Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); manager.Import(beatmapSet); - Add(detachedBeatmapStore); + Add(beatmapStore); } public override void SetUpSteps() diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 3a95aca6b9..3d86b214fd 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -56,20 +56,20 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - DetachedBeatmapStore detachedBeatmapStore; + BeatmapStore beatmapStore; // These DI caches are required to ensure for interactive runs this test scene doesn't nuke all user beatmaps in the local install. // At a point we have isolated interactive test runs enough, this can likely be removed. Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(Realm); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); - Dependencies.Cache(detachedBeatmapStore = new DetachedBeatmapStore()); + Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(music = new MusicController()); // required to get bindables attached Add(music); - Add(detachedBeatmapStore); + Add(beatmapStore); Dependencies.Cache(config = new OsuConfigManager(LocalStorage)); } diff --git a/osu.Game/Database/BeatmapStore.cs b/osu.Game/Database/BeatmapStore.cs new file mode 100644 index 0000000000..f288279a79 --- /dev/null +++ b/osu.Game/Database/BeatmapStore.cs @@ -0,0 +1,35 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; + +namespace osu.Game.Database +{ + /// + /// A store which contains a thread-safe representation of beatmaps available game-wide. + /// This exposes changes to available beatmaps, such as post-import or deletion. + /// + /// + /// The main goal of classes which implement this interface should be to provide change + /// tracking and thread safety in a performant way, rather than having to worry about such + /// concerns at the point of usage. + /// + public abstract partial class BeatmapStore : Component + { + /// + /// Get all available beatmaps. + /// + /// A cancellation token which allows early abort from the operation. + /// A bindable list of all available beatmap sets. + /// + /// This operation may block during the initial load process. + /// + /// It is generally expected that once a beatmap store is in a good state, the overhead of this call + /// should be negligible. + /// + public abstract IBindableList GetBeatmaps(CancellationToken? cancellationToken); + } +} diff --git a/osu.Game/Database/DetachedBeatmapStore.cs b/osu.Game/Database/RealmDetachedBeatmapStore.cs similarity index 96% rename from osu.Game/Database/DetachedBeatmapStore.cs rename to osu.Game/Database/RealmDetachedBeatmapStore.cs index 5b65f608b2..bc0dc2ae93 100644 --- a/osu.Game/Database/DetachedBeatmapStore.cs +++ b/osu.Game/Database/RealmDetachedBeatmapStore.cs @@ -8,14 +8,13 @@ using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; using Realms; namespace osu.Game.Database { - public partial class DetachedBeatmapStore : Component + public partial class RealmDetachedBeatmapStore : BeatmapStore { private readonly ManualResetEventSlim loaded = new ManualResetEventSlim(); @@ -28,7 +27,7 @@ namespace osu.Game.Database [Resolved] private RealmAccess realm { get; set; } = null!; - public IBindableList GetDetachedBeatmaps(CancellationToken? cancellationToken) + public override IBindableList GetBeatmaps(CancellationToken? cancellationToken) { loaded.Wait(cancellationToken ?? CancellationToken.None); return detachedBeatmapSets.GetBoundCopy(); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index d8145c8246..e808e570c7 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1143,7 +1143,7 @@ namespace osu.Game loadComponentSingleFile(new MedalOverlay(), topMostOverlayContent.Add); loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add); - loadComponentSingleFile(new DetachedBeatmapStore(), Add, true); + loadComponentSingleFile(new RealmDetachedBeatmapStore(), Add, true); Add(externalLinkOpener = new ExternalLinkOpener()); Add(new MusicKeyBindingHandler()); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f0c3b1f477..6dfb834317 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select private RealmAccess realm { get; set; } = null!; [Resolved] - private DetachedBeatmapStore? detachedBeatmapStore { get; set; } + private BeatmapStore? beatmapStore { get; set; } private IBindableList? detachedBeatmapSets; @@ -244,9 +244,9 @@ namespace osu.Game.Screens.Select RightClickScrollingEnabled.BindValueChanged(enabled => Scroll.RightMouseScrollbar = enabled.NewValue, true); - if (detachedBeatmapStore != null && detachedBeatmapSets == null) + if (beatmapStore != null && detachedBeatmapSets == null) { - detachedBeatmapSets = detachedBeatmapStore.GetDetachedBeatmaps(cancellationToken); + detachedBeatmapSets = beatmapStore.GetBeatmaps(cancellationToken); detachedBeatmapSets.BindCollectionChanged(beatmapSetsChanged); loadNewRoot(); } From a868c33380e4423c572e3a3ce13cedbe63753d88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2024 16:17:18 +0900 Subject: [PATCH 196/326] Remove `BeatmapCarousel` testing backdoor --- .../Background/TestSceneUserDimBackgrounds.cs | 2 +- .../Visual/Multiplayer/QueueModeTestScene.cs | 2 +- .../Multiplayer/TestSceneMultiplayer.cs | 2 +- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../TestScenePlaylistsSongSelect.cs | 2 +- .../SongSelect/TestSceneBeatmapCarousel.cs | 8 +++++- .../SongSelect/TestScenePlaySongSelect.cs | 2 +- .../TestSceneUpdateBeatmapSetButton.cs | 13 +++++---- .../TestSceneFirstRunScreenUIScale.cs | 5 ++++ .../TestSceneFirstRunSetupOverlay.cs | 3 +++ osu.Game/Database/BeatmapStore.cs | 2 +- .../Database/RealmDetachedBeatmapStore.cs | 2 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 27 ++++--------------- osu.Game/Tests/Beatmaps/TestBeatmapStore.cs | 16 +++++++++++ 14 files changed, 52 insertions(+), 36 deletions(-) create mode 100644 osu.Game/Tests/Beatmaps/TestBeatmapStore.cs diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 5bbbfb0284..693e1e48d4 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Background Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); - Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); + Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); manager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index ab0a4e8e03..0e01751d76 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); + Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); Add(beatmapStore); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 0f3fa7511d..fb653cea8b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); + Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); Add(beatmapStore); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 3ea96bae84..8e4c83c4b4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); + Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); importedBeatmapSet = manager.Import(TestResources.CreateTestBeatmapSetInfo(8, rulesets.AvailableRulesets.ToArray()))!; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 24b67bc4a1..726d0ac9f9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); + Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(Realm); var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 97c46a11fc..11e754c868 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -16,6 +16,7 @@ using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; @@ -23,6 +24,7 @@ using osu.Game.Rulesets.Taiko; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; using osuTK.Input; @@ -42,6 +44,9 @@ namespace osu.Game.Tests.Visual.SongSelect private const int set_count = 5; private const int diff_count = 3; + [Cached(typeof(BeatmapStore))] + private TestBeatmapStore beatmaps = new TestBeatmapStore(); + [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { @@ -1329,7 +1334,8 @@ namespace osu.Game.Tests.Visual.SongSelect carouselAdjust?.Invoke(carousel); - carousel.BeatmapSets = beatmapSets; + beatmaps.BeatmapSets.Clear(); + beatmaps.BeatmapSets.AddRange(beatmapSets); (target ?? this).Child = carousel; }); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 3d86b214fd..c415fc876f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.SongSelect Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(Realm); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); - Dependencies.Cache(beatmapStore = new RealmDetachedBeatmapStore()); + Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore()); Dependencies.Cache(music = new MusicController()); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs index 0b0cd0317a..ff0f35576c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.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 NUnit.Framework; using osu.Framework.Allocation; @@ -10,12 +9,14 @@ using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Online; using osu.Game.Tests.Resources; using osuTK.Input; @@ -31,6 +32,9 @@ namespace osu.Game.Tests.Visual.SongSelect private BeatmapSetInfo testBeatmapSetInfo = null!; + [Cached(typeof(BeatmapStore))] + private TestBeatmapStore beatmaps = new TestBeatmapStore(); + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -246,13 +250,12 @@ namespace osu.Game.Tests.Visual.SongSelect private BeatmapCarousel createCarousel() { + beatmaps.BeatmapSets.Clear(); + beatmaps.BeatmapSets.Add(testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(5)); + return carousel = new BeatmapCarousel(new FilterCriteria()) { RelativeSizeAxes = Axes.Both, - BeatmapSets = new List - { - (testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(5)), - } }; } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunScreenUIScale.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunScreenUIScale.cs index 2dee57f4cb..4d180f6507 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunScreenUIScale.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunScreenUIScale.cs @@ -3,8 +3,10 @@ using osu.Framework.Allocation; using osu.Framework.Screens; +using osu.Game.Database; using osu.Game.Overlays; using osu.Game.Overlays.FirstRunSetup; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.UserInterface { @@ -13,6 +15,9 @@ namespace osu.Game.Tests.Visual.UserInterface [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + [Cached(typeof(BeatmapStore))] + private BeatmapStore beatmapStore = new TestBeatmapStore(); + public TestSceneFirstRunScreenUIScale() { AddStep("load screen", () => diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs index 2ca06bf2f4..dc51e5516a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFirstRunSetupOverlay.cs @@ -17,12 +17,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.FirstRunSetup; using osu.Game.Overlays.Notifications; using osu.Game.Screens; using osu.Game.Screens.Footer; +using osu.Game.Tests.Beatmaps; using osuTK; using osuTK.Input; @@ -47,6 +49,7 @@ namespace osu.Game.Tests.Visual.UserInterface Dependencies.Cache(LocalConfig = new OsuConfigManager(LocalStorage)); Dependencies.CacheAs(performer.Object); Dependencies.CacheAs(notificationOverlay.Object); + Dependencies.CacheAs(new TestBeatmapStore()); } [SetUpSteps] diff --git a/osu.Game/Database/BeatmapStore.cs b/osu.Game/Database/BeatmapStore.cs index f288279a79..9853e4b9cf 100644 --- a/osu.Game/Database/BeatmapStore.cs +++ b/osu.Game/Database/BeatmapStore.cs @@ -30,6 +30,6 @@ namespace osu.Game.Database /// It is generally expected that once a beatmap store is in a good state, the overhead of this call /// should be negligible. /// - public abstract IBindableList GetBeatmaps(CancellationToken? cancellationToken); + public abstract IBindableList GetBeatmapSets(CancellationToken? cancellationToken); } } diff --git a/osu.Game/Database/RealmDetachedBeatmapStore.cs b/osu.Game/Database/RealmDetachedBeatmapStore.cs index bc0dc2ae93..b05e07ef31 100644 --- a/osu.Game/Database/RealmDetachedBeatmapStore.cs +++ b/osu.Game/Database/RealmDetachedBeatmapStore.cs @@ -27,7 +27,7 @@ namespace osu.Game.Database [Resolved] private RealmAccess realm { get; set; } = null!; - public override IBindableList GetBeatmaps(CancellationToken? cancellationToken) + public override IBindableList GetBeatmapSets(CancellationToken? cancellationToken) { loaded.Wait(cancellationToken ?? CancellationToken.None); return detachedBeatmapSets.GetBoundCopy(); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 6dfb834317..65c4133ea2 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -112,27 +112,13 @@ namespace osu.Game.Screens.Select [Resolved] private RealmAccess realm { get; set; } = null!; - [Resolved] - private BeatmapStore? beatmapStore { get; set; } - private IBindableList? detachedBeatmapSets; private readonly NoResultsPlaceholder noResultsPlaceholder; private IEnumerable beatmapSets => root.Items.OfType(); - internal IEnumerable BeatmapSets - { - get => beatmapSets.Select(g => g.BeatmapSet); - set - { - if (LoadState != LoadState.NotLoaded) - throw new InvalidOperationException("If not using a realm source, beatmap sets must be set before load."); - - detachedBeatmapSets = new BindableList(value); - Schedule(loadNewRoot); - } - } + internal IEnumerable BeatmapSets => beatmapSets.Select(g => g.BeatmapSet); private void loadNewRoot() { @@ -234,7 +220,7 @@ namespace osu.Game.Screens.Select } [BackgroundDependencyLoader] - private void load(OsuConfigManager config, AudioManager audio, CancellationToken? cancellationToken) + private void load(OsuConfigManager config, AudioManager audio, BeatmapStore beatmaps, CancellationToken? cancellationToken) { spinSample = audio.Samples.Get("SongSelect/random-spin"); randomSelectSample = audio.Samples.Get(@"SongSelect/select-random"); @@ -244,12 +230,9 @@ namespace osu.Game.Screens.Select RightClickScrollingEnabled.BindValueChanged(enabled => Scroll.RightMouseScrollbar = enabled.NewValue, true); - if (beatmapStore != null && detachedBeatmapSets == null) - { - detachedBeatmapSets = beatmapStore.GetBeatmaps(cancellationToken); - detachedBeatmapSets.BindCollectionChanged(beatmapSetsChanged); - loadNewRoot(); - } + detachedBeatmapSets = beatmaps.GetBeatmapSets(cancellationToken); + detachedBeatmapSets.BindCollectionChanged(beatmapSetsChanged); + loadNewRoot(); } private readonly HashSet setsRequiringUpdate = new HashSet(); diff --git a/osu.Game/Tests/Beatmaps/TestBeatmapStore.cs b/osu.Game/Tests/Beatmaps/TestBeatmapStore.cs new file mode 100644 index 0000000000..1734f1397f --- /dev/null +++ b/osu.Game/Tests/Beatmaps/TestBeatmapStore.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.Threading; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Database; + +namespace osu.Game.Tests.Beatmaps +{ + internal partial class TestBeatmapStore : BeatmapStore + { + public readonly BindableList BeatmapSets = new BindableList(); + public override IBindableList GetBeatmapSets(CancellationToken? cancellationToken) => BeatmapSets; + } +} From 0aa17a905b45dcc55e7444722b8593e2957b365f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2024 18:08:34 +0900 Subject: [PATCH 197/326] Increase timed update frequency and add inline comment --- .../Screens/OnlinePlay/Components/StatusColouredContainer.cs | 3 ++- .../Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs b/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs index a811ee3371..7147803412 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StatusColouredContainer.cs @@ -30,7 +30,8 @@ namespace osu.Game.Screens.OnlinePlay.Components room.PropertyChanged += onRoomPropertyChanged; - Scheduler.AddDelayed(updateRoomStatus, 5000, true); + // Timed update required to track rooms which have hit the end time, see `HasEnded`. + Scheduler.AddDelayed(updateRoomStatus, 1000, true); updateRoomStatus(); } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs index 6da8f3ecbd..092f17a643 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/RoomStatusPill.cs @@ -37,7 +37,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components room.PropertyChanged += onRoomPropertyChanged; - Scheduler.AddDelayed(updateDisplay, 5000, true); + // Timed update required to track rooms which have hit the end time, see `HasEnded`. + Scheduler.AddDelayed(updateDisplay, 1000, true); updateDisplay(); FinishTransforms(true); } From e8c0e27cc0e826d18e60abd3b665c56d6d3c2964 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 11 Dec 2024 18:17:59 +0900 Subject: [PATCH 198/326] Adjust in line with upstream changes --- osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs | 3 +-- .../Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs | 3 +-- .../Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs | 7 +------ 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 7d36cec7ba..0a55472c2d 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -26,7 +26,6 @@ using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Rooms; -using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Lounge.Components; @@ -168,7 +167,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge }) }; - if (Room.Type == MatchType.Playlists && Room.Host?.Id == api.LocalUser.Value.Id && Room.StartDate?.AddMinutes(5) >= DateTimeOffset.Now && Room.Status is not RoomStatusEnded) + if (Room.Type == MatchType.Playlists && Room.Host?.Id == api.LocalUser.Value.Id && Room.StartDate?.AddMinutes(5) >= DateTimeOffset.Now && !Room.HasEnded) { items.Add(new OsuMenuItem("Close playlist", MenuItemType.Destructive, () => { diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs index 6089b4734e..f9b1edcd59 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomFooter.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.Rooms; -using osu.Game.Online.Rooms.RoomStatuses; using osuTK; namespace osu.Game.Screens.OnlinePlay.Playlists @@ -99,7 +98,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists if (room.Host?.Id == api.LocalUser.Value.Id) { - if (deletionGracePeriodRemaining > TimeSpan.Zero && room.Status is not RoomStatusEnded) + if (deletionGracePeriodRemaining > TimeSpan.Zero && !room.HasEnded) { closeButton.FadeIn(); using (BeginDelayedSequence(deletionGracePeriodRemaining.Value.TotalMilliseconds)) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 9573155f5a..9b4630ac0b 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -16,7 +16,6 @@ using osu.Game.Input; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Rooms; -using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Match.Components; @@ -286,11 +285,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists DialogOverlay?.Push(new ClosePlaylistDialog(Room, () => { var request = new ClosePlaylistRequest(Room.RoomID!.Value); - request.Success += () => - { - Room.Status = new RoomStatusEnded(); - Room.EndDate = DateTimeOffset.UtcNow; - }; + request.Success += () => Room.EndDate = DateTimeOffset.UtcNow; API.Queue(request); })); } From 26f15def70a6c2ccf1f66bdac5aad14097524efe Mon Sep 17 00:00:00 2001 From: Nicholas Chin Date: Wed, 11 Dec 2024 23:15:05 +0800 Subject: [PATCH 199/326] Add missing mania tooltip overlay for 4k and 7k --- osu.Game/Localisation/CommonStrings.cs | 12 ++++- .../Profile/Header/Components/MainDetails.cs | 48 ++++++++++++++++++- osu.Game/Users/UserStatistics.cs | 40 ++++++++++++++++ 3 files changed, 97 insertions(+), 3 deletions(-) diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs index 243a100029..88766a608c 100644 --- a/osu.Game/Localisation/CommonStrings.cs +++ b/osu.Game/Localisation/CommonStrings.cs @@ -179,6 +179,16 @@ namespace osu.Game.Localisation /// public static LocalisableString CopyLink => new TranslatableString(getKey(@"copy_link"), @"Copy link"); + /// + /// "4K" + /// + public static LocalisableString FourKey => new TranslatableString(getKey(@"four_key"), @"4K"); + + /// + /// "7K" + /// + public static LocalisableString SevenKey => new TranslatableString(getKey(@"seven_key"), @"7K"); + private static string getKey(string key) => $@"{prefix}:{key}"; } -} \ No newline at end of file +} diff --git a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs index 3d97082230..84919d18bb 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MainDetails.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.Extensions.LocalisationExtensions; @@ -164,13 +165,56 @@ namespace osu.Game.Overlays.Profile.Header.Components detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; var rankHighest = user?.RankHighest; + var variants = user?.Statistics.Variants; - detailGlobalRank.ContentTooltipText = rankHighest != null - ? UsersStrings.ShowRankHighest(rankHighest.Rank.ToLocalisableString("\\##,##0"), rankHighest.UpdatedAt.ToLocalisableString(@"d MMM yyyy")) + #region Global rank tooltip + var tooltipParts = new List(); + + if (variants?.Count > 0) + { + foreach (var variant in variants) + { + if (variant.GlobalRank != null) + { + tooltipParts.Add($"{variant.VariantDisplay}: {variant.GlobalRank.ToLocalisableString("\\##,##0")}"); + } + } + } + + if (rankHighest != null) + { + tooltipParts.Add(UsersStrings.ShowRankHighest( + rankHighest.Rank.ToLocalisableString("\\##,##0"), + rankHighest.UpdatedAt.ToLocalisableString(@"d MMM yyyy")) + ); + } + + detailGlobalRank.ContentTooltipText = tooltipParts.Any() + ? string.Join("\n", tooltipParts) : string.Empty; + #endregion detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; + #region Country rank tooltip + var countryTooltipParts = new List(); + + if (variants?.Count > 0) + { + foreach (var variant in variants) + { + if (variant.CountryRank != null) + { + countryTooltipParts.Add($"{variant.VariantDisplay}: {variant.CountryRank.Value.ToLocalisableString("\\##,##0")}"); + } + } + } + + detailCountryRank.ContentTooltipText = countryTooltipParts.Any() + ? string.Join("\n", countryTooltipParts) + : string.Empty; + #endregion + rankGraph.Statistics.Value = user?.Statistics; } diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 918a1b6968..d18675198f 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -4,8 +4,12 @@ #nullable disable using System; +using System.Collections.Generic; +using System.Runtime.Serialization; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; using osu.Framework.Localisation; +using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; using osu.Game.Scoring; using osu.Game.Utils; @@ -74,6 +78,9 @@ namespace osu.Game.Users [JsonProperty(@"grade_counts")] public Grades GradesCount; + [JsonProperty(@"variants")] + public List Variants = null!; + public struct Grades { [JsonProperty(@"ssh")] @@ -118,5 +125,38 @@ namespace osu.Game.Users } } } + public enum GameVariant + { + [EnumMember(Value = "4k")] + FourKey, + [EnumMember(Value = "7k")] + SevenKey + } + + public class Variant + { + [JsonProperty("country_rank")] + public int? CountryRank; + + [JsonProperty("global_rank")] + public int? GlobalRank; + + [JsonProperty("mode")] + public string Mode; + + [JsonProperty("pp")] + public decimal PP; + + [JsonProperty("variant")] + [JsonConverter(typeof(StringEnumConverter))] + public GameVariant? VariantType; + + public LocalisableString VariantDisplay => VariantType switch + { + GameVariant.FourKey => CommonStrings.FourKey, + GameVariant.SevenKey => CommonStrings.SevenKey, + _ => string.Empty + }; + } } } From 862b41c38e90bb39b6a106da8ae0b42954fa42a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 12 Dec 2024 12:53:05 +0900 Subject: [PATCH 200/326] Move `BeatmapInfoWedgeV2` to correct namespace --- .../Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs | 1 + .../{Select => SelectV2}/BeatmapInfoWedgeV2.cs | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) rename osu.Game/Screens/{Select => SelectV2}/BeatmapInfoWedgeV2.cs (99%) diff --git a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs index fbbab3a604..5b717887e2 100644 --- a/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs +++ b/osu.Game.Tests/Visual/SongSelectV2/TestSceneBeatmapInfoWedge.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Screens.Select; +using osu.Game.Screens.SelectV2; namespace osu.Game.Tests.Visual.SongSelectV2 { diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/SelectV2/BeatmapInfoWedgeV2.cs similarity index 99% rename from osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs rename to osu.Game/Screens/SelectV2/BeatmapInfoWedgeV2.cs index 3c76ae1f08..b294896c77 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs +++ b/osu.Game/Screens/SelectV2/BeatmapInfoWedgeV2.cs @@ -3,23 +3,24 @@ using System; using System.Threading; -using osuTK; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; 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.Localisation; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; +using osu.Game.Screens.Select; +using osuTK; -namespace osu.Game.Screens.Select +namespace osu.Game.Screens.SelectV2 { public partial class BeatmapInfoWedgeV2 : VisibilityContainer { From 61ee830588f10275253d8f4daac9649bce381afc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 12 Dec 2024 15:16:11 +0900 Subject: [PATCH 201/326] Adjust copy --- .../OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index 303ba60875..9904d503f7 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -61,7 +61,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer yield return showInProgress = new OsuCheckbox { - LabelText = "Show playing rooms", + LabelText = "Show in-progress rooms", RelativeSizeAxes = Axes.None, Width = 200, Padding = new MarginPadding { Vertical = 5, }, From 032870888920d7d86502cfc6b35e58491e005612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 12 Dec 2024 15:16:24 +0900 Subject: [PATCH 202/326] Store value of toggle to setting --- osu.Game/Configuration/OsuConfigManager.cs | 4 +++- .../OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 4f62db8cf7..df0a823648 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -202,6 +202,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.HideCountryFlags, false); SetDefault(OsuSetting.MultiplayerRoomFilter, RoomPermissionsFilter.All); + SetDefault(OsuSetting.MultiplayerShowInProgressFilter, true); SetDefault(OsuSetting.LastProcessedMetadataId, -1); @@ -447,6 +448,7 @@ namespace osu.Game.Configuration EditorRotationOrigin, EditorTimelineShowBreaks, EditorAdjustExistingObjectsOnTimingChanges, - AlwaysRequireHoldingForPause + AlwaysRequireHoldingForPause, + MultiplayerShowInProgressFilter, } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index 9904d503f7..7f7f94504f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -65,7 +65,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer RelativeSizeAxes = Axes.None, Width = 200, Padding = new MarginPadding { Vertical = 5, }, - Current = { Value = true } + Current = Config.GetBindable(OsuSetting.MultiplayerShowInProgressFilter), }; showInProgress.Current.BindValueChanged(_ => UpdateFilter()); From a22f3416d6f7c0a03f5fbfddb0ec4e47cff0e723 Mon Sep 17 00:00:00 2001 From: Nicholas Chin Date: Thu, 12 Dec 2024 22:39:21 +0800 Subject: [PATCH 203/326] Replace switch expression with LocalisableDescription attribute for variant display Use existing localisation strings from BeatmapsStrings instead of CommonStrings for consistent localisation handling --- osu.Game/Localisation/CommonStrings.cs | 12 +----------- osu.Game/Users/UserStatistics.cs | 12 +++++------- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/osu.Game/Localisation/CommonStrings.cs b/osu.Game/Localisation/CommonStrings.cs index 88766a608c..243a100029 100644 --- a/osu.Game/Localisation/CommonStrings.cs +++ b/osu.Game/Localisation/CommonStrings.cs @@ -179,16 +179,6 @@ namespace osu.Game.Localisation /// public static LocalisableString CopyLink => new TranslatableString(getKey(@"copy_link"), @"Copy link"); - /// - /// "4K" - /// - public static LocalisableString FourKey => new TranslatableString(getKey(@"four_key"), @"4K"); - - /// - /// "7K" - /// - public static LocalisableString SevenKey => new TranslatableString(getKey(@"seven_key"), @"7K"); - private static string getKey(string key) => $@"{prefix}:{key}"; } -} +} \ No newline at end of file diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index d18675198f..b485485d48 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -8,9 +8,10 @@ using System.Collections.Generic; using System.Runtime.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using osu.Framework.Extensions; using osu.Framework.Localisation; -using osu.Game.Localisation; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Resources.Localisation.Web; using osu.Game.Scoring; using osu.Game.Utils; @@ -128,8 +129,10 @@ namespace osu.Game.Users public enum GameVariant { [EnumMember(Value = "4k")] + [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.VariantMania4k))] FourKey, [EnumMember(Value = "7k")] + [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.VariantMania7k))] SevenKey } @@ -151,12 +154,7 @@ namespace osu.Game.Users [JsonConverter(typeof(StringEnumConverter))] public GameVariant? VariantType; - public LocalisableString VariantDisplay => VariantType switch - { - GameVariant.FourKey => CommonStrings.FourKey, - GameVariant.SevenKey => CommonStrings.SevenKey, - _ => string.Empty - }; + public LocalisableString VariantDisplay => VariantType?.GetLocalisableDescription() ?? string.Empty; } } } From 3035e8435d3f7c45ffa58d31b425c286d6b5b4fc Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 10 Dec 2024 15:02:41 -0800 Subject: [PATCH 204/326] Apply `NRT` to incoming changed files --- osu.Game/Beatmaps/DifficultyRecommender.cs | 10 +++------- .../BeatmapListing/BeatmapListingSearchControl.cs | 10 ++++------ .../BeatmapListing/BeatmapSearchGeneralFilterRow.cs | 12 +++++------- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index d132b86052..e50f877a9b 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.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.Bindables; using osu.Framework.Extensions.ObjectExtensions; @@ -26,7 +23,7 @@ namespace osu.Game.Beatmaps private readonly LocalUserStatisticsProvider statisticsProvider; [Resolved] - private Bindable gameRuleset { get; set; } + private Bindable gameRuleset { get; set; } = null!; [Resolved] private RulesetStore rulesets { get; set; } = null!; @@ -90,15 +87,14 @@ namespace osu.Game.Beatmaps /// /// A collection of beatmaps to select a difficulty from. /// The recommended difficulty, or null if a recommendation could not be provided. - [CanBeNull] - public BeatmapInfo GetRecommendedBeatmap(IEnumerable beatmaps) + public BeatmapInfo? GetRecommendedBeatmap(IEnumerable beatmaps) { foreach (string r in orderedRulesets) { if (!recommendedDifficultyMapping.TryGetValue(r, out double recommendation)) continue; - BeatmapInfo beatmapInfo = beatmaps.Where(b => b.Ruleset.ShortName.Equals(r, StringComparison.Ordinal)).MinBy(b => + BeatmapInfo? beatmapInfo = beatmaps.Where(b => b.Ruleset.ShortName.Equals(r, StringComparison.Ordinal)).MinBy(b => { double difference = b.StarRating - recommendation; return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index bab64165cb..77a0e64fd1 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.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; @@ -29,7 +27,7 @@ namespace osu.Game.Overlays.BeatmapListing /// /// Any time the text box receives key events (even while masked). /// - public Action TypingStarted; + public Action? TypingStarted; public Bindable Query => textBox.Current; @@ -51,7 +49,7 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable ExplicitContent => explicitContentFilter.Current; - public APIBeatmapSet BeatmapSet + public APIBeatmapSet? BeatmapSet { set { @@ -151,7 +149,7 @@ namespace osu.Game.Overlays.BeatmapListing categoryFilter.Current.Value = SearchCategory.Leaderboard; } - private IBindable allowExplicitContent; + private IBindable allowExplicitContent = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuConfigManager config) @@ -172,7 +170,7 @@ namespace osu.Game.Overlays.BeatmapListing /// /// Any time the text box receives key events (even while masked). /// - public Action TextChanged; + public Action? TextChanged; protected override Color4 SelectionColour => Color4.Gray; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs index 2d56c60de6..044910980d 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.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; @@ -40,7 +38,7 @@ namespace osu.Game.Overlays.BeatmapListing private partial class FeaturedArtistsTabItem : MultipleSelectionFilterTabItem { - private Bindable disclaimerShown; + private Bindable disclaimerShown = null!; public FeaturedArtistsTabItem() : base(SearchGeneral.FeaturedArtists) @@ -48,13 +46,13 @@ namespace osu.Game.Overlays.BeatmapListing } [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; [Resolved] - private SessionStatics sessionStatics { get; set; } + private SessionStatics sessionStatics { get; set; } = null!; - [Resolved(canBeNull: true)] - private IDialogOverlay dialogOverlay { get; set; } + [Resolved] + private IDialogOverlay? dialogOverlay { get; set; } protected override void LoadComplete() { From e95dc2b308fa186d75dcc1f6758d6b9d2d24fa18 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 10 Dec 2024 15:04:06 -0800 Subject: [PATCH 205/326] Add `FormatStarRating()` method util --- osu.Game.Tournament/Components/SongBar.cs | 3 ++- osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs | 4 ++-- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 3 ++- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 2 +- osu.Game/Utils/FormatUtils.cs | 6 ++++++ 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index ae59e92e33..cff86cf0a1 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics; using osu.Game.Models; using osu.Game.Rulesets; using osu.Game.Screens.Menu; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -207,7 +208,7 @@ namespace osu.Game.Tournament.Components Children = new Drawable[] { new DiffPiece(stats), - new DiffPiece(("Star Rating", $"{beatmap.StarRating:0.00}{srExtra}")) + new DiffPiece(("Star Rating", $"{beatmap.StarRating.FormatStarRating()}{srExtra}")) } }, new FillFlowContainer diff --git a/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs b/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs index 55ef6f705e..4119ffb636 100644 --- a/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs +++ b/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs @@ -5,7 +5,6 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -14,6 +13,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; +using osu.Game.Utils; using osuTK; using osuTK.Graphics; @@ -156,7 +156,7 @@ namespace osu.Game.Beatmaps.Drawables displayedStars.BindValueChanged(s => { - starsText.Text = s.NewValue < 0 ? "-" : s.NewValue.ToLocalisableString("0.00"); + starsText.Text = s.NewValue < 0 ? "-" : s.NewValue.FormatStarRating(); background.Colour = colours.ForStarDifficulty(s.NewValue); diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 5f021803b0..a7838651a9 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -21,6 +21,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; +using osu.Game.Utils; using osuTK; namespace osu.Game.Overlays.BeatmapSet @@ -185,7 +186,7 @@ namespace osu.Game.Overlays.BeatmapSet OnHovered = beatmap => { showBeatmap(beatmap); - starRating.Text = beatmap.StarRating.ToLocalisableString(@"0.00"); + starRating.Text = beatmap.StarRating.FormatStarRating(); starRatingContainer.FadeIn(100); }, OnClicked = beatmap => { Beatmap.Value = beatmap; }, diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index f1c27434fa..d9f7eedfb5 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -226,7 +226,7 @@ namespace osu.Game.Skinning.Components return computeDifficulty().ApproachRate.ToLocalisableString(@"0.##"); case BeatmapAttribute.StarRating: - return (starDifficulty?.Stars ?? 0).ToLocalisableString(@"F2"); + return (starDifficulty?.Stars ?? 0).FormatStarRating(); case BeatmapAttribute.MaxPP: return Math.Round(starDifficulty?.PerformanceAttributes?.Total ?? 0, MidpointRounding.AwayFromZero).ToLocalisableString(); diff --git a/osu.Game/Utils/FormatUtils.cs b/osu.Game/Utils/FormatUtils.cs index cccad3711c..e93a494b65 100644 --- a/osu.Game/Utils/FormatUtils.cs +++ b/osu.Game/Utils/FormatUtils.cs @@ -32,6 +32,12 @@ namespace osu.Game.Utils /// The rank/position to be formatted. public static string FormatRank(this int rank) => rank.ToMetric(decimals: rank < 100_000 ? 1 : 0); + /// + /// Formats the supplied star rating in a consistent, simplified way. + /// + /// The star rating to be formatted. + public static LocalisableString FormatStarRating(this double starRating) => starRating.ToLocalisableString("0.00"); + /// /// Finds the number of digits after the decimal. /// From 92e07b4f99c8610848ec2509a9f20c846d84e3b7 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 10 Dec 2024 15:09:11 -0800 Subject: [PATCH 206/326] Add recommended difficulty numerical value near filter in beatmap listing --- osu.Game/Beatmaps/DifficultyRecommender.cs | 6 ++ .../BeatmapListingSearchControl.cs | 9 ++- .../BeatmapSearchGeneralFilterRow.cs | 68 +++++++++++++++++-- 3 files changed, 78 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index e50f877a9b..bd864422d1 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -20,6 +20,8 @@ namespace osu.Game.Beatmaps ///
public partial class DifficultyRecommender : Component { + public event Action? StarRatingUpdated; + private readonly LocalUserStatisticsProvider statisticsProvider; [Resolved] @@ -77,8 +79,12 @@ namespace osu.Game.Beatmaps { // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 recommendedDifficultyMapping[ruleset.ShortName] = Math.Pow((double)(statistics.PP ?? 0), 0.4) * 0.195; + + StarRatingUpdated?.Invoke(); } + public double GetRecommendedStarRatingFor(RulesetInfo ruleset) => recommendedDifficultyMapping[ruleset.ShortName]; + /// /// Find the recommended difficulty from a selection of available difficulties for the current local user. /// diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 77a0e64fd1..72d7e0c752 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -65,7 +65,7 @@ namespace osu.Game.Overlays.BeatmapListing } private readonly BeatmapSearchTextBox textBox; - private readonly BeatmapSearchMultipleSelectionFilterRow generalFilter; + private readonly BeatmapSearchGeneralFilterRow generalFilter; private readonly BeatmapSearchRulesetFilterRow modeFilter; private readonly BeatmapSearchFilterRow categoryFilter; private readonly BeatmapSearchFilterRow genreFilter; @@ -163,6 +163,13 @@ namespace osu.Game.Overlays.BeatmapListing }, true); } + protected override void LoadComplete() + { + base.LoadComplete(); + + generalFilter.Ruleset.BindTo(Ruleset); + } + public void TakeFocus() => textBox.TakeFocus(); private partial class BeatmapSearchTextBox : BasicSearchTextBox diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs index 044910980d..42d788dad7 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs @@ -4,13 +4,20 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Localisation; +using osu.Game.Online.API; using osu.Game.Overlays.Dialog; using osu.Game.Resources.Localisation.Web; +using osu.Game.Rulesets; +using osu.Game.Utils; using osuTK.Graphics; using CommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings; @@ -18,21 +25,74 @@ namespace osu.Game.Overlays.BeatmapListing { public partial class BeatmapSearchGeneralFilterRow : BeatmapSearchMultipleSelectionFilterRow { + public readonly IBindable Ruleset = new Bindable(); + public BeatmapSearchGeneralFilterRow() : base(BeatmapsStrings.ListingSearchFiltersGeneral) { } - protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new GeneralFilter(); + protected override MultipleSelectionFilter CreateMultipleSelectionFilter() => new GeneralFilter + { + Ruleset = { BindTarget = Ruleset } + }; private partial class GeneralFilter : MultipleSelectionFilter { + public readonly IBindable Ruleset = new Bindable(); + protected override MultipleSelectionFilterTabItem CreateTabItem(SearchGeneral value) { - if (value == SearchGeneral.FeaturedArtists) - return new FeaturedArtistsTabItem(); + switch (value) + { + case SearchGeneral.Recommended: + return new RecommendedDifficultyTabItem + { + Ruleset = { BindTarget = Ruleset } + }; - return new MultipleSelectionFilterTabItem(value); + case SearchGeneral.FeaturedArtists: + return new FeaturedArtistsTabItem(); + + default: + return new MultipleSelectionFilterTabItem(value); + } + } + } + + private partial class RecommendedDifficultyTabItem : MultipleSelectionFilterTabItem + { + public readonly IBindable Ruleset = new Bindable(); + + [Resolved] + private DifficultyRecommender? recommender { get; set; } + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + [Resolved] + private RulesetStore rulesets { get; set; } = null!; + + public RecommendedDifficultyTabItem() + : base(SearchGeneral.Recommended) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (recommender != null) recommender.StarRatingUpdated += updateText; + + Ruleset.BindValueChanged(_ => updateText(), true); + } + + private void updateText() + { + // fallback to profile default game mode if beatmap listing mode filter is set to Any + // TODO: find a way to update `PlayMode` when the profile default game mode has changed + var ruleset = Ruleset.Value.IsLegacyRuleset() ? Ruleset.Value : rulesets.GetRuleset(api.LocalUser.Value.PlayMode)!; + Text.Text = LocalisableString.Interpolate($"{Value.GetLocalisableDescription()} ({recommender?.GetRecommendedStarRatingFor(ruleset).FormatStarRating()})"); } } From f7364de01af250ec7e292702086544b6b4fe8f36 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 10 Dec 2024 18:18:34 -0800 Subject: [PATCH 207/326] Add test and null protections --- .../TestSceneBeatmapRecommendations.cs | 44 +++++++++++++++++++ osu.Game/Beatmaps/DifficultyRecommender.cs | 3 +- .../BeatmapSearchGeneralFilterRow.cs | 12 ++++- 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index bd5c43d242..4c8c1d7ad2 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -13,9 +13,12 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Extensions; +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.BeatmapListing; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; @@ -23,6 +26,8 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Taiko; using osu.Game.Tests.Resources; using osu.Game.Users; +using osu.Game.Utils; +using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelect { @@ -170,6 +175,45 @@ namespace osu.Game.Tests.Visual.SongSelect presentAndConfirm(() => maniaSet, 5); } + [Test] + public void TestBeatmapListingFilter() + { + AddStep("set playmode to taiko", () => ((DummyAPIAccess)API).LocalUser.Value.PlayMode = "taiko"); + + AddStep("open beatmap listing", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.PressKey(Key.B); + InputManager.ReleaseKey(Key.B); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + AddUntilStep("wait for load", () => Game.ChildrenOfType().SingleOrDefault()?.IsLoaded, () => Is.True); + + checkRecommendedDifficulty(3); + + AddStep("change mode filter to osu!", () => Game.ChildrenOfType().Single().ChildrenOfType>().ElementAt(1).TriggerClick()); + + checkRecommendedDifficulty(2); + + AddStep("change mode filter to osu!taiko", () => Game.ChildrenOfType().Single().ChildrenOfType>().ElementAt(2).TriggerClick()); + + checkRecommendedDifficulty(3); + + AddStep("change mode filter to osu!catch", () => Game.ChildrenOfType().Single().ChildrenOfType>().ElementAt(3).TriggerClick()); + + checkRecommendedDifficulty(4); + + AddStep("change mode filter to osu!mania", () => Game.ChildrenOfType().Single().ChildrenOfType>().ElementAt(4).TriggerClick()); + + checkRecommendedDifficulty(5); + + void checkRecommendedDifficulty(double starRating) + => AddAssert($"recommended difficulty is {starRating}", + () => Game.ChildrenOfType().Single().ChildrenOfType().ElementAt(1).Text.ToString(), + () => Is.EqualTo($"Recommended difficulty ({starRating.FormatStarRating()})")); + } + private BeatmapSetInfo importBeatmapSet(IEnumerable difficultyRulesets) { var rulesets = difficultyRulesets.ToArray(); diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index bd864422d1..a5c7371b4d 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -83,7 +83,8 @@ namespace osu.Game.Beatmaps StarRatingUpdated?.Invoke(); } - public double GetRecommendedStarRatingFor(RulesetInfo ruleset) => recommendedDifficultyMapping[ruleset.ShortName]; + public double? GetRecommendedStarRatingFor(RulesetInfo ruleset) + => recommendedDifficultyMapping.TryGetValue(ruleset.ShortName, out double starRating) ? starRating : null; /// /// Find the recommended difficulty from a selection of available difficulties for the current local user. diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs index 42d788dad7..66a0a16549 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs @@ -91,8 +91,16 @@ namespace osu.Game.Overlays.BeatmapListing { // fallback to profile default game mode if beatmap listing mode filter is set to Any // TODO: find a way to update `PlayMode` when the profile default game mode has changed - var ruleset = Ruleset.Value.IsLegacyRuleset() ? Ruleset.Value : rulesets.GetRuleset(api.LocalUser.Value.PlayMode)!; - Text.Text = LocalisableString.Interpolate($"{Value.GetLocalisableDescription()} ({recommender?.GetRecommendedStarRatingFor(ruleset).FormatStarRating()})"); + RulesetInfo? ruleset = Ruleset.Value.IsLegacyRuleset() ? Ruleset.Value : rulesets.GetRuleset(api.LocalUser.Value.PlayMode); + + if (ruleset == null) return; + + double? starRating = recommender?.GetRecommendedStarRatingFor(ruleset); + + if (starRating != null) + Text.Text = LocalisableString.Interpolate($"{Value.GetLocalisableDescription()} ({starRating.Value.FormatStarRating()})"); + else + Text.Text = Value.GetLocalisableDescription(); } } From 38b3d5fc00e7d38d35a88cf52e5d611ff39abfdb Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 12 Dec 2024 16:17:57 -0800 Subject: [PATCH 208/326] Update recommended difficulty for osu!taiko --- osu.Game/Beatmaps/DifficultyRecommender.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index d132b86052..31c9fcafe6 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -78,8 +78,11 @@ namespace osu.Game.Beatmaps private void updateMapping(RulesetInfo ruleset, UserStatistics statistics) { - // algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505 - recommendedDifficultyMapping[ruleset.ShortName] = Math.Pow((double)(statistics.PP ?? 0), 0.4) * 0.195; + // algorithm taken from https://github.com/ppy/osu-web/blob/027026fccc91525e39cee5d2f369f1b343eb1bf1/app/Models/UserStatistics/Model.php#L93-L94 + recommendedDifficultyMapping[ruleset.ShortName] = + ruleset.ShortName == @"taiko" + ? Math.Pow((double)(statistics.PP ?? 0), 0.35) * 0.27 + : Math.Pow((double)(statistics.PP ?? 0), 0.4) * 0.195; } /// From 313de33986467f12b8c7e0847f757be2718f1bdd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2024 15:42:30 +0900 Subject: [PATCH 209/326] Adjust padding to avoid wrapping on checkbox text --- .../OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs index 7f7f94504f..dd61caa3db 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerLoungeSubScreen.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { LabelText = "Show in-progress rooms", RelativeSizeAxes = Axes.None, - Width = 200, + Width = 220, Padding = new MarginPadding { Vertical = 5, }, Current = Config.GetBindable(OsuSetting.MultiplayerShowInProgressFilter), }; From 12e5999700bf1a6082b3fbc64a372bf2164e158a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 13 Dec 2024 15:53:27 +0900 Subject: [PATCH 210/326] Add another failing test --- .../ManiaBeatmapConversionTest.cs | 1 + .../Beatmaps/4869637-expected-conversion.json | 1 + .../Resources/Testing/Beatmaps/4869637.osu | 1442 +++++++++++++++++ 3 files changed, 1444 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/4869637-expected-conversion.json create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/4869637.osu diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs index b167ea3ab1..92a01f8627 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapConversionTest.cs @@ -26,6 +26,7 @@ namespace osu.Game.Rulesets.Mania.Tests [TestCase("20544")] [TestCase("100374")] [TestCase("1450162")] + [TestCase("4869637")] 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/4869637-expected-conversion.json b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/4869637-expected-conversion.json new file mode 100644 index 0000000000..05429cae7e --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/4869637-expected-conversion.json @@ -0,0 +1 @@ +{"Mappings":[{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":355.0,"Objects":[{"StartTime":355.0,"EndTime":355.0,"Column":1}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":712.0,"Objects":[{"StartTime":712.0,"EndTime":712.0,"Column":1}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":1307.0,"Objects":[{"StartTime":1307.0,"EndTime":1307.0,"Column":2}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":1664.0,"Objects":[{"StartTime":1664.0,"EndTime":1664.0,"Column":2}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":2259.0,"Objects":[{"StartTime":2259.0,"EndTime":2259.0,"Column":3}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":2616.0,"Objects":[{"StartTime":2616.0,"EndTime":2616.0,"Column":2}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":3212.0,"Objects":[{"StartTime":3212.0,"EndTime":3212.0,"Column":2}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":3569.0,"Objects":[{"StartTime":3569.0,"EndTime":3569.0,"Column":3}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":4164.0,"Objects":[{"StartTime":4164.0,"EndTime":4164.0,"Column":1}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":4521.0,"Objects":[{"StartTime":4521.0,"EndTime":4521.0,"Column":2}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":5117.0,"Objects":[{"StartTime":5117.0,"EndTime":5117.0,"Column":2}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":5474.0,"Objects":[{"StartTime":5474.0,"EndTime":5474.0,"Column":1}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":6069.0,"Objects":[{"StartTime":6069.0,"EndTime":6069.0,"Column":3}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":6426.0,"Objects":[{"StartTime":6426.0,"EndTime":6426.0,"Column":2}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":7022.0,"Objects":[{"StartTime":7022.0,"EndTime":7022.0,"Column":2}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":7378.0,"Objects":[{"StartTime":7378.0,"EndTime":7378.0,"Column":3}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":7974.0,"Objects":[{"StartTime":7974.0,"EndTime":7974.0,"Column":1}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":7974.0,"Objects":[{"StartTime":7974.0,"EndTime":7974.0,"Column":0}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":8450.0,"Objects":[{"StartTime":8450.0,"EndTime":8450.0,"Column":0}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":8450.0,"Objects":[{"StartTime":8450.0,"EndTime":8450.0,"Column":2}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":8926.0,"Objects":[{"StartTime":8926.0,"EndTime":8926.0,"Column":0}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":8927.0,"Objects":[{"StartTime":8927.0,"EndTime":8927.0,"Column":2}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":9402.0,"Objects":[{"StartTime":9402.0,"EndTime":9402.0,"Column":1}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":9402.0,"Objects":[{"StartTime":9402.0,"EndTime":9402.0,"Column":0}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":9878.0,"Objects":[{"StartTime":9878.0,"EndTime":9878.0,"Column":0}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":9879.0,"Objects":[{"StartTime":9879.0,"EndTime":9879.0,"Column":3}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":10354.0,"Objects":[{"StartTime":10354.0,"EndTime":10354.0,"Column":2}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":10354.0,"Objects":[{"StartTime":10354.0,"EndTime":10354.0,"Column":0}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":10831.0,"Objects":[{"StartTime":10831.0,"EndTime":10831.0,"Column":0}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":10832.0,"Objects":[{"StartTime":10832.0,"EndTime":10832.0,"Column":2}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":11307.0,"Objects":[{"StartTime":11307.0,"EndTime":11307.0,"Column":3}]},{"RandomW":273326509,"RandomX":386,"RandomY":842502087,"RandomZ":3579807591,"StartTime":11307.0,"Objects":[{"StartTime":11307.0,"EndTime":11307.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":11783.0,"Objects":[{"StartTime":11783.0,"EndTime":15116.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":11783.0,"Objects":[{"StartTime":11783.0,"EndTime":11783.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":12259.0,"Objects":[{"StartTime":12259.0,"EndTime":12259.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":12735.0,"Objects":[{"StartTime":12735.0,"EndTime":12735.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":13212.0,"Objects":[{"StartTime":13212.0,"EndTime":13212.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":13688.0,"Objects":[{"StartTime":13688.0,"EndTime":13688.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":14164.0,"Objects":[{"StartTime":14164.0,"EndTime":14164.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":14640.0,"Objects":[{"StartTime":14640.0,"EndTime":14640.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":15116.0,"Objects":[{"StartTime":15116.0,"EndTime":15235.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":15593.0,"Objects":[{"StartTime":15593.0,"EndTime":15593.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":15831.0,"Objects":[{"StartTime":15831.0,"EndTime":15831.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":16069.0,"Objects":[{"StartTime":16069.0,"EndTime":16069.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":16307.0,"Objects":[{"StartTime":16307.0,"EndTime":16307.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":16545.0,"Objects":[{"StartTime":16545.0,"EndTime":16783.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":17021.0,"Objects":[{"StartTime":17021.0,"EndTime":17259.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":17259.0,"Objects":[{"StartTime":17259.0,"EndTime":17259.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":17497.0,"Objects":[{"StartTime":17497.0,"EndTime":17735.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":17974.0,"Objects":[{"StartTime":17974.0,"EndTime":18212.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":18212.0,"Objects":[{"StartTime":18212.0,"EndTime":18212.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":18450.0,"Objects":[{"StartTime":18450.0,"EndTime":18688.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":18926.0,"Objects":[{"StartTime":18926.0,"EndTime":19164.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":19402.0,"Objects":[{"StartTime":19402.0,"EndTime":19402.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":19640.0,"Objects":[{"StartTime":19640.0,"EndTime":19640.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":19878.0,"Objects":[{"StartTime":19878.0,"EndTime":19878.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":20116.0,"Objects":[{"StartTime":20116.0,"EndTime":20116.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":20354.0,"Objects":[{"StartTime":20354.0,"EndTime":20592.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":20831.0,"Objects":[{"StartTime":20831.0,"EndTime":21069.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":21069.0,"Objects":[{"StartTime":21069.0,"EndTime":21069.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":21307.0,"Objects":[{"StartTime":21307.0,"EndTime":21545.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":21783.0,"Objects":[{"StartTime":21783.0,"EndTime":22021.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":22021.0,"Objects":[{"StartTime":22021.0,"EndTime":22021.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":22259.0,"Objects":[{"StartTime":22259.0,"EndTime":22497.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":22735.0,"Objects":[{"StartTime":22735.0,"EndTime":22973.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":23212.0,"Objects":[{"StartTime":23212.0,"EndTime":23212.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":23450.0,"Objects":[{"StartTime":23450.0,"EndTime":23450.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":23688.0,"Objects":[{"StartTime":23688.0,"EndTime":23688.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":23926.0,"Objects":[{"StartTime":23926.0,"EndTime":23926.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":24164.0,"Objects":[{"StartTime":24164.0,"EndTime":24402.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":24641.0,"Objects":[{"StartTime":24641.0,"EndTime":24879.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":24878.0,"Objects":[{"StartTime":24878.0,"EndTime":24878.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":25117.0,"Objects":[{"StartTime":25117.0,"EndTime":25355.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":25593.0,"Objects":[{"StartTime":25593.0,"EndTime":25831.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":25831.0,"Objects":[{"StartTime":25831.0,"EndTime":25831.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":26069.0,"Objects":[{"StartTime":26069.0,"EndTime":26307.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":26545.0,"Objects":[{"StartTime":26545.0,"EndTime":26783.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":27021.0,"Objects":[{"StartTime":27021.0,"EndTime":27021.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":27259.0,"Objects":[{"StartTime":27259.0,"EndTime":27259.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":27497.0,"Objects":[{"StartTime":27497.0,"EndTime":27497.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":27735.0,"Objects":[{"StartTime":27735.0,"EndTime":27735.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":27974.0,"Objects":[{"StartTime":27974.0,"EndTime":28212.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":28450.0,"Objects":[{"StartTime":28450.0,"EndTime":28688.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":28688.0,"Objects":[{"StartTime":28688.0,"EndTime":28688.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":28926.0,"Objects":[{"StartTime":28926.0,"EndTime":29164.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":29402.0,"Objects":[{"StartTime":29402.0,"EndTime":29640.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":29640.0,"Objects":[{"StartTime":29640.0,"EndTime":29640.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":29878.0,"Objects":[{"StartTime":29878.0,"EndTime":30116.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":30354.0,"Objects":[{"StartTime":30354.0,"EndTime":30592.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":30831.0,"Objects":[{"StartTime":30831.0,"EndTime":30831.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":30831.0,"Objects":[{"StartTime":30831.0,"EndTime":30831.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":31069.0,"Objects":[{"StartTime":31069.0,"EndTime":31069.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":31307.0,"Objects":[{"StartTime":31307.0,"EndTime":31307.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":31307.0,"Objects":[{"StartTime":31307.0,"EndTime":31307.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":31545.0,"Objects":[{"StartTime":31545.0,"EndTime":31545.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":31783.0,"Objects":[{"StartTime":31783.0,"EndTime":31783.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":31783.0,"Objects":[{"StartTime":31783.0,"EndTime":32021.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":32021.0,"Objects":[{"StartTime":32021.0,"EndTime":32021.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":32259.0,"Objects":[{"StartTime":32259.0,"EndTime":32497.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":32259.0,"Objects":[{"StartTime":32259.0,"EndTime":32259.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":32735.0,"Objects":[{"StartTime":32735.0,"EndTime":32735.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":32735.0,"Objects":[{"StartTime":32735.0,"EndTime":32973.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":32974.0,"Objects":[{"StartTime":32974.0,"EndTime":32974.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":33212.0,"Objects":[{"StartTime":33212.0,"EndTime":33450.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":33212.0,"Objects":[{"StartTime":33212.0,"EndTime":33212.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":33688.0,"Objects":[{"StartTime":33688.0,"EndTime":33688.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":33688.0,"Objects":[{"StartTime":33688.0,"EndTime":33926.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":34164.0,"Objects":[{"StartTime":34164.0,"EndTime":34402.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":34164.0,"Objects":[{"StartTime":34164.0,"EndTime":34164.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":34640.0,"Objects":[{"StartTime":34640.0,"EndTime":34640.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":34640.0,"Objects":[{"StartTime":34640.0,"EndTime":34640.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":34878.0,"Objects":[{"StartTime":34878.0,"EndTime":34878.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":35116.0,"Objects":[{"StartTime":35116.0,"EndTime":35116.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":35116.0,"Objects":[{"StartTime":35116.0,"EndTime":35116.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":35354.0,"Objects":[{"StartTime":35354.0,"EndTime":35354.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":35592.0,"Objects":[{"StartTime":35592.0,"EndTime":35592.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":35593.0,"Objects":[{"StartTime":35593.0,"EndTime":35831.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":35831.0,"Objects":[{"StartTime":35831.0,"EndTime":35831.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":36068.0,"Objects":[{"StartTime":36068.0,"EndTime":36068.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":36068.0,"Objects":[{"StartTime":36068.0,"EndTime":36306.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":36544.0,"Objects":[{"StartTime":36544.0,"EndTime":36544.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":36545.0,"Objects":[{"StartTime":36545.0,"EndTime":36783.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":36783.0,"Objects":[{"StartTime":36783.0,"EndTime":36783.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":37021.0,"Objects":[{"StartTime":37021.0,"EndTime":37259.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":37021.0,"Objects":[{"StartTime":37021.0,"EndTime":37021.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":37497.0,"Objects":[{"StartTime":37497.0,"EndTime":37497.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":37497.0,"Objects":[{"StartTime":37497.0,"EndTime":37735.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":37854.0,"Objects":[{"StartTime":37854.0,"EndTime":37854.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":37973.0,"Objects":[{"StartTime":37973.0,"EndTime":38211.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":38212.0,"Objects":[{"StartTime":38212.0,"EndTime":38212.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":38450.0,"Objects":[{"StartTime":38450.0,"EndTime":38450.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":38450.0,"Objects":[{"StartTime":38450.0,"EndTime":38450.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":38688.0,"Objects":[{"StartTime":38688.0,"EndTime":38688.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":38926.0,"Objects":[{"StartTime":38926.0,"EndTime":38926.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":38926.0,"Objects":[{"StartTime":38926.0,"EndTime":38926.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":39164.0,"Objects":[{"StartTime":39164.0,"EndTime":39164.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":39402.0,"Objects":[{"StartTime":39402.0,"EndTime":39402.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":39402.0,"Objects":[{"StartTime":39402.0,"EndTime":39640.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":39878.0,"Objects":[{"StartTime":39878.0,"EndTime":39878.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":39879.0,"Objects":[{"StartTime":39879.0,"EndTime":40117.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":40116.0,"Objects":[{"StartTime":40116.0,"EndTime":40116.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":40354.0,"Objects":[{"StartTime":40354.0,"EndTime":40592.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":40354.0,"Objects":[{"StartTime":40354.0,"EndTime":40354.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":40831.0,"Objects":[{"StartTime":40831.0,"EndTime":41069.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":40831.0,"Objects":[{"StartTime":40831.0,"EndTime":40831.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":41069.0,"Objects":[{"StartTime":41069.0,"EndTime":41069.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":41307.0,"Objects":[{"StartTime":41307.0,"EndTime":41545.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":41307.0,"Objects":[{"StartTime":41307.0,"EndTime":41307.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":41783.0,"Objects":[{"StartTime":41783.0,"EndTime":42021.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":41783.0,"Objects":[{"StartTime":41783.0,"EndTime":41783.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":42259.0,"Objects":[{"StartTime":42259.0,"EndTime":42259.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":42259.0,"Objects":[{"StartTime":42259.0,"EndTime":42259.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":42497.0,"Objects":[{"StartTime":42497.0,"EndTime":42497.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":42735.0,"Objects":[{"StartTime":42735.0,"EndTime":42735.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":42735.0,"Objects":[{"StartTime":42735.0,"EndTime":42735.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":42974.0,"Objects":[{"StartTime":42974.0,"EndTime":42974.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":43212.0,"Objects":[{"StartTime":43212.0,"EndTime":43450.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":43212.0,"Objects":[{"StartTime":43212.0,"EndTime":43212.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":43687.0,"Objects":[{"StartTime":43687.0,"EndTime":43925.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":43688.0,"Objects":[{"StartTime":43688.0,"EndTime":43688.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":43926.0,"Objects":[{"StartTime":43926.0,"EndTime":43926.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":44164.0,"Objects":[{"StartTime":44164.0,"EndTime":44402.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":44164.0,"Objects":[{"StartTime":44164.0,"EndTime":44164.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":44639.0,"Objects":[{"StartTime":44639.0,"EndTime":44877.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":44640.0,"Objects":[{"StartTime":44640.0,"EndTime":44640.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":44878.0,"Objects":[{"StartTime":44878.0,"EndTime":44878.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":45116.0,"Objects":[{"StartTime":45116.0,"EndTime":45116.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":45116.0,"Objects":[{"StartTime":45116.0,"EndTime":45354.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":45593.0,"Objects":[{"StartTime":45593.0,"EndTime":45593.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":45593.0,"Objects":[{"StartTime":45593.0,"EndTime":45831.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":45831.0,"Objects":[{"StartTime":45831.0,"EndTime":47497.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":45831.0,"Objects":[{"StartTime":45831.0,"EndTime":45831.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":46069.0,"Objects":[{"StartTime":46069.0,"EndTime":46069.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":46069.0,"Objects":[{"StartTime":46069.0,"EndTime":46069.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":46307.0,"Objects":[{"StartTime":46307.0,"EndTime":46307.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":46545.0,"Objects":[{"StartTime":46545.0,"EndTime":46545.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":46545.0,"Objects":[{"StartTime":46545.0,"EndTime":46545.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":46783.0,"Objects":[{"StartTime":46783.0,"EndTime":46783.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":47021.0,"Objects":[{"StartTime":47021.0,"EndTime":47259.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":47021.0,"Objects":[{"StartTime":47021.0,"EndTime":47021.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":47497.0,"Objects":[{"StartTime":47497.0,"EndTime":47497.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":47497.0,"Objects":[{"StartTime":47497.0,"EndTime":47735.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":47735.0,"Objects":[{"StartTime":47735.0,"EndTime":47735.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":47795.0,"Objects":[{"StartTime":47795.0,"EndTime":48449.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":47974.0,"Objects":[{"StartTime":47974.0,"EndTime":48212.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":47974.0,"Objects":[{"StartTime":47974.0,"EndTime":47974.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":48450.0,"Objects":[{"StartTime":48450.0,"EndTime":48688.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":48450.0,"Objects":[{"StartTime":48450.0,"EndTime":48450.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":48688.0,"Objects":[{"StartTime":48688.0,"EndTime":48688.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":48688.0,"Objects":[{"StartTime":48688.0,"EndTime":48688.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":48926.0,"Objects":[{"StartTime":48926.0,"EndTime":49164.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":48926.0,"Objects":[{"StartTime":48926.0,"EndTime":48926.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":49164.0,"Objects":[{"StartTime":49164.0,"EndTime":49402.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":49402.0,"Objects":[{"StartTime":49402.0,"EndTime":49402.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":49402.0,"Objects":[{"StartTime":49402.0,"EndTime":49640.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":49640.0,"Objects":[{"StartTime":49640.0,"EndTime":51306.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":49878.0,"Objects":[{"StartTime":49878.0,"EndTime":49878.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":49878.0,"Objects":[{"StartTime":49878.0,"EndTime":49878.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":50116.0,"Objects":[{"StartTime":50116.0,"EndTime":50116.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":50354.0,"Objects":[{"StartTime":50354.0,"EndTime":50354.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":50354.0,"Objects":[{"StartTime":50354.0,"EndTime":50354.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":50593.0,"Objects":[{"StartTime":50593.0,"EndTime":50593.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":50831.0,"Objects":[{"StartTime":50831.0,"EndTime":50831.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":50831.0,"Objects":[{"StartTime":50831.0,"EndTime":51069.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":51307.0,"Objects":[{"StartTime":51307.0,"EndTime":51307.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":51307.0,"Objects":[{"StartTime":51307.0,"EndTime":51545.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":51545.0,"Objects":[{"StartTime":51545.0,"EndTime":52259.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":51545.0,"Objects":[{"StartTime":51545.0,"EndTime":51545.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":51783.0,"Objects":[{"StartTime":51783.0,"EndTime":51783.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":51783.0,"Objects":[{"StartTime":51783.0,"EndTime":52021.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":52259.0,"Objects":[{"StartTime":52259.0,"EndTime":52497.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":52259.0,"Objects":[{"StartTime":52259.0,"EndTime":52259.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":52497.0,"Objects":[{"StartTime":52497.0,"EndTime":52497.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":52735.0,"Objects":[{"StartTime":52735.0,"EndTime":52973.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":52735.0,"Objects":[{"StartTime":52735.0,"EndTime":52735.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":52974.0,"Objects":[{"StartTime":52974.0,"EndTime":53212.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":53212.0,"Objects":[{"StartTime":53212.0,"EndTime":53450.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":53212.0,"Objects":[{"StartTime":53212.0,"EndTime":53212.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":53450.0,"Objects":[{"StartTime":53450.0,"EndTime":53450.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":53450.0,"Objects":[{"StartTime":53450.0,"EndTime":54164.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":53688.0,"Objects":[{"StartTime":53688.0,"EndTime":53688.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":53926.0,"Objects":[{"StartTime":53926.0,"EndTime":53926.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":54164.0,"Objects":[{"StartTime":54164.0,"EndTime":54164.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":54164.0,"Objects":[{"StartTime":54164.0,"EndTime":54164.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":54402.0,"Objects":[{"StartTime":54402.0,"EndTime":55592.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":54402.0,"Objects":[{"StartTime":54402.0,"EndTime":54402.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":54640.0,"Objects":[{"StartTime":54640.0,"EndTime":54640.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":54640.0,"Objects":[{"StartTime":54640.0,"EndTime":54878.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":55116.0,"Objects":[{"StartTime":55116.0,"EndTime":55116.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":55116.0,"Objects":[{"StartTime":55116.0,"EndTime":55354.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":55354.0,"Objects":[{"StartTime":55354.0,"EndTime":55354.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":55593.0,"Objects":[{"StartTime":55593.0,"EndTime":55593.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":55593.0,"Objects":[{"StartTime":55593.0,"EndTime":55831.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":56069.0,"Objects":[{"StartTime":56069.0,"EndTime":56069.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":56069.0,"Objects":[{"StartTime":56069.0,"EndTime":56307.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":56307.0,"Objects":[{"StartTime":56307.0,"EndTime":56307.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":56545.0,"Objects":[{"StartTime":56545.0,"EndTime":56545.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":56545.0,"Objects":[{"StartTime":56545.0,"EndTime":56783.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":56783.0,"Objects":[{"StartTime":56783.0,"EndTime":56783.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":56783.0,"Objects":[{"StartTime":56783.0,"EndTime":57021.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":57021.0,"Objects":[{"StartTime":57021.0,"EndTime":57259.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":57021.0,"Objects":[{"StartTime":57021.0,"EndTime":57021.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":57259.0,"Objects":[{"StartTime":57259.0,"EndTime":57973.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":57497.0,"Objects":[{"StartTime":57497.0,"EndTime":57497.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":57497.0,"Objects":[{"StartTime":57497.0,"EndTime":57497.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":57735.0,"Objects":[{"StartTime":57735.0,"EndTime":57735.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":57974.0,"Objects":[{"StartTime":57974.0,"EndTime":57974.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":57974.0,"Objects":[{"StartTime":57974.0,"EndTime":57974.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":58212.0,"Objects":[{"StartTime":58212.0,"EndTime":60354.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":58212.0,"Objects":[{"StartTime":58212.0,"EndTime":58212.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":58450.0,"Objects":[{"StartTime":58450.0,"EndTime":58450.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":58450.0,"Objects":[{"StartTime":58450.0,"EndTime":58688.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":58926.0,"Objects":[{"StartTime":58926.0,"EndTime":58926.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":58926.0,"Objects":[{"StartTime":58926.0,"EndTime":59164.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":59164.0,"Objects":[{"StartTime":59164.0,"EndTime":59164.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":59402.0,"Objects":[{"StartTime":59402.0,"EndTime":59402.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":59402.0,"Objects":[{"StartTime":59402.0,"EndTime":59640.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":59878.0,"Objects":[{"StartTime":59878.0,"EndTime":59878.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":59878.0,"Objects":[{"StartTime":59878.0,"EndTime":60116.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":60116.0,"Objects":[{"StartTime":60116.0,"EndTime":60116.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":60354.0,"Objects":[{"StartTime":60354.0,"EndTime":60354.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":60354.0,"Objects":[{"StartTime":60354.0,"EndTime":60592.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":60354.0,"Objects":[{"StartTime":60354.0,"EndTime":60592.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":60593.0,"Objects":[{"StartTime":60593.0,"EndTime":60593.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":60831.0,"Objects":[{"StartTime":60831.0,"EndTime":60831.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":60831.0,"Objects":[{"StartTime":60831.0,"EndTime":61069.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":60831.0,"Objects":[{"StartTime":60831.0,"EndTime":60831.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":61069.0,"Objects":[{"StartTime":61069.0,"EndTime":61307.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":61307.0,"Objects":[{"StartTime":61307.0,"EndTime":61307.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":61307.0,"Objects":[{"StartTime":61307.0,"EndTime":61307.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":61545.0,"Objects":[{"StartTime":61545.0,"EndTime":61783.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":61545.0,"Objects":[{"StartTime":61545.0,"EndTime":61545.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":61783.0,"Objects":[{"StartTime":61783.0,"EndTime":61783.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":61783.0,"Objects":[{"StartTime":61783.0,"EndTime":61783.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":62021.0,"Objects":[{"StartTime":62021.0,"EndTime":62259.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":62021.0,"Objects":[{"StartTime":62021.0,"EndTime":62021.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":62259.0,"Objects":[{"StartTime":62259.0,"EndTime":62259.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":62259.0,"Objects":[{"StartTime":62259.0,"EndTime":62497.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":62497.0,"Objects":[{"StartTime":62497.0,"EndTime":62735.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":62735.0,"Objects":[{"StartTime":62735.0,"EndTime":62735.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":62735.0,"Objects":[{"StartTime":62735.0,"EndTime":62973.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":62974.0,"Objects":[{"StartTime":62974.0,"EndTime":63212.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":63212.0,"Objects":[{"StartTime":63212.0,"EndTime":63212.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":63212.0,"Objects":[{"StartTime":63212.0,"EndTime":63450.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":63450.0,"Objects":[{"StartTime":63450.0,"EndTime":63926.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":63688.0,"Objects":[{"StartTime":63688.0,"EndTime":63688.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":63688.0,"Objects":[{"StartTime":63688.0,"EndTime":63926.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":63926.0,"Objects":[{"StartTime":63926.0,"EndTime":64164.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":64164.0,"Objects":[{"StartTime":64164.0,"EndTime":64164.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":64164.0,"Objects":[{"StartTime":64164.0,"EndTime":64402.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":64402.0,"Objects":[{"StartTime":64402.0,"EndTime":64402.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":64640.0,"Objects":[{"StartTime":64640.0,"EndTime":64640.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":64640.0,"Objects":[{"StartTime":64640.0,"EndTime":64640.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":64640.0,"Objects":[{"StartTime":64640.0,"EndTime":64878.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":64878.0,"Objects":[{"StartTime":64878.0,"EndTime":65116.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":65116.0,"Objects":[{"StartTime":65116.0,"EndTime":65116.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":65116.0,"Objects":[{"StartTime":65116.0,"EndTime":65116.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":65354.0,"Objects":[{"StartTime":65354.0,"EndTime":65592.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":65354.0,"Objects":[{"StartTime":65354.0,"EndTime":65354.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":65593.0,"Objects":[{"StartTime":65593.0,"EndTime":65593.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":65593.0,"Objects":[{"StartTime":65593.0,"EndTime":65593.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":65831.0,"Objects":[{"StartTime":65831.0,"EndTime":66069.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":65831.0,"Objects":[{"StartTime":65831.0,"EndTime":65831.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":66069.0,"Objects":[{"StartTime":66069.0,"EndTime":66069.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":66069.0,"Objects":[{"StartTime":66069.0,"EndTime":66307.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":66307.0,"Objects":[{"StartTime":66307.0,"EndTime":66545.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":66545.0,"Objects":[{"StartTime":66545.0,"EndTime":66545.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":66545.0,"Objects":[{"StartTime":66545.0,"EndTime":66783.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":66783.0,"Objects":[{"StartTime":66783.0,"EndTime":67021.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":67021.0,"Objects":[{"StartTime":67021.0,"EndTime":67021.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":67021.0,"Objects":[{"StartTime":67021.0,"EndTime":67259.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":67259.0,"Objects":[{"StartTime":67259.0,"EndTime":67497.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":67497.0,"Objects":[{"StartTime":67497.0,"EndTime":67497.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":67497.0,"Objects":[{"StartTime":67497.0,"EndTime":67735.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":67735.0,"Objects":[{"StartTime":67735.0,"EndTime":67973.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":67974.0,"Objects":[{"StartTime":67974.0,"EndTime":67974.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":67974.0,"Objects":[{"StartTime":67974.0,"EndTime":68212.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":68212.0,"Objects":[{"StartTime":68212.0,"EndTime":68450.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":68331.0,"Objects":[{"StartTime":68331.0,"EndTime":68331.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":68450.0,"Objects":[{"StartTime":68450.0,"EndTime":68688.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":68688.0,"Objects":[{"StartTime":68688.0,"EndTime":69164.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":68688.0,"Objects":[{"StartTime":68688.0,"EndTime":68688.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":68926.0,"Objects":[{"StartTime":68926.0,"EndTime":68926.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":68926.0,"Objects":[{"StartTime":68926.0,"EndTime":68926.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":69164.0,"Objects":[{"StartTime":69164.0,"EndTime":69402.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":69164.0,"Objects":[{"StartTime":69164.0,"EndTime":69164.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":69402.0,"Objects":[{"StartTime":69402.0,"EndTime":69402.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":69402.0,"Objects":[{"StartTime":69402.0,"EndTime":69402.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":69640.0,"Objects":[{"StartTime":69640.0,"EndTime":69878.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":69640.0,"Objects":[{"StartTime":69640.0,"EndTime":69640.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":69878.0,"Objects":[{"StartTime":69878.0,"EndTime":69878.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":69878.0,"Objects":[{"StartTime":69878.0,"EndTime":70116.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":70116.0,"Objects":[{"StartTime":70116.0,"EndTime":70354.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":70354.0,"Objects":[{"StartTime":70354.0,"EndTime":70354.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":70354.0,"Objects":[{"StartTime":70354.0,"EndTime":70592.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":70593.0,"Objects":[{"StartTime":70593.0,"EndTime":70831.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":70831.0,"Objects":[{"StartTime":70831.0,"EndTime":70831.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":70831.0,"Objects":[{"StartTime":70831.0,"EndTime":71069.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":71069.0,"Objects":[{"StartTime":71069.0,"EndTime":71307.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":71069.0,"Objects":[{"StartTime":71069.0,"EndTime":71307.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":71307.0,"Objects":[{"StartTime":71307.0,"EndTime":71307.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":71307.0,"Objects":[{"StartTime":71307.0,"EndTime":71545.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":71545.0,"Objects":[{"StartTime":71545.0,"EndTime":71783.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":71783.0,"Objects":[{"StartTime":71783.0,"EndTime":71783.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":71783.0,"Objects":[{"StartTime":71783.0,"EndTime":72021.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":72021.0,"Objects":[{"StartTime":72021.0,"EndTime":72259.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":72259.0,"Objects":[{"StartTime":72259.0,"EndTime":72259.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":72259.0,"Objects":[{"StartTime":72259.0,"EndTime":72497.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":72497.0,"Objects":[{"StartTime":72497.0,"EndTime":72973.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":72735.0,"Objects":[{"StartTime":72735.0,"EndTime":72735.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":72735.0,"Objects":[{"StartTime":72735.0,"EndTime":72735.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":72974.0,"Objects":[{"StartTime":72974.0,"EndTime":73212.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":72974.0,"Objects":[{"StartTime":72974.0,"EndTime":72974.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":73212.0,"Objects":[{"StartTime":73212.0,"EndTime":73212.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":73212.0,"Objects":[{"StartTime":73212.0,"EndTime":73212.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":73450.0,"Objects":[{"StartTime":73450.0,"EndTime":73688.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":73450.0,"Objects":[{"StartTime":73450.0,"EndTime":73450.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":73688.0,"Objects":[{"StartTime":73688.0,"EndTime":73688.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":73688.0,"Objects":[{"StartTime":73688.0,"EndTime":73926.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":73926.0,"Objects":[{"StartTime":73926.0,"EndTime":74164.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":74164.0,"Objects":[{"StartTime":74164.0,"EndTime":74164.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":74164.0,"Objects":[{"StartTime":74164.0,"EndTime":74402.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":74402.0,"Objects":[{"StartTime":74402.0,"EndTime":75116.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":74402.0,"Objects":[{"StartTime":74402.0,"EndTime":74402.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":74640.0,"Objects":[{"StartTime":74640.0,"EndTime":74640.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":74640.0,"Objects":[{"StartTime":74640.0,"EndTime":74878.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":75116.0,"Objects":[{"StartTime":75116.0,"EndTime":75116.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":75116.0,"Objects":[{"StartTime":75116.0,"EndTime":75354.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":75354.0,"Objects":[{"StartTime":75354.0,"EndTime":75830.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":75593.0,"Objects":[{"StartTime":75593.0,"EndTime":75593.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":75593.0,"Objects":[{"StartTime":75593.0,"EndTime":75831.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":75831.0,"Objects":[{"StartTime":75831.0,"EndTime":75831.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":75950.0,"Objects":[{"StartTime":75950.0,"EndTime":75950.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":76069.0,"Objects":[{"StartTime":76069.0,"EndTime":76307.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":76069.0,"Objects":[{"StartTime":76069.0,"EndTime":76069.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":76307.0,"Objects":[{"StartTime":76307.0,"EndTime":76545.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":76307.0,"Objects":[{"StartTime":76307.0,"EndTime":76307.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":76545.0,"Objects":[{"StartTime":76545.0,"EndTime":76545.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":76545.0,"Objects":[{"StartTime":76545.0,"EndTime":76783.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":76783.0,"Objects":[{"StartTime":76783.0,"EndTime":77021.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":77021.0,"Objects":[{"StartTime":77021.0,"EndTime":77021.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":77021.0,"Objects":[{"StartTime":77021.0,"EndTime":77259.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":77259.0,"Objects":[{"StartTime":77259.0,"EndTime":77497.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":77497.0,"Objects":[{"StartTime":77497.0,"EndTime":77735.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":77498.0,"Objects":[{"StartTime":77498.0,"EndTime":77498.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":77735.0,"Objects":[{"StartTime":77735.0,"EndTime":78211.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":77974.0,"Objects":[{"StartTime":77974.0,"EndTime":77974.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":77974.0,"Objects":[{"StartTime":77974.0,"EndTime":78212.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":78212.0,"Objects":[{"StartTime":78212.0,"EndTime":78450.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":78450.0,"Objects":[{"StartTime":78450.0,"EndTime":78450.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":78450.0,"Objects":[{"StartTime":78450.0,"EndTime":78450.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":78688.0,"Objects":[{"StartTime":78688.0,"EndTime":78688.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":78688.0,"Objects":[{"StartTime":78688.0,"EndTime":78926.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":78926.0,"Objects":[{"StartTime":78926.0,"EndTime":78926.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":78926.0,"Objects":[{"StartTime":78926.0,"EndTime":78926.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":79164.0,"Objects":[{"StartTime":79164.0,"EndTime":79164.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":79164.0,"Objects":[{"StartTime":79164.0,"EndTime":79402.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":79402.0,"Objects":[{"StartTime":79402.0,"EndTime":79640.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":79402.0,"Objects":[{"StartTime":79402.0,"EndTime":79402.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":79640.0,"Objects":[{"StartTime":79640.0,"EndTime":79640.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":79878.0,"Objects":[{"StartTime":79878.0,"EndTime":79878.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":79878.0,"Objects":[{"StartTime":79878.0,"EndTime":80116.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":79878.0,"Objects":[{"StartTime":79878.0,"EndTime":79878.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":80117.0,"Objects":[{"StartTime":80117.0,"EndTime":80355.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":80355.0,"Objects":[{"StartTime":80355.0,"EndTime":80593.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":80355.0,"Objects":[{"StartTime":80355.0,"EndTime":80355.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":80593.0,"Objects":[{"StartTime":80593.0,"EndTime":80831.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":80831.0,"Objects":[{"StartTime":80831.0,"EndTime":81069.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":80831.0,"Objects":[{"StartTime":80831.0,"EndTime":80831.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":81069.0,"Objects":[{"StartTime":81069.0,"EndTime":81307.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":81307.0,"Objects":[{"StartTime":81307.0,"EndTime":81545.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":81307.0,"Objects":[{"StartTime":81307.0,"EndTime":81307.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":81545.0,"Objects":[{"StartTime":81545.0,"EndTime":81783.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":81783.0,"Objects":[{"StartTime":81783.0,"EndTime":82021.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":81783.0,"Objects":[{"StartTime":81783.0,"EndTime":81783.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":82021.0,"Objects":[{"StartTime":82021.0,"EndTime":82497.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":82259.0,"Objects":[{"StartTime":82259.0,"EndTime":82259.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":82259.0,"Objects":[{"StartTime":82259.0,"EndTime":82259.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":82498.0,"Objects":[{"StartTime":82498.0,"EndTime":82736.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":82498.0,"Objects":[{"StartTime":82498.0,"EndTime":82498.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":82736.0,"Objects":[{"StartTime":82736.0,"EndTime":82736.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":82736.0,"Objects":[{"StartTime":82736.0,"EndTime":82736.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":82974.0,"Objects":[{"StartTime":82974.0,"EndTime":83212.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":82974.0,"Objects":[{"StartTime":82974.0,"EndTime":82974.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":83212.0,"Objects":[{"StartTime":83212.0,"EndTime":83450.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":83212.0,"Objects":[{"StartTime":83212.0,"EndTime":83212.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":83450.0,"Objects":[{"StartTime":83450.0,"EndTime":83688.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":83569.0,"Objects":[{"StartTime":83569.0,"EndTime":83569.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":83688.0,"Objects":[{"StartTime":83688.0,"EndTime":83926.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":83926.0,"Objects":[{"StartTime":83926.0,"EndTime":84402.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":83926.0,"Objects":[{"StartTime":83926.0,"EndTime":83926.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":84164.0,"Objects":[{"StartTime":84164.0,"EndTime":84402.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":84164.0,"Objects":[{"StartTime":84164.0,"EndTime":84164.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":84402.0,"Objects":[{"StartTime":84402.0,"EndTime":84640.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":84640.0,"Objects":[{"StartTime":84640.0,"EndTime":84878.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":84640.0,"Objects":[{"StartTime":84640.0,"EndTime":84640.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":84878.0,"Objects":[{"StartTime":84878.0,"EndTime":85354.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":85117.0,"Objects":[{"StartTime":85117.0,"EndTime":85117.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":85117.0,"Objects":[{"StartTime":85117.0,"EndTime":85355.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":85354.0,"Objects":[{"StartTime":85354.0,"EndTime":85592.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":85593.0,"Objects":[{"StartTime":85593.0,"EndTime":85831.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":85593.0,"Objects":[{"StartTime":85593.0,"EndTime":85593.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":85831.0,"Objects":[{"StartTime":85831.0,"EndTime":86069.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":86069.0,"Objects":[{"StartTime":86069.0,"EndTime":86069.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":86069.0,"Objects":[{"StartTime":86069.0,"EndTime":86307.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":86307.0,"Objects":[{"StartTime":86307.0,"EndTime":86545.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":86307.0,"Objects":[{"StartTime":86307.0,"EndTime":86545.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":86545.0,"Objects":[{"StartTime":86545.0,"EndTime":86545.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":86545.0,"Objects":[{"StartTime":86545.0,"EndTime":86783.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":86783.0,"Objects":[{"StartTime":86783.0,"EndTime":87021.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":87021.0,"Objects":[{"StartTime":87021.0,"EndTime":87021.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":87021.0,"Objects":[{"StartTime":87021.0,"EndTime":87259.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":87259.0,"Objects":[{"StartTime":87259.0,"EndTime":87497.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":87497.0,"Objects":[{"StartTime":87497.0,"EndTime":87497.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":87497.0,"Objects":[{"StartTime":87497.0,"EndTime":87735.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":87735.0,"Objects":[{"StartTime":87735.0,"EndTime":88211.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":87973.0,"Objects":[{"StartTime":87973.0,"EndTime":87973.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":87974.0,"Objects":[{"StartTime":87974.0,"EndTime":87974.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":88212.0,"Objects":[{"StartTime":88212.0,"EndTime":88450.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":88212.0,"Objects":[{"StartTime":88212.0,"EndTime":88212.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":88450.0,"Objects":[{"StartTime":88450.0,"EndTime":88450.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":88450.0,"Objects":[{"StartTime":88450.0,"EndTime":88450.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":88688.0,"Objects":[{"StartTime":88688.0,"EndTime":88926.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":88688.0,"Objects":[{"StartTime":88688.0,"EndTime":88688.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":88926.0,"Objects":[{"StartTime":88926.0,"EndTime":88926.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":88926.0,"Objects":[{"StartTime":88926.0,"EndTime":89164.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":89164.0,"Objects":[{"StartTime":89164.0,"EndTime":89402.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":89402.0,"Objects":[{"StartTime":89402.0,"EndTime":89640.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":89402.0,"Objects":[{"StartTime":89402.0,"EndTime":89402.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":89640.0,"Objects":[{"StartTime":89640.0,"EndTime":89878.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":89878.0,"Objects":[{"StartTime":89878.0,"EndTime":89878.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":89878.0,"Objects":[{"StartTime":89878.0,"EndTime":90116.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":89878.0,"Objects":[{"StartTime":89878.0,"EndTime":90354.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":90116.0,"Objects":[{"StartTime":90116.0,"EndTime":90354.0,"Column":1}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":90354.0,"Objects":[{"StartTime":90354.0,"EndTime":90354.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":90354.0,"Objects":[{"StartTime":90354.0,"EndTime":90592.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":90593.0,"Objects":[{"StartTime":90593.0,"EndTime":90831.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":90831.0,"Objects":[{"StartTime":90831.0,"EndTime":90831.0,"Column":0}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":90831.0,"Objects":[{"StartTime":90831.0,"EndTime":91069.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":91069.0,"Objects":[{"StartTime":91069.0,"EndTime":91307.0,"Column":2}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":91307.0,"Objects":[{"StartTime":91307.0,"EndTime":91545.0,"Column":3}]},{"RandomW":273071671,"RandomX":842502087,"RandomY":3579807591,"RandomZ":273326509,"StartTime":91307.0,"Objects":[{"StartTime":91307.0,"EndTime":91307.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":91545.0,"Objects":[{"StartTime":91545.0,"EndTime":92735.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":91545.0,"Objects":[{"StartTime":91545.0,"EndTime":91545.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":91783.0,"Objects":[{"StartTime":91783.0,"EndTime":91783.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":91783.0,"Objects":[{"StartTime":91783.0,"EndTime":91783.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":92021.0,"Objects":[{"StartTime":92021.0,"EndTime":92021.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":92259.0,"Objects":[{"StartTime":92259.0,"EndTime":92259.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":92259.0,"Objects":[{"StartTime":92259.0,"EndTime":92259.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":92497.0,"Objects":[{"StartTime":92497.0,"EndTime":92497.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":92735.0,"Objects":[{"StartTime":92735.0,"EndTime":92973.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":92735.0,"Objects":[{"StartTime":92735.0,"EndTime":92735.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":92974.0,"Objects":[{"StartTime":92974.0,"EndTime":93212.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":93212.0,"Objects":[{"StartTime":93212.0,"EndTime":93450.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":93212.0,"Objects":[{"StartTime":93212.0,"EndTime":93212.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":93450.0,"Objects":[{"StartTime":93450.0,"EndTime":93450.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":93688.0,"Objects":[{"StartTime":93688.0,"EndTime":93688.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":93688.0,"Objects":[{"StartTime":93688.0,"EndTime":93926.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":93688.0,"Objects":[{"StartTime":93688.0,"EndTime":94164.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":94164.0,"Objects":[{"StartTime":94164.0,"EndTime":94402.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":94164.0,"Objects":[{"StartTime":94164.0,"EndTime":94164.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":94402.0,"Objects":[{"StartTime":94402.0,"EndTime":94402.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":94402.0,"Objects":[{"StartTime":94402.0,"EndTime":94402.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":94640.0,"Objects":[{"StartTime":94640.0,"EndTime":94640.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":94640.0,"Objects":[{"StartTime":94640.0,"EndTime":94878.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":94640.0,"Objects":[{"StartTime":94640.0,"EndTime":94640.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":95116.0,"Objects":[{"StartTime":95116.0,"EndTime":95592.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":95116.0,"Objects":[{"StartTime":95116.0,"EndTime":95116.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":95116.0,"Objects":[{"StartTime":95116.0,"EndTime":95354.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":95593.0,"Objects":[{"StartTime":95593.0,"EndTime":95593.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":95593.0,"Objects":[{"StartTime":95593.0,"EndTime":95593.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":95831.0,"Objects":[{"StartTime":95831.0,"EndTime":95831.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":96069.0,"Objects":[{"StartTime":96069.0,"EndTime":96069.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":96069.0,"Objects":[{"StartTime":96069.0,"EndTime":96069.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":96307.0,"Objects":[{"StartTime":96307.0,"EndTime":96307.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":96545.0,"Objects":[{"StartTime":96545.0,"EndTime":96545.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":96545.0,"Objects":[{"StartTime":96545.0,"EndTime":96783.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":96783.0,"Objects":[{"StartTime":96783.0,"EndTime":97259.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":97021.0,"Objects":[{"StartTime":97021.0,"EndTime":97021.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":97021.0,"Objects":[{"StartTime":97021.0,"EndTime":97259.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":97259.0,"Objects":[{"StartTime":97259.0,"EndTime":97259.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":97497.0,"Objects":[{"StartTime":97497.0,"EndTime":97497.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":97497.0,"Objects":[{"StartTime":97497.0,"EndTime":97497.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":97497.0,"Objects":[{"StartTime":97497.0,"EndTime":97735.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":97735.0,"Objects":[{"StartTime":97735.0,"EndTime":98211.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":97974.0,"Objects":[{"StartTime":97974.0,"EndTime":97974.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":97974.0,"Objects":[{"StartTime":97974.0,"EndTime":98212.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":98212.0,"Objects":[{"StartTime":98212.0,"EndTime":98212.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":98450.0,"Objects":[{"StartTime":98450.0,"EndTime":98450.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":98450.0,"Objects":[{"StartTime":98450.0,"EndTime":98450.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":98450.0,"Objects":[{"StartTime":98450.0,"EndTime":98688.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":98747.0,"Objects":[{"StartTime":98747.0,"EndTime":98747.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":98926.0,"Objects":[{"StartTime":98926.0,"EndTime":99640.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":98926.0,"Objects":[{"StartTime":98926.0,"EndTime":99164.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":99164.0,"Objects":[{"StartTime":99164.0,"EndTime":99164.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":99402.0,"Objects":[{"StartTime":99402.0,"EndTime":99402.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":99402.0,"Objects":[{"StartTime":99402.0,"EndTime":99402.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":99640.0,"Objects":[{"StartTime":99640.0,"EndTime":99640.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":99878.0,"Objects":[{"StartTime":99878.0,"EndTime":99878.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":99878.0,"Objects":[{"StartTime":99878.0,"EndTime":99878.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":100116.0,"Objects":[{"StartTime":100116.0,"EndTime":100116.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":100354.0,"Objects":[{"StartTime":100354.0,"EndTime":100354.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":100354.0,"Objects":[{"StartTime":100354.0,"EndTime":100830.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":100354.0,"Objects":[{"StartTime":100354.0,"EndTime":100592.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":100831.0,"Objects":[{"StartTime":100831.0,"EndTime":101069.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":100831.0,"Objects":[{"StartTime":100831.0,"EndTime":100831.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":101069.0,"Objects":[{"StartTime":101069.0,"EndTime":101069.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":101307.0,"Objects":[{"StartTime":101307.0,"EndTime":101545.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":101307.0,"Objects":[{"StartTime":101307.0,"EndTime":101783.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":101307.0,"Objects":[{"StartTime":101307.0,"EndTime":101307.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":101783.0,"Objects":[{"StartTime":101783.0,"EndTime":102021.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":101783.0,"Objects":[{"StartTime":101783.0,"EndTime":101783.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":102021.0,"Objects":[{"StartTime":102021.0,"EndTime":102021.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":102021.0,"Objects":[{"StartTime":102021.0,"EndTime":102021.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":102259.0,"Objects":[{"StartTime":102259.0,"EndTime":102497.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":102259.0,"Objects":[{"StartTime":102259.0,"EndTime":102497.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":102259.0,"Objects":[{"StartTime":102259.0,"EndTime":102259.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":102735.0,"Objects":[{"StartTime":102735.0,"EndTime":103449.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":102735.0,"Objects":[{"StartTime":102735.0,"EndTime":102973.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":102735.0,"Objects":[{"StartTime":102735.0,"EndTime":102735.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":103212.0,"Objects":[{"StartTime":103212.0,"EndTime":103212.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":103212.0,"Objects":[{"StartTime":103212.0,"EndTime":103212.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":103450.0,"Objects":[{"StartTime":103450.0,"EndTime":103450.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":103688.0,"Objects":[{"StartTime":103688.0,"EndTime":103688.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":103688.0,"Objects":[{"StartTime":103688.0,"EndTime":103688.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":103926.0,"Objects":[{"StartTime":103926.0,"EndTime":103926.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":104164.0,"Objects":[{"StartTime":104164.0,"EndTime":104164.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":104164.0,"Objects":[{"StartTime":104164.0,"EndTime":104402.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":104164.0,"Objects":[{"StartTime":104164.0,"EndTime":104164.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":104402.0,"Objects":[{"StartTime":104402.0,"EndTime":104640.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":104640.0,"Objects":[{"StartTime":104640.0,"EndTime":104640.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":104640.0,"Objects":[{"StartTime":104640.0,"EndTime":104878.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":104878.0,"Objects":[{"StartTime":104878.0,"EndTime":104878.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":105116.0,"Objects":[{"StartTime":105116.0,"EndTime":105354.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":105116.0,"Objects":[{"StartTime":105116.0,"EndTime":105116.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":105116.0,"Objects":[{"StartTime":105116.0,"EndTime":105116.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":105593.0,"Objects":[{"StartTime":105593.0,"EndTime":105593.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":105593.0,"Objects":[{"StartTime":105593.0,"EndTime":105831.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":105593.0,"Objects":[{"StartTime":105593.0,"EndTime":105593.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":105831.0,"Objects":[{"StartTime":105831.0,"EndTime":105831.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":105831.0,"Objects":[{"StartTime":105831.0,"EndTime":105831.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":106069.0,"Objects":[{"StartTime":106069.0,"EndTime":106307.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":106069.0,"Objects":[{"StartTime":106069.0,"EndTime":106069.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":106069.0,"Objects":[{"StartTime":106069.0,"EndTime":106069.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":106307.0,"Objects":[{"StartTime":106307.0,"EndTime":106307.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":106426.0,"Objects":[{"StartTime":106426.0,"EndTime":106426.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":106545.0,"Objects":[{"StartTime":106545.0,"EndTime":108449.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":106545.0,"Objects":[{"StartTime":106545.0,"EndTime":106783.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":106783.0,"Objects":[{"StartTime":106783.0,"EndTime":106783.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":107021.0,"Objects":[{"StartTime":107021.0,"EndTime":107021.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":107021.0,"Objects":[{"StartTime":107021.0,"EndTime":107021.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":107259.0,"Objects":[{"StartTime":107259.0,"EndTime":107259.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":107497.0,"Objects":[{"StartTime":107497.0,"EndTime":107497.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":107497.0,"Objects":[{"StartTime":107497.0,"EndTime":107497.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":107735.0,"Objects":[{"StartTime":107735.0,"EndTime":107735.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":107974.0,"Objects":[{"StartTime":107974.0,"EndTime":107974.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":107974.0,"Objects":[{"StartTime":107974.0,"EndTime":108212.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":108450.0,"Objects":[{"StartTime":108450.0,"EndTime":108450.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":108450.0,"Objects":[{"StartTime":108450.0,"EndTime":108688.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":108688.0,"Objects":[{"StartTime":108688.0,"EndTime":108688.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":108926.0,"Objects":[{"StartTime":108926.0,"EndTime":108926.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":108926.0,"Objects":[{"StartTime":108926.0,"EndTime":109164.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":109164.0,"Objects":[{"StartTime":109164.0,"EndTime":109640.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":109402.0,"Objects":[{"StartTime":109402.0,"EndTime":109402.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":109402.0,"Objects":[{"StartTime":109402.0,"EndTime":109640.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":109640.0,"Objects":[{"StartTime":109640.0,"EndTime":109640.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":109878.0,"Objects":[{"StartTime":109878.0,"EndTime":109878.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":109878.0,"Objects":[{"StartTime":109878.0,"EndTime":110116.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":110116.0,"Objects":[{"StartTime":110116.0,"EndTime":110116.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":110354.0,"Objects":[{"StartTime":110354.0,"EndTime":110354.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":110354.0,"Objects":[{"StartTime":110354.0,"EndTime":110592.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":110593.0,"Objects":[{"StartTime":110593.0,"EndTime":111307.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":110831.0,"Objects":[{"StartTime":110831.0,"EndTime":110831.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":110831.0,"Objects":[{"StartTime":110831.0,"EndTime":110831.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":111069.0,"Objects":[{"StartTime":111069.0,"EndTime":111069.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":111307.0,"Objects":[{"StartTime":111307.0,"EndTime":111307.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":111307.0,"Objects":[{"StartTime":111307.0,"EndTime":111307.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":111545.0,"Objects":[{"StartTime":111545.0,"EndTime":112259.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":111545.0,"Objects":[{"StartTime":111545.0,"EndTime":111545.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":111783.0,"Objects":[{"StartTime":111783.0,"EndTime":111783.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":111783.0,"Objects":[{"StartTime":111783.0,"EndTime":112021.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":112259.0,"Objects":[{"StartTime":112259.0,"EndTime":112259.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":112259.0,"Objects":[{"StartTime":112259.0,"EndTime":112497.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":112497.0,"Objects":[{"StartTime":112497.0,"EndTime":113449.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":112497.0,"Objects":[{"StartTime":112497.0,"EndTime":112497.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":112735.0,"Objects":[{"StartTime":112735.0,"EndTime":112735.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":112735.0,"Objects":[{"StartTime":112735.0,"EndTime":112973.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":113212.0,"Objects":[{"StartTime":113212.0,"EndTime":113212.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":113212.0,"Objects":[{"StartTime":113212.0,"EndTime":113450.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":113450.0,"Objects":[{"StartTime":113450.0,"EndTime":113450.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":113688.0,"Objects":[{"StartTime":113688.0,"EndTime":113688.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":113688.0,"Objects":[{"StartTime":113688.0,"EndTime":113926.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":113926.0,"Objects":[{"StartTime":113926.0,"EndTime":113926.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":113985.0,"Objects":[{"StartTime":113985.0,"EndTime":113985.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":114164.0,"Objects":[{"StartTime":114164.0,"EndTime":114402.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":114402.0,"Objects":[{"StartTime":114402.0,"EndTime":114402.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":114402.0,"Objects":[{"StartTime":114402.0,"EndTime":115116.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":114640.0,"Objects":[{"StartTime":114640.0,"EndTime":114640.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":114640.0,"Objects":[{"StartTime":114640.0,"EndTime":114640.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":114878.0,"Objects":[{"StartTime":114878.0,"EndTime":114878.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":115116.0,"Objects":[{"StartTime":115116.0,"EndTime":115116.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":115116.0,"Objects":[{"StartTime":115116.0,"EndTime":115116.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":115354.0,"Objects":[{"StartTime":115354.0,"EndTime":115354.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":115354.0,"Objects":[{"StartTime":115354.0,"EndTime":116306.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":115593.0,"Objects":[{"StartTime":115593.0,"EndTime":115831.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":115593.0,"Objects":[{"StartTime":115593.0,"EndTime":115593.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":116069.0,"Objects":[{"StartTime":116069.0,"EndTime":116307.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":116069.0,"Objects":[{"StartTime":116069.0,"EndTime":116069.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":116307.0,"Objects":[{"StartTime":116307.0,"EndTime":116307.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":116545.0,"Objects":[{"StartTime":116545.0,"EndTime":116783.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":116545.0,"Objects":[{"StartTime":116545.0,"EndTime":117021.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":116545.0,"Objects":[{"StartTime":116545.0,"EndTime":116545.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":117021.0,"Objects":[{"StartTime":117021.0,"EndTime":117021.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":117021.0,"Objects":[{"StartTime":117021.0,"EndTime":117259.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":117021.0,"Objects":[{"StartTime":117021.0,"EndTime":117021.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":117259.0,"Objects":[{"StartTime":117259.0,"EndTime":117259.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":117259.0,"Objects":[{"StartTime":117259.0,"EndTime":117497.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":117497.0,"Objects":[{"StartTime":117497.0,"EndTime":117497.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":117497.0,"Objects":[{"StartTime":117497.0,"EndTime":117735.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":117735.0,"Objects":[{"StartTime":117735.0,"EndTime":117973.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":117974.0,"Objects":[{"StartTime":117974.0,"EndTime":117974.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":117974.0,"Objects":[{"StartTime":117974.0,"EndTime":118212.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":118212.0,"Objects":[{"StartTime":118212.0,"EndTime":118926.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":118450.0,"Objects":[{"StartTime":118450.0,"EndTime":118450.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":118450.0,"Objects":[{"StartTime":118450.0,"EndTime":118450.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":118450.0,"Objects":[{"StartTime":118450.0,"EndTime":118450.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":118688.0,"Objects":[{"StartTime":118688.0,"EndTime":118688.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":118688.0,"Objects":[{"StartTime":118688.0,"EndTime":118688.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":118926.0,"Objects":[{"StartTime":118926.0,"EndTime":118926.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":118926.0,"Objects":[{"StartTime":118926.0,"EndTime":118926.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":119164.0,"Objects":[{"StartTime":119164.0,"EndTime":120830.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":119164.0,"Objects":[{"StartTime":119164.0,"EndTime":119164.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":119402.0,"Objects":[{"StartTime":119402.0,"EndTime":119402.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":119402.0,"Objects":[{"StartTime":119402.0,"EndTime":119640.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":119878.0,"Objects":[{"StartTime":119878.0,"EndTime":119878.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":119878.0,"Objects":[{"StartTime":119878.0,"EndTime":120116.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":120116.0,"Objects":[{"StartTime":120116.0,"EndTime":120116.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":120354.0,"Objects":[{"StartTime":120354.0,"EndTime":120354.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":120354.0,"Objects":[{"StartTime":120354.0,"EndTime":120592.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":120831.0,"Objects":[{"StartTime":120831.0,"EndTime":120831.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":120831.0,"Objects":[{"StartTime":120831.0,"EndTime":121069.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":121069.0,"Objects":[{"StartTime":121069.0,"EndTime":121307.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":121307.0,"Objects":[{"StartTime":121307.0,"EndTime":121307.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":121307.0,"Objects":[{"StartTime":121307.0,"EndTime":121545.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":121545.0,"Objects":[{"StartTime":121545.0,"EndTime":121545.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":121664.0,"Objects":[{"StartTime":121664.0,"EndTime":121664.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":121783.0,"Objects":[{"StartTime":121783.0,"EndTime":122021.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":121783.0,"Objects":[{"StartTime":121783.0,"EndTime":121783.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":122021.0,"Objects":[{"StartTime":122021.0,"EndTime":122259.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":122021.0,"Objects":[{"StartTime":122021.0,"EndTime":122021.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":122259.0,"Objects":[{"StartTime":122259.0,"EndTime":122259.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":122260.0,"Objects":[{"StartTime":122260.0,"EndTime":122260.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":122497.0,"Objects":[{"StartTime":122497.0,"EndTime":122735.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":122497.0,"Objects":[{"StartTime":122497.0,"EndTime":122497.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":122736.0,"Objects":[{"StartTime":122736.0,"EndTime":122736.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":122736.0,"Objects":[{"StartTime":122736.0,"EndTime":122736.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":122974.0,"Objects":[{"StartTime":122974.0,"EndTime":122974.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":122974.0,"Objects":[{"StartTime":122974.0,"EndTime":123212.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":123212.0,"Objects":[{"StartTime":123212.0,"EndTime":123450.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":123212.0,"Objects":[{"StartTime":123212.0,"EndTime":123212.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":123450.0,"Objects":[{"StartTime":123450.0,"EndTime":123688.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":123688.0,"Objects":[{"StartTime":123688.0,"EndTime":123688.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":123688.0,"Objects":[{"StartTime":123688.0,"EndTime":123926.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":123926.0,"Objects":[{"StartTime":123926.0,"EndTime":124164.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":124164.0,"Objects":[{"StartTime":124164.0,"EndTime":124164.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":124164.0,"Objects":[{"StartTime":124164.0,"EndTime":124402.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":124403.0,"Objects":[{"StartTime":124403.0,"EndTime":124641.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":124641.0,"Objects":[{"StartTime":124641.0,"EndTime":124879.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":124641.0,"Objects":[{"StartTime":124641.0,"EndTime":124641.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":124879.0,"Objects":[{"StartTime":124879.0,"EndTime":125117.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":125116.0,"Objects":[{"StartTime":125116.0,"EndTime":125354.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":125117.0,"Objects":[{"StartTime":125117.0,"EndTime":125117.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":125354.0,"Objects":[{"StartTime":125354.0,"EndTime":125354.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":125593.0,"Objects":[{"StartTime":125593.0,"EndTime":125593.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":125593.0,"Objects":[{"StartTime":125593.0,"EndTime":125831.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":125593.0,"Objects":[{"StartTime":125593.0,"EndTime":125593.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":125831.0,"Objects":[{"StartTime":125831.0,"EndTime":126069.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":126069.0,"Objects":[{"StartTime":126069.0,"EndTime":126069.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":126069.0,"Objects":[{"StartTime":126069.0,"EndTime":126069.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":126307.0,"Objects":[{"StartTime":126307.0,"EndTime":126545.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":126307.0,"Objects":[{"StartTime":126307.0,"EndTime":126307.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":126545.0,"Objects":[{"StartTime":126545.0,"EndTime":126545.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":126545.0,"Objects":[{"StartTime":126545.0,"EndTime":126545.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":126783.0,"Objects":[{"StartTime":126783.0,"EndTime":127021.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":126783.0,"Objects":[{"StartTime":126783.0,"EndTime":126783.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":127021.0,"Objects":[{"StartTime":127021.0,"EndTime":127259.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":127022.0,"Objects":[{"StartTime":127022.0,"EndTime":127022.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":127260.0,"Objects":[{"StartTime":127260.0,"EndTime":127498.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":127498.0,"Objects":[{"StartTime":127498.0,"EndTime":127736.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":127498.0,"Objects":[{"StartTime":127498.0,"EndTime":127498.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":127736.0,"Objects":[{"StartTime":127736.0,"EndTime":128212.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":127974.0,"Objects":[{"StartTime":127974.0,"EndTime":128212.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":127974.0,"Objects":[{"StartTime":127974.0,"EndTime":127974.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":128212.0,"Objects":[{"StartTime":128212.0,"EndTime":128450.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":128450.0,"Objects":[{"StartTime":128450.0,"EndTime":128688.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":128450.0,"Objects":[{"StartTime":128450.0,"EndTime":128450.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":128688.0,"Objects":[{"StartTime":128688.0,"EndTime":128926.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":128926.0,"Objects":[{"StartTime":128926.0,"EndTime":129164.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":128926.0,"Objects":[{"StartTime":128926.0,"EndTime":128926.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":129164.0,"Objects":[{"StartTime":129164.0,"EndTime":129402.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":129283.0,"Objects":[{"StartTime":129283.0,"EndTime":129283.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":129403.0,"Objects":[{"StartTime":129403.0,"EndTime":129641.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":129640.0,"Objects":[{"StartTime":129640.0,"EndTime":130116.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":129640.0,"Objects":[{"StartTime":129640.0,"EndTime":129640.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":129878.0,"Objects":[{"StartTime":129878.0,"EndTime":129878.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":129879.0,"Objects":[{"StartTime":129879.0,"EndTime":129879.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":130116.0,"Objects":[{"StartTime":130116.0,"EndTime":130116.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":130116.0,"Objects":[{"StartTime":130116.0,"EndTime":130354.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":130354.0,"Objects":[{"StartTime":130354.0,"EndTime":130354.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":130355.0,"Objects":[{"StartTime":130355.0,"EndTime":130355.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":130593.0,"Objects":[{"StartTime":130593.0,"EndTime":130831.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":130593.0,"Objects":[{"StartTime":130593.0,"EndTime":130593.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":130831.0,"Objects":[{"StartTime":130831.0,"EndTime":130831.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":130831.0,"Objects":[{"StartTime":130831.0,"EndTime":131069.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":131069.0,"Objects":[{"StartTime":131069.0,"EndTime":131307.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":131307.0,"Objects":[{"StartTime":131307.0,"EndTime":131545.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":131307.0,"Objects":[{"StartTime":131307.0,"EndTime":131307.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":131545.0,"Objects":[{"StartTime":131545.0,"EndTime":131783.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":131783.0,"Objects":[{"StartTime":131783.0,"EndTime":132021.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":131783.0,"Objects":[{"StartTime":131783.0,"EndTime":131783.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":132021.0,"Objects":[{"StartTime":132021.0,"EndTime":132259.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":132022.0,"Objects":[{"StartTime":132022.0,"EndTime":132260.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":132260.0,"Objects":[{"StartTime":132260.0,"EndTime":132498.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":132260.0,"Objects":[{"StartTime":132260.0,"EndTime":132260.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":132497.0,"Objects":[{"StartTime":132497.0,"EndTime":132735.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":132498.0,"Objects":[{"StartTime":132498.0,"EndTime":132736.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":132736.0,"Objects":[{"StartTime":132736.0,"EndTime":132974.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":132736.0,"Objects":[{"StartTime":132736.0,"EndTime":132736.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":132974.0,"Objects":[{"StartTime":132974.0,"EndTime":133212.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":133212.0,"Objects":[{"StartTime":133212.0,"EndTime":133450.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":133212.0,"Objects":[{"StartTime":133212.0,"EndTime":133212.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":133450.0,"Objects":[{"StartTime":133450.0,"EndTime":133926.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":133688.0,"Objects":[{"StartTime":133688.0,"EndTime":133688.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":133688.0,"Objects":[{"StartTime":133688.0,"EndTime":133688.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":133926.0,"Objects":[{"StartTime":133926.0,"EndTime":133926.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":133926.0,"Objects":[{"StartTime":133926.0,"EndTime":134164.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":134164.0,"Objects":[{"StartTime":134164.0,"EndTime":134164.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":134164.0,"Objects":[{"StartTime":134164.0,"EndTime":134164.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":134403.0,"Objects":[{"StartTime":134403.0,"EndTime":134403.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":134403.0,"Objects":[{"StartTime":134403.0,"EndTime":134641.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":134640.0,"Objects":[{"StartTime":134640.0,"EndTime":134878.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":134641.0,"Objects":[{"StartTime":134641.0,"EndTime":134641.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":134878.0,"Objects":[{"StartTime":134878.0,"EndTime":135116.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":135117.0,"Objects":[{"StartTime":135117.0,"EndTime":135355.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":135117.0,"Objects":[{"StartTime":135117.0,"EndTime":135117.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":135354.0,"Objects":[{"StartTime":135354.0,"EndTime":136068.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":135354.0,"Objects":[{"StartTime":135354.0,"EndTime":135354.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":135593.0,"Objects":[{"StartTime":135593.0,"EndTime":135593.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":135593.0,"Objects":[{"StartTime":135593.0,"EndTime":135831.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":136069.0,"Objects":[{"StartTime":136069.0,"EndTime":136307.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":136069.0,"Objects":[{"StartTime":136069.0,"EndTime":136069.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":136307.0,"Objects":[{"StartTime":136307.0,"EndTime":136783.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":136545.0,"Objects":[{"StartTime":136545.0,"EndTime":136783.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":136545.0,"Objects":[{"StartTime":136545.0,"EndTime":136545.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":136783.0,"Objects":[{"StartTime":136783.0,"EndTime":136783.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":136902.0,"Objects":[{"StartTime":136902.0,"EndTime":136902.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":137021.0,"Objects":[{"StartTime":137021.0,"EndTime":137021.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":137022.0,"Objects":[{"StartTime":137022.0,"EndTime":137260.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":137259.0,"Objects":[{"StartTime":137259.0,"EndTime":137497.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":137259.0,"Objects":[{"StartTime":137259.0,"EndTime":137259.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":137497.0,"Objects":[{"StartTime":137497.0,"EndTime":137497.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":137497.0,"Objects":[{"StartTime":137497.0,"EndTime":137497.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":137735.0,"Objects":[{"StartTime":137735.0,"EndTime":137735.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":137736.0,"Objects":[{"StartTime":137736.0,"EndTime":137974.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":137974.0,"Objects":[{"StartTime":137974.0,"EndTime":137974.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":137974.0,"Objects":[{"StartTime":137974.0,"EndTime":137974.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":138212.0,"Objects":[{"StartTime":138212.0,"EndTime":138450.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":138212.0,"Objects":[{"StartTime":138212.0,"EndTime":138212.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":138450.0,"Objects":[{"StartTime":138450.0,"EndTime":138688.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":138450.0,"Objects":[{"StartTime":138450.0,"EndTime":138450.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":138688.0,"Objects":[{"StartTime":138688.0,"EndTime":138926.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":138926.0,"Objects":[{"StartTime":138926.0,"EndTime":139164.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":138927.0,"Objects":[{"StartTime":138927.0,"EndTime":138927.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":139164.0,"Objects":[{"StartTime":139164.0,"EndTime":139402.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":139403.0,"Objects":[{"StartTime":139403.0,"EndTime":139641.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":139403.0,"Objects":[{"StartTime":139403.0,"EndTime":139403.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":139640.0,"Objects":[{"StartTime":139640.0,"EndTime":139878.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":139878.0,"Objects":[{"StartTime":139878.0,"EndTime":140116.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":139879.0,"Objects":[{"StartTime":139879.0,"EndTime":139879.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":140116.0,"Objects":[{"StartTime":140116.0,"EndTime":140592.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":140354.0,"Objects":[{"StartTime":140354.0,"EndTime":140592.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":140355.0,"Objects":[{"StartTime":140355.0,"EndTime":140355.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":140593.0,"Objects":[{"StartTime":140593.0,"EndTime":140593.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":140831.0,"Objects":[{"StartTime":140831.0,"EndTime":140831.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":140831.0,"Objects":[{"StartTime":140831.0,"EndTime":141069.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":140831.0,"Objects":[{"StartTime":140831.0,"EndTime":140831.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":141069.0,"Objects":[{"StartTime":141069.0,"EndTime":141307.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":141307.0,"Objects":[{"StartTime":141307.0,"EndTime":141545.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":141307.0,"Objects":[{"StartTime":141307.0,"EndTime":141307.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":141546.0,"Objects":[{"StartTime":141546.0,"EndTime":141784.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":141783.0,"Objects":[{"StartTime":141783.0,"EndTime":141783.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":141784.0,"Objects":[{"StartTime":141784.0,"EndTime":141784.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":142021.0,"Objects":[{"StartTime":142021.0,"EndTime":142021.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":142022.0,"Objects":[{"StartTime":142022.0,"EndTime":142260.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":142259.0,"Objects":[{"StartTime":142259.0,"EndTime":142259.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":142260.0,"Objects":[{"StartTime":142260.0,"EndTime":142260.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":142497.0,"Objects":[{"StartTime":142497.0,"EndTime":142497.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":142498.0,"Objects":[{"StartTime":142498.0,"EndTime":142736.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":142736.0,"Objects":[{"StartTime":142736.0,"EndTime":142736.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":142736.0,"Objects":[{"StartTime":142736.0,"EndTime":142974.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":142974.0,"Objects":[{"StartTime":142974.0,"EndTime":143450.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":143212.0,"Objects":[{"StartTime":143212.0,"EndTime":143212.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":143212.0,"Objects":[{"StartTime":143212.0,"EndTime":143450.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":143450.0,"Objects":[{"StartTime":143450.0,"EndTime":143688.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":143688.0,"Objects":[{"StartTime":143688.0,"EndTime":143688.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":143688.0,"Objects":[{"StartTime":143688.0,"EndTime":143926.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":143927.0,"Objects":[{"StartTime":143927.0,"EndTime":144165.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":144164.0,"Objects":[{"StartTime":144164.0,"EndTime":144402.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":144165.0,"Objects":[{"StartTime":144165.0,"EndTime":144165.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":144403.0,"Objects":[{"StartTime":144403.0,"EndTime":144641.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":144521.0,"Objects":[{"StartTime":144521.0,"EndTime":144521.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":144641.0,"Objects":[{"StartTime":144641.0,"EndTime":144879.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":144878.0,"Objects":[{"StartTime":144878.0,"EndTime":145354.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":144878.0,"Objects":[{"StartTime":144878.0,"EndTime":144878.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":145116.0,"Objects":[{"StartTime":145116.0,"EndTime":145116.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":145116.0,"Objects":[{"StartTime":145116.0,"EndTime":145116.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":145354.0,"Objects":[{"StartTime":145354.0,"EndTime":145354.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":145354.0,"Objects":[{"StartTime":145354.0,"EndTime":145592.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":145593.0,"Objects":[{"StartTime":145593.0,"EndTime":145593.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":145593.0,"Objects":[{"StartTime":145593.0,"EndTime":145593.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":145831.0,"Objects":[{"StartTime":145831.0,"EndTime":145831.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":145831.0,"Objects":[{"StartTime":145831.0,"EndTime":146069.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":146069.0,"Objects":[{"StartTime":146069.0,"EndTime":146069.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":146069.0,"Objects":[{"StartTime":146069.0,"EndTime":146307.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":146307.0,"Objects":[{"StartTime":146307.0,"EndTime":146545.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":146546.0,"Objects":[{"StartTime":146546.0,"EndTime":146784.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":146546.0,"Objects":[{"StartTime":146546.0,"EndTime":146546.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":146783.0,"Objects":[{"StartTime":146783.0,"EndTime":147021.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":147022.0,"Objects":[{"StartTime":147022.0,"EndTime":147260.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":147022.0,"Objects":[{"StartTime":147022.0,"EndTime":147022.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":147259.0,"Objects":[{"StartTime":147259.0,"EndTime":147497.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":147260.0,"Objects":[{"StartTime":147260.0,"EndTime":147498.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":147498.0,"Objects":[{"StartTime":147498.0,"EndTime":147736.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":147498.0,"Objects":[{"StartTime":147498.0,"EndTime":147498.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":147736.0,"Objects":[{"StartTime":147736.0,"EndTime":147974.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":147974.0,"Objects":[{"StartTime":147974.0,"EndTime":148212.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":147974.0,"Objects":[{"StartTime":147974.0,"EndTime":147974.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":148212.0,"Objects":[{"StartTime":148212.0,"EndTime":148450.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":148450.0,"Objects":[{"StartTime":148450.0,"EndTime":148688.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":148450.0,"Objects":[{"StartTime":148450.0,"EndTime":148450.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":148688.0,"Objects":[{"StartTime":148688.0,"EndTime":149164.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":148688.0,"Objects":[{"StartTime":148688.0,"EndTime":148688.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":148926.0,"Objects":[{"StartTime":148926.0,"EndTime":148926.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":148926.0,"Objects":[{"StartTime":148926.0,"EndTime":148926.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":149164.0,"Objects":[{"StartTime":149164.0,"EndTime":149402.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":149164.0,"Objects":[{"StartTime":149164.0,"EndTime":149164.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":149402.0,"Objects":[{"StartTime":149402.0,"EndTime":149402.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":149403.0,"Objects":[{"StartTime":149403.0,"EndTime":149403.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":149640.0,"Objects":[{"StartTime":149640.0,"EndTime":149878.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":149641.0,"Objects":[{"StartTime":149641.0,"EndTime":149641.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":149878.0,"Objects":[{"StartTime":149878.0,"EndTime":150116.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":149879.0,"Objects":[{"StartTime":149879.0,"EndTime":149879.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":150117.0,"Objects":[{"StartTime":150117.0,"EndTime":150355.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":150355.0,"Objects":[{"StartTime":150355.0,"EndTime":150593.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":150355.0,"Objects":[{"StartTime":150355.0,"EndTime":150355.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":150593.0,"Objects":[{"StartTime":150593.0,"EndTime":150831.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":150831.0,"Objects":[{"StartTime":150831.0,"EndTime":150831.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":150831.0,"Objects":[{"StartTime":150831.0,"EndTime":151069.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":151069.0,"Objects":[{"StartTime":151069.0,"EndTime":151307.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":151307.0,"Objects":[{"StartTime":151307.0,"EndTime":151545.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":151307.0,"Objects":[{"StartTime":151307.0,"EndTime":151307.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":151545.0,"Objects":[{"StartTime":151545.0,"EndTime":151783.0,"Column":2}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":151783.0,"Objects":[{"StartTime":151783.0,"EndTime":152021.0,"Column":3}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":151783.0,"Objects":[{"StartTime":151783.0,"EndTime":151783.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":152022.0,"Objects":[{"StartTime":152022.0,"EndTime":152260.0,"Column":1}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":152140.0,"Objects":[{"StartTime":152140.0,"EndTime":152140.0,"Column":0}]},{"RandomW":2659271247,"RandomX":3579807591,"RandomY":273326509,"RandomZ":273071671,"StartTime":152260.0,"Objects":[{"StartTime":152260.0,"EndTime":152498.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":152497.0,"Objects":[{"StartTime":152497.0,"EndTime":153687.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":152497.0,"Objects":[{"StartTime":152497.0,"EndTime":152497.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":152735.0,"Objects":[{"StartTime":152735.0,"EndTime":152735.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":153093.0,"Objects":[{"StartTime":153093.0,"EndTime":153093.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":153688.0,"Objects":[{"StartTime":153688.0,"EndTime":153688.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":153926.0,"Objects":[{"StartTime":153926.0,"EndTime":153926.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":154045.0,"Objects":[{"StartTime":154045.0,"EndTime":154045.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":154402.0,"Objects":[{"StartTime":154402.0,"EndTime":155116.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":154640.0,"Objects":[{"StartTime":154640.0,"EndTime":154640.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":154997.0,"Objects":[{"StartTime":154997.0,"EndTime":154997.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":155354.0,"Objects":[{"StartTime":155354.0,"EndTime":156068.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":155593.0,"Objects":[{"StartTime":155593.0,"EndTime":155593.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":155831.0,"Objects":[{"StartTime":155831.0,"EndTime":155831.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":155950.0,"Objects":[{"StartTime":155950.0,"EndTime":155950.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":156069.0,"Objects":[{"StartTime":156069.0,"EndTime":156069.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":156307.0,"Objects":[{"StartTime":156307.0,"EndTime":157021.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":156307.0,"Objects":[{"StartTime":156307.0,"EndTime":156307.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":156545.0,"Objects":[{"StartTime":156545.0,"EndTime":156545.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":156902.0,"Objects":[{"StartTime":156902.0,"EndTime":156902.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":157259.0,"Objects":[{"StartTime":157259.0,"EndTime":157973.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":157497.0,"Objects":[{"StartTime":157497.0,"EndTime":157497.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":157735.0,"Objects":[{"StartTime":157735.0,"EndTime":157735.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":157854.0,"Objects":[{"StartTime":157854.0,"EndTime":157854.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":158212.0,"Objects":[{"StartTime":158212.0,"EndTime":158926.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":158450.0,"Objects":[{"StartTime":158450.0,"EndTime":158450.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":158807.0,"Objects":[{"StartTime":158807.0,"EndTime":158807.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":159164.0,"Objects":[{"StartTime":159164.0,"EndTime":159878.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":159402.0,"Objects":[{"StartTime":159402.0,"EndTime":159402.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":159640.0,"Objects":[{"StartTime":159640.0,"EndTime":159640.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":159759.0,"Objects":[{"StartTime":159759.0,"EndTime":159759.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":159878.0,"Objects":[{"StartTime":159878.0,"EndTime":159878.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":160116.0,"Objects":[{"StartTime":160116.0,"EndTime":160830.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":160116.0,"Objects":[{"StartTime":160116.0,"EndTime":160116.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":160354.0,"Objects":[{"StartTime":160354.0,"EndTime":160354.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":160712.0,"Objects":[{"StartTime":160712.0,"EndTime":160712.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":161069.0,"Objects":[{"StartTime":161069.0,"EndTime":161783.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":161307.0,"Objects":[{"StartTime":161307.0,"EndTime":161307.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":161545.0,"Objects":[{"StartTime":161545.0,"EndTime":161545.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":161664.0,"Objects":[{"StartTime":161664.0,"EndTime":161664.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":162021.0,"Objects":[{"StartTime":162021.0,"EndTime":162735.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":162259.0,"Objects":[{"StartTime":162259.0,"EndTime":162259.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":162616.0,"Objects":[{"StartTime":162616.0,"EndTime":162616.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":162974.0,"Objects":[{"StartTime":162974.0,"EndTime":163688.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":163212.0,"Objects":[{"StartTime":163212.0,"EndTime":163212.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":163450.0,"Objects":[{"StartTime":163450.0,"EndTime":163450.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":163569.0,"Objects":[{"StartTime":163569.0,"EndTime":163569.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":163688.0,"Objects":[{"StartTime":163688.0,"EndTime":163688.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":163926.0,"Objects":[{"StartTime":163926.0,"EndTime":163926.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":163926.0,"Objects":[{"StartTime":163926.0,"EndTime":164640.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":164164.0,"Objects":[{"StartTime":164164.0,"EndTime":164164.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":164521.0,"Objects":[{"StartTime":164521.0,"EndTime":164521.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":164878.0,"Objects":[{"StartTime":164878.0,"EndTime":165592.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":165116.0,"Objects":[{"StartTime":165116.0,"EndTime":165116.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":165354.0,"Objects":[{"StartTime":165354.0,"EndTime":165354.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":165474.0,"Objects":[{"StartTime":165474.0,"EndTime":165474.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":165831.0,"Objects":[{"StartTime":165831.0,"EndTime":166545.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":166069.0,"Objects":[{"StartTime":166069.0,"EndTime":166069.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":166426.0,"Objects":[{"StartTime":166426.0,"EndTime":166426.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":166783.0,"Objects":[{"StartTime":166783.0,"EndTime":167973.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":167021.0,"Objects":[{"StartTime":167021.0,"EndTime":167021.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":167259.0,"Objects":[{"StartTime":167259.0,"EndTime":167259.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":167378.0,"Objects":[{"StartTime":167378.0,"EndTime":167378.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":167497.0,"Objects":[{"StartTime":167497.0,"EndTime":167497.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":167735.0,"Objects":[{"StartTime":167735.0,"EndTime":167973.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":167974.0,"Objects":[{"StartTime":167974.0,"EndTime":167974.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":168212.0,"Objects":[{"StartTime":168212.0,"EndTime":168212.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":168450.0,"Objects":[{"StartTime":168450.0,"EndTime":168450.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":168450.0,"Objects":[{"StartTime":168450.0,"EndTime":168450.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":168450.0,"Objects":[{"StartTime":168450.0,"EndTime":168450.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":168688.0,"Objects":[{"StartTime":168688.0,"EndTime":168688.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":168926.0,"Objects":[{"StartTime":168926.0,"EndTime":168926.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":168926.0,"Objects":[{"StartTime":168926.0,"EndTime":169164.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":169402.0,"Objects":[{"StartTime":169402.0,"EndTime":169402.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":169402.0,"Objects":[{"StartTime":169402.0,"EndTime":169402.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":169402.0,"Objects":[{"StartTime":169402.0,"EndTime":169640.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":169640.0,"Objects":[{"StartTime":169640.0,"EndTime":169640.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":169878.0,"Objects":[{"StartTime":169878.0,"EndTime":170354.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":169878.0,"Objects":[{"StartTime":169878.0,"EndTime":170116.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":170354.0,"Objects":[{"StartTime":170354.0,"EndTime":170354.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":170354.0,"Objects":[{"StartTime":170354.0,"EndTime":170592.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":170593.0,"Objects":[{"StartTime":170593.0,"EndTime":171069.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":170593.0,"Objects":[{"StartTime":170593.0,"EndTime":170593.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":170831.0,"Objects":[{"StartTime":170831.0,"EndTime":171069.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":171307.0,"Objects":[{"StartTime":171307.0,"EndTime":171307.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":171307.0,"Objects":[{"StartTime":171307.0,"EndTime":171783.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":171307.0,"Objects":[{"StartTime":171307.0,"EndTime":171545.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":171783.0,"Objects":[{"StartTime":171783.0,"EndTime":171783.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":172021.0,"Objects":[{"StartTime":172021.0,"EndTime":172021.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":172259.0,"Objects":[{"StartTime":172259.0,"EndTime":172259.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":172259.0,"Objects":[{"StartTime":172259.0,"EndTime":172259.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":172259.0,"Objects":[{"StartTime":172259.0,"EndTime":172259.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":172497.0,"Objects":[{"StartTime":172497.0,"EndTime":172497.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":172735.0,"Objects":[{"StartTime":172735.0,"EndTime":172735.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":172735.0,"Objects":[{"StartTime":172735.0,"EndTime":172973.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":173212.0,"Objects":[{"StartTime":173212.0,"EndTime":173212.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":173212.0,"Objects":[{"StartTime":173212.0,"EndTime":173212.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":173212.0,"Objects":[{"StartTime":173212.0,"EndTime":173450.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":173450.0,"Objects":[{"StartTime":173450.0,"EndTime":173450.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":173688.0,"Objects":[{"StartTime":173688.0,"EndTime":174164.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":173688.0,"Objects":[{"StartTime":173688.0,"EndTime":173688.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":173688.0,"Objects":[{"StartTime":173688.0,"EndTime":173926.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":173926.0,"Objects":[{"StartTime":173926.0,"EndTime":173926.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":174164.0,"Objects":[{"StartTime":174164.0,"EndTime":174164.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":174164.0,"Objects":[{"StartTime":174164.0,"EndTime":174164.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":174164.0,"Objects":[{"StartTime":174164.0,"EndTime":174402.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":174402.0,"Objects":[{"StartTime":174402.0,"EndTime":174878.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":174402.0,"Objects":[{"StartTime":174402.0,"EndTime":174402.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":174640.0,"Objects":[{"StartTime":174640.0,"EndTime":174640.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":174640.0,"Objects":[{"StartTime":174640.0,"EndTime":174878.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":174878.0,"Objects":[{"StartTime":174878.0,"EndTime":174878.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":175116.0,"Objects":[{"StartTime":175116.0,"EndTime":175116.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":175116.0,"Objects":[{"StartTime":175116.0,"EndTime":175592.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":175116.0,"Objects":[{"StartTime":175116.0,"EndTime":175116.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":175116.0,"Objects":[{"StartTime":175116.0,"EndTime":175354.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":175354.0,"Objects":[{"StartTime":175354.0,"EndTime":175592.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":175593.0,"Objects":[{"StartTime":175593.0,"EndTime":175593.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":175831.0,"Objects":[{"StartTime":175831.0,"EndTime":176307.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":175831.0,"Objects":[{"StartTime":175831.0,"EndTime":175831.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":176069.0,"Objects":[{"StartTime":176069.0,"EndTime":176069.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":176069.0,"Objects":[{"StartTime":176069.0,"EndTime":176069.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":176069.0,"Objects":[{"StartTime":176069.0,"EndTime":176069.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":176307.0,"Objects":[{"StartTime":176307.0,"EndTime":176307.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":176545.0,"Objects":[{"StartTime":176545.0,"EndTime":176545.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":176545.0,"Objects":[{"StartTime":176545.0,"EndTime":176783.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":177021.0,"Objects":[{"StartTime":177021.0,"EndTime":177021.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":177021.0,"Objects":[{"StartTime":177021.0,"EndTime":177021.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":177021.0,"Objects":[{"StartTime":177021.0,"EndTime":177259.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":177259.0,"Objects":[{"StartTime":177259.0,"EndTime":177259.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":177497.0,"Objects":[{"StartTime":177497.0,"EndTime":177973.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":177497.0,"Objects":[{"StartTime":177497.0,"EndTime":177497.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":177497.0,"Objects":[{"StartTime":177497.0,"EndTime":177735.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":177974.0,"Objects":[{"StartTime":177974.0,"EndTime":177974.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":177974.0,"Objects":[{"StartTime":177974.0,"EndTime":177974.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":177974.0,"Objects":[{"StartTime":177974.0,"EndTime":178212.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":178212.0,"Objects":[{"StartTime":178212.0,"EndTime":178212.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":178450.0,"Objects":[{"StartTime":178450.0,"EndTime":178450.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":178450.0,"Objects":[{"StartTime":178450.0,"EndTime":178688.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":178450.0,"Objects":[{"StartTime":178450.0,"EndTime":178688.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":178926.0,"Objects":[{"StartTime":178926.0,"EndTime":178926.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":178926.0,"Objects":[{"StartTime":178926.0,"EndTime":179402.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":178926.0,"Objects":[{"StartTime":178926.0,"EndTime":178926.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":178926.0,"Objects":[{"StartTime":178926.0,"EndTime":179164.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":179164.0,"Objects":[{"StartTime":179164.0,"EndTime":179402.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":179402.0,"Objects":[{"StartTime":179402.0,"EndTime":179402.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":179640.0,"Objects":[{"StartTime":179640.0,"EndTime":179640.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":179878.0,"Objects":[{"StartTime":179878.0,"EndTime":180354.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":179878.0,"Objects":[{"StartTime":179878.0,"EndTime":179878.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":179878.0,"Objects":[{"StartTime":179878.0,"EndTime":179878.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":180116.0,"Objects":[{"StartTime":180116.0,"EndTime":180116.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":180354.0,"Objects":[{"StartTime":180354.0,"EndTime":180354.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":180354.0,"Objects":[{"StartTime":180354.0,"EndTime":180592.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":180593.0,"Objects":[{"StartTime":180593.0,"EndTime":181069.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":180831.0,"Objects":[{"StartTime":180831.0,"EndTime":180831.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":180831.0,"Objects":[{"StartTime":180831.0,"EndTime":181069.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":181069.0,"Objects":[{"StartTime":181069.0,"EndTime":181069.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":181306.0,"Objects":[{"StartTime":181306.0,"EndTime":181782.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":181307.0,"Objects":[{"StartTime":181307.0,"EndTime":181783.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":181307.0,"Objects":[{"StartTime":181307.0,"EndTime":181545.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":181783.0,"Objects":[{"StartTime":181783.0,"EndTime":182021.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":182021.0,"Objects":[{"StartTime":182021.0,"EndTime":182497.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":182021.0,"Objects":[{"StartTime":182021.0,"EndTime":182497.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":182259.0,"Objects":[{"StartTime":182259.0,"EndTime":182497.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":182497.0,"Objects":[{"StartTime":182497.0,"EndTime":182497.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":182735.0,"Objects":[{"StartTime":182735.0,"EndTime":182735.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":182735.0,"Objects":[{"StartTime":182735.0,"EndTime":182735.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":182974.0,"Objects":[{"StartTime":182974.0,"EndTime":183212.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":183211.0,"Objects":[{"StartTime":183211.0,"EndTime":183211.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":183211.0,"Objects":[{"StartTime":183211.0,"EndTime":183211.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":183449.0,"Objects":[{"StartTime":183449.0,"EndTime":183687.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":183449.0,"Objects":[{"StartTime":183449.0,"EndTime":183449.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":183687.0,"Objects":[{"StartTime":183687.0,"EndTime":183687.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":183687.0,"Objects":[{"StartTime":183687.0,"EndTime":183687.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":183925.0,"Objects":[{"StartTime":183925.0,"EndTime":183925.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":183925.0,"Objects":[{"StartTime":183925.0,"EndTime":184163.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":184163.0,"Objects":[{"StartTime":184163.0,"EndTime":184401.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":184163.0,"Objects":[{"StartTime":184163.0,"EndTime":184163.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":184401.0,"Objects":[{"StartTime":184401.0,"EndTime":184639.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":184639.0,"Objects":[{"StartTime":184639.0,"EndTime":184639.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":184639.0,"Objects":[{"StartTime":184639.0,"EndTime":184877.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":184878.0,"Objects":[{"StartTime":184878.0,"EndTime":185116.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":185116.0,"Objects":[{"StartTime":185116.0,"EndTime":185354.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":185116.0,"Objects":[{"StartTime":185116.0,"EndTime":185116.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":185354.0,"Objects":[{"StartTime":185354.0,"EndTime":185830.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":185592.0,"Objects":[{"StartTime":185592.0,"EndTime":185592.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":185592.0,"Objects":[{"StartTime":185592.0,"EndTime":185830.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":185830.0,"Objects":[{"StartTime":185830.0,"EndTime":186068.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":186068.0,"Objects":[{"StartTime":186068.0,"EndTime":186306.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":186068.0,"Objects":[{"StartTime":186068.0,"EndTime":186068.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":186306.0,"Objects":[{"StartTime":186306.0,"EndTime":186306.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":186544.0,"Objects":[{"StartTime":186544.0,"EndTime":186782.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":186544.0,"Objects":[{"StartTime":186544.0,"EndTime":186544.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":186544.0,"Objects":[{"StartTime":186544.0,"EndTime":186544.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":186782.0,"Objects":[{"StartTime":186782.0,"EndTime":187020.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":187020.0,"Objects":[{"StartTime":187020.0,"EndTime":187020.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":187020.0,"Objects":[{"StartTime":187020.0,"EndTime":187020.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":187258.0,"Objects":[{"StartTime":187258.0,"EndTime":187258.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":187258.0,"Objects":[{"StartTime":187258.0,"EndTime":187496.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":187497.0,"Objects":[{"StartTime":187497.0,"EndTime":187497.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":187497.0,"Objects":[{"StartTime":187497.0,"EndTime":187497.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":187735.0,"Objects":[{"StartTime":187735.0,"EndTime":187735.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":187735.0,"Objects":[{"StartTime":187735.0,"EndTime":187973.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":187973.0,"Objects":[{"StartTime":187973.0,"EndTime":188211.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":187973.0,"Objects":[{"StartTime":187973.0,"EndTime":187973.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":188211.0,"Objects":[{"StartTime":188211.0,"EndTime":188449.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":188449.0,"Objects":[{"StartTime":188449.0,"EndTime":188687.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":188449.0,"Objects":[{"StartTime":188449.0,"EndTime":188449.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":188688.0,"Objects":[{"StartTime":188688.0,"EndTime":188926.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":188925.0,"Objects":[{"StartTime":188925.0,"EndTime":189163.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":188925.0,"Objects":[{"StartTime":188925.0,"EndTime":188925.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":188926.0,"Objects":[{"StartTime":188926.0,"EndTime":189164.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":189163.0,"Objects":[{"StartTime":189163.0,"EndTime":189401.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":189401.0,"Objects":[{"StartTime":189401.0,"EndTime":189639.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":189401.0,"Objects":[{"StartTime":189401.0,"EndTime":189401.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":189402.0,"Objects":[{"StartTime":189402.0,"EndTime":189878.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":189639.0,"Objects":[{"StartTime":189639.0,"EndTime":189877.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":189878.0,"Objects":[{"StartTime":189878.0,"EndTime":190116.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":189878.0,"Objects":[{"StartTime":189878.0,"EndTime":189878.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":190116.0,"Objects":[{"StartTime":190116.0,"EndTime":190354.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":190235.0,"Objects":[{"StartTime":190235.0,"EndTime":190235.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":190354.0,"Objects":[{"StartTime":190354.0,"EndTime":190592.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":190592.0,"Objects":[{"StartTime":190592.0,"EndTime":190949.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":190593.0,"Objects":[{"StartTime":190593.0,"EndTime":190593.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":190830.0,"Objects":[{"StartTime":190830.0,"EndTime":190830.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":190830.0,"Objects":[{"StartTime":190830.0,"EndTime":190830.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":191068.0,"Objects":[{"StartTime":191068.0,"EndTime":191068.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":191068.0,"Objects":[{"StartTime":191068.0,"EndTime":191306.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":191069.0,"Objects":[{"StartTime":191069.0,"EndTime":191069.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":191306.0,"Objects":[{"StartTime":191306.0,"EndTime":191306.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":191306.0,"Objects":[{"StartTime":191306.0,"EndTime":191306.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":191307.0,"Objects":[{"StartTime":191307.0,"EndTime":191307.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":191544.0,"Objects":[{"StartTime":191544.0,"EndTime":191544.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":191544.0,"Objects":[{"StartTime":191544.0,"EndTime":191782.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":191545.0,"Objects":[{"StartTime":191545.0,"EndTime":191783.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":191782.0,"Objects":[{"StartTime":191782.0,"EndTime":192020.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":191782.0,"Objects":[{"StartTime":191782.0,"EndTime":191782.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":192020.0,"Objects":[{"StartTime":192020.0,"EndTime":192258.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":192021.0,"Objects":[{"StartTime":192021.0,"EndTime":192259.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":192258.0,"Objects":[{"StartTime":192258.0,"EndTime":192496.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":192258.0,"Objects":[{"StartTime":192258.0,"EndTime":192258.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":192497.0,"Objects":[{"StartTime":192497.0,"EndTime":192735.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":192497.0,"Objects":[{"StartTime":192497.0,"EndTime":193449.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":192735.0,"Objects":[{"StartTime":192735.0,"EndTime":192973.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":192735.0,"Objects":[{"StartTime":192735.0,"EndTime":192735.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":192973.0,"Objects":[{"StartTime":192973.0,"EndTime":193211.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":193211.0,"Objects":[{"StartTime":193211.0,"EndTime":193449.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":193211.0,"Objects":[{"StartTime":193211.0,"EndTime":193211.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":193450.0,"Objects":[{"StartTime":193450.0,"EndTime":193688.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":193687.0,"Objects":[{"StartTime":193687.0,"EndTime":193925.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":193688.0,"Objects":[{"StartTime":193688.0,"EndTime":193688.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":193925.0,"Objects":[{"StartTime":193925.0,"EndTime":194163.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":194163.0,"Objects":[{"StartTime":194163.0,"EndTime":194401.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":194163.0,"Objects":[{"StartTime":194163.0,"EndTime":194163.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":194639.0,"Objects":[{"StartTime":194639.0,"EndTime":194639.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":194640.0,"Objects":[{"StartTime":194640.0,"EndTime":194878.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":194640.0,"Objects":[{"StartTime":194640.0,"EndTime":194640.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":194640.0,"Objects":[{"StartTime":194640.0,"EndTime":194640.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":194878.0,"Objects":[{"StartTime":194878.0,"EndTime":194878.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":194878.0,"Objects":[{"StartTime":194878.0,"EndTime":195116.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":195116.0,"Objects":[{"StartTime":195116.0,"EndTime":195116.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":195116.0,"Objects":[{"StartTime":195116.0,"EndTime":195116.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":195116.0,"Objects":[{"StartTime":195116.0,"EndTime":195116.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":195354.0,"Objects":[{"StartTime":195354.0,"EndTime":195354.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":195354.0,"Objects":[{"StartTime":195354.0,"EndTime":195592.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":195354.0,"Objects":[{"StartTime":195354.0,"EndTime":195592.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":195592.0,"Objects":[{"StartTime":195592.0,"EndTime":195830.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":195592.0,"Objects":[{"StartTime":195592.0,"EndTime":195592.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":195830.0,"Objects":[{"StartTime":195830.0,"EndTime":196068.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":195831.0,"Objects":[{"StartTime":195831.0,"EndTime":196069.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":196068.0,"Objects":[{"StartTime":196068.0,"EndTime":196306.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":196068.0,"Objects":[{"StartTime":196068.0,"EndTime":196068.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":196306.0,"Objects":[{"StartTime":196306.0,"EndTime":197496.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":196307.0,"Objects":[{"StartTime":196307.0,"EndTime":196545.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":196544.0,"Objects":[{"StartTime":196544.0,"EndTime":196782.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":196544.0,"Objects":[{"StartTime":196544.0,"EndTime":196544.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":197020.0,"Objects":[{"StartTime":197020.0,"EndTime":197258.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":197020.0,"Objects":[{"StartTime":197020.0,"EndTime":197020.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":197258.0,"Objects":[{"StartTime":197258.0,"EndTime":197734.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":197497.0,"Objects":[{"StartTime":197497.0,"EndTime":197735.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":197497.0,"Objects":[{"StartTime":197497.0,"EndTime":197497.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":197735.0,"Objects":[{"StartTime":197735.0,"EndTime":197735.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":197854.0,"Objects":[{"StartTime":197854.0,"EndTime":197854.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":197973.0,"Objects":[{"StartTime":197973.0,"EndTime":197973.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":197973.0,"Objects":[{"StartTime":197973.0,"EndTime":198211.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":198211.0,"Objects":[{"StartTime":198211.0,"EndTime":198449.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":198212.0,"Objects":[{"StartTime":198212.0,"EndTime":198212.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":198449.0,"Objects":[{"StartTime":198449.0,"EndTime":198687.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":198449.0,"Objects":[{"StartTime":198449.0,"EndTime":198449.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":198687.0,"Objects":[{"StartTime":198687.0,"EndTime":198925.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":198925.0,"Objects":[{"StartTime":198925.0,"EndTime":199163.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":198925.0,"Objects":[{"StartTime":198925.0,"EndTime":198925.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":199163.0,"Objects":[{"StartTime":199163.0,"EndTime":199401.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":199401.0,"Objects":[{"StartTime":199401.0,"EndTime":199639.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":199402.0,"Objects":[{"StartTime":199402.0,"EndTime":199402.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":199639.0,"Objects":[{"StartTime":199639.0,"EndTime":200115.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":199640.0,"Objects":[{"StartTime":199640.0,"EndTime":199878.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":199878.0,"Objects":[{"StartTime":199878.0,"EndTime":200116.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":199878.0,"Objects":[{"StartTime":199878.0,"EndTime":199878.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":200116.0,"Objects":[{"StartTime":200116.0,"EndTime":200354.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":200354.0,"Objects":[{"StartTime":200354.0,"EndTime":200354.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":200354.0,"Objects":[{"StartTime":200354.0,"EndTime":200354.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":200592.0,"Objects":[{"StartTime":200592.0,"EndTime":200830.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":200592.0,"Objects":[{"StartTime":200592.0,"EndTime":200592.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":200830.0,"Objects":[{"StartTime":200830.0,"EndTime":200830.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":200830.0,"Objects":[{"StartTime":200830.0,"EndTime":200830.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":201068.0,"Objects":[{"StartTime":201068.0,"EndTime":201306.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":201068.0,"Objects":[{"StartTime":201068.0,"EndTime":201068.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":201306.0,"Objects":[{"StartTime":201306.0,"EndTime":201306.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":201306.0,"Objects":[{"StartTime":201306.0,"EndTime":201544.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":201307.0,"Objects":[{"StartTime":201307.0,"EndTime":201545.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":201545.0,"Objects":[{"StartTime":201545.0,"EndTime":201545.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":201782.0,"Objects":[{"StartTime":201782.0,"EndTime":202020.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":201782.0,"Objects":[{"StartTime":201782.0,"EndTime":201782.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":201783.0,"Objects":[{"StartTime":201783.0,"EndTime":201783.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":202021.0,"Objects":[{"StartTime":202021.0,"EndTime":202259.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":202021.0,"Objects":[{"StartTime":202021.0,"EndTime":202735.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":202259.0,"Objects":[{"StartTime":202259.0,"EndTime":202259.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":202259.0,"Objects":[{"StartTime":202259.0,"EndTime":202497.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":202497.0,"Objects":[{"StartTime":202497.0,"EndTime":202735.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":202735.0,"Objects":[{"StartTime":202735.0,"EndTime":202735.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":202735.0,"Objects":[{"StartTime":202735.0,"EndTime":202973.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":202973.0,"Objects":[{"StartTime":202973.0,"EndTime":203211.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":203211.0,"Objects":[{"StartTime":203211.0,"EndTime":203211.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":203211.0,"Objects":[{"StartTime":203211.0,"EndTime":203449.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":203212.0,"Objects":[{"StartTime":203212.0,"EndTime":203450.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":203449.0,"Objects":[{"StartTime":203449.0,"EndTime":203687.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":203687.0,"Objects":[{"StartTime":203687.0,"EndTime":203687.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":203687.0,"Objects":[{"StartTime":203687.0,"EndTime":203925.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":203925.0,"Objects":[{"StartTime":203925.0,"EndTime":204401.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":203926.0,"Objects":[{"StartTime":203926.0,"EndTime":204640.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":204163.0,"Objects":[{"StartTime":204163.0,"EndTime":204163.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":204163.0,"Objects":[{"StartTime":204163.0,"EndTime":204163.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":204402.0,"Objects":[{"StartTime":204402.0,"EndTime":204402.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":204640.0,"Objects":[{"StartTime":204640.0,"EndTime":204640.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":204640.0,"Objects":[{"StartTime":204640.0,"EndTime":204640.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":204640.0,"Objects":[{"StartTime":204640.0,"EndTime":205116.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":204878.0,"Objects":[{"StartTime":204878.0,"EndTime":204878.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":204878.0,"Objects":[{"StartTime":204878.0,"EndTime":205116.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":205116.0,"Objects":[{"StartTime":205116.0,"EndTime":205116.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":205116.0,"Objects":[{"StartTime":205116.0,"EndTime":205354.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":205354.0,"Objects":[{"StartTime":205354.0,"EndTime":205592.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":205474.0,"Objects":[{"StartTime":205474.0,"EndTime":205474.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":205592.0,"Objects":[{"StartTime":205592.0,"EndTime":205830.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":205830.0,"Objects":[{"StartTime":205830.0,"EndTime":206068.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":205831.0,"Objects":[{"StartTime":205831.0,"EndTime":205831.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":206068.0,"Objects":[{"StartTime":206068.0,"EndTime":206068.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":206068.0,"Objects":[{"StartTime":206068.0,"EndTime":206306.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":206069.0,"Objects":[{"StartTime":206069.0,"EndTime":206069.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":206307.0,"Objects":[{"StartTime":206307.0,"EndTime":206545.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":206307.0,"Objects":[{"StartTime":206307.0,"EndTime":206545.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":206544.0,"Objects":[{"StartTime":206544.0,"EndTime":206544.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":206544.0,"Objects":[{"StartTime":206544.0,"EndTime":206782.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":206782.0,"Objects":[{"StartTime":206782.0,"EndTime":207020.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":206783.0,"Objects":[{"StartTime":206783.0,"EndTime":207021.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":207021.0,"Objects":[{"StartTime":207021.0,"EndTime":207259.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":207021.0,"Objects":[{"StartTime":207021.0,"EndTime":207021.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":207259.0,"Objects":[{"StartTime":207259.0,"EndTime":207497.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":207259.0,"Objects":[{"StartTime":207259.0,"EndTime":207497.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":207497.0,"Objects":[{"StartTime":207497.0,"EndTime":207735.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":207497.0,"Objects":[{"StartTime":207497.0,"EndTime":207497.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":207735.0,"Objects":[{"StartTime":207735.0,"EndTime":208449.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":207973.0,"Objects":[{"StartTime":207973.0,"EndTime":208211.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":207973.0,"Objects":[{"StartTime":207973.0,"EndTime":207973.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":208211.0,"Objects":[{"StartTime":208211.0,"EndTime":208449.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":208449.0,"Objects":[{"StartTime":208449.0,"EndTime":208687.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":208449.0,"Objects":[{"StartTime":208449.0,"EndTime":208449.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":208687.0,"Objects":[{"StartTime":208687.0,"EndTime":208925.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":208925.0,"Objects":[{"StartTime":208925.0,"EndTime":209163.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":208925.0,"Objects":[{"StartTime":208925.0,"EndTime":208925.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":209163.0,"Objects":[{"StartTime":209163.0,"EndTime":209401.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":209164.0,"Objects":[{"StartTime":209164.0,"EndTime":209640.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":209401.0,"Objects":[{"StartTime":209401.0,"EndTime":209639.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":209401.0,"Objects":[{"StartTime":209401.0,"EndTime":209401.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":209639.0,"Objects":[{"StartTime":209639.0,"EndTime":209877.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":209878.0,"Objects":[{"StartTime":209878.0,"EndTime":209878.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":209878.0,"Objects":[{"StartTime":209878.0,"EndTime":209878.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":210116.0,"Objects":[{"StartTime":210116.0,"EndTime":210116.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":210116.0,"Objects":[{"StartTime":210116.0,"EndTime":210354.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":210116.0,"Objects":[{"StartTime":210116.0,"EndTime":210354.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":210354.0,"Objects":[{"StartTime":210354.0,"EndTime":210354.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":210354.0,"Objects":[{"StartTime":210354.0,"EndTime":210354.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":210592.0,"Objects":[{"StartTime":210592.0,"EndTime":210592.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":210592.0,"Objects":[{"StartTime":210592.0,"EndTime":210830.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":210593.0,"Objects":[{"StartTime":210593.0,"EndTime":210831.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":210830.0,"Objects":[{"StartTime":210830.0,"EndTime":211068.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":210830.0,"Objects":[{"StartTime":210830.0,"EndTime":210830.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":211068.0,"Objects":[{"StartTime":211068.0,"EndTime":211306.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":211069.0,"Objects":[{"StartTime":211069.0,"EndTime":211307.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":211306.0,"Objects":[{"StartTime":211306.0,"EndTime":211306.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":211306.0,"Objects":[{"StartTime":211306.0,"EndTime":211544.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":211544.0,"Objects":[{"StartTime":211544.0,"EndTime":212258.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":211545.0,"Objects":[{"StartTime":211545.0,"EndTime":211783.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":211782.0,"Objects":[{"StartTime":211782.0,"EndTime":212020.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":211782.0,"Objects":[{"StartTime":211782.0,"EndTime":211782.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":212021.0,"Objects":[{"StartTime":212021.0,"EndTime":212259.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":212258.0,"Objects":[{"StartTime":212258.0,"EndTime":212496.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":212258.0,"Objects":[{"StartTime":212258.0,"EndTime":212258.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":212497.0,"Objects":[{"StartTime":212497.0,"EndTime":212735.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":212497.0,"Objects":[{"StartTime":212497.0,"EndTime":212735.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":212735.0,"Objects":[{"StartTime":212735.0,"EndTime":212735.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":212735.0,"Objects":[{"StartTime":212735.0,"EndTime":212973.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":212974.0,"Objects":[{"StartTime":212974.0,"EndTime":212974.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":213093.0,"Objects":[{"StartTime":213093.0,"EndTime":213093.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":213212.0,"Objects":[{"StartTime":213212.0,"EndTime":213212.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":213212.0,"Objects":[{"StartTime":213212.0,"EndTime":213450.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":213450.0,"Objects":[{"StartTime":213450.0,"EndTime":213688.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":213450.0,"Objects":[{"StartTime":213450.0,"EndTime":214402.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":213450.0,"Objects":[{"StartTime":213450.0,"EndTime":213450.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":213688.0,"Objects":[{"StartTime":213688.0,"EndTime":213688.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":213688.0,"Objects":[{"StartTime":213688.0,"EndTime":213688.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":213926.0,"Objects":[{"StartTime":213926.0,"EndTime":214164.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":213926.0,"Objects":[{"StartTime":213926.0,"EndTime":213926.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":214164.0,"Objects":[{"StartTime":214164.0,"EndTime":214164.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":214402.0,"Objects":[{"StartTime":214402.0,"EndTime":214640.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":214402.0,"Objects":[{"StartTime":214402.0,"EndTime":214402.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":214402.0,"Objects":[{"StartTime":214402.0,"EndTime":214402.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":214640.0,"Objects":[{"StartTime":214640.0,"EndTime":214640.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":214640.0,"Objects":[{"StartTime":214640.0,"EndTime":214878.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":214878.0,"Objects":[{"StartTime":214878.0,"EndTime":215116.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":215116.0,"Objects":[{"StartTime":215116.0,"EndTime":215354.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":215354.0,"Objects":[{"StartTime":215354.0,"EndTime":215592.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":215354.0,"Objects":[{"StartTime":215354.0,"EndTime":215354.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":215593.0,"Objects":[{"StartTime":215593.0,"EndTime":215593.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":215593.0,"Objects":[{"StartTime":215593.0,"EndTime":215831.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":215831.0,"Objects":[{"StartTime":215831.0,"EndTime":216307.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":216069.0,"Objects":[{"StartTime":216069.0,"EndTime":216307.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":216307.0,"Objects":[{"StartTime":216307.0,"EndTime":216545.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":216307.0,"Objects":[{"StartTime":216307.0,"EndTime":216307.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":216545.0,"Objects":[{"StartTime":216545.0,"EndTime":216545.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":216545.0,"Objects":[{"StartTime":216545.0,"EndTime":216783.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":216783.0,"Objects":[{"StartTime":216783.0,"EndTime":216783.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":217021.0,"Objects":[{"StartTime":217021.0,"EndTime":217259.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":217259.0,"Objects":[{"StartTime":217259.0,"EndTime":217497.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":217259.0,"Objects":[{"StartTime":217259.0,"EndTime":217497.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":217497.0,"Objects":[{"StartTime":217497.0,"EndTime":217497.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":217497.0,"Objects":[{"StartTime":217497.0,"EndTime":217497.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":217735.0,"Objects":[{"StartTime":217735.0,"EndTime":217973.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":217735.0,"Objects":[{"StartTime":217735.0,"EndTime":217735.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":217974.0,"Objects":[{"StartTime":217974.0,"EndTime":217974.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":218212.0,"Objects":[{"StartTime":218212.0,"EndTime":218450.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":218212.0,"Objects":[{"StartTime":218212.0,"EndTime":218212.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":218450.0,"Objects":[{"StartTime":218450.0,"EndTime":218450.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":218450.0,"Objects":[{"StartTime":218450.0,"EndTime":218688.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":218688.0,"Objects":[{"StartTime":218688.0,"EndTime":218926.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":218926.0,"Objects":[{"StartTime":218926.0,"EndTime":219164.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":219164.0,"Objects":[{"StartTime":219164.0,"EndTime":219402.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":219164.0,"Objects":[{"StartTime":219164.0,"EndTime":219164.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":219283.0,"Objects":[{"StartTime":219283.0,"EndTime":219283.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":219402.0,"Objects":[{"StartTime":219402.0,"EndTime":219402.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":219402.0,"Objects":[{"StartTime":219402.0,"EndTime":219640.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":219640.0,"Objects":[{"StartTime":219640.0,"EndTime":219878.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":219878.0,"Objects":[{"StartTime":219878.0,"EndTime":220116.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":220116.0,"Objects":[{"StartTime":220116.0,"EndTime":220354.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":220116.0,"Objects":[{"StartTime":220116.0,"EndTime":220116.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":220354.0,"Objects":[{"StartTime":220354.0,"EndTime":220592.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":220593.0,"Objects":[{"StartTime":220593.0,"EndTime":220831.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":220593.0,"Objects":[{"StartTime":220593.0,"EndTime":220593.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":220831.0,"Objects":[{"StartTime":220831.0,"EndTime":220831.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":220831.0,"Objects":[{"StartTime":220831.0,"EndTime":221069.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":221069.0,"Objects":[{"StartTime":221069.0,"EndTime":221545.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":221069.0,"Objects":[{"StartTime":221069.0,"EndTime":221307.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":221307.0,"Objects":[{"StartTime":221307.0,"EndTime":221307.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":221545.0,"Objects":[{"StartTime":221545.0,"EndTime":221783.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":221545.0,"Objects":[{"StartTime":221545.0,"EndTime":221545.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":221783.0,"Objects":[{"StartTime":221783.0,"EndTime":221783.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":222021.0,"Objects":[{"StartTime":222021.0,"EndTime":222259.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":222021.0,"Objects":[{"StartTime":222021.0,"EndTime":222021.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":222021.0,"Objects":[{"StartTime":222021.0,"EndTime":222021.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":222259.0,"Objects":[{"StartTime":222259.0,"EndTime":222497.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":222497.0,"Objects":[{"StartTime":222497.0,"EndTime":222735.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":222735.0,"Objects":[{"StartTime":222735.0,"EndTime":222973.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":222974.0,"Objects":[{"StartTime":222974.0,"EndTime":223212.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":222974.0,"Objects":[{"StartTime":222974.0,"EndTime":222974.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":223212.0,"Objects":[{"StartTime":223212.0,"EndTime":223212.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":223212.0,"Objects":[{"StartTime":223212.0,"EndTime":223450.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":223450.0,"Objects":[{"StartTime":223450.0,"EndTime":223688.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":223688.0,"Objects":[{"StartTime":223688.0,"EndTime":223688.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":223688.0,"Objects":[{"StartTime":223688.0,"EndTime":223926.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":223926.0,"Objects":[{"StartTime":223926.0,"EndTime":224164.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":223926.0,"Objects":[{"StartTime":223926.0,"EndTime":224164.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":223926.0,"Objects":[{"StartTime":223926.0,"EndTime":223926.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":224164.0,"Objects":[{"StartTime":224164.0,"EndTime":224402.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":224402.0,"Objects":[{"StartTime":224402.0,"EndTime":224640.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":224640.0,"Objects":[{"StartTime":224640.0,"EndTime":224878.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":224878.0,"Objects":[{"StartTime":224878.0,"EndTime":226306.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":225116.0,"Objects":[{"StartTime":225116.0,"EndTime":225116.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":225116.0,"Objects":[{"StartTime":225116.0,"EndTime":225116.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":225354.0,"Objects":[{"StartTime":225354.0,"EndTime":225592.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":225354.0,"Objects":[{"StartTime":225354.0,"EndTime":225354.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":225593.0,"Objects":[{"StartTime":225593.0,"EndTime":225593.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":225831.0,"Objects":[{"StartTime":225831.0,"EndTime":226069.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":225831.0,"Objects":[{"StartTime":225831.0,"EndTime":225831.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":226069.0,"Objects":[{"StartTime":226069.0,"EndTime":226069.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":226069.0,"Objects":[{"StartTime":226069.0,"EndTime":226307.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":226307.0,"Objects":[{"StartTime":226307.0,"EndTime":226545.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":226545.0,"Objects":[{"StartTime":226545.0,"EndTime":226783.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":226783.0,"Objects":[{"StartTime":226783.0,"EndTime":227021.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":226783.0,"Objects":[{"StartTime":226783.0,"EndTime":226783.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":226902.0,"Objects":[{"StartTime":226902.0,"EndTime":226902.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":227021.0,"Objects":[{"StartTime":227021.0,"EndTime":227021.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":227021.0,"Objects":[{"StartTime":227021.0,"EndTime":227259.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":227259.0,"Objects":[{"StartTime":227259.0,"EndTime":227497.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":227497.0,"Objects":[{"StartTime":227497.0,"EndTime":227735.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":227735.0,"Objects":[{"StartTime":227735.0,"EndTime":227973.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":227735.0,"Objects":[{"StartTime":227735.0,"EndTime":227735.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":227974.0,"Objects":[{"StartTime":227974.0,"EndTime":228212.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":228212.0,"Objects":[{"StartTime":228212.0,"EndTime":228450.0,"Column":1}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":228450.0,"Objects":[{"StartTime":228450.0,"EndTime":228688.0,"Column":3}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":228688.0,"Objects":[{"StartTime":228688.0,"EndTime":229878.0,"Column":2}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":228688.0,"Objects":[{"StartTime":228688.0,"EndTime":229402.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":230116.0,"Objects":[{"StartTime":230116.0,"EndTime":230116.0,"Column":0}]},{"RandomW":3083635271,"RandomX":273326509,"RandomY":273071671,"RandomZ":2659271247,"StartTime":230593.0,"Objects":[{"StartTime":230593.0,"EndTime":231307.0,"Column":3}]},{"RandomW":4073591514,"RandomX":273071671,"RandomY":2659271247,"RandomZ":3083635271,"StartTime":231545.0,"Objects":[{"StartTime":231545.0,"EndTime":232974.0,"Column":3}]}]} \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/4869637.osu b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/4869637.osu new file mode 100644 index 0000000000..5c08994072 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Resources/Testing/Beatmaps/4869637.osu @@ -0,0 +1,1442 @@ +osu file format v10 + +[General] +Mode: 3 + +[Difficulty] +HPDrainRate:5 +CircleSize:4 +OverallDifficulty:5 +ApproachRate:0 +SliderMultiplier:2.6 +SliderTickRate:1 + +[TimingPoints] +355,476.190476190476,4,2,1,60,1,0 +60652,-100,4,2,1,60,0,1 +92735,-100,4,2,1,60,0,0 +121485,-100,4,2,1,60,0,1 +153688,-100,4,2,1,60,0,0 +182497,-100,4,2,1,60,0,1 +213688,-100,4,2,1,60,0,0 + +[HitObjects] +192,120,355,1,0,0:0 +192,300,712,1,0,0:0 +320,288,1307,1,0,0:0 +320,164,1664,1,0,0:0 +448,208,2259,1,0,0:0 +320,208,2616,1,0,0:0 +320,344,3212,1,0,0:0 +448,344,3569,1,0,0:0 +192,120,4164,1,0,0:0 +320,120,4521,1,0,0:0 +320,288,5117,1,0,0:0 +192,288,5474,1,0,0:0 +448,208,6069,1,0,0:0 +320,208,6426,1,0,0:0 +320,296,7022,1,0,0:0 +448,296,7378,1,0,0:0 +192,120,7974,1,0,0:0 +64,128,7974,1,0,0:0 +64,232,8450,1,0,0:0 +320,120,8450,1,0,0:0 +64,232,8926,1,0,0:0 +320,288,8927,1,0,0:0 +192,288,9402,1,0,0:0 +64,232,9402,1,0,0:0 +64,232,9878,1,0,0:0 +448,208,9879,1,0,0:0 +320,208,10354,1,0,0:0 +64,232,10354,1,0,0:0 +64,232,10831,1,0,0:0 +320,296,10832,1,0,0:0 +448,296,11307,1,0,0:0 +64,232,11307,1,0,0:0 +256,192,11783,12,0,15116,0:0 +448,228,11783,1,0,0:0 +448,228,12259,1,0,0:0 +448,228,12735,1,0,0:0 +448,228,13212,1,0,0:0 +448,228,13688,1,0,0:0 +448,228,14164,1,0,0:0 +448,228,14640,1,0,0:0 +192,252,15116,6,0,B|192:200,2,32.5 +64,216,15593,1,0,0:0 +64,304,15831,1,0,0:0 +192,324,16069,1,0,0:0 +64,112,16307,1,0,0:0 +320,68,16545,2,0,B|320:220,1,130 +448,160,17021,2,0,B|448:292,1,130 +192,232,17259,1,0,0:0 +320,272,17497,2,0,B|320:112,1,130 +448,76,17974,2,0,B|448:248,1,130 +192,176,18212,1,0,0:0 +320,104,18450,2,0,B|320:244,1,130 +448,144,18926,2,0,B|448:280,1,130 +64,336,19402,1,0,0:0 +192,176,19640,1,0,0:0 +192,244,19878,1,0,0:0 +64,200,20116,1,0,0:0 +320,260,20354,2,0,B|320:128,1,130 +448,152,20831,2,0,B|448:292,1,130 +192,176,21069,1,0,0:0 +320,156,21307,2,0,B|320:292,1,130 +448,176,21783,2,0,B|448:312,1,130 +192,176,22021,1,0,0:0 +320,232,22259,2,0,C|320:328|320:328|320:288,1,130 +448,312,22735,2,0,B|448:176,1,130 +64,156,23212,1,0,0:0 +64,264,23450,1,0,0:0 +192,176,23688,1,0,0:0 +192,228,23926,1,0,0:0 +320,260,24164,2,0,B|320:128,1,130 +448,152,24641,2,0,B|448:292,1,130 +192,176,24878,1,0,0:0 +320,156,25117,2,0,B|320:292,1,130 +448,176,25593,2,0,B|448:312,1,130 +192,136,25831,1,0,0:0 +320,260,26069,2,0,B|320:128,1,130 +448,176,26545,2,0,B|448:312,1,130 +192,136,27021,1,0,0:0 +192,244,27259,1,0,0:0 +64,156,27497,1,0,0:0 +64,208,27735,1,0,0:0 +320,180,27974,2,0,B|320:316,1,130 +448,264,28450,2,0,B|448:132,1,130 +192,168,28688,1,0,0:0 +320,188,28926,2,0,B|320:324,1,130 +448,272,29402,2,0,B|448:140,1,130 +192,168,29640,1,0,0:0 +320,188,29878,2,0,B|320:324,1,130 +448,272,30354,2,0,B|448:140,1,130 +64,200,30831,1,0,0:0 +320,260,30831,1,0,0:0 +192,168,31069,1,0,0:0 +192,264,31307,1,0,0:0 +64,200,31307,1,0,0:0 +320,320,31545,1,0,0:0 +64,200,31783,1,0,0:0 +448,264,31783,2,0,B|448:132,1,130 +192,168,32021,1,0,0:0 +320,188,32259,2,0,B|320:324,1,130 +64,200,32259,1,0,0:0 +64,200,32735,1,0,0:0 +448,28,32735,2,0,B|448:164,1,130 +192,168,32974,1,0,0:0 +320,172,33212,2,0,B|320:308,1,130 +64,200,33212,1,0,0:0 +64,200,33688,1,0,0:0 +448,208,33688,2,0,B|448:344,1,130 +320,188,34164,2,0,B|320:324,1,130 +64,200,34164,1,0,0:0 +64,200,34640,1,0,0:0 +320,260,34640,1,0,0:0 +192,168,34878,1,0,0:0 +192,264,35116,1,0,0:0 +64,300,35116,1,0,0:0 +320,320,35354,1,0,0:0 +64,200,35592,1,0,0:0 +448,264,35593,2,0,B|448:132,1,130 +192,168,35831,1,0,0:0 +64,200,36068,1,0,0:0 +320,224,36068,2,0,B|320:360,1,130 +64,200,36544,1,0,0:0 +448,208,36545,2,0,B|448:344,1,130 +192,168,36783,1,0,0:0 +320,172,37021,2,0,B|320:308,1,130 +64,200,37021,1,0,0:0 +64,200,37497,1,0,0:0 +448,208,37497,2,0,B|448:344,1,130 +64,120,37854,1,0,0:0 +320,188,37973,2,0,B|320:324,1,130 +64,200,38212,1,0,0:0 +320,260,38450,1,0,0:0 +64,120,38450,1,0,0:0 +192,168,38688,1,0,0:0 +64,200,38926,1,0,0:0 +192,264,38926,1,0,0:0 +320,320,39164,1,0,0:0 +64,200,39402,1,0,0:0 +320,192,39402,2,0,B|320:328,1,130 +64,200,39878,1,0,0:0 +448,264,39879,2,0,B|448:132,1,130 +192,168,40116,1,0,0:0 +320,228,40354,2,0,B|320:364,1,130 +64,200,40354,1,0,0:0 +448,208,40831,2,0,B|448:344,1,130 +64,200,40831,1,0,0:0 +192,164,41069,1,0,0:0 +320,172,41307,2,0,B|320:308,1,130 +64,200,41307,1,0,0:0 +448,208,41783,2,0,B|448:344,1,130 +64,200,41783,1,0,0:0 +64,200,42259,1,0,0:0 +192,204,42259,1,0,0:0 +192,204,42497,1,0,0:0 +64,200,42735,1,0,0:0 +320,244,42735,1,0,0:0 +320,244,42974,1,0,0:0 +320,256,43212,2,0,B|320:124,1,130 +64,200,43212,1,0,0:0 +448,180,43687,2,0,B|448:316,1,130 +64,200,43688,1,0,0:0 +192,128,43926,1,0,0:0 +320,344,44164,2,0,B|320:212,1,130 +64,200,44164,1,0,0:0 +448,180,44639,2,0,B|448:316,1,130 +64,200,44640,1,0,0:0 +192,128,44878,1,0,0:0 +64,112,45116,1,0,0:0 +320,228,45116,2,0,B|320:380,1,130 +64,200,45593,1,0,0:0 +448,36,45593,2,0,B|448:180,1,130 +64,348,45831,2,0,L|64:340|64:340|64:156|64:380|64:-128|64:-128,1,910 +192,260,45831,1,0,0:0 +448,192,46069,1,0,0:0 +192,324,46069,1,0,0:0 +448,192,46307,1,0,0:0 +192,324,46545,1,0,0:0 +320,208,46545,1,0,0:0 +320,208,46783,1,0,0:0 +320,344,47021,2,0,B|320:204,1,130 +192,324,47021,1,0,0:0 +192,216,47497,1,0,0:0 +448,40,47497,2,0,B|448:184,1,130 +320,208,47735,1,0,0:0 +64,239,47795,2,0,B|64:471|64:31,1,357.5 +320,112,47974,2,0,B|320:252,1,130 +192,216,47974,1,0,0:0 +448,304,48450,2,0,B|448:160,1,130 +192,163,48450,1,0,0:0 +64,332,48688,1,0,0:0 +320,208,48688,1,0,0:0 +448,48,48926,2,0,B|448:188,1,130 +192,320,48926,1,0,0:0 +64,74,49164,2,0,B|64:226,1,130 +192,320,49402,1,0,0:0 +320,133,49402,2,0,B|320:268,1,130 +64,356,49640,2,0,B|294:236|120:80|120:80|64:312|64:312|64:-4,1,910 +192,320,49878,1,0,0:0 +320,331,49878,1,0,0:0 +320,331,50116,1,0,0:0 +192,320,50354,1,0,0:0 +448,140,50354,1,0,0:0 +448,140,50593,1,0,0:0 +192,320,50831,1,0,0:0 +320,119,50831,2,0,B|320:264,1,130 +192,320,51307,1,0,0:0 +448,304,51307,2,0,B|448:170,1,130 +64,121,51545,2,0,B|64:293|64:293|64:57,1,390 +320,188,51545,1,0,0:0 +192,320,51783,1,0,0:0 +320,295,51783,2,0,B|320:161,1,130 +448,248,52259,2,0,B|448:118,1,130 +192,172,52259,1,0,0:0 +320,188,52497,1,0,0:0 +320,246,52735,2,0,B|320:113,1,130 +192,172,52735,1,0,0:0 +64,300,52974,2,0,B|64:143,1,130 +448,327,53212,2,0,B|448:198,1,130 +320,188,53212,1,0,0:0 +192,304,53450,1,0,0:0 +64,313,53450,2,0,B|64:29|64:358|64:358|64:268,1,390 +192,172,53688,1,0,0:0 +320,122,53926,1,0,0:0 +192,172,54164,1,0,0:0 +320,122,54164,1,0,0:0 +64,20,54402,2,0,B|64:299|64:299|64:98|64:98|64:274,1,650 +448,153,54402,1,0,0:0 +192,172,54640,1,0,0:0 +448,93,54640,2,0,B|448:245,1,130 +192,172,55116,1,0,0:0 +448,321,55116,2,0,B|448:174,1,130 +320,276,55354,1,0,0:0 +192,288,55593,1,0,0:0 +448,44,55593,2,0,B|448:187,1,130 +320,164,56069,1,0,0:0 +448,344,56069,2,0,B|448:199,1,130 +192,172,56307,1,0,0:0 +320,28,56545,1,0,0:0 +448,45,56545,2,0,B|448:183,1,130 +192,96,56783,1,0,0:0 +64,341,56783,2,0,B|64:192,1,130 +448,321,57021,2,0,B|448:172,1,130 +320,66,57021,1,0,0:0 +64,26,57259,2,0,B|64:162|64:162|64:296|64:296|64:164,1,390 +192,239,57497,1,0,0:0 +320,332,57497,1,0,0:0 +320,248,57735,1,0,0:0 +192,239,57974,1,0,0:0 +448,265,57974,1,0,0:0 +64,352,58212,2,0,B|64:-37|64:-37|425:177|425:177|64:144,1,1170 +448,265,58212,1,0,0:0 +192,239,58450,1,0,0:0 +320,327,58450,2,0,B|320:170,1,130 +192,239,58926,5,0,0:0 +448,66,58926,2,0,B|448:204,1,130 +320,197,59164,1,0,0:0 +192,239,59402,1,0,0:0 +320,327,59402,2,0,B|320:192,1,130 +192,239,59878,1,0,0:0 +448,330,59878,2,0,B|448:189,1,130 +320,197,60116,1,0,0:0 +192,239,60354,1,0,0:0 +320,328,60354,2,0,B|320:193,1,130 +448,28,60354,2,0,B|448:183,1,130 +192,158,60593,1,0,0:0 +320,133,60831,1,0,0:0 +448,282,60831,2,0,B|448:134,1,130 +64,233,60831,1,0,0:0 +320,272,61069,2,0,B|320:128,1,130 +64,233,61307,1,0,0:0 +448,94,61307,1,0,0:0 +192,319,61545,2,0,B|192:184,1,130 +448,94,61545,1,0,0:0 +64,233,61783,1,0,0:0 +448,94,61783,1,0,0:0 +320,334,62021,2,0,B|320:195,1,130 +448,173,62021,1,0,0:0 +64,233,62259,1,0,0:0 +448,345,62259,2,0,B|448:208,1,130 +192,93,62497,2,0,B|192:224,1,130 +64,233,62735,1,0,0:0 +448,345,62735,2,0,B|448:190,1,130 +320,265,62974,2,0,B|320:119,1,130 +64,233,63212,1,0,0:0 +448,345,63212,2,0,B|448:189,1,130 +192,334,63450,2,0,B|192:66,1,260 +64,233,63688,1,0,0:0 +448,345,63688,2,0,B|448:191,1,130 +320,239,63926,2,0,B|320:85,1,130 +64,233,64164,1,0,0:0 +448,345,64164,2,0,B|448:192,1,130 +320,263,64402,1,0,0:0 +192,264,64640,1,0,0:0 +64,233,64640,1,0,0:0 +448,345,64640,2,0,B|448:192,1,130 +192,185,64878,2,0,B|192:34,1,130 +64,233,65116,1,0,0:0 +448,62,65116,1,0,0:0 +320,296,65354,2,0,B|320:154,1,130 +448,62,65354,1,0,0:0 +64,233,65593,1,0,0:0 +448,62,65593,1,0,0:0 +192,338,65831,2,0,B|192:201,1,130 +448,62,65831,1,0,0:0 +64,233,66069,1,0,0:0 +448,341,66069,2,0,B|448:194,1,130 +192,33,66307,2,0,B|192:186,1,130 +64,233,66545,1,0,0:0 +448,341,66545,2,0,B|448:200,1,130 +320,292,66783,2,0,B|320:140,1,130 +64,233,67021,1,0,0:0 +448,341,67021,2,0,B|448:195,1,130 +192,53,67259,2,0,B|192:195,1,130 +64,233,67497,1,0,0:0 +448,341,67497,2,0,B|448:206,1,130 +320,354,67735,2,0,B|320:203,1,130 +64,233,67974,1,0,0:0 +448,341,67974,2,0,B|448:204,1,130 +192,344,68212,2,0,B|192:203,1,130 +64,152,68331,1,0,0:0 +448,341,68450,2,0,B|448:191,1,130 +320,232,68688,2,0,B|320:-36,1,260 +64,152,68688,1,0,0:0 +64,233,68926,1,0,0:0 +448,76,68926,1,0,0:0 +192,280,69164,2,0,B|192:144,1,130 +448,76,69164,1,0,0:0 +64,233,69402,1,0,0:0 +448,76,69402,1,0,0:0 +192,14,69640,2,0,B|192:154,1,130 +448,76,69640,1,0,0:0 +64,233,69878,1,0,0:0 +448,340,69878,2,0,B|448:206,1,130 +320,319,70116,2,0,B|320:164,1,130 +64,233,70354,1,0,0:0 +448,340,70354,2,0,B|448:192,1,130 +192,204,70593,2,0,B|192:45,1,130 +64,233,70831,1,0,0:0 +448,340,70831,2,0,B|448:186,1,130 +192,346,71069,2,0,B|192:205,1,130 +320,296,71069,2,0,B|320:152,1,130 +64,233,71307,1,0,0:0 +448,340,71307,2,0,B|448:184,1,130 +320,36,71545,2,0,B|320:170,1,130 +64,233,71783,1,0,0:0 +448,340,71783,2,0,B|448:194,1,130 +320,327,72021,2,0,B|320:172,1,130 +64,233,72259,1,0,0:0 +448,340,72259,2,0,B|448:181,1,130 +192,312,72497,2,0,B|192:26,1,260 +64,233,72735,1,0,0:0 +448,83,72735,1,0,0:0 +320,277,72974,2,0,B|320:144,1,130 +448,83,72974,1,0,0:0 +64,233,73212,1,0,0:0 +448,83,73212,1,0,0:0 +320,22,73450,2,0,B|320:176,1,130 +448,83,73450,1,0,0:0 +64,233,73688,1,0,0:0 +448,338,73688,2,0,B|448:196,1,130 +192,36,73926,2,0,B|192:179,1,130 +64,233,74164,1,0,0:0 +448,338,74164,2,0,B|448:193,1,130 +320,333,74402,2,0,B|320:100|320:100|320:280,1,390 +192,247,74402,1,0,0:0 +64,233,74640,1,0,0:0 +448,338,74640,2,0,B|448:193,1,130 +64,233,75116,1,0,0:0 +448,338,75116,2,0,B|448:208,1,130 +192,330,75354,2,0,B|192:46,1,260 +64,233,75593,1,0,0:0 +448,338,75593,2,0,B|448:203,1,130 +320,130,75831,1,0,0:0 +64,156,75950,1,0,0:0 +448,338,76069,2,0,B|448:184,1,130 +320,210,76069,1,0,0:0 +192,207,76307,2,0,B|192:63,1,130 +64,156,76307,1,0,0:0 +64,233,76545,1,0,0:0 +448,338,76545,2,0,B|448:200,1,130 +320,320,76783,2,0,B|320:168,1,130 +64,233,77021,1,0,0:0 +448,338,77021,2,0,B|448:188,1,130 +192,328,77259,2,0,B|192:168,1,130 +448,338,77497,2,0,B|448:184,1,130 +64,233,77498,1,0,0:0 +320,272,77735,2,0,B|320:8,1,260 +64,233,77974,1,0,0:0 +448,338,77974,2,0,B|448:200,1,130 +192,312,78212,2,0,B|192:168,1,130 +448,76,78450,1,0,0:0 +64,233,78450,1,0,0:0 +448,76,78688,1,0,0:0 +320,276,78688,2,0,B|320:140,1,130 +448,76,78926,1,0,0:0 +64,233,78926,1,0,0:0 +448,76,79164,1,0,0:0 +192,14,79164,2,0,B|192:154,1,130 +448,340,79402,2,0,B|448:206,1,130 +64,233,79402,1,0,0:0 +320,296,79640,1,0,0:0 +64,233,79878,1,0,0:0 +448,340,79878,2,0,B|448:192,1,130 +320,296,79878,1,0,0:0 +192,204,80117,2,0,B|192:45,1,130 +448,340,80355,2,0,B|448:186,1,130 +64,233,80355,1,0,0:0 +192,346,80593,2,0,B|192:205,1,130 +448,340,80831,2,0,B|448:184,1,130 +64,233,80831,1,0,0:0 +320,36,81069,2,0,B|320:170,1,130 +448,340,81307,2,0,B|448:194,1,130 +64,233,81307,1,0,0:0 +320,327,81545,2,0,B|320:172,1,130 +448,340,81783,2,0,B|448:181,1,130 +64,233,81783,1,0,0:0 +192,312,82021,2,0,B|192:26,1,260 +64,233,82259,1,0,0:0 +448,83,82259,1,0,0:0 +320,277,82498,2,0,B|320:144,1,130 +448,83,82498,1,0,0:0 +448,83,82736,1,0,0:0 +64,233,82736,1,0,0:0 +320,22,82974,2,0,B|320:176,1,130 +448,83,82974,1,0,0:0 +448,338,83212,2,0,B|448:196,1,130 +64,233,83212,1,0,0:0 +192,36,83450,2,0,B|192:179,1,130 +64,233,83569,1,0,0:0 +448,338,83688,2,0,B|448:193,1,130 +320,384,83926,2,0,B|320:227|320:227|320:331,1,260 +64,148,83926,1,0,0:0 +448,338,84164,2,0,B|448:193,1,130 +64,233,84164,1,0,0:0 +192,207,84402,2,0,B|192:52,1,130 +448,338,84640,2,0,B|448:208,1,130 +64,233,84640,1,0,0:0 +192,330,84878,2,0,B|192:46,1,260 +64,233,85117,1,0,0:0 +448,338,85117,2,0,B|448:203,1,130 +320,124,85354,2,0,B|320:260,1,130 +448,338,85593,2,0,B|448:184,1,130 +64,246,85593,1,0,0:0 +192,208,85831,2,0,B|192:64,1,130 +64,233,86069,1,0,0:0 +448,338,86069,2,0,B|448:192,1,130 +320,344,86307,2,0,B|320:188,1,130 +192,320,86307,2,0,B|192:172,1,130 +64,233,86545,1,0,0:0 +448,338,86545,2,0,B|448:204,1,130 +192,204,86783,2,0,B|192:56,1,130 +64,233,87021,1,0,0:0 +448,338,87021,2,0,B|448:200,1,130 +320,344,87259,2,0,B|320:200,1,130 +64,233,87497,1,0,0:0 +448,338,87497,2,0,B|448:204,1,130 +320,344,87735,2,0,B|320:80,1,260 +64,233,87973,1,0,0:0 +448,68,87974,1,0,0:0 +192,204,88212,2,0,B|192:45,1,130 +448,68,88212,1,0,0:0 +64,233,88450,1,0,0:0 +448,68,88450,1,0,0:0 +192,346,88688,2,0,B|192:205,1,130 +448,68,88688,1,0,0:0 +64,233,88926,1,0,0:0 +448,340,88926,2,0,B|448:184,1,130 +320,36,89164,2,0,B|320:170,1,130 +448,340,89402,2,0,B|448:194,1,130 +64,233,89402,1,0,0:0 +192,320,89640,2,0,B|192:165,1,130 +64,233,89878,1,0,0:0 +448,340,89878,2,0,B|448:181,1,130 +320,332,89878,2,0,B|320:46,1,260 +192,104,90116,2,0,B|192:248,1,130 +64,233,90354,1,0,0:0 +448,340,90354,2,0,B|448:208,1,130 +320,277,90593,2,0,B|320:144,1,130 +64,233,90831,1,0,0:0 +448,340,90831,2,0,B|448:204,1,130 +320,22,91069,2,0,B|320:176,1,130 +448,338,91307,2,0,B|448:196,1,130 +64,233,91307,1,0,0:0 +256,192,91545,12,0,92735,0:0 +192,232,91545,1,0,0:0 +448,64,91783,1,0,0:0 +192,180,91783,1,0,0:0 +448,64,92021,1,0,0:0 +448,64,92259,1,0,0:0 +192,184,92259,1,0,0:0 +448,64,92497,1,0,0:0 +448,336,92735,2,0,B|448:176,1,130 +192,180,92735,1,0,0:0 +192,324,92974,2,0,B|192:168,1,130 +320,316,93212,2,0,B|320:160,1,130 +64,160,93212,1,0,0:0 +448,132,93450,1,0,0:0 +64,160,93688,1,0,0:0 +448,336,93688,2,0,B|448:192,1,130 +192,328,93688,2,0,B|192:64,1,260 +320,320,94164,2,0,B|320:160,1,130 +64,160,94164,1,0,0:0 +192,224,94402,1,0,0:0 +448,132,94402,1,0,0:0 +64,160,94640,1,0,0:0 +448,336,94640,2,0,B|448:184,1,130 +320,100,94640,1,0,0:0 +192,328,95116,2,0,B|192:56,1,260 +64,160,95116,1,0,0:0 +320,320,95116,2,0,B|320:164,1,130 +64,160,95593,1,0,0:0 +448,300,95593,1,0,0:0 +448,300,95831,1,0,0:0 +64,160,96069,1,0,0:0 +320,320,96069,1,0,0:0 +320,320,96307,1,0,0:0 +64,160,96545,1,0,0:0 +448,300,96545,2,0,B|448:168,1,130 +192,340,96783,2,0,B|192:56,1,260 +64,160,97021,1,0,0:0 +320,320,97021,2,0,B|320:176,1,130 +448,96,97259,1,0,0:0 +64,160,97497,1,0,0:0 +320,224,97497,1,0,0:0 +448,296,97497,2,0,B|448:136,1,130 +192,296,97735,2,0,B|192:28,1,260 +64,160,97974,1,0,0:0 +320,104,97974,2,0,B|320:256,1,130 +448,96,98212,1,0,0:0 +320,180,98450,1,0,0:0 +64,160,98450,1,0,0:0 +448,296,98450,2,0,B|448:160,1,130 +64,160,98747,1,0,0:0 +192,320,98926,2,0,B|6:242|192:188|346:133|192:24,1,390 +320,312,98926,2,0,B|320:168,1,130 +64,160,99164,1,0,0:0 +64,160,99402,1,0,0:0 +448,296,99402,1,0,0:0 +448,296,99640,1,0,0:0 +64,160,99878,1,0,0:0 +320,312,99878,1,0,0:0 +320,312,100116,1,0,0:0 +64,160,100354,1,0,0:0 +192,308,100354,2,0,B|146:207|146:207|192:140|192:140|192:56,1,260 +448,296,100354,2,0,B|448:144,1,130 +320,312,100831,2,0,B|320:176,1,130 +64,160,100831,1,0,0:0 +448,80,101069,1,0,0:0 +448,296,101307,2,0,B|448:156,1,130 +192,308,101307,2,0,B|192:44,1,260 +64,160,101307,1,0,0:0 +320,176,101783,2,0,B|320:40,1,130 +64,160,101783,1,0,0:0 +192,196,102021,1,0,0:0 +448,80,102021,1,0,0:0 +320,304,102259,2,0,B|320:168,1,130 +448,300,102259,2,0,B|448:148,1,130 +64,160,102259,1,0,0:0 +192,256,102735,2,0,B|389:228|389:228|192:144,1,390 +320,304,102735,2,0,B|320:144,1,130 +64,160,102735,1,0,0:0 +64,160,103212,1,0,0:0 +448,300,103212,1,0,0:0 +448,300,103450,1,0,0:0 +64,160,103688,1,0,0:0 +320,304,103688,1,0,0:0 +320,304,103926,1,0,0:0 +64,160,104164,1,0,0:0 +448,300,104164,2,0,B|448:164,1,130 +192,264,104164,1,0,0:0 +192,264,104402,2,0,B|192:120,1,130 +64,160,104640,1,0,0:0 +320,304,104640,2,0,B|320:164,1,130 +448,68,104878,1,0,0:0 +448,300,105116,2,0,B|448:168,1,130 +320,216,105116,1,0,0:0 +64,160,105116,1,0,0:0 +64,160,105593,1,0,0:0 +320,304,105593,2,0,B|320:164,1,130 +192,176,105593,1,0,0:0 +192,176,105831,1,0,0:0 +448,68,105831,1,0,0:0 +448,300,106069,2,0,B|448:168,1,130 +192,208,106069,1,0,0:0 +64,160,106069,1,0,0:0 +320,248,106307,1,0,0:0 +64,160,106426,1,0,0:0 +192,304,106545,2,0,B|83:196|83:196|380:273|380:273|433:170|433:170|493:76|422:20|422:20|192:252,1,1040 +320,304,106545,2,0,B|320:164,1,130 +64,160,106783,1,0,0:0 +64,160,107021,1,0,0:0 +448,300,107021,1,0,0:0 +448,300,107259,1,0,0:0 +64,160,107497,1,0,0:0 +320,304,107497,1,0,0:0 +320,304,107735,1,0,0:0 +64,160,107974,1,0,0:0 +448,300,107974,2,0,B|448:156,1,130 +64,160,108450,1,0,0:0 +320,304,108450,2,0,B|320:160,1,130 +448,68,108688,1,0,0:0 +64,160,108926,1,0,0:0 +448,300,108926,2,0,B|448:164,1,130 +192,280,109164,2,0,B|192:0,1,260 +64,160,109402,1,0,0:0 +320,280,109402,2,0,B|320:132,1,130 +448,68,109640,1,0,0:0 +64,160,109878,1,0,0:0 +448,300,109878,2,0,B|448:152,1,130 +320,280,110116,1,0,0:0 +64,160,110354,1,0,0:0 +320,280,110354,2,0,B|320:124,1,130 +192,276,110593,2,0,B|192:-158|192:234,1,390 +64,160,110831,1,0,0:0 +448,300,110831,1,0,0:0 +448,300,111069,1,0,0:0 +64,160,111307,1,0,0:0 +320,280,111307,1,0,0:0 +192,344,111545,2,0,B|192:32|192:-60|192:-60,1,390 +320,280,111545,1,0,0:0 +64,160,111783,1,0,0:0 +448,300,111783,2,0,B|448:160,1,130 +64,160,112259,1,0,0:0 +320,280,112259,2,0,B|320:136,1,130 +192,340,112497,2,0,B|344:340|354:170|354:170|277:34|277:34|192:84|192:84,1,520 +448,68,112497,1,0,0:0 +64,160,112735,1,0,0:0 +448,300,112735,2,0,B|448:160,1,130 +64,160,113212,1,0,0:0 +320,280,113212,2,0,B|320:132,1,130 +448,68,113450,1,0,0:0 +64,160,113688,1,0,0:0 +448,300,113688,2,0,B|448:164,1,130 +192,340,113926,1,0,0:0 +64,160,113985,1,0,0:0 +320,280,114164,2,0,B|320:136,1,130 +64,160,114402,1,0,0:0 +192,340,114402,2,0,B|449:220|192:288|192:36,1,390 +64,160,114640,1,0,0:0 +448,300,114640,1,0,0:0 +448,300,114878,1,0,0:0 +64,160,115116,1,0,0:0 +320,280,115116,1,0,0:0 +320,280,115354,1,0,0:0 +192,340,115354,2,0,B|446:222|446:222|192:156,1,520 +448,300,115593,2,0,B|448:160,1,130 +64,160,115593,1,0,0:0 +320,280,116069,2,0,B|320:132,1,130 +64,160,116069,1,0,0:0 +448,68,116307,1,0,0:0 +448,300,116545,2,0,B|448:144,1,130 +320,280,116545,2,0,B|320:16,1,260 +64,160,116545,1,0,0:0 +192,252,117021,1,0,0:0 +448,300,117021,2,0,B|448:160,1,130 +64,160,117021,1,0,0:0 +320,208,117259,1,0,0:0 +192,176,117259,2,0,B|192:32,1,130 +64,160,117497,1,0,0:0 +448,300,117497,2,0,B|448:156,1,130 +320,280,117735,2,0,B|320:120,1,130 +64,160,117974,1,0,0:0 +448,300,117974,2,0,B|448:152,1,130 +192,336,118212,2,0,B|462:215|192:80,1,390 +64,160,118450,1,0,0:0 +320,312,118450,1,0,0:0 +448,56,118450,1,0,0:0 +320,312,118688,1,0,0:0 +448,56,118688,1,0,0:0 +64,160,118926,1,0,0:0 +448,300,118926,1,0,0:0 +320,312,119164,2,0,L|450:178|320:-64|320:204|320:24|136:186,1,910 +448,300,119164,1,0,0:0 +64,160,119402,1,0,0:0 +448,300,119402,2,0,B|448:160,1,130 +64,160,119878,1,0,0:0 +448,300,119878,2,0,B|448:160,1,130 +192,124,120116,1,0,0:0 +64,160,120354,1,0,0:0 +448,300,120354,2,0,B|448:160,1,130 +64,160,120831,1,0,0:0 +448,300,120831,2,0,B|448:160,1,130 +192,324,121069,2,0,B|192:168,1,130 +64,160,121307,1,0,0:0 +448,300,121307,2,0,B|448:164,1,130 +320,324,121545,1,0,0:0 +64,160,121664,1,0,0:0 +448,300,121783,2,0,B|448:160,1,130 +320,324,121783,1,0,0:0 +192,319,122021,2,0,B|192:168,1,130 +64,160,122021,1,0,0:0 +448,94,122259,1,0,0:0 +64,233,122260,1,0,0:0 +320,252,122497,2,0,B|320:120,1,130 +448,94,122497,1,0,0:0 +448,94,122736,1,0,0:0 +64,233,122736,1,0,0:0 +448,173,122974,1,0,0:0 +192,336,122974,2,0,B|192:180,1,130 +448,345,123212,2,0,B|448:208,1,130 +64,233,123212,1,0,0:0 +320,334,123450,2,0,B|320:195,1,130 +64,233,123688,1,0,0:0 +448,345,123688,2,0,B|448:190,1,130 +192,93,123926,2,0,B|192:224,1,130 +64,233,124164,1,0,0:0 +448,345,124164,2,0,B|448:204,1,130 +320,265,124403,2,0,B|320:119,1,130 +448,345,124641,2,0,B|448:189,1,130 +64,233,124641,1,0,0:0 +192,334,124879,2,0,B|192:176,1,130 +448,345,125116,2,0,B|448:192,1,130 +64,233,125117,1,0,0:0 +320,124,125354,1,0,0:0 +64,233,125593,1,0,0:0 +448,345,125593,2,0,B|448:184,1,130 +320,124,125593,1,0,0:0 +320,348,125831,2,0,B|320:200,1,130 +448,80,126069,1,0,0:0 +64,233,126069,1,0,0:0 +192,185,126307,2,0,B|192:34,1,130 +448,80,126307,1,0,0:0 +64,233,126545,1,0,0:0 +448,80,126545,1,0,0:0 +320,296,126783,2,0,B|320:154,1,130 +448,80,126783,1,0,0:0 +448,341,127021,2,0,B|448:196,1,130 +64,233,127022,1,0,0:0 +192,338,127260,2,0,B|192:201,1,130 +448,341,127498,2,0,B|448:194,1,130 +64,233,127498,1,0,0:0 +192,33,127736,2,0,B|192:300,1,260 +448,341,127974,2,0,B|448:200,1,130 +64,233,127974,1,0,0:0 +320,292,128212,2,0,B|320:140,1,130 +448,341,128450,2,0,B|448:195,1,130 +64,233,128450,1,0,0:0 +192,53,128688,2,0,B|192:195,1,130 +448,341,128926,2,0,B|448:206,1,130 +64,233,128926,1,0,0:0 +320,354,129164,2,0,B|320:203,1,130 +64,233,129283,1,0,0:0 +448,341,129403,2,0,B|448:204,1,130 +320,300,129640,2,0,B|320:32,1,260 +64,220,129640,1,0,0:0 +448,148,129878,1,0,0:0 +64,233,129879,1,0,0:0 +448,148,130116,1,0,0:0 +192,308,130116,2,0,B|192:148,1,130 +64,233,130354,1,0,0:0 +448,76,130355,1,0,0:0 +320,284,130593,2,0,B|320:148,1,130 +448,76,130593,1,0,0:0 +64,233,130831,1,0,0:0 +448,340,130831,2,0,B|448:196,1,130 +192,14,131069,2,0,B|192:154,1,130 +448,340,131307,2,0,B|448:206,1,130 +64,233,131307,1,0,0:0 +320,319,131545,2,0,B|320:164,1,130 +448,340,131783,2,0,B|448:192,1,130 +64,233,131783,1,0,0:0 +320,264,132021,2,0,B|320:120,1,130 +192,204,132022,2,0,B|192:45,1,130 +448,340,132260,2,0,B|448:186,1,130 +64,233,132260,1,0,0:0 +320,264,132497,2,0,B|320:124,1,130 +192,346,132498,2,0,B|192:205,1,130 +448,340,132736,2,0,B|448:184,1,130 +64,233,132736,1,0,0:0 +320,36,132974,2,0,B|320:170,1,130 +448,340,133212,2,0,B|448:194,1,130 +64,233,133212,1,0,0:0 +192,312,133450,2,0,B|192:26,1,260 +64,233,133688,1,0,0:0 +448,83,133688,1,0,0:0 +448,83,133926,1,0,0:0 +320,327,133926,2,0,B|320:172,1,130 +448,83,134164,1,0,0:0 +64,233,134164,1,0,0:0 +448,83,134403,1,0,0:0 +192,276,134403,2,0,B|192:143,1,130 +448,338,134640,2,0,B|448:196,1,130 +64,233,134641,1,0,0:0 +320,22,134878,2,0,B|320:176,1,130 +448,338,135117,2,0,B|448:196,1,130 +64,233,135117,1,0,0:0 +192,328,135354,2,0,B|192:95|192:95|192:275,1,390 +320,152,135354,1,0,0:0 +64,233,135593,1,0,0:0 +448,338,135593,2,0,B|448:193,1,130 +448,338,136069,2,0,B|448:193,1,130 +64,233,136069,1,0,0:0 +320,320,136307,2,0,B|320:48,1,260 +448,338,136545,2,0,B|448:208,1,130 +64,233,136545,1,0,0:0 +192,296,136783,1,0,0:0 +64,233,136902,1,0,0:0 +192,296,137021,1,0,0:0 +448,338,137022,2,0,B|448:203,1,130 +320,248,137259,2,0,B|320:112,1,130 +64,176,137259,1,0,0:0 +448,96,137497,1,0,0:0 +64,176,137497,1,0,0:0 +448,96,137735,1,0,0:0 +192,207,137736,2,0,B|192:63,1,130 +64,233,137974,1,0,0:0 +448,96,137974,1,0,0:0 +320,320,138212,2,0,B|320:168,1,130 +448,96,138212,1,0,0:0 +448,338,138450,2,0,B|448:188,1,130 +64,233,138450,1,0,0:0 +192,328,138688,2,0,B|192:168,1,130 +448,338,138926,2,0,B|448:184,1,130 +64,233,138927,1,0,0:0 +320,316,139164,2,0,B|320:176,1,130 +448,338,139403,2,0,B|448:200,1,130 +64,233,139403,1,0,0:0 +192,328,139640,2,0,B|192:172,1,130 +448,338,139878,2,0,B|448:184,1,130 +64,233,139879,1,0,0:0 +192,296,140116,2,0,B|192:36,1,260 +448,338,140354,2,0,B|448:200,1,130 +64,233,140355,1,0,0:0 +320,144,140593,1,0,0:0 +64,233,140831,1,0,0:0 +448,340,140831,2,0,B|448:206,1,130 +320,144,140831,1,0,0:0 +320,319,141069,2,0,B|320:164,1,130 +448,340,141307,2,0,B|448:192,1,130 +64,233,141307,1,0,0:0 +192,204,141546,2,0,B|192:45,1,130 +448,104,141783,1,0,0:0 +64,233,141784,1,0,0:0 +448,104,142021,1,0,0:0 +192,346,142022,2,0,B|192:205,1,130 +448,104,142259,1,0,0:0 +64,233,142260,1,0,0:0 +448,104,142497,1,0,0:0 +320,36,142498,2,0,B|320:170,1,130 +64,233,142736,1,0,0:0 +448,340,142736,2,0,B|448:194,1,130 +192,312,142974,2,0,B|192:26,1,260 +64,233,143212,1,0,0:0 +448,340,143212,2,0,B|448:181,1,130 +320,336,143450,2,0,B|320:200,1,130 +64,233,143688,1,0,0:0 +448,340,143688,2,0,B|448:204,1,130 +192,284,143927,2,0,B|192:151,1,130 +448,340,144164,2,0,B|448:204,1,130 +64,233,144165,1,0,0:0 +320,22,144403,2,0,B|320:176,1,130 +64,233,144521,1,0,0:0 +448,338,144641,2,0,B|448:196,1,130 +192,328,144878,2,0,B|192:171|192:171|192:275,1,260 +64,160,144878,1,0,0:0 +448,88,145116,1,0,0:0 +64,233,145116,1,0,0:0 +448,88,145354,1,0,0:0 +320,316,145354,2,0,B|320:168,1,130 +64,233,145593,1,0,0:0 +448,88,145593,1,0,0:0 +448,88,145831,1,0,0:0 +192,288,145831,2,0,B|192:152,1,130 +64,233,146069,1,0,0:0 +448,338,146069,2,0,B|448:208,1,130 +320,328,146307,2,0,B|320:174,1,130 +448,338,146546,2,0,B|448:203,1,130 +64,233,146546,1,0,0:0 +192,300,146783,2,0,B|192:140,1,130 +448,338,147022,2,0,B|448:184,1,130 +64,246,147022,1,0,0:0 +192,100,147259,2,0,B|192:240,1,130 +320,236,147260,2,0,B|320:92,1,130 +448,338,147498,2,0,B|448:192,1,130 +64,233,147498,1,0,0:0 +192,336,147736,2,0,B|192:180,1,130 +448,338,147974,2,0,B|448:204,1,130 +64,233,147974,1,0,0:0 +320,280,148212,2,0,B|320:132,1,130 +448,338,148450,2,0,B|448:200,1,130 +64,233,148450,1,0,0:0 +320,344,148688,2,0,B|320:80,1,260 +192,148,148688,1,0,0:0 +64,233,148926,1,0,0:0 +448,68,148926,1,0,0:0 +192,204,149164,2,0,B|192:45,1,130 +448,68,149164,1,0,0:0 +64,233,149402,1,0,0:0 +448,68,149403,1,0,0:0 +320,280,149640,2,0,B|320:148,1,130 +448,68,149641,1,0,0:0 +448,340,149878,2,0,B|448:196,1,130 +64,233,149879,1,0,0:0 +192,346,150117,2,0,B|192:205,1,130 +448,340,150355,2,0,B|448:184,1,130 +64,233,150355,1,0,0:0 +320,36,150593,2,0,B|320:170,1,130 +64,233,150831,1,0,0:0 +448,340,150831,2,0,B|448:194,1,130 +192,232,151069,2,0,B|192:77,1,130 +448,340,151307,2,0,B|448:181,1,130 +64,233,151307,1,0,0:0 +320,320,151545,2,0,B|320:160,1,130 +448,340,151783,2,0,B|448:208,1,130 +64,233,151783,1,0,0:0 +192,280,152022,2,0,B|192:147,1,130 +64,233,152140,1,0,0:0 +448,340,152260,2,0,B|448:204,1,130 +256,192,152497,12,0,153687,0:0 +64,176,152497,1,0,0:0 +64,260,152735,1,0,0:0 +64,304,153093,1,0,0:0 +64,264,153688,1,0,0:0 +192,232,153926,1,0,0:0 +64,288,154045,1,0,0:0 +320,320,154402,2,0,B|320:120|320:120|320:324,1,390 +64,264,154640,1,0,0:0 +64,264,154997,1,0,0:0 +192,324,155354,2,0,B|192:88|192:88|192:256,1,390 +64,288,155593,1,0,0:0 +320,240,155831,1,0,0:0 +64,264,155950,1,0,0:0 +448,240,156069,1,0,0:0 +192,324,156307,2,0,C|192:88|192:88|192:256,1,390 +320,240,156307,1,0,0:0 +64,144,156545,1,0,0:0 +64,144,156902,1,0,0:0 +320,316,157259,2,0,C|320:80|320:80|320:248,1,390 +64,144,157497,1,0,0:0 +192,168,157735,1,0,0:0 +64,144,157854,1,0,0:0 +192,324,158212,2,0,L|192:88|192:88|192:256,1,390 +64,144,158450,1,0,0:0 +64,144,158807,1,0,0:0 +320,384,159164,2,0,L|320:148|320:148|320:316,1,390 +64,144,159402,1,0,0:0 +448,152,159640,1,0,0:0 +64,144,159759,1,0,0:0 +448,108,159878,1,0,0:0 +192,344,160116,2,0,L|192:108|192:108|192:276,1,390 +320,168,160116,1,0,0:0 +64,144,160354,1,0,0:0 +64,144,160712,1,0,0:0 +320,336,161069,2,0,B|320:100|320:100|320:268,1,390 +64,144,161307,1,0,0:0 +192,180,161545,1,0,0:0 +64,144,161664,1,0,0:0 +192,324,162021,2,0,B|192:88|192:88|192:256,1,390 +64,144,162259,1,0,0:0 +64,144,162616,1,0,0:0 +320,324,162974,2,0,B|320:88|320:88|320:256,1,390 +64,144,163212,1,0,0:0 +192,184,163450,1,0,0:0 +64,144,163569,1,0,0:0 +448,260,163688,1,0,0:0 +320,200,163926,1,0,0:0 +192,324,163926,2,0,B|192:88|192:88|192:256,1,390 +64,144,164164,1,0,0:0 +64,144,164521,1,0,0:0 +320,324,164878,2,0,B|320:88|320:88|320:256,1,390 +64,144,165116,1,0,0:0 +192,172,165354,1,0,0:0 +64,144,165474,1,0,0:0 +192,324,165831,2,0,B|192:88|192:88|192:256,1,390 +64,144,166069,1,0,0:0 +64,144,166426,1,0,0:0 +320,324,166783,2,0,B|320:224|242:196|242:196|320:88|320:88|420:209|420:209|320:380,1,650 +64,144,167021,1,0,0:0 +192,176,167259,1,0,0:0 +64,144,167378,1,0,0:0 +192,176,167497,1,0,0:0 +192,320,167735,2,0,B|192:168,1,130 +448,288,167974,1,0,0:0 +448,288,168212,1,0,0:0 +64,144,168450,1,0,0:0 +192,176,168450,1,0,0:0 +448,288,168450,1,0,0:0 +448,288,168688,1,0,0:0 +192,176,168926,1,0,0:0 +448,72,168926,2,0,B|448:224,1,130 +64,144,169402,1,0,0:0 +320,228,169402,1,0,0:0 +448,72,169402,2,0,B|448:216,1,130 +192,128,169640,1,0,0:0 +320,336,169878,2,0,B|320:60,1,260 +448,72,169878,2,0,B|448:216,1,130 +64,144,170354,1,0,0:0 +448,72,170354,2,0,B|448:220,1,130 +192,304,170593,2,0,B|192:44,1,260 +320,152,170593,1,0,0:0 +448,72,170831,2,0,B|448:220,1,130 +64,144,171307,1,0,0:0 +320,328,171307,2,0,B|320:64,1,260 +448,72,171307,2,0,B|448:216,1,130 +448,308,171783,1,0,0:0 +448,308,172021,1,0,0:0 +64,144,172259,1,0,0:0 +192,188,172259,1,0,0:0 +448,308,172259,1,0,0:0 +448,308,172497,1,0,0:0 +192,188,172735,1,0,0:0 +448,72,172735,2,0,B|448:136,4,32.5 +64,144,173212,1,0,0:0 +320,240,173212,1,0,0:0 +448,72,173212,2,0,B|448:216,1,130 +320,136,173450,1,0,0:0 +320,240,173688,2,0,B|320:-28,1,260 +192,188,173688,1,0,0:0 +448,72,173688,2,0,B|448:208,1,130 +192,148,173926,1,0,0:0 +64,144,174164,1,0,0:0 +192,188,174164,1,0,0:0 +448,72,174164,2,0,B|448:208,1,130 +320,320,174402,2,0,B|320:48,1,260 +192,188,174402,1,0,0:0 +192,188,174640,1,0,0:0 +448,72,174640,2,0,B|448:212,1,130 +192,188,174878,1,0,0:0 +64,144,175116,1,0,0:0 +320,40,175116,2,0,B|320:312,1,260 +192,148,175116,1,0,0:0 +448,72,175116,2,0,B|448:208,1,130 +192,264,175354,2,0,B|192:120,1,130 +448,304,175593,1,0,0:0 +192,320,175831,2,0,B|192:60,1,260 +448,304,175831,1,0,0:0 +64,144,176069,1,0,0:0 +320,220,176069,1,0,0:0 +448,304,176069,1,0,0:0 +448,304,176307,1,0,0:0 +320,184,176545,1,0,0:0 +448,72,176545,2,0,B|448:208,1,130 +64,144,177021,1,0,0:0 +320,184,177021,1,0,0:0 +448,72,177021,2,0,B|448:216,1,130 +192,272,177259,1,0,0:0 +320,328,177497,2,0,B|320:60,1,260 +192,204,177497,1,0,0:0 +448,72,177497,2,0,B|448:208,1,130 +64,144,177974,1,0,0:0 +192,184,177974,1,0,0:0 +448,72,177974,2,0,B|448:212,1,130 +192,232,178212,1,0,0:0 +192,184,178450,1,0,0:0 +320,300,178450,2,0,B|320:144,1,130 +448,72,178450,2,0,B|448:208,1,130 +64,144,178926,1,0,0:0 +320,56,178926,2,0,B|320:328,1,260 +192,120,178926,1,0,0:0 +448,72,178926,2,0,B|448:208,1,130 +192,336,179164,2,0,B|192:184,1,130 +448,304,179402,1,0,0:0 +448,304,179640,1,0,0:0 +192,332,179878,2,0,B|192:56,1,260 +320,176,179878,1,0,0:0 +448,304,179878,1,0,0:0 +448,304,180116,1,0,0:0 +320,176,180354,1,0,0:0 +448,76,180354,2,0,B|448:212,1,130 +192,72,180593,2,0,B|192:344,1,260 +320,176,180831,1,0,0:0 +448,76,180831,2,0,B|448:220,1,130 +320,192,181069,1,0,0:0 +320,332,181306,2,0,B|320:72,1,260 +192,344,181307,2,0,B|192:76,1,260 +448,76,181307,2,0,B|448:212,1,130 +448,76,181783,2,0,B|448:216,1,130 +320,356,182021,2,0,B|320:80,1,260 +192,72,182021,2,0,B|192:340,1,260 +448,76,182259,2,0,B|448:220,1,130 +64,136,182497,5,0,0:0 +64,328,182735,5,0,0:0 +320,192,182735,1,0,0:0 +320,272,182974,2,0,B|320:120,1,130 +448,94,183211,1,0,0:0 +64,272,183211,1,0,0:0 +192,319,183449,2,0,B|192:184,1,130 +448,94,183449,1,0,0:0 +64,233,183687,1,0,0:0 +448,94,183687,1,0,0:0 +448,173,183925,1,0,0:0 +320,334,183925,2,0,B|320:195,1,130 +448,345,184163,2,0,B|448:208,1,130 +64,233,184163,1,0,0:0 +192,93,184401,2,0,B|192:224,1,130 +64,233,184639,1,0,0:0 +448,345,184639,2,0,B|448:190,1,130 +320,265,184878,2,0,B|320:119,1,130 +448,345,185116,2,0,B|448:189,1,130 +64,233,185116,1,0,0:0 +192,334,185354,2,0,B|192:66,1,260 +64,233,185592,1,0,0:0 +448,345,185592,2,0,B|448:191,1,130 +320,239,185830,2,0,B|320:85,1,130 +448,345,186068,2,0,B|448:192,1,130 +64,233,186068,1,0,0:0 +320,263,186306,1,0,0:0 +448,345,186544,2,0,B|448:192,1,130 +64,233,186544,1,0,0:0 +192,264,186544,1,0,0:0 +192,185,186782,2,0,B|192:34,1,130 +448,62,187020,1,0,0:0 +64,233,187020,1,0,0:0 +448,62,187258,1,0,0:0 +320,296,187258,2,0,B|320:154,1,130 +448,62,187497,1,0,0:0 +64,233,187497,1,0,0:0 +448,62,187735,1,0,0:0 +192,338,187735,2,0,B|192:201,1,130 +448,341,187973,2,0,B|448:194,1,130 +64,233,187973,1,0,0:0 +320,100,188211,2,0,B|320:253,1,130 +448,341,188449,2,0,B|448:200,1,130 +64,233,188449,1,0,0:0 +320,292,188688,2,0,B|320:140,1,130 +448,341,188925,2,0,B|448:195,1,130 +64,233,188925,1,0,0:0 +192,60,188926,2,0,B|192:216,1,130 +320,92,189163,2,0,B|320:234,1,130 +448,341,189401,2,0,B|448:206,1,130 +64,233,189401,1,0,0:0 +192,88,189402,2,0,B|192:360,1,260 +320,354,189639,2,0,B|320:203,1,130 +448,341,189878,2,0,B|448:204,1,130 +64,233,189878,1,0,0:0 +192,344,190116,2,0,B|192:203,1,130 +64,233,190235,1,0,0:0 +448,341,190354,2,0,B|448:191,1,130 +320,232,190592,2,0,B|320:22,1,195 +64,233,190593,1,0,0:0 +448,76,190830,1,0,0:0 +64,233,190830,1,0,0:0 +448,76,191068,1,0,0:0 +192,280,191068,2,0,B|192:144,1,130 +320,144,191069,1,0,0:0 +448,76,191306,1,0,0:0 +64,233,191306,1,0,0:0 +320,144,191307,1,0,0:0 +448,76,191544,1,0,0:0 +192,14,191544,2,0,B|192:154,1,130 +320,92,191545,2,0,B|320:244,1,130 +448,340,191782,2,0,B|448:206,1,130 +64,233,191782,1,0,0:0 +320,319,192020,2,0,B|320:164,1,130 +192,104,192021,2,0,B|192:256,1,130 +448,340,192258,2,0,B|448:192,1,130 +64,233,192258,1,0,0:0 +192,204,192497,2,0,B|192:45,1,130 +320,376,192497,2,0,B|320:72|320:72|320:111|320:111|320:292|320:292,1,520 +448,340,192735,2,0,B|448:186,1,130 +64,233,192735,1,0,0:0 +192,346,192973,2,0,B|192:205,1,130 +448,340,193211,2,0,B|448:184,1,130 +64,233,193211,1,0,0:0 +192,276,193450,2,0,B|192:124,1,130 +448,340,193687,2,0,B|448:194,1,130 +64,233,193688,1,0,0:0 +320,327,193925,2,0,B|320:172,1,130 +448,340,194163,2,0,B|448:181,1,130 +64,233,194163,1,0,0:0 +448,83,194639,1,0,0:0 +192,312,194640,2,0,B|192:160,1,130 +64,233,194640,1,0,0:0 +320,208,194640,1,0,0:0 +448,83,194878,1,0,0:0 +320,277,194878,2,0,B|320:144,1,130 +448,83,195116,1,0,0:0 +64,233,195116,1,0,0:0 +192,160,195116,1,0,0:0 +448,83,195354,1,0,0:0 +320,22,195354,2,0,B|320:176,1,130 +192,316,195354,2,0,B|192:168,1,130 +448,338,195592,2,0,B|448:196,1,130 +64,233,195592,1,0,0:0 +192,36,195830,2,0,B|192:179,1,130 +320,104,195831,2,0,B|320:260,1,130 +448,338,196068,2,0,B|448:193,1,130 +64,233,196068,1,0,0:0 +320,333,196306,2,0,B|320:-16|320:-16|320:284|320:284|320:280,1,650 +192,272,196307,2,0,B|192:128,1,130 +448,338,196544,2,0,B|448:193,1,130 +64,233,196544,1,0,0:0 +448,338,197020,2,0,B|448:208,1,130 +64,233,197020,1,0,0:0 +192,330,197258,2,0,B|192:46,1,260 +448,338,197497,2,0,B|448:203,1,130 +64,233,197497,1,0,0:0 +320,130,197735,1,0,0:0 +64,246,197854,1,0,0:0 +192,204,197973,1,0,0:0 +448,338,197973,2,0,B|448:184,1,130 +192,207,198211,2,0,B|192:63,1,130 +64,233,198212,1,0,0:0 +448,338,198449,2,0,B|448:200,1,130 +64,233,198449,1,0,0:0 +320,320,198687,2,0,B|320:168,1,130 +448,338,198925,2,0,B|448:188,1,130 +64,233,198925,1,0,0:0 +192,352,199163,2,0,B|192:192,1,130 +448,338,199401,2,0,B|448:184,1,130 +64,233,199402,1,0,0:0 +320,312,199639,2,0,B|320:48,1,260 +192,224,199640,2,0,B|192:368,1,130 +448,338,199878,2,0,B|448:200,1,130 +64,233,199878,1,0,0:0 +192,92,200116,2,0,B|192:232,1,130 +64,233,200354,1,0,0:0 +448,76,200354,1,0,0:0 +320,352,200592,2,0,B|320:216,1,130 +448,76,200592,1,0,0:0 +64,233,200830,1,0,0:0 +448,76,200830,1,0,0:0 +192,14,201068,2,0,B|192:154,1,130 +448,76,201068,1,0,0:0 +64,233,201306,1,0,0:0 +448,340,201306,2,0,B|448:206,1,130 +320,288,201307,2,0,B|320:128,1,130 +192,144,201545,1,0,0:0 +448,340,201782,2,0,B|448:192,1,130 +64,233,201782,1,0,0:0 +192,144,201783,1,0,0:0 +192,204,202021,2,0,B|192:45,1,130 +320,356,202021,2,0,B|320:192|320:192|320:-64,1,390 +64,233,202259,1,0,0:0 +448,340,202259,2,0,B|448:186,1,130 +192,346,202497,2,0,B|192:205,1,130 +64,233,202735,1,0,0:0 +448,340,202735,2,0,B|448:184,1,130 +320,36,202973,2,0,B|320:170,1,130 +64,233,203211,1,0,0:0 +448,340,203211,2,0,B|448:194,1,130 +192,304,203212,2,0,B|192:156,1,130 +320,327,203449,2,0,B|320:172,1,130 +64,233,203687,1,0,0:0 +448,340,203687,2,0,B|448:181,1,130 +320,384,203925,2,0,B|320:98,1,260 +192,356,203926,2,0,B|265:192|265:192|192:-12,1,390 +448,83,204163,1,0,0:0 +64,233,204163,1,0,0:0 +448,83,204402,1,0,0:0 +64,233,204640,1,0,0:0 +448,83,204640,1,0,0:0 +320,72,204640,2,0,B|320:336,1,260 +448,83,204878,1,0,0:0 +192,44,204878,2,0,B|192:198,1,130 +64,233,205116,1,0,0:0 +448,338,205116,2,0,B|448:196,1,130 +192,36,205354,2,0,B|192:179,1,130 +64,233,205474,1,0,0:0 +448,338,205592,2,0,B|448:193,1,130 +320,333,205830,2,0,B|320:228|320:228|320:280,1,130 +64,233,205831,1,0,0:0 +64,233,206068,1,0,0:0 +448,338,206068,2,0,B|448:193,1,130 +192,136,206069,1,0,0:0 +192,276,206307,2,0,B|192:128,1,130 +320,128,206307,2,0,B|320:284,1,130 +64,233,206544,1,0,0:0 +448,338,206544,2,0,B|448:208,1,130 +192,196,206782,2,0,B|192:46,1,130 +320,88,206783,2,0,B|320:240,1,130 +448,338,207021,2,0,B|448:203,1,130 +64,233,207021,1,0,0:0 +320,128,207259,2,0,B|320:272,1,130 +192,328,207259,2,0,B|192:188,1,130 +448,338,207497,2,0,B|448:184,1,130 +64,246,207497,1,0,0:0 +192,336,207735,2,0,B|251:210|251:210|192:-36,1,390 +448,338,207973,2,0,B|448:192,1,130 +64,233,207973,1,0,0:0 +320,344,208211,2,0,B|320:188,1,130 +448,338,208449,2,0,B|448:204,1,130 +64,233,208449,1,0,0:0 +192,204,208687,2,0,B|192:56,1,130 +448,338,208925,2,0,B|448:200,1,130 +64,233,208925,1,0,0:0 +320,344,209163,2,0,B|320:200,1,130 +192,336,209164,2,0,B|192:56,1,260 +448,338,209401,2,0,B|448:204,1,130 +64,233,209401,1,0,0:0 +320,344,209639,2,0,B|320:184,1,130 +448,68,209878,1,0,0:0 +64,233,209878,1,0,0:0 +448,68,210116,1,0,0:0 +192,204,210116,2,0,B|192:45,1,130 +320,214,210116,2,0,B|320:76,1,130 +448,68,210354,1,0,0:0 +64,233,210354,1,0,0:0 +448,68,210592,1,0,0:0 +192,346,210592,2,0,B|192:205,1,130 +320,264,210593,2,0,B|320:120,1,130 +448,340,210830,2,0,B|448:184,1,130 +64,233,210830,1,0,0:0 +320,36,211068,2,0,B|320:170,1,130 +192,264,211069,2,0,B|192:112,1,130 +64,233,211306,1,0,0:0 +448,340,211306,2,0,B|448:194,1,130 +320,327,211544,2,0,B|403:173|403:173|320:-40,1,390 +192,200,211545,2,0,B|192:56,1,130 +448,340,211782,2,0,B|448:181,1,130 +64,233,211782,1,0,0:0 +192,328,212021,2,0,B|192:168,1,130 +448,340,212258,2,0,B|448:208,1,130 +64,233,212258,1,0,0:0 +320,277,212497,2,0,C|320:144,1,130 +192,252,212497,2,0,B|192:112,1,130 +64,233,212735,1,0,0:0 +448,340,212735,2,0,B|448:200,1,130 +192,300,212974,1,0,0:0 +64,160,213093,1,0,0:0 +192,204,213212,1,0,0:0 +448,340,213212,2,0,B|448:192,1,130 +320,324,213450,2,0,B|320:172,1,130 +192,360,213450,2,0,B|280:158|280:158|192:-128,1,520 +64,160,213450,1,0,0:0 +64,160,213688,1,0,0:0 +448,120,213688,1,0,0:0 +320,128,213926,2,0,B|320:276,1,130 +448,120,213926,1,0,0:0 +448,120,214164,1,0,0:0 +320,348,214402,2,0,B|320:192,1,130 +64,204,214402,1,0,0:0 +448,120,214402,1,0,0:0 +64,204,214640,1,0,0:0 +448,340,214640,2,0,B|448:184,1,130 +192,328,214878,2,0,B|192:176,1,130 +448,340,215116,2,0,B|448:192,1,130 +320,280,215354,2,0,B|320:144,1,130 +64,184,215354,1,0,0:0 +64,184,215593,1,0,0:0 +448,340,215593,2,0,B|448:192,1,130 +192,304,215831,2,0,B|192:40,1,260 +448,340,216069,2,0,B|448:204,1,130 +320,340,216307,2,0,B|320:180,1,130 +64,184,216307,1,0,0:0 +64,184,216545,1,0,0:0 +448,340,216545,2,0,B|448:196,1,130 +192,184,216783,1,0,0:0 +448,340,217021,2,0,B|448:200,1,130 +320,276,217259,2,0,B|320:128,1,130 +192,296,217259,2,0,B|192:148,1,130 +64,184,217497,1,0,0:0 +448,92,217497,1,0,0:0 +192,88,217735,2,0,B|192:228,1,130 +448,92,217735,1,0,0:0 +448,92,217974,1,0,0:0 +320,336,218212,2,0,B|320:184,1,130 +448,92,218212,1,0,0:0 +64,184,218450,1,0,0:0 +448,340,218450,2,0,B|448:184,1,130 +192,296,218688,2,0,B|192:136,1,130 +448,340,218926,2,0,B|448:192,1,130 +320,328,219164,2,0,B|320:176,1,130 +64,144,219164,1,0,0:0 +192,128,219283,1,0,0:0 +64,184,219402,1,0,0:0 +448,340,219402,2,0,B|448:200,1,130 +192,344,219640,2,0,B|192:204,1,130 +448,340,219878,2,0,B|448:192,1,130 +320,328,220116,2,0,B|320:192,1,130 +64,184,220116,1,0,0:0 +448,340,220354,2,0,B|448:200,1,130 +192,288,220593,2,0,B|192:132,1,130 +64,232,220593,1,0,0:0 +64,240,220831,1,0,0:0 +448,340,220831,2,0,B|448:200,1,130 +320,352,221069,2,0,B|320:88,1,260 +64,304,221069,2,0,B|64:152,1,130 +448,96,221307,1,0,0:0 +192,336,221545,2,0,B|192:176,1,130 +448,96,221545,1,0,0:0 +448,96,221783,1,0,0:0 +320,320,222021,2,0,B|320:184,1,130 +64,156,222021,1,0,0:0 +448,96,222021,1,0,0:0 +448,344,222259,2,0,B|448:200,1,130 +192,300,222497,2,0,B|192:152,1,130 +448,344,222735,2,0,B|448:204,1,130 +320,328,222974,2,0,B|320:188,1,130 +64,156,222974,1,0,0:0 +64,156,223212,1,0,0:0 +448,344,223212,2,0,B|448:192,1,130 +192,336,223450,2,0,B|192:180,1,130 +320,168,223688,1,0,0:0 +448,344,223688,2,0,B|448:200,1,130 +192,112,223926,2,0,B|192:252,1,130 +320,100,223926,2,0,B|320:232,1,130 +64,156,223926,1,0,0:0 +448,344,224164,2,0,B|448:204,1,130 +192,308,224402,2,0,B|192:156,1,130 +448,344,224640,2,0,B|448:200,1,130 +320,296,224878,2,0,B|320:104|320:104|320:340|320:340|320:-12,1,780 +64,176,225116,1,0,0:0 +448,96,225116,1,0,0:0 +192,312,225354,2,0,B|192:160,1,130 +448,96,225354,1,0,0:0 +448,96,225593,1,0,0:0 +192,252,225831,2,0,B|192:116,1,130 +448,96,225831,1,0,0:0 +64,176,226069,1,0,0:0 +448,344,226069,2,0,B|448:188,1,130 +192,328,226307,2,0,B|192:176,1,130 +448,344,226545,2,0,B|448:200,1,130 +320,300,226783,2,0,B|320:148,1,130 +64,176,226783,1,0,0:0 +192,168,226902,1,0,0:0 +64,136,227021,1,0,0:0 +448,344,227021,2,0,B|448:184,1,130 +192,288,227259,2,0,B|192:152,1,130 +448,344,227497,2,0,B|448:192,1,130 +320,312,227735,2,0,B|320:176,1,130 +64,176,227735,1,0,0:0 +448,344,227974,2,0,B|448:204,1,130 +192,264,228212,2,0,B|192:116,1,130 +448,344,228450,2,0,B|448:196,1,130 +320,328,228688,2,0,B|372:262|372:262|233:179|233:179|320:136|320:136|438:177|438:177|320:32,1,650 +64,336,228688,2,0,B|64:-56,1,390 +64,240,230116,1,0,0:0 +448,320,230593,2,0,B|299:178|299:178|448:48,1,390 +256,192,231545,12,0,232974,0:0 \ No newline at end of file From d597232c2a06d3338adbce224b57fd883af73d4e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 13 Dec 2024 16:08:12 +0900 Subject: [PATCH 211/326] Fix incorrect `lastPattern` value In particular, mania-specific beatmaps that normally go via the "passthrough" generator should not adjust the stored pattern value. The "spinner" generator, which was previously intended to be used for non-mania-specific beatmaps, is now valid even for mania-specific beatmaps, and uses this value. In other words, another way of writing this would be: ```csharp if (conversion is SpinnerPatternGenerator || conversion is PassThroughPatternGenerator) ? lastPattern : newPattern; ``` --- osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 79234a3ba2..96550618c0 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -220,8 +220,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps foreach (var newPattern in conversion.Generate()) { - lastPattern = conversion is SpinnerPatternGenerator ? lastPattern : newPattern; - lastStair = (conversion as HitCirclePatternGenerator)?.StairType ?? lastStair; + if (conversion is HitCirclePatternGenerator circleGenerator) + lastStair = circleGenerator.StairType; + + if (conversion is HitCirclePatternGenerator || conversion is SliderPatternGenerator) + lastPattern = newPattern; foreach (var obj in newPattern.HitObjects) yield return obj; From 7bb1a5118e26d761b89e3b7e27a10c5a3c8ffb08 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 13 Dec 2024 16:39:16 +0900 Subject: [PATCH 212/326] Unbind event on disposal --- .../BeatmapListing/BeatmapSearchGeneralFilterRow.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs index 66a0a16549..34b7d45a77 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs @@ -82,7 +82,8 @@ namespace osu.Game.Overlays.BeatmapListing { base.LoadComplete(); - if (recommender != null) recommender.StarRatingUpdated += updateText; + if (recommender != null) + recommender.StarRatingUpdated += updateText; Ruleset.BindValueChanged(_ => updateText(), true); } @@ -102,6 +103,14 @@ namespace osu.Game.Overlays.BeatmapListing else Text.Text = Value.GetLocalisableDescription(); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (recommender != null) + recommender.StarRatingUpdated -= updateText; + } } private partial class FeaturedArtistsTabItem : MultipleSelectionFilterTabItem From b470e30cc0efb2d40ed579aa63bcc3a1955b0d43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2024 17:17:52 +0900 Subject: [PATCH 213/326] Add failing test showing player settings appearing in skin editor --- .../TestSceneSkinEditorNavigation.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs index 5267a57a05..0af4dacb92 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneSkinEditorNavigation.cs @@ -23,6 +23,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Skinning; using osu.Game.Tests.Beatmaps.IO; @@ -212,6 +213,33 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("no mod selected", () => !((Player)Game.ScreenStack.CurrentScreen).Mods.Value.Any()); } + [Test] + public void TestGameplaySettingsDoesNotExpandWhenSkinOverlayPresent() + { + advanceToSongSelect(); + 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(); + + AddUntilStep("wait for settings", () => getPlayerSettingsOverlay() != null); + AddAssert("settings not visible", () => getPlayerSettingsOverlay().DrawWidth, () => Is.EqualTo(0)); + + AddStep("move cursor to right of screen", () => InputManager.MoveMouseTo(InputManager.ScreenSpaceDrawQuad.TopRight)); + AddAssert("settings not visible", () => getPlayerSettingsOverlay().DrawWidth, () => Is.EqualTo(0)); + + toggleSkinEditor(); + + AddStep("move cursor slightly", () => InputManager.MoveMouseTo(InputManager.ScreenSpaceDrawQuad.TopRight + new Vector2(1))); + AddUntilStep("settings visible", () => getPlayerSettingsOverlay().DrawWidth, () => Is.GreaterThan(0)); + + AddStep("move cursor to right of screen too far", () => InputManager.MoveMouseTo(InputManager.ScreenSpaceDrawQuad.TopRight + new Vector2(10240, 0))); + AddUntilStep("settings not visible", () => getPlayerSettingsOverlay().DrawWidth, () => Is.EqualTo(0)); + + PlayerSettingsOverlay getPlayerSettingsOverlay() => ((Player)Game.ScreenStack.CurrentScreen).ChildrenOfType().SingleOrDefault(); + } + [Test] public void TestCinemaModRemovedOnEnteringGameplay() { From 1e809c7f16dcd5e7b543f82e75cc791189e57209 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2024 17:18:00 +0900 Subject: [PATCH 214/326] Fix player settings overlay appearing while in skin editor --- .../Screens/Play/HUD/PlayerSettingsOverlay.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index 18d7f6a503..d2fb2e719a 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -86,11 +86,31 @@ namespace osu.Game.Screens.Play.HUD inputManager = GetContainingInputManager()!; } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + screenSpacePos.X > button.ScreenSpaceDrawQuad.TopLeft.X; + + protected override bool OnMouseMove(MouseMoveEvent e) + { + checkExpanded(); + return base.OnMouseMove(e); + } + protected override void Update() { base.Update(); - Expanded.Value = inputManager.CurrentState.Mouse.Position.X >= button.ScreenSpaceDrawQuad.TopLeft.X; + // Only check expanded if already expanded. + // This is because if we are always checking, it would bypass blocking overlays. + // Case in point: the skin editor overlay blocks input from reaching the player, but checking raw coordinates would make settings pop out. + if (Expanded.Value) + checkExpanded(); + } + + private void checkExpanded() + { + float screenMouseX = inputManager.CurrentState.Mouse.Position.X; + + Expanded.Value = screenMouseX >= button.ScreenSpaceDrawQuad.TopLeft.X && screenMouseX <= ToScreenSpace(new Vector2(DrawWidth + EXPANDED_WIDTH, 0)).X; } protected override void OnHoverLost(HoverLostEvent e) From a796af95110d2f21a4f1431d3748ca5c2a0f68e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2024 17:28:15 +0900 Subject: [PATCH 215/326] Fix player settings overlay cog overlapping skin elements This brings it down to be in line with the flowing elements that usually do their best to not get in the way. Decided against putting it in the `HUDOverlay` flow for simplicity. It will work fine until it doesn't. --- osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs | 7 +++++++ osu.Game/Screens/Play/HUDOverlay.cs | 11 ++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index d2fb2e719a..1cac4db021 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.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.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -43,6 +44,9 @@ namespace osu.Game.Screens.Play.HUD private InputManager inputManager = null!; + [Resolved] + private HUDOverlay? hudOverlay { get; set; } + public PlayerSettingsOverlay() : base(0, EXPANDED_WIDTH) { @@ -99,6 +103,9 @@ namespace osu.Game.Screens.Play.HUD { base.Update(); + if (hudOverlay != null) + button.Y = ToLocalSpace(hudOverlay.TopRightElements.ScreenSpaceDrawQuad.BottomRight).Y; + // Only check expanded if already expanded. // This is because if we are always checking, it would bypass blocking overlays. // Case in point: the skin editor overlay blocks input from reaching the player, but checking raw coordinates would make settings pop out. diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index fca871e42f..ad165d7d9f 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -87,7 +87,8 @@ namespace osu.Game.Screens.Play private static bool hasShownNotificationOnce; private readonly FillFlowContainer bottomRightElements; - private readonly FillFlowContainer topRightElements; + + internal readonly FillFlowContainer TopRightElements; internal readonly IBindable IsPlaying = new Bindable(); @@ -136,7 +137,7 @@ namespace osu.Game.Screens.Play PlayfieldSkinLayer = drawableRuleset != null ? new SkinnableContainer(new GlobalSkinnableContainerLookup(GlobalSkinnableContainers.Playfield, drawableRuleset.Ruleset.RulesetInfo)) { AlwaysPresent = true, } : Empty(), - topRightElements = new FillFlowContainer + TopRightElements = new FillFlowContainer { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -182,7 +183,7 @@ namespace osu.Game.Screens.Play }, }; - hideTargets = new List { mainComponents, topRightElements, rightSettings }; + hideTargets = new List { mainComponents, TopRightElements, rightSettings }; if (rulesetComponents != null) hideTargets.Add(rulesetComponents); @@ -275,9 +276,9 @@ namespace osu.Game.Screens.Play processDrawables(rulesetComponents); if (lowestTopScreenSpaceRight.HasValue) - topRightElements.Y = MathHelper.Clamp(ToLocalSpace(new Vector2(0, lowestTopScreenSpaceRight.Value)).Y, 0, DrawHeight - topRightElements.DrawHeight); + TopRightElements.Y = MathHelper.Clamp(ToLocalSpace(new Vector2(0, lowestTopScreenSpaceRight.Value)).Y, 0, DrawHeight - TopRightElements.DrawHeight); else - topRightElements.Y = 0; + TopRightElements.Y = 0; if (lowestTopScreenSpaceLeft.HasValue) LeaderboardFlow.Y = MathHelper.Clamp(ToLocalSpace(new Vector2(0, lowestTopScreenSpaceLeft.Value)).Y, 0, DrawHeight - LeaderboardFlow.DrawHeight); From fdc41ace7e8e8a442c311615b62dd36de3f2eb65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 13 Dec 2024 17:33:49 +0900 Subject: [PATCH 216/326] Fix flaky editor beatmap creation test As seen in https://github.com/ppy/osu/actions/runs/12289146465/job/34294167417#step:5:1588 or https://github.com/ppy/osu/actions/runs/12310133160/job/34358241666#step:5:53. Exception messages hint pretty strongly at this being a threading issue and there does seem to be a rather frivolous lack of waiting for `CreateNewDifficulty()` to do its thing, so I'm thinking maybe this will help. --- .../Editing/TestSceneEditorBeatmapCreation.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 32d019dd9f..b7990b64c1 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -203,12 +203,19 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestCreateNewDifficultyWithScrollSpeed_SameRuleset() { - string firstDifficultyName = Guid.NewGuid().ToString(); + string previousDifficultyName = null!; + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = previousDifficultyName = Guid.NewGuid().ToString()); AddStep("save beatmap", () => Editor.Save()); AddStep("create new difficulty", () => Editor.CreateNewDifficulty(new ManiaRuleset().RulesetInfo)); - AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName); + AddUntilStep("wait for created", () => + { + string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; + return difficultyName != null && difficultyName != previousDifficultyName; + }); + + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = previousDifficultyName = Guid.NewGuid().ToString()); AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 })); AddStep("add effect points", () => { @@ -229,7 +236,7 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("wait for created", () => { string? difficultyName = Editor.ChildrenOfType().SingleOrDefault()?.BeatmapInfo.DifficultyName; - return difficultyName != null && difficultyName != firstDifficultyName; + return difficultyName != null && difficultyName != previousDifficultyName; }); AddAssert("created difficulty has timing point", () => From edbaaa94685a1b934b2c889299c4e4ac67e3df2b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 13 Dec 2024 17:41:55 +0900 Subject: [PATCH 217/326] Fix test --- .../Visual/SongSelect/TestSceneBeatmapRecommendations.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 4c8c1d7ad2..aa452101bf 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.SongSelect return 336; // recommended star rating of 2 case 1: - return 928; // SR 3 + return 973; // SR 3 case 2: return 1905; // SR 4 From 0e0d96829f45c65eb0befea8e7a35b7e0208f68a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2024 18:08:29 +0900 Subject: [PATCH 218/326] Fix "quick retry" hotkey not working for autoplay --- osu.Game/Screens/Play/ReplayPlayer.cs | 11 +++++++++-- osu.Game/Screens/Ranking/RetryButton.cs | 9 +++++++-- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 0c125264a1..c1b5397e61 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -34,10 +34,12 @@ namespace osu.Game.Screens.Play protected override UserActivity InitialActivity => new UserActivity.WatchingReplay(Score.ScoreInfo); + private bool isAutoplayPlayback => GameplayState.Mods.OfType().Any(); + // Disallow replays from failing. (see https://github.com/ppy/osu/issues/6108) protected override bool CheckModsAllowFailure() { - if (!replayIsFailedScore && !GameplayState.Mods.OfType().Any()) + if (!replayIsFailedScore && !isAutoplayPlayback) return false; return base.CheckModsAllowFailure(); @@ -102,7 +104,12 @@ namespace osu.Game.Screens.Play Scores = { BindTarget = LeaderboardScores } }; - protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score); + protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score) + { + // Only show the relevant button otherwise things look silly. + AllowWatchingReplay = !isAutoplayPlayback, + AllowRetry = isAutoplayPlayback, + }; public bool OnPressed(KeyBindingPressEvent e) { diff --git a/osu.Game/Screens/Ranking/RetryButton.cs b/osu.Game/Screens/Ranking/RetryButton.cs index d977f25323..8b4f3ca14c 100644 --- a/osu.Game/Screens/Ranking/RetryButton.cs +++ b/osu.Game/Screens/Ranking/RetryButton.cs @@ -38,8 +38,6 @@ namespace osu.Game.Screens.Ranking Icon = FontAwesome.Solid.Redo, }, }; - - TooltipText = "retry"; } [BackgroundDependencyLoader] @@ -48,7 +46,14 @@ namespace osu.Game.Screens.Ranking background.Colour = colours.Green; if (player != null) + { + TooltipText = player is ReplayPlayer ? "replay" : "retry"; Action = () => player.Restart(); + } + else + { + TooltipText = "retry"; + } } } } From d00bc4bdd1e97a1400de9ca95a6b1167334cf16a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2024 18:14:45 +0900 Subject: [PATCH 219/326] Also allow using "quick retry" for other replays --- 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 507d138d90..e3284aac70 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -84,6 +84,7 @@ namespace osu.Game.Screens.Ranking /// public bool ShowUserStatistics { get; init; } + // Only show the relevant button otherwise things look silly. private Sample? popInSample; protected ResultsScreen(ScoreInfo? score) @@ -186,6 +187,8 @@ namespace osu.Game.Screens.Ranking Scheduler.AddDelayed(() => OverlayActivationMode.Value = OverlayActivation.All, shouldFlair ? AccuracyCircle.TOTAL_DURATION + 1000 : 0); } + bool allowHotkeyRetry = false; + if (AllowWatchingReplay) { buttons.Add(new ReplayDownloadButton(SelectedScore.Value) @@ -193,12 +196,19 @@ namespace osu.Game.Screens.Ranking Score = { BindTarget = SelectedScore }, Width = 300 }); + + // for simplicity, only allow when we're guaranteed the replay is already downloaded and present. + allowHotkeyRetry = player is ReplayPlayer; } if (player != null && AllowRetry) { buttons.Add(new RetryButton { Width = 300 }); + allowHotkeyRetry = true; + } + if (allowHotkeyRetry) + { AddInternal(new HotkeyRetryOverlay { Action = () => From 4b0cdd761dd82ba8153c32848b59108a6748b552 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2024 18:58:10 +0900 Subject: [PATCH 220/326] Add note about player settings overlay button --- osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index d2fb2e719a..2968602564 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -62,6 +62,11 @@ namespace osu.Game.Screens.Play.HUD } }); + // For future consideration, this icon should probably not exist. + // + // If we remove it, the following needs attention: + // - Mobile support (swipe from side of screen?) + // - Consolidating this overlay with the one at player loader (to have the animation hint at its presence) AddInternal(button = new IconButton { Icon = FontAwesome.Solid.Cog, From 64555debc29bc4c16dc721d54537dee885f20d10 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2024 19:33:47 +0900 Subject: [PATCH 221/326] Fix adjusting control point offset after undo/redo causing catastrophic failure Closes https://github.com/ppy/osu/issues/31098. Low effort fix because it was already half broken. The test was testing in isolation but in actual editor usage it wasn't working as expected. --- .../Visual/Editing/TestSceneTimingScreen.cs | 66 ++++++++++++++----- .../Screens/Edit/Timing/ControlPointList.cs | 22 +++++++ 2 files changed, 71 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index cf07ce2431..eecfb7cb6e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -32,6 +32,7 @@ namespace osu.Game.Tests.Visual.Editing private TimingScreen timingScreen; private EditorBeatmap editorBeatmap; + private BeatmapEditorChangeHandler changeHandler; protected override bool ScrollUsingMouseWheel => false; @@ -46,6 +47,7 @@ namespace osu.Game.Tests.Visual.Editing private void reloadEditorBeatmap() { editorBeatmap = new EditorBeatmap(Beatmap.Value.GetPlayableBeatmap(Ruleset.Value)); + changeHandler = new BeatmapEditorChangeHandler(editorBeatmap); Child = new DependencyProvidingContainer { @@ -53,6 +55,7 @@ namespace osu.Game.Tests.Visual.Editing CachedDependencies = new (Type, object)[] { (typeof(EditorBeatmap), editorBeatmap), + (typeof(IEditorChangeHandler), changeHandler), (typeof(IBeatSnapProvider), editorBeatmap) }, Child = timingScreen = new TimingScreen @@ -72,8 +75,10 @@ namespace osu.Game.Tests.Visual.Editing AddUntilStep("Wait for rows to load", () => Child.ChildrenOfType().Any()); } + // TODO: this is best-effort for now, but the comment out test below should probably be how things should work. + // Was originally working as of https://github.com/ppy/osu/pull/26141; Regressed at some point. [Test] - public void TestSelectedRetainedOverUndo() + public void TestSelectionDismissedOnUndo() { AddStep("Select first timing point", () => { @@ -95,25 +100,52 @@ namespace osu.Game.Tests.Visual.Editing return timingScreen.SelectedGroup.Value.ControlPoints.Any(c => c is TimingControlPoint) && timingScreen.SelectedGroup.Value.Time > 2170; }); - AddStep("simulate undo", () => - { - var clone = editorBeatmap.ControlPointInfo.DeepClone(); + AddStep("undo", () => changeHandler?.RestoreState(-1)); - 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; - }); + AddUntilStep("selection dismissed", () => timingScreen.SelectedGroup.Value, () => Is.Null); } + // [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("undo", () => changeHandler?.RestoreState(-1)); + // + // AddUntilStep("selection retained", () => + // { + // return timingScreen.SelectedGroup.Value.ControlPoints.Any(c => c is TimingControlPoint) && timingScreen.SelectedGroup.Value.Time > 2170; + // }); + // + // AddAssert("check group count", () => editorBeatmap.ControlPointInfo.Groups.Count, () => Is.EqualTo(10)); + // + // AddStep("Adjust offset", () => + // { + // InputManager.MoveMouseTo(timingScreen.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre + new Vector2(20, 0)); + // InputManager.Click(MouseButton.Left); + // }); + // + // AddAssert("check group count", () => editorBeatmap.ControlPointInfo.Groups.Count, () => Is.EqualTo(10)); + // } + [Test] public void TestScrollControlGroupIntoView() { diff --git a/osu.Game/Screens/Edit/Timing/ControlPointList.cs b/osu.Game/Screens/Edit/Timing/ControlPointList.cs index 49e5b76dd6..12c6390812 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointList.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointList.cs @@ -34,6 +34,9 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private Bindable selectedGroup { get; set; } = null!; + [Resolved] + private IEditorChangeHandler? editorChangeHandler { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours, OverlayColourProvider colourProvider) { @@ -110,6 +113,9 @@ namespace osu.Game.Screens.Edit.Timing } }, }; + + if (editorChangeHandler != null) + editorChangeHandler.OnStateChange += onUndoRedo; } protected override void LoadComplete() @@ -185,5 +191,21 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.Value = group; } + + private void onUndoRedo() + { + // Best effort. We have no tracking of control points through undo/redo changes. + // If we don't deselect, things like offset changes could spawn groups to be added from previous states (see https://github.com/ppy/osu/issues/31098). + if (selectedGroup.Value != null && !Beatmap.ControlPointInfo.Groups.Contains(selectedGroup.Value)) + selectedGroup.Value = null; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (editorChangeHandler != null) + editorChangeHandler.OnStateChange -= onUndoRedo; + } } } From da840e3fac164afa7fcd900895d42c38c305f518 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2024 19:45:18 +0900 Subject: [PATCH 222/326] Change the way "current" points are hinted on timing screen I actually thought things were bugged with the previous display method, since the hinting was very similar to the hover colour/state. I've adjusted this to hopefully give users a better idea of what this is intending to show them. --- .../Screens/Edit/Timing/ControlPointTable.cs | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index fd812cfe2b..56fa251bd3 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -5,6 +5,7 @@ using System; 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; @@ -21,6 +22,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Overlays; using osu.Game.Screens.Edit.Timing.RowAttributes; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Timing { @@ -177,7 +179,7 @@ namespace osu.Game.Screens.Edit.Timing private readonly BindableWithCurrent current = new BindableWithCurrent(); private Box background = null!; - private Box currentIndicator = null!; + private Drawable currentIndicator = null!; [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -210,11 +212,26 @@ namespace osu.Game.Screens.Edit.Timing Colour = colourProvider.Background1, Alpha = 0, }, - currentIndicator = new Box + currentIndicator = new Container { - RelativeSizeAxes = Axes.Y, - Width = 5, + RelativeSizeAxes = Axes.Both, Alpha = 0, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Y, + Width = 5, + }, + new Box + { + RelativeSizeAxes = Axes.Y, + Blending = BlendingParameters.Additive, + X = 5, + Width = 150, + Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0.1f), Color4.White.Opacity(0)) + }, + } }, new Container { @@ -281,14 +298,8 @@ namespace osu.Game.Screens.Edit.Timing bool hasCurrentTimingPoint = activeTimingPoint.Value != null && current.Value.ControlPoints.Contains(activeTimingPoint.Value); bool hasCurrentEffectPoint = activeEffectPoint.Value != null && current.Value.ControlPoints.Contains(activeEffectPoint.Value); - if (IsHovered || isSelected) - background.FadeIn(100, Easing.OutQuint); - else if (hasCurrentTimingPoint || hasCurrentEffectPoint) - background.FadeTo(0.2f, 100, Easing.OutQuint); - else - background.FadeOut(100, Easing.OutQuint); - - background.Colour = isSelected ? colourProvider.Colour3 : colourProvider.Background1; + background.FadeTo(IsHovered || isSelected ? 1 : 0, 100, Easing.OutQuint); + background.FadeColour(isSelected ? colourProvider.Colour3 : colourProvider.Background1, 100, Easing.OutQuint); if (hasCurrentTimingPoint || hasCurrentEffectPoint) { From 9025103b8bb9bcb2dbb4e5fcca80c0ec96b84e96 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 13 Dec 2024 20:02:17 +0900 Subject: [PATCH 223/326] Reword comment to hopefully be more understandable --- osu.Game/Screens/Ranking/ResultsScreen.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index e3284aac70..5e91171051 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -84,7 +84,6 @@ namespace osu.Game.Screens.Ranking /// public bool ShowUserStatistics { get; init; } - // Only show the relevant button otherwise things look silly. private Sample? popInSample; protected ResultsScreen(ScoreInfo? score) @@ -197,7 +196,10 @@ namespace osu.Game.Screens.Ranking Width = 300 }); - // for simplicity, only allow when we're guaranteed the replay is already downloaded and present. + // for simplicity, only allow this when coming from a replay player where we know the replay is ready to be played. + // + // if we show it in all cases, consider the case where a user comes from song select and potentially has to download + // the replay before it can be played back. it wouldn't flow well with the quick retry in such a case. allowHotkeyRetry = player is ReplayPlayer; } From c0b6e784a5076dbaf6addbfdae00bdebd35c3f6f Mon Sep 17 00:00:00 2001 From: Nicholas Chin Date: Fri, 13 Dec 2024 21:58:23 +0800 Subject: [PATCH 224/326] Fix text anchor for mania tooltip --- osu.Game/Graphics/Cursor/OsuTooltipContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index 0d36cc1d08..4180825a8d 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -80,6 +80,7 @@ namespace osu.Game.Graphics.Cursor Margin = new MarginPadding(5), AutoSizeAxes = Axes.Both, MaximumSize = new Vector2(max_width, float.PositiveInfinity), + TextAnchor = Anchor.TopCentre, } }; } From 153e6c0c22504fb5b1e8b32068f54ddd0832de48 Mon Sep 17 00:00:00 2001 From: Nicholas Chin Date: Sat, 14 Dec 2024 08:29:32 +0800 Subject: [PATCH 225/326] Use Count comparison instead of Any --- osu.Game/Overlays/Profile/Header/Components/MainDetails.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs index 84919d18bb..a3208bb85d 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MainDetails.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.LocalisationExtensions; @@ -189,7 +188,7 @@ namespace osu.Game.Overlays.Profile.Header.Components ); } - detailGlobalRank.ContentTooltipText = tooltipParts.Any() + detailGlobalRank.ContentTooltipText = tooltipParts.Count > 0 ? string.Join("\n", tooltipParts) : string.Empty; #endregion @@ -210,7 +209,7 @@ namespace osu.Game.Overlays.Profile.Header.Components } } - detailCountryRank.ContentTooltipText = countryTooltipParts.Any() + detailCountryRank.ContentTooltipText = countryTooltipParts.Count > 0 ? string.Join("\n", countryTooltipParts) : string.Empty; #endregion From e2edd9e0d5351a295468f068d8a643ed57dea3ba Mon Sep 17 00:00:00 2001 From: Nicholas Chin Date: Sun, 15 Dec 2024 13:53:33 +0800 Subject: [PATCH 226/326] Fix code quality issues --- osu.Game/Overlays/Profile/Header/Components/MainDetails.cs | 6 +++++- osu.Game/Users/UserStatistics.cs | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs index a3208bb85d..5df755473d 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs @@ -164,9 +164,10 @@ namespace osu.Game.Overlays.Profile.Header.Components detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; var rankHighest = user?.RankHighest; - var variants = user?.Statistics.Variants; + var variants = user?.Statistics?.Variants; #region Global rank tooltip + var tooltipParts = new List(); if (variants?.Count > 0) @@ -191,11 +192,13 @@ namespace osu.Game.Overlays.Profile.Header.Components detailGlobalRank.ContentTooltipText = tooltipParts.Count > 0 ? string.Join("\n", tooltipParts) : string.Empty; + #endregion detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; #region Country rank tooltip + var countryTooltipParts = new List(); if (variants?.Count > 0) @@ -212,6 +215,7 @@ namespace osu.Game.Overlays.Profile.Header.Components detailCountryRank.ContentTooltipText = countryTooltipParts.Count > 0 ? string.Join("\n", countryTooltipParts) : string.Empty; + #endregion rankGraph.Statistics.Value = user?.Statistics; diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index b485485d48..1effacb36b 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -126,11 +126,13 @@ namespace osu.Game.Users } } } + public enum GameVariant { [EnumMember(Value = "4k")] [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.VariantMania4k))] FourKey, + [EnumMember(Value = "7k")] [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.VariantMania7k))] SevenKey From a6e00d6eac9ee5e14436aec06f456cb61c7753ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 16 Dec 2024 10:49:19 +0900 Subject: [PATCH 227/326] Implement ability to mark beatmap as played Reported at https://osu.ppy.sh/community/forums/topics/2015478?n=1. Would you believe it that this button that has been there for literal years never did anything? Implemented at a per-beatmap level. Also additionally added to context menu (at @peppy's suggestion), and also copy reworded from "Delete from unplayed" to "Mark as played" because double negation hurt my tiny brain. --- osu.Game/Beatmaps/BeatmapManager.cs | 10 ++++++++++ .../Screens/Select/Carousel/DrawableCarouselBeatmap.cs | 8 +++++++- osu.Game/Screens/Select/SongSelect.cs | 2 +- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 148bd90f28..aa67d3c548 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -533,6 +533,16 @@ namespace osu.Game.Beatmaps } } + public void MarkPlayed(BeatmapInfo beatmapSetInfo) => Realm.Run(r => + { + using var transaction = r.BeginWrite(); + + var beatmap = r.Find(beatmapSetInfo.ID)!; + beatmap.LastPlayed = DateTimeOffset.Now; + + transaction.Commit(); + }); + #region Implementation of ICanAcceptFiles public Task Import(params string[] paths) => beatmapImporter.Import(paths); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 75c13c1be6..4451cfcf32 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -88,6 +88,9 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private OsuGame? game { get; set; } + [Resolved] + private BeatmapManager? manager { get; set; } + private IBindable starDifficultyBindable = null!; private CancellationTokenSource? starDifficultyCancellationSource; @@ -98,7 +101,7 @@ namespace osu.Game.Screens.Select.Carousel } [BackgroundDependencyLoader] - private void load(BeatmapManager? manager, SongSelect? songSelect) + private void load(SongSelect? songSelect) { Header.Height = height; @@ -300,6 +303,9 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapInfo.GetOnlineURL(api, ruleset.Value) is string url) items.Add(new OsuMenuItem(CommonStrings.CopyLink, MenuItemType.Standard, () => game?.CopyUrlToClipboard(url))); + if (manager != null) + items.Add(new OsuMenuItem("Mark as played", MenuItemType.Standard, () => manager.MarkPlayed(beatmapInfo))); + if (hideRequested != null) items.Add(new OsuMenuItem(WebCommonStrings.ButtonsHide.ToSentence(), MenuItemType.Destructive, () => hideRequested(beatmapInfo))); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 9f7a2c02ff..651a7fe4a1 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -375,7 +375,7 @@ namespace osu.Game.Screens.Select BeatmapOptions.AddButton(@"Manage", @"collections", FontAwesome.Solid.Book, colours.Green, () => manageCollectionsDialog?.Show()); BeatmapOptions.AddButton(@"Delete", @"all difficulties", FontAwesome.Solid.Trash, colours.Pink, () => DeleteBeatmap(Beatmap.Value.BeatmapSetInfo)); - BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.Regular.TimesCircle, colours.Purple, null); + BeatmapOptions.AddButton(@"Mark", @"as played", FontAwesome.Regular.TimesCircle, colours.Purple, () => beatmaps.MarkPlayed(Beatmap.Value.BeatmapInfo)); BeatmapOptions.AddButton(@"Clear", @"local scores", FontAwesome.Solid.Eraser, colours.Purple, () => ClearScores(Beatmap.Value.BeatmapInfo)); } From 1058abb4ab63cdc0878f436d4414206535fce868 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 16 Dec 2024 12:22:06 +0900 Subject: [PATCH 228/326] Fix code quality --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 56fa251bd3..a37674b104 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -204,7 +204,7 @@ namespace osu.Game.Screens.Edit.Timing { RelativeSizeAxes = Axes.Both; - InternalChildren = new Drawable[] + InternalChildren = new[] { background = new Box { From a8948628e69b4203b0ada2decc71268417c1e144 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Dec 2024 13:12:21 +0900 Subject: [PATCH 229/326] Expose high precision mouse toggle when searching for "sensitivity" and other keywords --- osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs index 6eb512fa35..3fb4016498 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/MouseSettings.cs @@ -57,10 +57,11 @@ namespace osu.Game.Overlays.Settings.Sections.Input LabelText = MouseSettingsStrings.HighPrecisionMouse, TooltipText = MouseSettingsStrings.HighPrecisionMouseTooltip, Current = relativeMode, - Keywords = new[] { @"raw", @"input", @"relative", @"cursor" } + Keywords = new[] { @"raw", @"input", @"relative", @"cursor", "sensitivity", "speed", "velocity" }, }, new SensitivitySetting { + Keywords = new[] { "speed", "velocity" }, LabelText = MouseSettingsStrings.CursorSensitivity, Current = localSensitivity }, From 8d1d026f56bc8bfc0f4ef6eee2c7babce9adcae5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 16 Dec 2024 12:46:25 +0900 Subject: [PATCH 230/326] Clean up model - Properly annotate things as nullable - Remove weird passthrough property (more on that later) --- .../Overlays/Profile/Header/Components/MainDetails.cs | 5 +++-- osu.Game/Users/UserStatistics.cs | 11 +++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs index 5df755473d..6d7eaa4265 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -176,7 +177,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { if (variant.GlobalRank != null) { - tooltipParts.Add($"{variant.VariantDisplay}: {variant.GlobalRank.ToLocalisableString("\\##,##0")}"); + tooltipParts.Add($"{variant.VariantType.GetLocalisableDescription()}: {variant.GlobalRank.ToLocalisableString("\\##,##0")}"); } } } @@ -207,7 +208,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { if (variant.CountryRank != null) { - countryTooltipParts.Add($"{variant.VariantDisplay}: {variant.CountryRank.Value.ToLocalisableString("\\##,##0")}"); + countryTooltipParts.Add($"{variant.VariantType.GetLocalisableDescription()}: {variant.CountryRank.Value.ToLocalisableString("\\##,##0")}"); } } } diff --git a/osu.Game/Users/UserStatistics.cs b/osu.Game/Users/UserStatistics.cs index 1effacb36b..687dd52594 100644 --- a/osu.Game/Users/UserStatistics.cs +++ b/osu.Game/Users/UserStatistics.cs @@ -6,9 +6,9 @@ using System; using System.Collections.Generic; using System.Runtime.Serialization; +using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Converters; -using osu.Framework.Extensions; using osu.Framework.Localisation; using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; @@ -80,7 +80,8 @@ namespace osu.Game.Users public Grades GradesCount; [JsonProperty(@"variants")] - public List Variants = null!; + [CanBeNull] + public List Variants; public struct Grades { @@ -127,7 +128,7 @@ namespace osu.Game.Users } } - public enum GameVariant + public enum RulesetVariant { [EnumMember(Value = "4k")] [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.VariantMania4k))] @@ -154,9 +155,7 @@ namespace osu.Game.Users [JsonProperty("variant")] [JsonConverter(typeof(StringEnumConverter))] - public GameVariant? VariantType; - - public LocalisableString VariantDisplay => VariantType?.GetLocalisableDescription() ?? string.Empty; + public RulesetVariant VariantType; } } } From cfdb959cf69287a8bed61576103313c27f27a331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 16 Dec 2024 13:14:07 +0900 Subject: [PATCH 231/326] Split actual methods & fix completely broken localisation Localisable strings cannot be plainly interpolated or joined. That is a lossy operation that loses data. --- .../Profile/Header/Components/MainDetails.cs | 61 +++++++++++-------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs index 6d7eaa4265..4bdd5425c0 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MainDetails.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; 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.Scoring; @@ -163,13 +164,20 @@ namespace osu.Game.Overlays.Profile.Header.Components scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0; detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; + detailGlobalRank.ContentTooltipText = getGlobalRankTooltipText(user); + detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; + detailCountryRank.ContentTooltipText = getCountryRankTooltipText(user); + + rankGraph.Statistics.Value = user?.Statistics; + } + + private static LocalisableString getGlobalRankTooltipText(APIUser? user) + { var rankHighest = user?.RankHighest; var variants = user?.Statistics?.Variants; - #region Global rank tooltip - - var tooltipParts = new List(); + LocalisableString? result = null; if (variants?.Count > 0) { @@ -177,30 +185,36 @@ namespace osu.Game.Overlays.Profile.Header.Components { if (variant.GlobalRank != null) { - tooltipParts.Add($"{variant.VariantType.GetLocalisableDescription()}: {variant.GlobalRank.ToLocalisableString("\\##,##0")}"); + var variantText = LocalisableString.Interpolate($"{variant.VariantType.GetLocalisableDescription()}: {variant.GlobalRank.ToLocalisableString("\\##,##0")}"); + + if (result == null) + result = variantText; + else + result = LocalisableString.Interpolate($"{result}\n{variantText}"); } } } if (rankHighest != null) { - tooltipParts.Add(UsersStrings.ShowRankHighest( + var rankHighestText = UsersStrings.ShowRankHighest( rankHighest.Rank.ToLocalisableString("\\##,##0"), - rankHighest.UpdatedAt.ToLocalisableString(@"d MMM yyyy")) - ); + rankHighest.UpdatedAt.ToLocalisableString(@"d MMM yyyy")); + + if (result == null) + result = rankHighestText; + else + result = LocalisableString.Interpolate($"{result}\n{rankHighestText}"); } - detailGlobalRank.ContentTooltipText = tooltipParts.Count > 0 - ? string.Join("\n", tooltipParts) - : string.Empty; + return result ?? default; + } - #endregion + private static LocalisableString getCountryRankTooltipText(APIUser? user) + { + var variants = user?.Statistics?.Variants; - detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; - - #region Country rank tooltip - - var countryTooltipParts = new List(); + LocalisableString? result = null; if (variants?.Count > 0) { @@ -208,18 +222,17 @@ namespace osu.Game.Overlays.Profile.Header.Components { if (variant.CountryRank != null) { - countryTooltipParts.Add($"{variant.VariantType.GetLocalisableDescription()}: {variant.CountryRank.Value.ToLocalisableString("\\##,##0")}"); + var variantText = LocalisableString.Interpolate($"{variant.VariantType.GetLocalisableDescription()}: {variant.CountryRank.ToLocalisableString("\\##,##0")}"); + + if (result == null) + result = variantText; + else + result = LocalisableString.Interpolate($"{result}\n{variantText}"); } } } - detailCountryRank.ContentTooltipText = countryTooltipParts.Count > 0 - ? string.Join("\n", countryTooltipParts) - : string.Empty; - - #endregion - - rankGraph.Statistics.Value = user?.Statistics; + return result ?? default; } private partial class ScoreRankInfo : CompositeDrawable From ecb7a809f2242ad4f71b1a22f0a1d8cb453fb67a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 16 Dec 2024 13:18:45 +0900 Subject: [PATCH 232/326] Revert "Fix text anchor for mania tooltip" This reverts commit c0b6e784a5076dbaf6addbfdae00bdebd35c3f6f. The change affects editor and other stuff and I'm not sure it's correct. It's not like client needs to match the appearance really. It already doesn't in many places. --- osu.Game/Graphics/Cursor/OsuTooltipContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index 4180825a8d..0d36cc1d08 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -80,7 +80,6 @@ namespace osu.Game.Graphics.Cursor Margin = new MarginPadding(5), AutoSizeAxes = Axes.Both, MaximumSize = new Vector2(max_width, float.PositiveInfinity), - TextAnchor = Anchor.TopCentre, } }; } From 85ada3275b23d49bd5dea344c07072a204cb7e07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Dec 2024 14:14:30 +0900 Subject: [PATCH 233/326] Skip the pause cooldown when in intro / break time Had a quick look at adding test coverage in `TestScenePause` but the setup to get into either of these states seems a bit annoying.. --- 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 a762d2ae82..406a59a3b6 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1032,7 +1032,7 @@ namespace osu.Game.Screens.Play private double? lastPauseActionTime; protected bool PauseCooldownActive => - lastPauseActionTime.HasValue && GameplayClockContainer.CurrentTime < lastPauseActionTime + PauseCooldownDuration; + PlayingState.Value == LocalUserPlayingState.Playing && lastPauseActionTime.HasValue && GameplayClockContainer.CurrentTime < lastPauseActionTime + PauseCooldownDuration; /// /// A set of conditionals which defines whether the current game state and configuration allows for From bdd417c1a1cd832b0433863d3ce151af60f99093 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Dec 2024 15:18:39 +0900 Subject: [PATCH 234/326] Move "global" scroll-adjusts-volume to a per-screen component-based implementation --- .../TestSceneOverlayContainer.cs | 19 +++++-- .../UserInterface/TestSceneVolumeOverlay.cs | 18 +++--- osu.Game/OsuGame.cs | 20 ++++--- .../Volume/GlobalScrollAdjustsVolume.cs | 40 +++++++++++++ .../Overlays/Volume/VolumeControlReceptor.cs | 57 ------------------- osu.Game/Screens/Menu/MainMenu.cs | 2 + osu.Game/Screens/Play/Player.cs | 7 ++- osu.Game/Screens/Play/PlayerLoader.cs | 2 + osu.Game/Screens/Select/SongSelect.cs | 3 + 9 files changed, 92 insertions(+), 76 deletions(-) create mode 100644 osu.Game/Overlays/Volume/GlobalScrollAdjustsVolume.cs delete mode 100644 osu.Game/Overlays/Volume/VolumeControlReceptor.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayContainer.cs index bb94912c83..e544fb127d 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOverlayContainer.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Volume; @@ -59,13 +60,12 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestAltScrollNotBlocked() { - bool scrollReceived = false; + TestGlobalScrollAdjustsVolume volumeAdjust = null!; - AddStep("add volume control receptor", () => Add(new VolumeControlReceptor + AddStep("add volume control receptor", () => Add(volumeAdjust = new TestGlobalScrollAdjustsVolume { RelativeSizeAxes = Axes.Both, Depth = float.MaxValue, - ScrollActionRequested = (_, _, _) => scrollReceived = true, })); AddStep("hold alt", () => InputManager.PressKey(Key.AltLeft)); @@ -75,10 +75,21 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.ScrollVerticalBy(10); }); - AddAssert("receptor received scroll input", () => scrollReceived); + AddAssert("receptor received scroll input", () => volumeAdjust.ScrollReceived); AddStep("release alt", () => InputManager.ReleaseKey(Key.AltLeft)); } + public partial class TestGlobalScrollAdjustsVolume : GlobalScrollAdjustsVolume + { + public bool ScrollReceived { get; private set; } + + protected override bool OnScroll(ScrollEvent e) + { + ScrollReceived = true; + return base.OnScroll(e); + } + } + private partial class TestOverlay : OsuFocusedOverlayContainer { [BackgroundDependencyLoader] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneVolumeOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneVolumeOverlay.cs index 52543c68ce..c2b8ec76f4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneVolumeOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneVolumeOverlay.cs @@ -1,8 +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.Game.Overlays; using osu.Game.Overlays.Volume; @@ -11,7 +10,14 @@ namespace osu.Game.Tests.Visual.UserInterface { public partial class TestSceneVolumeOverlay : OsuTestScene { - private VolumeOverlay volume; + private VolumeOverlay volume = null!; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(volume = new VolumeOverlay()); + return dependencies; + } protected override void LoadComplete() { @@ -19,12 +25,10 @@ namespace osu.Game.Tests.Visual.UserInterface AddRange(new Drawable[] { - volume = new VolumeOverlay(), - new VolumeControlReceptor + volume, + new GlobalScrollAdjustsVolume { RelativeSizeAxes = Axes.Both, - ActionRequested = action => volume.Adjust(action), - ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise), }, }); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e808e570c7..60fcd17ac6 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -57,7 +57,6 @@ using osu.Game.Overlays.Notifications; using osu.Game.Overlays.OSD; using osu.Game.Overlays.SkinEditor; using osu.Game.Overlays.Toolbar; -using osu.Game.Overlays.Volume; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Screens; @@ -980,12 +979,6 @@ namespace osu.Game AddRange(new Drawable[] { - new VolumeControlReceptor - { - RelativeSizeAxes = Axes.Both, - ActionRequested = action => volume.Adjust(action), - ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise), - }, ScreenOffsetContainer = new Container { RelativeSizeAxes = Axes.Both, @@ -1432,6 +1425,19 @@ namespace osu.Game switch (e.Action) { + case GlobalAction.DecreaseVolume: + case GlobalAction.IncreaseVolume: + return volume.Adjust(e.Action); + + case GlobalAction.ToggleMute: + case GlobalAction.NextVolumeMeter: + case GlobalAction.PreviousVolumeMeter: + + if (!e.Repeat) + return true; + + return volume.Adjust(e.Action); + case GlobalAction.ToggleFPSDisplay: fpsCounter.ToggleVisibility(); return true; diff --git a/osu.Game/Overlays/Volume/GlobalScrollAdjustsVolume.cs b/osu.Game/Overlays/Volume/GlobalScrollAdjustsVolume.cs new file mode 100644 index 0000000000..81be084d22 --- /dev/null +++ b/osu.Game/Overlays/Volume/GlobalScrollAdjustsVolume.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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; +using osu.Game.Input.Bindings; + +namespace osu.Game.Overlays.Volume +{ + /// + /// Add to a container or screen to make scrolling anywhere in the container cause the global game volume to be adjusted. + /// + /// + /// This is generally expected behaviour in many locations in osu!stable. + /// + public partial class GlobalScrollAdjustsVolume : Container + { + [Resolved] + private VolumeOverlay? volumeOverlay { get; set; } + + public GlobalScrollAdjustsVolume() + { + RelativeSizeAxes = Axes.Both; + } + + protected override bool OnScroll(ScrollEvent e) + { + if (e.ScrollDelta.Y == 0) + return false; + + // forward any unhandled mouse scroll events to the volume control. + return volumeOverlay?.Adjust(GlobalAction.IncreaseVolume, e.ScrollDelta.Y, e.IsPrecise) ?? false; + } + + public bool OnScroll(KeyBindingScrollEvent e) => + volumeOverlay?.Adjust(e.Action, e.ScrollAmount, e.IsPrecise) ?? false; + } +} diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs deleted file mode 100644 index 2e8d86d4c7..0000000000 --- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs +++ /dev/null @@ -1,57 +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 osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Input.Bindings; - -namespace osu.Game.Overlays.Volume -{ - public partial class VolumeControlReceptor : Container, IScrollBindingHandler, IHandleGlobalKeyboardInput - { - public Func ActionRequested; - public Func ScrollActionRequested; - - public bool OnPressed(KeyBindingPressEvent e) - { - switch (e.Action) - { - case GlobalAction.DecreaseVolume: - case GlobalAction.IncreaseVolume: - return ActionRequested?.Invoke(e.Action) == true; - - case GlobalAction.ToggleMute: - case GlobalAction.NextVolumeMeter: - case GlobalAction.PreviousVolumeMeter: - if (!e.Repeat) - return ActionRequested?.Invoke(e.Action) == true; - - return false; - } - - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - - protected override bool OnScroll(ScrollEvent e) - { - if (e.ScrollDelta.Y == 0) - return false; - - // forward any unhandled mouse scroll events to the volume control. - ScrollActionRequested?.Invoke(GlobalAction.IncreaseVolume, e.ScrollDelta.Y, e.IsPrecise); - return true; - } - - public bool OnScroll(KeyBindingScrollEvent e) => - ScrollActionRequested?.Invoke(e.Action, e.ScrollAmount, e.IsPrecise) ?? false; - } -} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 0630b9612e..ae1ad4dceb 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -28,6 +28,7 @@ using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; using osu.Game.Overlays.SkinEditor; +using osu.Game.Overlays.Volume; using osu.Game.Rulesets; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; @@ -124,6 +125,7 @@ namespace osu.Game.Screens.Menu AddRangeInternal(new[] { + new GlobalScrollAdjustsVolume(), buttonsContainer = new ParallaxContainer { ParallaxAmount = 0.01f, diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a762d2ae82..1c186485b8 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -28,6 +28,7 @@ using osu.Game.Graphics.Containers; using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Overlays; +using osu.Game.Overlays.Volume; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -251,7 +252,11 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(HealthProcessor); - InternalChild = GameplayClockContainer = CreateGameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime); + InternalChildren = new Drawable[] + { + new GlobalScrollAdjustsVolume(), + GameplayClockContainer = CreateGameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime), + }; AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 20985c20e0..837974a8f2 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -27,6 +27,7 @@ using osu.Game.Input; using osu.Game.Localisation; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Overlays.Volume; using osu.Game.Performance; using osu.Game.Scoring; using osu.Game.Screens.Menu; @@ -190,6 +191,7 @@ namespace osu.Game.Screens.Play InternalChildren = new Drawable[] { + new GlobalScrollAdjustsVolume(), (content = new LogoTrackingContainer { Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 9f7a2c02ff..210f8203f4 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -31,6 +31,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Overlays; using osu.Game.Overlays.Mods; +using osu.Game.Overlays.Volume; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Backgrounds; @@ -169,10 +170,12 @@ namespace osu.Game.Screens.Select AddRangeInternal(new Drawable[] { + new GlobalScrollAdjustsVolume(), new VerticalMaskingContainer { Children = new Drawable[] { + new GlobalScrollAdjustsVolume(), new GridContainer // used for max width implementation { RelativeSizeAxes = Axes.Both, From d97ea781364323383fa59512e45cac494387fb4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Dec 2024 15:22:30 +0900 Subject: [PATCH 235/326] Change beat snap divisior adjust defaults to be Ctrl+Scroll instead of Ctrl+Shift+Scroll Matches stable. - [ ] Depends on https://github.com/ppy/osu/pull/31146, else this will adjust the global volume. --- 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 170d247023..c343b4e1e6 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -144,8 +144,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelUp }, GlobalAction.EditorIncreaseDistanceSpacing), // Framework automatically converts wheel up/down to left/right when shift is held. // See https://github.com/ppy/osu-framework/blob/master/osu.Framework/Input/StateChanges/MouseScrollRelativeInput.cs#L37-L38. - 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.MouseWheelRight }, GlobalAction.EditorCyclePreviousBeatSnapDivisor), + new KeyBinding(new[] { InputKey.Control, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor), new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl), new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl), new KeyBinding(new[] { InputKey.Control, InputKey.Left }, GlobalAction.EditorSeekToPreviousHitObject), From 68a5618e81013b40eafadd7cf4bb3b8962fc9a8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 16 Dec 2024 16:03:26 +0900 Subject: [PATCH 236/326] Add test coverage --- .../Visual/Gameplay/TestScenePause.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 6aa2c4e40d..7855c138ab 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; @@ -19,6 +20,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.UI; using osu.Game.Screens.Play; using osu.Game.Skinning; +using osu.Game.Storyboards; using osuTK; using osuTK.Input; @@ -28,6 +30,12 @@ namespace osu.Game.Tests.Visual.Gameplay { protected new PausePlayer Player => (PausePlayer)base.Player; + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + { + beatmap.AudioLeadIn = 4000; + return base.CreateWorkingBeatmap(beatmap, storyboard); + } + private readonly Container content; protected override Container Content => content; @@ -202,6 +210,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestUserPauseDuringCooldownTooSoon() { + AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(0)); AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); pauseAndConfirm(); @@ -213,9 +222,23 @@ namespace osu.Game.Tests.Visual.Gameplay confirmNotExited(); } + [Test] + public void TestUserPauseDuringIntroSkipsCooldown() + { + AddStep("seek before gameplay", () => Player.GameplayClockContainer.Seek(-5000)); + AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); + + pauseAndConfirm(); + + resume(); + pauseViaBackAction(); + confirmPaused(); + } + [Test] public void TestQuickExitDuringCooldownTooSoon() { + AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(0)); AddStep("move cursor outside", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.TopLeft - new Vector2(10))); pauseAndConfirm(); From 09fc30e377ea255387059d00a5a72faa8060c0e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 16 Dec 2024 17:36:40 +0900 Subject: [PATCH 237/326] Hide `!mp` commands from tournament streaming chat --- .../TestSceneTournamentMatchChatDisplay.cs | 6 +++++ .../Components/TournamentMatchChatDisplay.cs | 9 ++++++- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 27 ++++++++++--------- osu.Game/Overlays/Chat/DrawableChannel.cs | 9 +++++-- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs index de91a66e56..231bd77655 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentMatchChatDisplay.cs @@ -152,6 +152,12 @@ namespace osu.Game.Tournament.Tests.Components AddStep("change channel to 2", () => chatDisplay.Channel.Value = testChannel2); AddStep("change channel to 1", () => chatDisplay.Channel.Value = testChannel); + + AddStep("!mp message (shouldn't display)", () => testChannel.AddNewMessages(new Message(nextMessageId()) + { + Sender = redUser.ToAPIUser(), + Content = "!mp wangs" + })); } private int messageId; diff --git a/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs index 0998e606e9..c04dbdcdd6 100644 --- a/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs +++ b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.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; @@ -72,7 +73,13 @@ namespace osu.Game.Tournament.Components public void Contract() => this.FadeOut(200); - protected override ChatLine CreateMessage(Message message) => new MatchMessage(message, ladderInfo); + protected override ChatLine? CreateMessage(Message message) + { + if (message.Content.StartsWith("!mp", StringComparison.Ordinal)) + return null; + + return new MatchMessage(message, ladderInfo); + } protected override StandAloneDrawableChannel CreateDrawableChannel(Channel channel) => new MatchChannel(channel); diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 187191d232..667ef072a9 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -1,9 +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 osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -21,18 +20,18 @@ using osuTK.Input; namespace osu.Game.Online.Chat { /// - /// Display a chat channel in an insolated region. + /// Display a chat channel in an isolated region. /// public partial class StandAloneChatDisplay : CompositeDrawable { [Cached] - public readonly Bindable Channel = new Bindable(); + public readonly Bindable Channel = new Bindable(); - protected readonly ChatTextBox TextBox; + protected readonly ChatTextBox? TextBox; - private ChannelManager channelManager; + private ChannelManager? channelManager; - private StandAloneDrawableChannel drawableChannel; + private StandAloneDrawableChannel? drawableChannel; private readonly bool postingTextBox; @@ -93,6 +92,8 @@ namespace osu.Game.Online.Chat private void postMessage(TextBox sender, bool newText) { + Debug.Assert(TextBox != null); + string text = TextBox.Text.Trim(); if (string.IsNullOrWhiteSpace(text)) @@ -106,9 +107,9 @@ namespace osu.Game.Online.Chat TextBox.Text = string.Empty; } - protected virtual ChatLine CreateMessage(Message message) => new StandAloneMessage(message); + protected virtual ChatLine? CreateMessage(Message message) => new StandAloneMessage(message); - private void channelChanged(ValueChangedEvent e) + private void channelChanged(ValueChangedEvent e) { drawableChannel?.Expire(); @@ -128,8 +129,8 @@ namespace osu.Game.Online.Chat public partial class ChatTextBox : HistoryTextBox { - public Action Focus; - public Action FocusLost; + public Action? Focus; + public Action? FocusLost; protected override bool OnKeyDown(KeyDownEvent e) { @@ -171,14 +172,14 @@ namespace osu.Game.Online.Chat public partial class StandAloneDrawableChannel : DrawableChannel { - public Func CreateChatLineAction; + public Func? CreateChatLineAction; public StandAloneDrawableChannel(Channel channel) : base(channel) { } - protected override ChatLine CreateChatLine(Message m) => CreateChatLineAction(m); + protected override ChatLine? CreateChatLine(Message m) => CreateChatLineAction?.Invoke(m) ?? null; protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new StandAloneDaySeparator(time); } diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 41098ef823..b1b91f5fe3 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -155,8 +155,13 @@ namespace osu.Game.Overlays.Chat { addDaySeparatorIfRequired(lastMessage, message); - ChatLineFlow.Add(CreateChatLine(message)); - lastMessage = message; + var chatLine = CreateChatLine(message); + + if (chatLine != null) + { + ChatLineFlow.Add(chatLine); + lastMessage = message; + } } var staleMessages = chatLines.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); From c46e81d8908c2e73f6d194f1b233a8b6fd81f6aa Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 11 Dec 2024 03:31:51 -0500 Subject: [PATCH 238/326] Roll our own iOS application delegates --- osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs | 15 +++++++++++++++ .../{Application.cs => Program.cs} | 7 +++---- osu.Game.Rulesets.Mania.Tests.iOS/AppDelegate.cs | 15 +++++++++++++++ .../{Application.cs => Program.cs} | 7 +++---- osu.Game.Rulesets.Osu.Tests.iOS/AppDelegate.cs | 15 +++++++++++++++ .../{Application.cs => Program.cs} | 7 +++---- osu.Game.Rulesets.Taiko.Tests.iOS/AppDelegate.cs | 15 +++++++++++++++ .../{Application.cs => Program.cs} | 7 +++---- osu.Game.Tests.iOS/AppDelegate.cs | 14 ++++++++++++++ osu.Game.Tests.iOS/{Application.cs => Program.cs} | 6 +++--- osu.iOS/AppDelegate.cs | 14 ++++++++++++++ osu.iOS/{Application.cs => Program.cs} | 6 +++--- 12 files changed, 106 insertions(+), 22 deletions(-) create mode 100644 osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs rename osu.Game.Rulesets.Catch.Tests.iOS/{Application.cs => Program.cs} (66%) create mode 100644 osu.Game.Rulesets.Mania.Tests.iOS/AppDelegate.cs rename osu.Game.Rulesets.Mania.Tests.iOS/{Application.cs => Program.cs} (66%) create mode 100644 osu.Game.Rulesets.Osu.Tests.iOS/AppDelegate.cs rename osu.Game.Rulesets.Osu.Tests.iOS/{Application.cs => Program.cs} (66%) create mode 100644 osu.Game.Rulesets.Taiko.Tests.iOS/AppDelegate.cs rename osu.Game.Rulesets.Taiko.Tests.iOS/{Application.cs => Program.cs} (66%) create mode 100644 osu.Game.Tests.iOS/AppDelegate.cs rename osu.Game.Tests.iOS/{Application.cs => Program.cs} (69%) create mode 100644 osu.iOS/AppDelegate.cs rename osu.iOS/{Application.cs => Program.cs} (69%) diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs b/osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.cs new file mode 100644 index 0000000000..b594d28611 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests.iOS/AppDelegate.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 Foundation; +using osu.Framework.iOS; +using osu.Game.Tests; + +namespace osu.Game.Rulesets.Catch.Tests.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameApplicationDelegate + { + protected override Framework.Game CreateGame() => new OsuTestBrowser(); + } +} diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs b/osu.Game.Rulesets.Catch.Tests.iOS/Program.cs similarity index 66% rename from osu.Game.Rulesets.Catch.Tests.iOS/Application.cs rename to osu.Game.Rulesets.Catch.Tests.iOS/Program.cs index d097c6a698..6b887ae2d4 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Program.cs @@ -1,16 +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.iOS; -using osu.Game.Tests; +using UIKit; namespace osu.Game.Rulesets.Catch.Tests.iOS { - public static class Application + public static class Program { public static void Main(string[] args) { - GameApplication.Main(new OsuTestBrowser()); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/AppDelegate.cs b/osu.Game.Rulesets.Mania.Tests.iOS/AppDelegate.cs new file mode 100644 index 0000000000..09bed3b42b --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests.iOS/AppDelegate.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 Foundation; +using osu.Framework.iOS; +using osu.Game.Tests; + +namespace osu.Game.Rulesets.Mania.Tests.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameApplicationDelegate + { + protected override Framework.Game CreateGame() => new OsuTestBrowser(); + } +} diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs b/osu.Game.Rulesets.Mania.Tests.iOS/Program.cs similarity index 66% rename from osu.Game.Rulesets.Mania.Tests.iOS/Application.cs rename to osu.Game.Rulesets.Mania.Tests.iOS/Program.cs index 75a5a73058..696816c47b 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Program.cs @@ -1,16 +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.iOS; -using osu.Game.Tests; +using UIKit; namespace osu.Game.Rulesets.Mania.Tests.iOS { - public static class Application + public static class Program { public static void Main(string[] args) { - GameApplication.Main(new OsuTestBrowser()); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/AppDelegate.cs b/osu.Game.Rulesets.Osu.Tests.iOS/AppDelegate.cs new file mode 100644 index 0000000000..77177e93f1 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests.iOS/AppDelegate.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 Foundation; +using osu.Framework.iOS; +using osu.Game.Tests; + +namespace osu.Game.Rulesets.Osu.Tests.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameApplicationDelegate + { + protected override Framework.Game CreateGame() => new OsuTestBrowser(); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs b/osu.Game.Rulesets.Osu.Tests.iOS/Program.cs similarity index 66% rename from osu.Game.Rulesets.Osu.Tests.iOS/Application.cs rename to osu.Game.Rulesets.Osu.Tests.iOS/Program.cs index f9059014a5..579e20e05a 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Program.cs @@ -1,16 +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.iOS; -using osu.Game.Tests; +using UIKit; namespace osu.Game.Rulesets.Osu.Tests.iOS { - public static class Application + public static class Program { public static void Main(string[] args) { - GameApplication.Main(new OsuTestBrowser()); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/AppDelegate.cs b/osu.Game.Rulesets.Taiko.Tests.iOS/AppDelegate.cs new file mode 100644 index 0000000000..4bfc12e7e8 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/AppDelegate.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 Foundation; +using osu.Framework.iOS; +using osu.Game.Tests; + +namespace osu.Game.Rulesets.Taiko.Tests.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameApplicationDelegate + { + protected override Framework.Game CreateGame() => new OsuTestBrowser(); + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs b/osu.Game.Rulesets.Taiko.Tests.iOS/Program.cs similarity index 66% rename from osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs rename to osu.Game.Rulesets.Taiko.Tests.iOS/Program.cs index 0b6a11d8c2..bf2ffecb23 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Program.cs @@ -1,16 +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.iOS; -using osu.Game.Tests; +using UIKit; namespace osu.Game.Rulesets.Taiko.Tests.iOS { - public static class Application + public static class Program { public static void Main(string[] args) { - GameApplication.Main(new OsuTestBrowser()); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } diff --git a/osu.Game.Tests.iOS/AppDelegate.cs b/osu.Game.Tests.iOS/AppDelegate.cs new file mode 100644 index 0000000000..bfad59de43 --- /dev/null +++ b/osu.Game.Tests.iOS/AppDelegate.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 Foundation; +using osu.Framework.iOS; + +namespace osu.Game.Tests.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameApplicationDelegate + { + protected override Framework.Game CreateGame() => new OsuTestBrowser(); + } +} diff --git a/osu.Game.Tests.iOS/Application.cs b/osu.Game.Tests.iOS/Program.cs similarity index 69% rename from osu.Game.Tests.iOS/Application.cs rename to osu.Game.Tests.iOS/Program.cs index e5df79f3de..35a90d7213 100644 --- a/osu.Game.Tests.iOS/Application.cs +++ b/osu.Game.Tests.iOS/Program.cs @@ -1,15 +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.iOS; +using UIKit; namespace osu.Game.Tests.iOS { - public static class Application + public static class Program { public static void Main(string[] args) { - GameApplication.Main(new OsuTestBrowser()); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } diff --git a/osu.iOS/AppDelegate.cs b/osu.iOS/AppDelegate.cs new file mode 100644 index 0000000000..e88b39f710 --- /dev/null +++ b/osu.iOS/AppDelegate.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 Foundation; +using osu.Framework.iOS; + +namespace osu.iOS +{ + [Register("AppDelegate")] + public class AppDelegate : GameApplicationDelegate + { + protected override Framework.Game CreateGame() => new OsuGameIOS(); + } +} diff --git a/osu.iOS/Application.cs b/osu.iOS/Program.cs similarity index 69% rename from osu.iOS/Application.cs rename to osu.iOS/Program.cs index 74bd58acb8..fd24ecf419 100644 --- a/osu.iOS/Application.cs +++ b/osu.iOS/Program.cs @@ -1,15 +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.iOS; +using UIKit; namespace osu.iOS { - public static class Application + public static class Program { public static void Main(string[] args) { - GameApplication.Main(new OsuGameIOS()); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } From 4bf90a5571bb508444178f3d98a2b2a10c549534 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 16 Dec 2024 08:24:22 -0500 Subject: [PATCH 239/326] Use time-based resume overlay when playing osu! on touchscreen --- osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs index ab69b67051..12d5363469 100644 --- a/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/UI/DrawableOsuRuleset.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override ResumeOverlay CreateResumeOverlay() { - if (Mods.Any(m => m is OsuModAutopilot)) + if (Mods.Any(m => m is OsuModAutopilot or OsuModTouchDevice)) return new DelayedResumeOverlay { Scale = new Vector2(0.65f) }; return new OsuResumeOverlay(); From 22e74cc0ee2c83b3e52c84286523041a6c3b1b06 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 16 Dec 2024 12:22:28 -0500 Subject: [PATCH 240/326] Fix iOS app configuration missing certain specifications --- osu.iOS/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index ae36d00910..0be75fffd8 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -153,5 +153,7 @@ LSApplicationCategoryType public.app-category.music-games + LSSupportsOpeningDocumentsInPlace + From 47d81e7dee802a47da83732e00690bb823996718 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 17 Dec 2024 19:10:09 +0900 Subject: [PATCH 241/326] Fix null inspections on `GameplayChatDisplay` --- .../Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs index 9a03a131b4..befaf115ae 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/GameplayChatDisplay.cs @@ -19,6 +19,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved(CanBeNull = true)] private ILocalUserPlayInfo? localUserInfo { get; set; } + protected new ChatTextBox TextBox => base.TextBox!; + private readonly IBindable localUserPlaying = new Bindable(); public override bool PropagatePositionalInputSubTree => localUserPlaying.Value != LocalUserPlayingState.Playing; @@ -58,7 +60,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer localUserPlaying.BindValueChanged(playing => { - // for now let's never hold focus. this avoid misdirected gameplay keys entering chat. + // for now let's never hold focus. this avoids misdirected gameplay keys entering chat. // note that this is done within this callback as it triggers an un-focus as well. TextBox.HoldFocus = false; From c68dc1141215e97cae0c2f8f27d41e54bbe028d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Dec 2024 00:01:36 +0900 Subject: [PATCH 242/326] Fix being able to click through slider tail drag handles Closes https://github.com/ppy/osu/issues/31176. --- .../Edit/Blueprints/Sliders/SliderEndDragMarker.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderEndDragMarker.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderEndDragMarker.cs index 37383544dc..326dd82fc6 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderEndDragMarker.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderEndDragMarker.cs @@ -76,6 +76,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders base.OnDragEnd(e); } + protected override bool OnMouseDown(MouseDownEvent e) => true; + + protected override bool OnClick(ClickEvent e) => true; + private void updateState() { Colour = IsHovered || IsDragged ? colours.Red : colours.Yellow; From 75d694d3dff0131e24b04deef4a34689628b1b76 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 18 Dec 2024 12:43:20 -0500 Subject: [PATCH 243/326] Add key value for `NSBluetoothAlwaysUsageDescription` --- 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 0be75fffd8..29410938a3 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -34,9 +34,11 @@ CADisableMinimumFrameDurationOnPhone NSCameraUsageDescription - We don't really use the camera. + We don't use the camera. NSMicrophoneUsageDescription - We don't really use the microphone. + We don't use the microphone. + NSBluetoothAlwaysUsageDescription + We don't use Bluetooth. UISupportedInterfaceOrientations UIInterfaceOrientationLandscapeRight From 532c681e3c53c0b3f36f18201afca884ebcdf144 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Wed, 18 Dec 2024 12:48:24 -0500 Subject: [PATCH 244/326] 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 632325725a..6770b0254f 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 62a65f291d..640e6bdd94 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From c7354d9c4104d0692d567c116af3ff84364986bf Mon Sep 17 00:00:00 2001 From: mini <39670899+minisbett@users.noreply.github.com> Date: Sun, 15 Dec 2024 17:31:13 +0100 Subject: [PATCH 245/326] Apply type inheritance check --- .../IO/Serialization/Converters/TypedListConverter.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index de25d3e30e..19ef6b8fe6 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -62,8 +62,12 @@ namespace osu.Game.IO.Serialization.Converters if (tok["$type"] == null) throw new JsonException("Expected $type token."); - string typeName = lookupTable[(int)tok["$type"]]; - var instance = (T)Activator.CreateInstance(Type.GetType(typeName).AsNonNull())!; + // Prevent instantiation of types that do not inherit the type targetted by this converter + Type type = Type.GetType(lookupTable[(int)tok["$type"]]).AsNonNull(); + if (!type.IsAssignableTo(typeof(T))) + continue; + + var instance = (T)Activator.CreateInstance(type)!; serializer.Populate(itemReader, instance); list.Add(instance); From dedf8ad0936927b400b9d4b8ea3f411dde7e72ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Dec 2024 17:25:02 +0900 Subject: [PATCH 246/326] 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 847c209cc4..3f9a8142ca 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 6dc681f0e9d501c2747b4a14e9b9e182c5d2aa41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 19 Dec 2024 12:50:48 +0100 Subject: [PATCH 247/326] Annotate virtual as potentially nullable --- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index b1b91f5fe3..cb7cd03584 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.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.Graphics; @@ -132,6 +133,7 @@ namespace osu.Game.Overlays.Chat Channel.PendingMessageResolved -= pendingMessageResolved; } + [CanBeNull] protected virtual ChatLine CreateChatLine(Message m) => new ChatLine(m); protected virtual DaySeparator CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time); From 772ac2d3261595d1b23e97661f091ac41829bb88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 19 Dec 2024 14:48:18 +0100 Subject: [PATCH 248/326] Fix mod display not fading out after start of play This was very weird on master - `ModDisplay` applied a fade-in on the `iconsContainer` that lasted 1000ms, and `HUDOverlay` would stack another 200ms fade-in on top if a replay was loaded. Moving that first fadeout to a higher level broke fade-out because transforms got overwritten. --- osu.Game/Screens/Play/HUDOverlay.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 5d92fee841..f7b1a95c23 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -35,8 +35,6 @@ namespace osu.Game.Screens.Play { public const float FADE_DURATION = 300; - private const float mods_fade_duration = 1000; - public const Easing FADE_EASING = Easing.OutQuint; /// @@ -238,7 +236,7 @@ namespace osu.Game.Screens.Play { if (e.NewValue) { - ModDisplay.FadeIn(200); + ModDisplay.FadeIn(1000, FADE_EASING); InputCountController.Margin = new MarginPadding(10) { Bottom = 30 }; } else @@ -255,8 +253,6 @@ namespace osu.Game.Screens.Play { ModDisplay.ExpansionMode = ExpansionMode.ExpandOnHover; }, 1200); - - ModDisplay.FadeInFromZero(mods_fade_duration, FADE_EASING); } protected override void Update() From 7d1473c5d0d2c3a2ba2f7467cbd5d06069b01ab1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 19 Dec 2024 14:52:27 +0100 Subject: [PATCH 249/326] Simplify expand/contract code --- osu.Game/Screens/Play/HUD/ModDisplay.cs | 47 ++++++++++++------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 9f42175a70..38417fae04 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -33,12 +33,7 @@ namespace osu.Game.Screens.Play.HUD expansionMode = value; if (IsLoaded) - { - if (expansionMode == ExpansionMode.AlwaysExpanded || (expansionMode == ExpansionMode.ExpandOnHover && IsHovered)) - expand(); - else if (expansionMode == ExpansionMode.AlwaysContracted || (expansionMode == ExpansionMode.ExpandOnHover && !IsHovered)) - contract(); - } + updateExpansionMode(); } } @@ -88,24 +83,7 @@ namespace osu.Game.Screens.Play.HUD base.LoadComplete(); Current.BindValueChanged(updateDisplay, true); - - switch (expansionMode) - { - case ExpansionMode.AlwaysExpanded: - expand(0); - break; - - case ExpansionMode.AlwaysContracted: - contract(0); - break; - - case ExpansionMode.ExpandOnHover: - if (IsHovered) - expand(0); - else - contract(0); - break; - } + updateExpansionMode(0); } private void updateDisplay(ValueChangedEvent> mods) @@ -116,6 +94,27 @@ namespace osu.Game.Screens.Play.HUD iconsContainer.Add(new ModIcon(mod, showExtendedInformation: showExtendedInformation) { Scale = new Vector2(0.6f) }); } + private void updateExpansionMode(double duration = 500) + { + switch (expansionMode) + { + case ExpansionMode.AlwaysExpanded: + expand(duration); + break; + + case ExpansionMode.AlwaysContracted: + contract(duration); + break; + + case ExpansionMode.ExpandOnHover: + if (IsHovered) + expand(duration); + else + contract(duration); + break; + } + } + private void expand(double duration = 500) { if (ExpansionMode != ExpansionMode.AlwaysContracted) From e458f540ac857d934a851094a4e03743cbf421e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 19 Dec 2024 14:54:57 +0100 Subject: [PATCH 250/326] Adjust formatting --- osu.Game/Screens/Play/HUDOverlay.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index f7b1a95c23..c9ab754e94 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -249,10 +249,7 @@ namespace osu.Game.Screens.Play }, true); ModDisplay.ExpansionMode = ExpansionMode.AlwaysExpanded; - Scheduler.AddDelayed(() => - { - ModDisplay.ExpansionMode = ExpansionMode.ExpandOnHover; - }, 1200); + Scheduler.AddDelayed(() => ModDisplay.ExpansionMode = ExpansionMode.ExpandOnHover, 1200); } protected override void Update() From 2cab8f4e8a38f7a2da570cb792bf7ab50efa57d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 19 Dec 2024 15:02:49 +0100 Subject: [PATCH 251/326] Add localisation support --- .../SkinnableModDisplayStrings.cs | 49 +++++++++++++++++++ osu.Game/Screens/Play/HUD/ModDisplay.cs | 6 +++ .../Screens/Play/HUD/SkinnableModDisplay.cs | 8 +-- 3 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Localisation/SkinComponents/SkinnableModDisplayStrings.cs diff --git a/osu.Game/Localisation/SkinComponents/SkinnableModDisplayStrings.cs b/osu.Game/Localisation/SkinComponents/SkinnableModDisplayStrings.cs new file mode 100644 index 0000000000..d3e8c0f8c8 --- /dev/null +++ b/osu.Game/Localisation/SkinComponents/SkinnableModDisplayStrings.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.SkinComponents +{ + public static class SkinnableModDisplayStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.SkinnableModDisplay"; + + /// + /// "Show extended information" + /// + public static LocalisableString ShowExtendedInformation => new TranslatableString(getKey(@"show_extended_information"), @"Show extended information"); + + /// + /// "Whether to show extended information for each mod." + /// + public static LocalisableString ShowExtendedInformationDescription => new TranslatableString(getKey(@"whether_to_show_extended_information"), @"Whether to show extended information for each mod."); + + /// + /// "Expansion mode" + /// + public static LocalisableString ExpansionMode => new TranslatableString(getKey(@"expansion_mode"), @"Expansion mode"); + + /// + /// "How the mod display expands when interacted with." + /// + public static LocalisableString ExpansionModeDescription => new TranslatableString(getKey(@"how_the_mod_display_expands"), @"How the mod display expands when interacted with."); + + /// + /// "Expand on hover" + /// + public static LocalisableString ExpandOnHover => new TranslatableString(getKey(@"expand_on_hover"), @"Expand on hover"); + + /// + /// "Always contracted" + /// + public static LocalisableString AlwaysContracted => new TranslatableString(getKey(@"always_contracted"), @"Always contracted"); + + /// + /// "Always expanded" + /// + public static LocalisableString AlwaysExpanded => new TranslatableString(getKey(@"always_expanded"), @"Always expanded"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 38417fae04..d076d11b1f 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -8,7 +8,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics.Containers; +using osu.Game.Localisation; +using osu.Game.Localisation.SkinComponents; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osuTK; @@ -145,16 +148,19 @@ namespace osu.Game.Screens.Play.HUD /// /// The will expand only when hovered. /// + [LocalisableDescription(typeof(SkinnableModDisplayStrings), nameof(SkinnableModDisplayStrings.ExpandOnHover))] ExpandOnHover, /// /// The will always be expanded. /// + [LocalisableDescription(typeof(SkinnableModDisplayStrings), nameof(SkinnableModDisplayStrings.AlwaysExpanded))] AlwaysExpanded, /// /// The will always be contracted. /// + [LocalisableDescription(typeof(SkinnableModDisplayStrings), nameof(SkinnableModDisplayStrings.AlwaysContracted))] AlwaysContracted, } } diff --git a/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs b/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs index ce4a4e978e..b81b2d1520 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs @@ -9,6 +9,8 @@ using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Skinning; +using osu.Game.Localisation; +using osu.Game.Localisation.SkinComponents; namespace osu.Game.Screens.Play.HUD { @@ -22,11 +24,11 @@ namespace osu.Game.Screens.Play.HUD [Resolved] private Bindable> mods { get; set; } = null!; - [SettingSource("Show extended info", "Whether to show extended information for each mod.")] + [SettingSource(typeof(SkinnableModDisplayStrings), nameof(SkinnableModDisplayStrings.ShowExtendedInformation), nameof(SkinnableModDisplayStrings.ShowExtendedInformationDescription))] public Bindable ShowExtendedInformation { get; } = new Bindable(true); - [SettingSource("Expansion mode", "How the mod display expands when interacted with.")] - public Bindable ExpansionModeSetting { get; } = new Bindable(ExpansionMode.ExpandOnHover); + [SettingSource(typeof(SkinnableModDisplayStrings), nameof(SkinnableModDisplayStrings.ExpansionMode), nameof(SkinnableModDisplayStrings.ExpansionModeDescription))] + public Bindable ExpansionModeSetting { get; } = new Bindable(); [BackgroundDependencyLoader] private void load() From df607ac3ea33cd531272e35df0fb1023cf21dcfd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2024 00:38:46 +0900 Subject: [PATCH 252/326] Load seasonal backgrounds without requiring being logged in --- osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs index 6f6febb646..b4be330f9c 100644 --- a/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs +++ b/osu.Game/Graphics/Backgrounds/SeasonalBackgroundLoader.cs @@ -28,7 +28,6 @@ namespace osu.Game.Graphics.Backgrounds [Resolved] private IAPIProvider api { get; set; } - private readonly IBindable apiState = new Bindable(); private Bindable seasonalBackgroundMode; private Bindable seasonalBackgrounds; @@ -47,13 +46,12 @@ namespace osu.Game.Graphics.Backgrounds SeasonalBackgroundChanged?.Invoke(); }); - apiState.BindTo(api.State); - apiState.BindValueChanged(fetchSeasonalBackgrounds, true); + fetchSeasonalBackgrounds(); } - private void fetchSeasonalBackgrounds(ValueChangedEvent stateChanged) + private void fetchSeasonalBackgrounds() { - if (seasonalBackgrounds.Value != null || stateChanged.NewValue != APIState.Online) + if (seasonalBackgrounds.Value != null) return; var request = new GetSeasonalBackgroundsRequest(); From f9939e7f9562ed24ad83db4cf18cf19c30eba113 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2024 00:50:53 +0900 Subject: [PATCH 253/326] Remove invalid test --- .../TestSceneSeasonalBackgroundLoader.cs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs index 54a722cee0..7b22ff1d6a 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneSeasonalBackgroundLoader.cs @@ -131,21 +131,6 @@ namespace osu.Game.Tests.Visual.Background assertNoBackgrounds(); } - [Test] - public void TestDelayedConnectivity() - { - registerBackgroundsResponse(DateTimeOffset.Now.AddDays(30)); - setSeasonalBackgroundMode(SeasonalBackgroundMode.Always); - AddStep("go offline", () => dummyAPI.SetState(APIState.Offline)); - - createLoader(); - assertNoBackgrounds(); - - AddStep("go online", () => dummyAPI.SetState(APIState.Online)); - - assertAnyBackground(); - } - private void registerBackgroundsResponse(DateTimeOffset endDate) => AddStep("setup request handler", () => { @@ -185,7 +170,8 @@ namespace osu.Game.Tests.Visual.Background { previousBackground = (SeasonalBackground)backgroundContainer.SingleOrDefault(); background = backgroundLoader.LoadNextBackground(); - LoadComponentAsync(background, bg => backgroundContainer.Child = bg); + if (background != null) + LoadComponentAsync(background, bg => backgroundContainer.Child = bg); }); AddUntilStep("background loaded", () => background.IsLoaded); From 9f8c390735e5acc96a872dcf5f0bbca52d62cb43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2024 12:39:33 +0900 Subject: [PATCH 254/326] 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 632325725a..f13760bd21 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 62a65f291d..3e618a3a74 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From 7c1482366dbbc7328d987fa80922839b2bb30ec9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2024 14:07:27 +0900 Subject: [PATCH 255/326] Remove unused using statements --- osu.Game/Screens/Play/HUD/ModDisplay.cs | 1 - osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index d076d11b1f..417ce355a5 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Containers; -using osu.Game.Localisation; using osu.Game.Localisation.SkinComponents; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; diff --git a/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs b/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs index b81b2d1520..819484e8ba 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Skinning; -using osu.Game.Localisation; using osu.Game.Localisation.SkinComponents; namespace osu.Game.Screens.Play.HUD From a94ada2ec6563bf2ca8d84444506d477677a11a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2024 14:19:03 +0900 Subject: [PATCH 256/326] 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 f011b7c3d1..fe3bdbffa3 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 80ae7942dfd4e6a8c4ece991243dfcc7e5cf167a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Dec 2024 17:52:50 +0900 Subject: [PATCH 257/326] Add christmas-specific logo heartbeat --- osu.Game/Screens/Menu/OsuLogo.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index f2e2e25fa6..f3c37c6960 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -271,8 +271,16 @@ namespace osu.Game.Screens.Menu private void load(TextureStore textures, AudioManager audio) { sampleClick = audio.Samples.Get(@"Menu/osu-logo-select"); - sampleBeat = audio.Samples.Get(@"Menu/osu-logo-heartbeat"); - sampleDownbeat = audio.Samples.Get(@"Menu/osu-logo-downbeat"); + + if (SeasonalUI.ENABLED) + { + sampleDownbeat = sampleBeat = audio.Samples.Get(@"Menu/osu-logo-heartbeat-bell"); + } + else + { + sampleBeat = audio.Samples.Get(@"Menu/osu-logo-heartbeat"); + sampleDownbeat = audio.Samples.Get(@"Menu/osu-logo-downbeat"); + } logo.Texture = textures.Get(@"Menu/logo"); ripple.Texture = textures.Get(@"Menu/logo"); @@ -303,7 +311,10 @@ namespace osu.Game.Screens.Menu else { var channel = sampleBeat.GetChannel(); - channel.Frequency.Value = 0.95 + RNG.NextDouble(0.1); + if (SeasonalUI.ENABLED) + channel.Frequency.Value = 0.99 + RNG.NextDouble(0.02); + else + channel.Frequency.Value = 0.95 + RNG.NextDouble(0.1); channel.Play(); } }); From 180a381b6fb0973b04d414c6b7f4755a8958d724 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Dec 2024 17:57:12 +0900 Subject: [PATCH 258/326] Adjust menu side flashes to be brighter and coloured when seasonal active --- osu.Game/Screens/Menu/MenuSideFlashes.cs | 25 +++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index 533c39826c..cc2d22a7fa 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -3,22 +3,23 @@ #nullable disable -using osuTK.Graphics; +using System; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Skinning; using osu.Game.Online.API; -using System; -using osu.Framework.Audio.Track; -using osu.Framework.Bindables; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Skinning; +using osuTK.Graphics; namespace osu.Game.Screens.Menu { @@ -67,7 +68,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Y, - Width = box_width * 2, + Width = box_width * (SeasonalUI.ENABLED ? 4 : 2), Height = 1.5f, // align off-screen to make sure our edges don't become visible during parallax. X = -box_width, @@ -79,7 +80,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Y, - Width = box_width * 2, + Width = box_width * (SeasonalUI.ENABLED ? 4 : 2), Height = 1.5f, X = box_width, Alpha = 0, @@ -104,7 +105,11 @@ namespace osu.Game.Screens.Menu private void flash(Drawable d, double beatLength, bool kiai, ChannelAmplitudes amplitudes) { - d.FadeTo(Math.Max(0, ((ReferenceEquals(d, leftBox) ? amplitudes.LeftChannel : amplitudes.RightChannel) - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), box_fade_in_time) + if (SeasonalUI.ENABLED) + updateColour(); + + d.FadeTo(Math.Clamp(0.1f + ((ReferenceEquals(d, leftBox) ? amplitudes.LeftChannel : amplitudes.RightChannel) - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier), 0.1f, 1), + box_fade_in_time) .Then() .FadeOut(beatLength, Easing.In); } @@ -113,7 +118,9 @@ namespace osu.Game.Screens.Menu { Color4 baseColour = colours.Blue; - if (user.Value?.IsSupporter ?? false) + if (SeasonalUI.ENABLED) + baseColour = RNG.NextBool() ? SeasonalUI.PRIMARY_COLOUR_1 : SeasonalUI.PRIMARY_COLOUR_2; + else if (user.Value?.IsSupporter ?? false) baseColour = skin.Value.GetConfig(GlobalSkinColours.MenuGlow)?.Value ?? baseColour; // linear colour looks better in this case, so let's use it for now. From a4bf29e98f4aac7306164eb90edab065d83198eb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Dec 2024 17:57:42 +0900 Subject: [PATCH 259/326] Adjust menu logo visualiser to use seasonal colours --- osu.Game/Screens/Menu/MenuLogoVisualisation.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/MenuLogoVisualisation.cs b/osu.Game/Screens/Menu/MenuLogoVisualisation.cs index f4e992be9a..4537b79b62 100644 --- a/osu.Game/Screens/Menu/MenuLogoVisualisation.cs +++ b/osu.Game/Screens/Menu/MenuLogoVisualisation.cs @@ -3,12 +3,12 @@ #nullable disable -using osuTK.Graphics; -using osu.Game.Skinning; -using osu.Game.Online.API; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Skinning; +using osuTK.Graphics; namespace osu.Game.Screens.Menu { @@ -29,7 +29,9 @@ namespace osu.Game.Screens.Menu private void updateColour() { - if (user.Value?.IsSupporter ?? false) + if (SeasonalUI.ENABLED) + Colour = SeasonalUI.AMBIENT_COLOUR_1; + else if (user.Value?.IsSupporter ?? false) Colour = skin.Value.GetConfig(GlobalSkinColours.MenuGlow)?.Value ?? Color4.White; else Colour = Color4.White; From 618a9849e314a99aff70baec7f2b1ef295b4e1e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Dec 2024 17:59:31 +0900 Subject: [PATCH 260/326] Increase intro time allowance to account for seasonal tracks with actual long intros --- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 0dc54b321f..9885c061a9 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -207,7 +207,7 @@ namespace osu.Game.Screens.Menu Text = NotificationsStrings.AudioPlaybackIssue }); } - }, 5000); + }, 8000); } public override void OnResuming(ScreenTransitionEvent e) From 024029822ab0e74880de27ce073fe88d735659b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Dec 2024 17:59:48 +0900 Subject: [PATCH 261/326] Add christmas intro --- .../Visual/Menus/TestSceneIntroChristmas.cs | 15 + osu.Game/Screens/Loader.cs | 3 + osu.Game/Screens/Menu/IntroChristmas.cs | 328 ++++++++++++++++++ osu.Game/Screens/SeasonalUI.cs | 21 ++ 4 files changed, 367 insertions(+) create mode 100644 osu.Game.Tests/Visual/Menus/TestSceneIntroChristmas.cs create mode 100644 osu.Game/Screens/Menu/IntroChristmas.cs create mode 100644 osu.Game/Screens/SeasonalUI.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroChristmas.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroChristmas.cs new file mode 100644 index 0000000000..13377f49df --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroChristmas.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 NUnit.Framework; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual.Menus +{ + [TestFixture] + public partial class TestSceneIntroChristmas : IntroTestScene + { + protected override bool IntroReliesOnTrack => true; + protected override IntroScreen CreateScreen() => new IntroChristmas(); + } +} diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index d71ee05b27..811e4600eb 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -37,6 +37,9 @@ namespace osu.Game.Screens private IntroScreen getIntroSequence() { + if (SeasonalUI.ENABLED) + return new IntroChristmas(createMainMenu); + if (introSequence == IntroSequence.Random) introSequence = (IntroSequence)RNG.Next(0, (int)IntroSequence.Random); diff --git a/osu.Game/Screens/Menu/IntroChristmas.cs b/osu.Game/Screens/Menu/IntroChristmas.cs new file mode 100644 index 0000000000..0a1cf32b85 --- /dev/null +++ b/osu.Game/Screens/Menu/IntroChristmas.cs @@ -0,0 +1,328 @@ +// 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.Audio.Track; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Textures; +using osu.Framework.Screens; +using osu.Framework.Timing; +using osu.Framework.Utils; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Menu +{ + public partial class IntroChristmas : IntroScreen + { + protected override string BeatmapHash => "7e26183e72a496f672c3a21292e6b469fdecd084d31c259ea10a31df5b46cd77"; + + protected override string BeatmapFile => "christmas2024.osz"; + + private const double beat_length = 60000 / 172.0; + private const double offset = 5924; + + protected override string SeeyaSampleName => "Intro/Welcome/seeya"; + + private TrianglesIntroSequence intro = null!; + + public IntroChristmas(Func? createNextScreen = null) + : base(createNextScreen) + { + } + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + + if (!resuming) + { + PrepareMenuLoad(); + + var decouplingClock = new DecouplingFramedClock(UsingThemedIntro ? Track : null); + + LoadComponentAsync(intro = new TrianglesIntroSequence(logo, () => FadeInBackground()) + { + RelativeSizeAxes = Axes.Both, + Clock = new InterpolatingFramedClock(decouplingClock), + LoadMenu = LoadMenu + }, _ => + { + AddInternal(intro); + + // There is a chance that the intro timed out before being displayed, and this scheduled callback could + // happen during the outro rather than intro. + // In such a scenario, we don't want to play the intro sample, nor attempt to start the intro track + // (that may have already been since disposed by MusicController). + if (DidLoadMenu) + return; + + // If the user has requested no theme, fallback to the same intro voice and delay as IntroCircles. + // The triangles intro voice and theme are combined which makes it impossible to use. + StartTrack(); + + // no-op for the case of themed intro, no harm in calling for both scenarios as a safety measure. + decouplingClock.Start(); + }); + } + } + + public override void OnSuspending(ScreenTransitionEvent e) + { + base.OnSuspending(e); + + // important as there is a clock attached to a track which will likely be disposed before returning to this screen. + intro.Expire(); + } + + private partial class TrianglesIntroSequence : CompositeDrawable + { + private readonly OsuLogo logo; + private readonly Action showBackgroundAction; + private OsuSpriteText welcomeText = null!; + + private Container logoContainerSecondary = null!; + private LazerLogo lazerLogo = null!; + + private Drawable triangles = null!; + + public Action LoadMenu = null!; + + [Resolved] + private OsuGameBase game { get; set; } = null!; + + public TrianglesIntroSequence(OsuLogo logo, Action showBackgroundAction) + { + this.logo = logo; + this.showBackgroundAction = showBackgroundAction; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new[] + { + welcomeText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Padding = new MarginPadding { Bottom = 10 }, + Font = OsuFont.GetFont(weight: FontWeight.Light, size: 42), + Alpha = 1, + Spacing = new Vector2(5), + }, + logoContainerSecondary = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = lazerLogo = new LazerLogo + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + } + }, + triangles = new CircularContainer + { + Alpha = 0, + Masking = true, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(960), + Child = new GlitchingTriangles + { + RelativeSizeAxes = Axes.Both, + }, + } + }; + } + + private static double getTimeForBeat(int beat) => offset + beat_length * beat; + + protected override void LoadComplete() + { + base.LoadComplete(); + + lazerLogo.Hide(); + + using (BeginAbsoluteSequence(0)) + { + using (BeginDelayedSequence(getTimeForBeat(-16))) + welcomeText.FadeIn().OnComplete(t => t.Text = "welcome to osu!"); + + using (BeginDelayedSequence(getTimeForBeat(-15))) + welcomeText.FadeIn().OnComplete(t => t.Text = ""); + + using (BeginDelayedSequence(getTimeForBeat(-14))) + welcomeText.FadeIn().OnComplete(t => t.Text = "welcome to osu!"); + + using (BeginDelayedSequence(getTimeForBeat(-13))) + welcomeText.FadeIn().OnComplete(t => t.Text = ""); + + using (BeginDelayedSequence(getTimeForBeat(-12))) + welcomeText.FadeIn().OnComplete(t => t.Text = "merry christmas!"); + + using (BeginDelayedSequence(getTimeForBeat(-11))) + welcomeText.FadeIn().OnComplete(t => t.Text = ""); + + using (BeginDelayedSequence(getTimeForBeat(-10))) + welcomeText.FadeIn().OnComplete(t => t.Text = "merry osumas!"); + + using (BeginDelayedSequence(getTimeForBeat(-9))) + { + welcomeText.FadeIn().OnComplete(t => t.Text = ""); + } + + lazerLogo.Scale = new Vector2(0.2f); + triangles.Scale = new Vector2(0.2f); + + for (int i = 0; i < 8; i++) + { + using (BeginDelayedSequence(getTimeForBeat(-8 + i))) + { + triangles.FadeIn(); + + lazerLogo.ScaleTo(new Vector2(0.2f + (i + 1) / 8f * 0.3f), beat_length * 1, Easing.OutQuint); + triangles.ScaleTo(new Vector2(0.2f + (i + 1) / 8f * 0.3f), beat_length * 1, Easing.OutQuint); + lazerLogo.FadeTo((i + 1) * 0.06f); + lazerLogo.TransformTo(nameof(LazerLogo.Progress), (i + 1) / 10f); + } + } + + GameWideFlash flash = new GameWideFlash(); + + using (BeginDelayedSequence(getTimeForBeat(-2))) + { + lazerLogo.FadeIn().OnComplete(_ => game.Add(flash)); + } + + flash.FadeInCompleted = () => + { + logoContainerSecondary.Remove(lazerLogo, true); + triangles.FadeOut(); + logo.FadeIn(); + showBackgroundAction(); + LoadMenu(); + }; + } + } + + private partial class GameWideFlash : Box + { + public Action? FadeInCompleted; + + public GameWideFlash() + { + Colour = Color4.White; + RelativeSizeAxes = Axes.Both; + Blending = BlendingParameters.Additive; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Alpha = 0; + + this.FadeTo(0.5f, beat_length * 2, Easing.In) + .OnComplete(_ => FadeInCompleted?.Invoke()); + + this.Delay(beat_length * 2) + .Then() + .FadeOutFromOne(3000, Easing.OutQuint); + } + } + + private partial class LazerLogo : CompositeDrawable + { + private LogoAnimation highlight = null!; + private LogoAnimation background = null!; + + public float Progress + { + get => background.AnimationProgress; + set + { + background.AnimationProgress = value; + highlight.AnimationProgress = value; + } + } + + public LazerLogo() + { + Size = new Vector2(960); + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore textures) + { + InternalChildren = new Drawable[] + { + highlight = new LogoAnimation + { + RelativeSizeAxes = Axes.Both, + Texture = textures.Get(@"Intro/Triangles/logo-highlight"), + Colour = Color4.White, + }, + background = new LogoAnimation + { + RelativeSizeAxes = Axes.Both, + Texture = textures.Get(@"Intro/Triangles/logo-background"), + Colour = OsuColour.Gray(0.6f), + }, + }; + } + } + + private partial class GlitchingTriangles : BeatSyncedContainer + { + private int beatsHandled; + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); + + Divisor = beatsHandled < 4 ? 1 : 4; + + for (int i = 0; i < (beatsHandled + 1); i++) + { + float angle = (float)(RNG.NextDouble() * 2 * Math.PI); + float randomRadius = (float)(Math.Sqrt(RNG.NextDouble())); + + float x = 0.5f + 0.5f * randomRadius * (float)Math.Cos(angle); + float y = 0.5f + 0.5f * randomRadius * (float)Math.Sin(angle); + + Color4 christmasColour = RNG.NextBool() ? SeasonalUI.PRIMARY_COLOUR_1 : SeasonalUI.PRIMARY_COLOUR_2; + + Drawable triangle = new Triangle + { + Size = new Vector2(RNG.NextSingle() + 1.2f) * 80, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.Both, + Position = new Vector2(x, y), + Colour = christmasColour + }; + + if (beatsHandled >= 10) + triangle.Blending = BlendingParameters.Additive; + + AddInternal(triangle); + triangle + .ScaleTo(0.9f) + .ScaleTo(1, beat_length / 2, Easing.Out); + triangle.FadeInFromZero(100, Easing.OutQuint); + } + + beatsHandled += 1; + } + } + } + } +} diff --git a/osu.Game/Screens/SeasonalUI.cs b/osu.Game/Screens/SeasonalUI.cs new file mode 100644 index 0000000000..ebe4d74301 --- /dev/null +++ b/osu.Game/Screens/SeasonalUI.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.Extensions.Color4Extensions; +using osuTK.Graphics; + +namespace osu.Game.Screens +{ + public static class SeasonalUI + { + public static readonly bool ENABLED = true; + + public static readonly Color4 PRIMARY_COLOUR_1 = Color4Extensions.FromHex("D32F2F"); + + public static readonly Color4 PRIMARY_COLOUR_2 = Color4Extensions.FromHex("388E3C"); + + public static readonly Color4 AMBIENT_COLOUR_1 = Color4Extensions.FromHex("FFC"); + + public static readonly Color4 AMBIENT_COLOUR_2 = Color4Extensions.FromHex("FFE4B5"); + } +} From 0954e0b0321d6872e16b73055a7b171f1cbbc9f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Dec 2024 18:00:00 +0900 Subject: [PATCH 262/326] Add seasonal lighting Replaces kiai fountains for now. --- .../TestSceneMainMenuSeasonalLighting.cs | 46 +++++ osu.Game/Screens/Menu/MainMenu.cs | 4 +- .../Screens/Menu/MainMenuSeasonalLighting.cs | 188 ++++++++++++++++++ 3 files changed, 237 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs create mode 100644 osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs new file mode 100644 index 0000000000..bfdc07fba6 --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs @@ -0,0 +1,46 @@ +// 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.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual.Menus +{ + public partial class TestSceneMainMenuSeasonalLighting : OsuTestScene + { + [Resolved] + private BeatmapManager beatmaps { get; set; } = null!; + + [Resolved] + private RealmAccess realm { get; set; } = null!; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("prepare beatmap", () => + { + var setInfo = beatmaps.QueryBeatmapSet(b => b.Protected && b.Hash == "7e26183e72a496f672c3a21292e6b469fdecd084d31c259ea10a31df5b46cd77"); + + Beatmap.Value = beatmaps.GetWorkingBeatmap(setInfo!.Value.Beatmaps.First()); + }); + + AddStep("create lighting", () => Child = new MainMenuSeasonalLighting()); + + AddStep("restart beatmap", () => + { + Beatmap.Value.Track.Start(); + Beatmap.Value.Track.Seek(4000); + }); + } + + [Test] + public void TestBasic() + { + } + } +} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 0630b9612e..42aa2342da 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -124,6 +124,7 @@ namespace osu.Game.Screens.Menu AddRangeInternal(new[] { + SeasonalUI.ENABLED ? new MainMenuSeasonalLighting() : Empty(), buttonsContainer = new ParallaxContainer { ParallaxAmount = 0.01f, @@ -166,7 +167,8 @@ namespace osu.Game.Screens.Menu Origin = Anchor.TopRight, Margin = new MarginPadding { Right = 15, Top = 5 } }, - new KiaiMenuFountains(), + // For now, this is too much alongside the seasonal lighting. + SeasonalUI.ENABLED ? Empty() : new KiaiMenuFountains(), bottomElementsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs b/osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs new file mode 100644 index 0000000000..7ba4e998d2 --- /dev/null +++ b/osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs @@ -0,0 +1,188 @@ +// 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.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; +using osu.Framework.Utils; +using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Menu +{ + public partial class MainMenuSeasonalLighting : CompositeDrawable + { + private IBindable working = null!; + + private InterpolatingFramedClock beatmapClock = null!; + + private List hitObjects = null!; + + [Resolved] + private RulesetStore rulesets { get; set; } = null!; + + public MainMenuSeasonalLighting() + { + RelativeChildSize = new Vector2(512, 384); + + RelativeSizeAxes = Axes.X; + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + [BackgroundDependencyLoader] + private void load(IBindable working) + { + this.working = working.GetBoundCopy(); + this.working.BindValueChanged(_ => Scheduler.AddOnce(updateBeatmap), true); + } + + private void updateBeatmap() + { + lastObjectIndex = null; + beatmapClock = new InterpolatingFramedClock(new FramedClock(working.Value.Track)); + hitObjects = working.Value.GetPlayableBeatmap(rulesets.GetRuleset(0)).HitObjects.SelectMany(h => h.NestedHitObjects.Prepend(h)) + .OrderBy(h => h.StartTime) + .ToList(); + } + + private int? lastObjectIndex; + + protected override void Update() + { + base.Update(); + + Height = DrawWidth / 16 * 10; + + beatmapClock.ProcessFrame(); + + // intentionally slightly early since we are doing fades on the lighting. + double time = beatmapClock.CurrentTime + 50; + + // handle seeks or OOB by skipping to current. + if (lastObjectIndex == null || lastObjectIndex >= hitObjects.Count || (lastObjectIndex >= 0 && hitObjects[lastObjectIndex.Value].StartTime > time) + || Math.Abs(beatmapClock.ElapsedFrameTime) > 500) + lastObjectIndex = hitObjects.Count(h => h.StartTime < time) - 1; + + while (lastObjectIndex < hitObjects.Count - 1) + { + var h = hitObjects[lastObjectIndex.Value + 1]; + + if (h.StartTime > time) + break; + + // Don't add lighting if the game is running too slow. + if (Clock.ElapsedFrameTime < 20) + addLight(h); + + lastObjectIndex++; + } + } + + private void addLight(HitObject h) + { + var light = new Light + { + RelativePositionAxes = Axes.Both, + Position = ((IHasPosition)h).Position + }; + + AddInternal(light); + + if (h.GetType().Name.Contains("Tick")) + { + light.Colour = SeasonalUI.AMBIENT_COLOUR_1; + light.Scale = new Vector2(0.5f); + light + .FadeInFromZero(250) + .Then() + .FadeOutFromOne(1000, Easing.Out); + + light.MoveToOffset(new Vector2(RNG.Next(-20, 20), RNG.Next(-20, 20)), 1400, Easing.Out); + } + else + { + // default green + Color4 col = SeasonalUI.PRIMARY_COLOUR_2; + + // whistle red + if (h.Samples.Any(s => s.Name == HitSampleInfo.HIT_WHISTLE)) + col = SeasonalUI.PRIMARY_COLOUR_1; + // clap is third colour + else if (h.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP)) + col = SeasonalUI.AMBIENT_COLOUR_1; + + light.Colour = col; + + // finish larger lighting + if (h.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH)) + light.Scale = new Vector2(3); + + light + .FadeInFromZero(150) + .Then() + .FadeOutFromOne(1000, Easing.In); + + light.Expire(); + } + } + + public partial class Light : CompositeDrawable + { + private readonly Circle circle; + + public new Color4 Colour + { + set + { + circle.Colour = value.Darken(0.8f); + circle.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = value, + Radius = 80, + }; + } + } + + public Light() + { + InternalChildren = new Drawable[] + { + circle = new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(12), + Colour = SeasonalUI.AMBIENT_COLOUR_1, + Blending = BlendingParameters.Additive, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = SeasonalUI.AMBIENT_COLOUR_2, + Radius = 80, + } + } + }; + + Origin = Anchor.Centre; + Alpha = 0.5f; + } + } + } +} From 22f3831c0d46d11f7770c62c2dab4c2ee1132e36 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Dec 2024 18:44:44 +0900 Subject: [PATCH 263/326] Add logo hat --- .../Visual/UserInterface/TestSceneOsuLogo.cs | 11 +++- osu.Game/Screens/Menu/OsuLogo.cs | 50 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuLogo.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuLogo.cs index 62a493815b..c112d26870 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuLogo.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuLogo.cs @@ -4,22 +4,31 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Screens.Menu; +using osuTK; namespace osu.Game.Tests.Visual.UserInterface { public partial class TestSceneOsuLogo : OsuTestScene { + private OsuLogo? logo; + [Test] public void TestBasic() { AddStep("Add logo", () => { - Child = new OsuLogo + Child = logo = new OsuLogo { Anchor = Anchor.Centre, Origin = Anchor.Centre, }; }); + + AddSliderStep("scale", 0.1, 2, 1, scale => + { + if (logo != null) + Child.Scale = new Vector2((float)scale); + }); } } } diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index f3c37c6960..2c62a10a8f 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -211,6 +212,15 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.Centre, Origin = Anchor.Centre, }, + SeasonalUI.ENABLED + ? hat = new Sprite + { + BypassAutoSizeAxes = Axes.Both, + Alpha = 0, + Origin = Anchor.BottomCentre, + Scale = new Vector2(-1, 1), + } + : Empty(), } }, impactContainer = new CircularContainer @@ -284,6 +294,8 @@ namespace osu.Game.Screens.Menu logo.Texture = textures.Get(@"Menu/logo"); ripple.Texture = textures.Get(@"Menu/logo"); + if (hat != null) + hat.Texture = textures.Get(@"Menu/hat"); } private int lastBeatIndex; @@ -369,6 +381,9 @@ namespace osu.Game.Screens.Menu const float scale_adjust_cutoff = 0.4f; + if (SeasonalUI.ENABLED) + updateHat(); + if (musicController.CurrentTrack.IsRunning) { float maxAmplitude = lastBeatIndex >= 0 ? musicController.CurrentTrack.CurrentAmplitudes.Maximum : 0; @@ -382,6 +397,38 @@ namespace osu.Game.Screens.Menu } } + private bool hasHat; + + private void updateHat() + { + if (hat == null) + return; + + bool shouldHat = DrawWidth * Scale.X < 400; + + if (shouldHat != hasHat) + { + hasHat = shouldHat; + + if (hasHat) + { + hat.Delay(400) + .Then() + .MoveTo(new Vector2(120, 160)) + .RotateTo(0) + .RotateTo(-20, 500, Easing.OutQuint) + .FadeIn(250, Easing.OutQuint); + } + else + { + hat.Delay(100) + .Then() + .MoveToOffset(new Vector2(0, -5), 500, Easing.OutQuint) + .FadeOut(500, Easing.OutQuint); + } + } + } + public override bool HandlePositionalInput => base.HandlePositionalInput && Alpha > 0.2f; protected override bool OnMouseDown(MouseDownEvent e) @@ -459,6 +506,9 @@ namespace osu.Game.Screens.Menu private Container currentProxyTarget; private Drawable proxy; + [CanBeNull] + private readonly Sprite hat; + public void StopSamplePlayback() => sampleClickChannel?.Stop(); public Drawable ProxyToContainer(Container c) From 4924a35c3133345ebc314d1fea03c8c69d8665c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 19 Dec 2024 19:14:48 +0900 Subject: [PATCH 264/326] Fix light expiry --- osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs b/osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs index 7ba4e998d2..fb16e8e0bb 100644 --- a/osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs +++ b/osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -137,9 +136,9 @@ namespace osu.Game.Screens.Menu .FadeInFromZero(150) .Then() .FadeOutFromOne(1000, Easing.In); - - light.Expire(); } + + light.Expire(); } public partial class Light : CompositeDrawable From 8c7af79f9667e1cd4db2e1ec3f480f98542b5945 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2024 14:21:45 +0900 Subject: [PATCH 265/326] Tidy up for pull request attempt --- .../TestSceneMainMenuSeasonalLighting.cs | 6 +-- osu.Game/Screens/Menu/IntroChristmas.cs | 5 ++- .../Screens/Menu/MainMenuSeasonalLighting.cs | 38 +++++++++++++------ osu.Game/Screens/Menu/OsuLogo.cs | 2 +- osu.Game/Screens/SeasonalUI.cs | 8 ++-- 5 files changed, 37 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs index bfdc07fba6..81862da9df 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Database; using osu.Game.Screens.Menu; namespace osu.Game.Tests.Visual.Menus @@ -16,15 +15,12 @@ namespace osu.Game.Tests.Visual.Menus [Resolved] private BeatmapManager beatmaps { get; set; } = null!; - [Resolved] - private RealmAccess realm { get; set; } = null!; - [SetUpSteps] public void SetUpSteps() { AddStep("prepare beatmap", () => { - var setInfo = beatmaps.QueryBeatmapSet(b => b.Protected && b.Hash == "7e26183e72a496f672c3a21292e6b469fdecd084d31c259ea10a31df5b46cd77"); + var setInfo = beatmaps.QueryBeatmapSet(b => b.Protected && b.Hash == IntroChristmas.CHRISTMAS_BEATMAP_SET_HASH); Beatmap.Value = beatmaps.GetWorkingBeatmap(setInfo!.Value.Beatmaps.First()); }); diff --git a/osu.Game/Screens/Menu/IntroChristmas.cs b/osu.Game/Screens/Menu/IntroChristmas.cs index 0a1cf32b85..273baa3c52 100644 --- a/osu.Game/Screens/Menu/IntroChristmas.cs +++ b/osu.Game/Screens/Menu/IntroChristmas.cs @@ -22,7 +22,10 @@ namespace osu.Game.Screens.Menu { public partial class IntroChristmas : IntroScreen { - protected override string BeatmapHash => "7e26183e72a496f672c3a21292e6b469fdecd084d31c259ea10a31df5b46cd77"; + // nekodex - circle the halls + public const string CHRISTMAS_BEATMAP_SET_HASH = "7e26183e72a496f672c3a21292e6b469fdecd084d31c259ea10a31df5b46cd77"; + + protected override string BeatmapHash => CHRISTMAS_BEATMAP_SET_HASH; protected override string BeatmapFile => "christmas2024.osz"; diff --git a/osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs b/osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs index fb16e8e0bb..f46a1387ab 100644 --- a/osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs +++ b/osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs @@ -31,11 +31,13 @@ namespace osu.Game.Screens.Menu private List hitObjects = null!; - [Resolved] - private RulesetStore rulesets { get; set; } = null!; + private RulesetInfo? osuRuleset; + + private int? lastObjectIndex; public MainMenuSeasonalLighting() { + // match beatmap playfield RelativeChildSize = new Vector2(512, 384); RelativeSizeAxes = Axes.X; @@ -45,23 +47,37 @@ namespace osu.Game.Screens.Menu } [BackgroundDependencyLoader] - private void load(IBindable working) + private void load(IBindable working, RulesetStore rulesets) { this.working = working.GetBoundCopy(); this.working.BindValueChanged(_ => Scheduler.AddOnce(updateBeatmap), true); + + // operate in osu! ruleset to keep things simple for now. + osuRuleset = rulesets.GetRuleset(0); } private void updateBeatmap() { lastObjectIndex = null; + + if (osuRuleset == null) + { + beatmapClock = new InterpolatingFramedClock(Clock); + hitObjects = new List(); + return; + } + + // Intentionally maintain separately so the lighting is not in audio clock space (it shouldn't rewind etc.) beatmapClock = new InterpolatingFramedClock(new FramedClock(working.Value.Track)); - hitObjects = working.Value.GetPlayableBeatmap(rulesets.GetRuleset(0)).HitObjects.SelectMany(h => h.NestedHitObjects.Prepend(h)) + + hitObjects = working.Value + .GetPlayableBeatmap(osuRuleset) + .HitObjects + .SelectMany(h => h.NestedHitObjects.Prepend(h)) .OrderBy(h => h.StartTime) .ToList(); } - private int? lastObjectIndex; - protected override void Update() { base.Update(); @@ -116,19 +132,19 @@ namespace osu.Game.Screens.Menu } else { - // default green + // default are green Color4 col = SeasonalUI.PRIMARY_COLOUR_2; - // whistle red + // whistles are red if (h.Samples.Any(s => s.Name == HitSampleInfo.HIT_WHISTLE)) col = SeasonalUI.PRIMARY_COLOUR_1; - // clap is third colour + // clap is third ambient (yellow) colour else if (h.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP)) col = SeasonalUI.AMBIENT_COLOUR_1; light.Colour = col; - // finish larger lighting + // finish results in larger lighting if (h.Samples.Any(s => s.Name == HitSampleInfo.HIT_FINISH)) light.Scale = new Vector2(3); @@ -141,7 +157,7 @@ namespace osu.Game.Screens.Menu light.Expire(); } - public partial class Light : CompositeDrawable + private partial class Light : CompositeDrawable { private readonly Circle circle; diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 2c62a10a8f..272f53e087 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -163,7 +163,7 @@ namespace osu.Game.Screens.Menu new Container { AutoSizeAxes = Axes.Both, - Children = new Drawable[] + Children = new[] { logoContainer = new CircularContainer { diff --git a/osu.Game/Screens/SeasonalUI.cs b/osu.Game/Screens/SeasonalUI.cs index ebe4d74301..fc2303f285 100644 --- a/osu.Game/Screens/SeasonalUI.cs +++ b/osu.Game/Screens/SeasonalUI.cs @@ -10,12 +10,12 @@ namespace osu.Game.Screens { public static readonly bool ENABLED = true; - public static readonly Color4 PRIMARY_COLOUR_1 = Color4Extensions.FromHex("D32F2F"); + public static readonly Color4 PRIMARY_COLOUR_1 = Color4Extensions.FromHex(@"D32F2F"); - public static readonly Color4 PRIMARY_COLOUR_2 = Color4Extensions.FromHex("388E3C"); + public static readonly Color4 PRIMARY_COLOUR_2 = Color4Extensions.FromHex(@"388E3C"); - public static readonly Color4 AMBIENT_COLOUR_1 = Color4Extensions.FromHex("FFC"); + public static readonly Color4 AMBIENT_COLOUR_1 = Color4Extensions.FromHex(@"FFFFCC"); - public static readonly Color4 AMBIENT_COLOUR_2 = Color4Extensions.FromHex("FFE4B5"); + public static readonly Color4 AMBIENT_COLOUR_2 = Color4Extensions.FromHex(@"FFE4B5"); } } From e5dbf9ce453e359a2e07b375ba9cbdcbe159b764 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2024 14:46:34 +0900 Subject: [PATCH 266/326] Subclass osu logo instead of adding much code to it --- .../TestSceneMainMenuSeasonalLighting.cs | 1 + .../Visual/UserInterface/TestSceneOsuLogo.cs | 29 ++++++- osu.Game/OsuGame.cs | 6 +- osu.Game/Screens/Loader.cs | 3 +- osu.Game/Screens/Menu/IntroChristmas.cs | 3 +- osu.Game/Screens/Menu/MainMenu.cs | 5 +- .../Screens/Menu/MenuLogoVisualisation.cs | 5 +- osu.Game/Screens/Menu/MenuSideFlashes.cs | 11 +-- osu.Game/Screens/Menu/OsuLogo.cs | 83 ++++--------------- .../MainMenuSeasonalLighting.cs | 14 ++-- osu.Game/Seasonal/OsuLogoChristmas.cs | 74 +++++++++++++++++ .../SeasonalUIConfig.cs} | 7 +- 12 files changed, 148 insertions(+), 93 deletions(-) rename osu.Game/{Screens/Menu => Seasonal}/MainMenuSeasonalLighting.cs (93%) create mode 100644 osu.Game/Seasonal/OsuLogoChristmas.cs rename osu.Game/{Screens/SeasonalUI.cs => Seasonal/SeasonalUIConfig.cs} (78%) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs index 81862da9df..bf499f1beb 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Screens.Menu; +using osu.Game.Seasonal; namespace osu.Game.Tests.Visual.Menus { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuLogo.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuLogo.cs index c112d26870..27d2ff97fa 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuLogo.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuLogo.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Screens.Menu; +using osu.Game.Seasonal; using osuTK; namespace osu.Game.Tests.Visual.UserInterface @@ -12,6 +13,19 @@ namespace osu.Game.Tests.Visual.UserInterface { private OsuLogo? logo; + private float scale = 1; + + protected override void LoadComplete() + { + base.LoadComplete(); + + AddSliderStep("scale", 0.1, 2, 1, scale => + { + if (logo != null) + Child.Scale = new Vector2(this.scale = (float)scale); + }); + } + [Test] public void TestBasic() { @@ -21,13 +35,22 @@ namespace osu.Game.Tests.Visual.UserInterface { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Scale = new Vector2(scale), }; }); + } - AddSliderStep("scale", 0.1, 2, 1, scale => + [Test] + public void TestChristmas() + { + AddStep("Add logo", () => { - if (logo != null) - Child.Scale = new Vector2((float)scale); + Child = logo = new OsuLogoChristmas + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(scale), + }; }); } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e808e570c7..0dd1746aa4 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -69,6 +69,7 @@ using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; +using osu.Game.Seasonal; using osu.Game.Skinning; using osu.Game.Updater; using osu.Game.Users; @@ -362,7 +363,10 @@ namespace osu.Game { SentryLogger.AttachUser(API.LocalUser); - dependencies.Cache(osuLogo = new OsuLogo { Alpha = 0 }); + if (SeasonalUIConfig.ENABLED) + dependencies.CacheAs(osuLogo = new OsuLogoChristmas { Alpha = 0 }); + else + dependencies.CacheAs(osuLogo = new OsuLogo { Alpha = 0 }); // bind config int to database RulesetInfo configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 811e4600eb..dfa5d2c369 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -15,6 +15,7 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; +using osu.Game.Seasonal; using IntroSequence = osu.Game.Configuration.IntroSequence; namespace osu.Game.Screens @@ -37,7 +38,7 @@ namespace osu.Game.Screens private IntroScreen getIntroSequence() { - if (SeasonalUI.ENABLED) + if (SeasonalUIConfig.ENABLED) return new IntroChristmas(createMainMenu); if (introSequence == IntroSequence.Random) diff --git a/osu.Game/Screens/Menu/IntroChristmas.cs b/osu.Game/Screens/Menu/IntroChristmas.cs index 273baa3c52..aa16f33c3d 100644 --- a/osu.Game/Screens/Menu/IntroChristmas.cs +++ b/osu.Game/Screens/Menu/IntroChristmas.cs @@ -15,6 +15,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Seasonal; using osuTK; using osuTK.Graphics; @@ -302,7 +303,7 @@ namespace osu.Game.Screens.Menu float x = 0.5f + 0.5f * randomRadius * (float)Math.Cos(angle); float y = 0.5f + 0.5f * randomRadius * (float)Math.Sin(angle); - Color4 christmasColour = RNG.NextBool() ? SeasonalUI.PRIMARY_COLOUR_1 : SeasonalUI.PRIMARY_COLOUR_2; + Color4 christmasColour = RNG.NextBool() ? SeasonalUIConfig.PRIMARY_COLOUR_1 : SeasonalUIConfig.PRIMARY_COLOUR_2; Drawable triangle = new Triangle { diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 42aa2342da..a4b269ad0d 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -35,6 +35,7 @@ using osu.Game.Screens.OnlinePlay.DailyChallenge; using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Select; +using osu.Game.Seasonal; using osuTK; using osuTK.Graphics; @@ -124,7 +125,7 @@ namespace osu.Game.Screens.Menu AddRangeInternal(new[] { - SeasonalUI.ENABLED ? new MainMenuSeasonalLighting() : Empty(), + SeasonalUIConfig.ENABLED ? new MainMenuSeasonalLighting() : Empty(), buttonsContainer = new ParallaxContainer { ParallaxAmount = 0.01f, @@ -168,7 +169,7 @@ namespace osu.Game.Screens.Menu Margin = new MarginPadding { Right = 15, Top = 5 } }, // For now, this is too much alongside the seasonal lighting. - SeasonalUI.ENABLED ? Empty() : new KiaiMenuFountains(), + SeasonalUIConfig.ENABLED ? Empty() : new KiaiMenuFountains(), bottomElementsFlow = new FillFlowContainer { AutoSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Menu/MenuLogoVisualisation.cs b/osu.Game/Screens/Menu/MenuLogoVisualisation.cs index 4537b79b62..32b5c706a3 100644 --- a/osu.Game/Screens/Menu/MenuLogoVisualisation.cs +++ b/osu.Game/Screens/Menu/MenuLogoVisualisation.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Seasonal; using osu.Game.Skinning; using osuTK.Graphics; @@ -29,8 +30,8 @@ namespace osu.Game.Screens.Menu private void updateColour() { - if (SeasonalUI.ENABLED) - Colour = SeasonalUI.AMBIENT_COLOUR_1; + if (SeasonalUIConfig.ENABLED) + Colour = SeasonalUIConfig.AMBIENT_COLOUR_1; else if (user.Value?.IsSupporter ?? false) Colour = skin.Value.GetConfig(GlobalSkinColours.MenuGlow)?.Value ?? Color4.White; else diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index cc2d22a7fa..808da5dd47 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Seasonal; using osu.Game.Skinning; using osuTK.Graphics; @@ -68,7 +69,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Y, - Width = box_width * (SeasonalUI.ENABLED ? 4 : 2), + Width = box_width * (SeasonalUIConfig.ENABLED ? 4 : 2), Height = 1.5f, // align off-screen to make sure our edges don't become visible during parallax. X = -box_width, @@ -80,7 +81,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Y, - Width = box_width * (SeasonalUI.ENABLED ? 4 : 2), + Width = box_width * (SeasonalUIConfig.ENABLED ? 4 : 2), Height = 1.5f, X = box_width, Alpha = 0, @@ -105,7 +106,7 @@ namespace osu.Game.Screens.Menu private void flash(Drawable d, double beatLength, bool kiai, ChannelAmplitudes amplitudes) { - if (SeasonalUI.ENABLED) + if (SeasonalUIConfig.ENABLED) updateColour(); d.FadeTo(Math.Clamp(0.1f + ((ReferenceEquals(d, leftBox) ? amplitudes.LeftChannel : amplitudes.RightChannel) - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier), 0.1f, 1), @@ -118,8 +119,8 @@ namespace osu.Game.Screens.Menu { Color4 baseColour = colours.Blue; - if (SeasonalUI.ENABLED) - baseColour = RNG.NextBool() ? SeasonalUI.PRIMARY_COLOUR_1 : SeasonalUI.PRIMARY_COLOUR_2; + if (SeasonalUIConfig.ENABLED) + baseColour = RNG.NextBool() ? SeasonalUIConfig.PRIMARY_COLOUR_1 : SeasonalUIConfig.PRIMARY_COLOUR_2; else if (user.Value?.IsSupporter ?? false) baseColour = skin.Value.GetConfig(GlobalSkinColours.MenuGlow)?.Value ?? baseColour; diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 272f53e087..dc2dfefddb 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -54,8 +53,10 @@ namespace osu.Game.Screens.Menu private Sample sampleClick; private SampleChannel sampleClickChannel; - private Sample sampleBeat; - private Sample sampleDownbeat; + protected virtual double BeatSampleVariance => 0.1; + + protected Sample SampleBeat; + protected Sample SampleDownbeat; private readonly Container colourAndTriangles; private readonly TrianglesV2 triangles; @@ -160,10 +161,10 @@ namespace osu.Game.Screens.Menu Alpha = visualizer_default_alpha, Size = SCALE_ADJUST }, - new Container + LogoElements = new Container { AutoSizeAxes = Axes.Both, - Children = new[] + Children = new Drawable[] { logoContainer = new CircularContainer { @@ -212,15 +213,6 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - SeasonalUI.ENABLED - ? hat = new Sprite - { - BypassAutoSizeAxes = Axes.Both, - Alpha = 0, - Origin = Anchor.BottomCentre, - Scale = new Vector2(-1, 1), - } - : Empty(), } }, impactContainer = new CircularContainer @@ -253,6 +245,8 @@ namespace osu.Game.Screens.Menu }; } + public Container LogoElements { get; private set; } + /// /// Schedule a new external animation. Handled queueing and finishing previous animations in a sane way. /// @@ -282,20 +276,11 @@ namespace osu.Game.Screens.Menu { sampleClick = audio.Samples.Get(@"Menu/osu-logo-select"); - if (SeasonalUI.ENABLED) - { - sampleDownbeat = sampleBeat = audio.Samples.Get(@"Menu/osu-logo-heartbeat-bell"); - } - else - { - sampleBeat = audio.Samples.Get(@"Menu/osu-logo-heartbeat"); - sampleDownbeat = audio.Samples.Get(@"Menu/osu-logo-downbeat"); - } + SampleBeat = audio.Samples.Get(@"Menu/osu-logo-heartbeat"); + SampleDownbeat = audio.Samples.Get(@"Menu/osu-logo-downbeat"); logo.Texture = textures.Get(@"Menu/logo"); ripple.Texture = textures.Get(@"Menu/logo"); - if (hat != null) - hat.Texture = textures.Get(@"Menu/hat"); } private int lastBeatIndex; @@ -318,15 +303,13 @@ namespace osu.Game.Screens.Menu { if (beatIndex % timingPoint.TimeSignature.Numerator == 0) { - sampleDownbeat?.Play(); + SampleDownbeat?.Play(); } else { - var channel = sampleBeat.GetChannel(); - if (SeasonalUI.ENABLED) - channel.Frequency.Value = 0.99 + RNG.NextDouble(0.02); - else - channel.Frequency.Value = 0.95 + RNG.NextDouble(0.1); + var channel = SampleBeat.GetChannel(); + + channel.Frequency.Value = 1 - BeatSampleVariance / 2 + RNG.NextDouble(BeatSampleVariance); channel.Play(); } }); @@ -381,9 +364,6 @@ namespace osu.Game.Screens.Menu const float scale_adjust_cutoff = 0.4f; - if (SeasonalUI.ENABLED) - updateHat(); - if (musicController.CurrentTrack.IsRunning) { float maxAmplitude = lastBeatIndex >= 0 ? musicController.CurrentTrack.CurrentAmplitudes.Maximum : 0; @@ -397,38 +377,6 @@ namespace osu.Game.Screens.Menu } } - private bool hasHat; - - private void updateHat() - { - if (hat == null) - return; - - bool shouldHat = DrawWidth * Scale.X < 400; - - if (shouldHat != hasHat) - { - hasHat = shouldHat; - - if (hasHat) - { - hat.Delay(400) - .Then() - .MoveTo(new Vector2(120, 160)) - .RotateTo(0) - .RotateTo(-20, 500, Easing.OutQuint) - .FadeIn(250, Easing.OutQuint); - } - else - { - hat.Delay(100) - .Then() - .MoveToOffset(new Vector2(0, -5), 500, Easing.OutQuint) - .FadeOut(500, Easing.OutQuint); - } - } - } - public override bool HandlePositionalInput => base.HandlePositionalInput && Alpha > 0.2f; protected override bool OnMouseDown(MouseDownEvent e) @@ -506,9 +454,6 @@ namespace osu.Game.Screens.Menu private Container currentProxyTarget; private Drawable proxy; - [CanBeNull] - private readonly Sprite hat; - public void StopSamplePlayback() => sampleClickChannel?.Stop(); public Drawable ProxyToContainer(Container c) diff --git a/osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs b/osu.Game/Seasonal/MainMenuSeasonalLighting.cs similarity index 93% rename from osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs rename to osu.Game/Seasonal/MainMenuSeasonalLighting.cs index f46a1387ab..a382785499 100644 --- a/osu.Game/Screens/Menu/MainMenuSeasonalLighting.cs +++ b/osu.Game/Seasonal/MainMenuSeasonalLighting.cs @@ -21,7 +21,7 @@ using osu.Game.Rulesets.Objects.Types; using osuTK; using osuTK.Graphics; -namespace osu.Game.Screens.Menu +namespace osu.Game.Seasonal { public partial class MainMenuSeasonalLighting : CompositeDrawable { @@ -121,7 +121,7 @@ namespace osu.Game.Screens.Menu if (h.GetType().Name.Contains("Tick")) { - light.Colour = SeasonalUI.AMBIENT_COLOUR_1; + light.Colour = SeasonalUIConfig.AMBIENT_COLOUR_1; light.Scale = new Vector2(0.5f); light .FadeInFromZero(250) @@ -133,14 +133,14 @@ namespace osu.Game.Screens.Menu else { // default are green - Color4 col = SeasonalUI.PRIMARY_COLOUR_2; + Color4 col = SeasonalUIConfig.PRIMARY_COLOUR_2; // whistles are red if (h.Samples.Any(s => s.Name == HitSampleInfo.HIT_WHISTLE)) - col = SeasonalUI.PRIMARY_COLOUR_1; + col = SeasonalUIConfig.PRIMARY_COLOUR_1; // clap is third ambient (yellow) colour else if (h.Samples.Any(s => s.Name == HitSampleInfo.HIT_CLAP)) - col = SeasonalUI.AMBIENT_COLOUR_1; + col = SeasonalUIConfig.AMBIENT_COLOUR_1; light.Colour = col; @@ -184,12 +184,12 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(12), - Colour = SeasonalUI.AMBIENT_COLOUR_1, + Colour = SeasonalUIConfig.AMBIENT_COLOUR_1, Blending = BlendingParameters.Additive, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Glow, - Colour = SeasonalUI.AMBIENT_COLOUR_2, + Colour = SeasonalUIConfig.AMBIENT_COLOUR_2, Radius = 80, } } diff --git a/osu.Game/Seasonal/OsuLogoChristmas.cs b/osu.Game/Seasonal/OsuLogoChristmas.cs new file mode 100644 index 0000000000..ec9cac94ea --- /dev/null +++ b/osu.Game/Seasonal/OsuLogoChristmas.cs @@ -0,0 +1,74 @@ +// 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.Audio; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Game.Screens.Menu; +using osuTK; + +namespace osu.Game.Seasonal +{ + public partial class OsuLogoChristmas : OsuLogo + { + protected override double BeatSampleVariance => 0.02; + + private Sprite? hat; + + private bool hasHat; + + [BackgroundDependencyLoader] + private void load(TextureStore textures, AudioManager audio) + { + LogoElements.Add(hat = new Sprite + { + BypassAutoSizeAxes = Axes.Both, + Alpha = 0, + Origin = Anchor.BottomCentre, + Scale = new Vector2(-1, 1), + Texture = textures.Get(@"Menu/hat"), + }); + + // override base samples with our preferred ones. + SampleDownbeat = SampleBeat = audio.Samples.Get(@"Menu/osu-logo-heartbeat-bell"); + } + + protected override void Update() + { + base.Update(); + updateHat(); + } + + private void updateHat() + { + if (hat == null) + return; + + bool shouldHat = DrawWidth * Scale.X < 400; + + if (shouldHat != hasHat) + { + hasHat = shouldHat; + + if (hasHat) + { + hat.Delay(400) + .Then() + .MoveTo(new Vector2(120, 160)) + .RotateTo(0) + .RotateTo(-20, 500, Easing.OutQuint) + .FadeIn(250, Easing.OutQuint); + } + else + { + hat.Delay(100) + .Then() + .MoveToOffset(new Vector2(0, -5), 500, Easing.OutQuint) + .FadeOut(500, Easing.OutQuint); + } + } + } + } +} diff --git a/osu.Game/Screens/SeasonalUI.cs b/osu.Game/Seasonal/SeasonalUIConfig.cs similarity index 78% rename from osu.Game/Screens/SeasonalUI.cs rename to osu.Game/Seasonal/SeasonalUIConfig.cs index fc2303f285..060913a8bf 100644 --- a/osu.Game/Screens/SeasonalUI.cs +++ b/osu.Game/Seasonal/SeasonalUIConfig.cs @@ -4,9 +4,12 @@ using osu.Framework.Extensions.Color4Extensions; using osuTK.Graphics; -namespace osu.Game.Screens +namespace osu.Game.Seasonal { - public static class SeasonalUI + /// + /// General configuration setting for seasonal event adjustments to the game. + /// + public static class SeasonalUIConfig { public static readonly bool ENABLED = true; From 2a720ef200897f0430a630d2d565ab52c8875278 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2024 14:51:33 +0900 Subject: [PATCH 267/326] Move christmas intro screen to seasonal namespace --- osu.Game.Tests/Visual/Menus/TestSceneIntroChristmas.cs | 1 + .../Visual/Menus/TestSceneMainMenuSeasonalLighting.cs | 1 - osu.Game/{Screens/Menu => Seasonal}/IntroChristmas.cs | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) rename osu.Game/{Screens/Menu => Seasonal}/IntroChristmas.cs (99%) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroChristmas.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroChristmas.cs index 13377f49df..0398b4fbb6 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroChristmas.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroChristmas.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Game.Screens.Menu; +using osu.Game.Seasonal; namespace osu.Game.Tests.Visual.Menus { diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs index bf499f1beb..11356f7eeb 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs @@ -6,7 +6,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Screens.Menu; using osu.Game.Seasonal; namespace osu.Game.Tests.Visual.Menus diff --git a/osu.Game/Screens/Menu/IntroChristmas.cs b/osu.Game/Seasonal/IntroChristmas.cs similarity index 99% rename from osu.Game/Screens/Menu/IntroChristmas.cs rename to osu.Game/Seasonal/IntroChristmas.cs index aa16f33c3d..ac3286f277 100644 --- a/osu.Game/Screens/Menu/IntroChristmas.cs +++ b/osu.Game/Seasonal/IntroChristmas.cs @@ -15,11 +15,11 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Seasonal; +using osu.Game.Screens.Menu; using osuTK; using osuTK.Graphics; -namespace osu.Game.Screens.Menu +namespace osu.Game.Seasonal { public partial class IntroChristmas : IntroScreen { From ad4a8a1e0a345c75b0f43186f00d985e653ad7bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2024 14:58:45 +0900 Subject: [PATCH 268/326] Subclass menu flashes instead of adding local code to it --- osu.Game/Screens/Menu/MainMenu.cs | 2 +- osu.Game/Screens/Menu/MenuSideFlashes.cs | 31 +++++++++++++------- osu.Game/Seasonal/SeasonalMenuSideFlashes.cs | 18 ++++++++++++ 3 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 osu.Game/Seasonal/SeasonalMenuSideFlashes.cs diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index a4b269ad0d..58d97bfe16 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -161,7 +161,7 @@ namespace osu.Game.Screens.Menu } }, logoTarget = new Container { RelativeSizeAxes = Axes.Both, }, - sideFlashes = new MenuSideFlashes(), + sideFlashes = SeasonalUIConfig.ENABLED ? new SeasonalMenuSideFlashes() : new MenuSideFlashes(), songTicker = new SongTicker { Anchor = Anchor.TopRight, diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index 808da5dd47..426896825e 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -11,14 +11,12 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Shapes; -using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Seasonal; using osu.Game.Skinning; using osuTK.Graphics; @@ -26,6 +24,10 @@ namespace osu.Game.Screens.Menu { public partial class MenuSideFlashes : BeatSyncedContainer { + protected virtual bool RefreshColoursEveryFlash => false; + + protected virtual float Intensity => 2; + private readonly IBindable beatmap = new Bindable(); private Box leftBox; @@ -69,7 +71,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Y, - Width = box_width * (SeasonalUIConfig.ENABLED ? 4 : 2), + Width = box_width * Intensity, Height = 1.5f, // align off-screen to make sure our edges don't become visible during parallax. X = -box_width, @@ -81,7 +83,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Y, - Width = box_width * (SeasonalUIConfig.ENABLED ? 4 : 2), + Width = box_width * Intensity, Height = 1.5f, X = box_width, Alpha = 0, @@ -89,8 +91,11 @@ namespace osu.Game.Screens.Menu } }; - user.ValueChanged += _ => updateColour(); - skin.BindValueChanged(_ => updateColour(), true); + if (!RefreshColoursEveryFlash) + { + user.ValueChanged += _ => updateColour(); + skin.BindValueChanged(_ => updateColour(), true); + } } protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) @@ -106,7 +111,7 @@ namespace osu.Game.Screens.Menu private void flash(Drawable d, double beatLength, bool kiai, ChannelAmplitudes amplitudes) { - if (SeasonalUIConfig.ENABLED) + if (RefreshColoursEveryFlash) updateColour(); d.FadeTo(Math.Clamp(0.1f + ((ReferenceEquals(d, leftBox) ? amplitudes.LeftChannel : amplitudes.RightChannel) - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier), 0.1f, 1), @@ -115,15 +120,19 @@ namespace osu.Game.Screens.Menu .FadeOut(beatLength, Easing.In); } - private void updateColour() + protected virtual Color4 GetBaseColour() { Color4 baseColour = colours.Blue; - if (SeasonalUIConfig.ENABLED) - baseColour = RNG.NextBool() ? SeasonalUIConfig.PRIMARY_COLOUR_1 : SeasonalUIConfig.PRIMARY_COLOUR_2; - else if (user.Value?.IsSupporter ?? false) + if (user.Value?.IsSupporter ?? false) baseColour = skin.Value.GetConfig(GlobalSkinColours.MenuGlow)?.Value ?? baseColour; + return baseColour; + } + + private void updateColour() + { + var baseColour = GetBaseColour(); // linear colour looks better in this case, so let's use it for now. Color4 gradientDark = baseColour.Opacity(0).ToLinear(); Color4 gradientLight = baseColour.Opacity(0.6f).ToLinear(); diff --git a/osu.Game/Seasonal/SeasonalMenuSideFlashes.cs b/osu.Game/Seasonal/SeasonalMenuSideFlashes.cs new file mode 100644 index 0000000000..46a0a973bb --- /dev/null +++ b/osu.Game/Seasonal/SeasonalMenuSideFlashes.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.Framework.Utils; +using osu.Game.Screens.Menu; +using osuTK.Graphics; + +namespace osu.Game.Seasonal +{ + public partial class SeasonalMenuSideFlashes : MenuSideFlashes + { + protected override bool RefreshColoursEveryFlash => true; + + protected override float Intensity => 4; + + protected override Color4 GetBaseColour() => RNG.NextBool() ? SeasonalUIConfig.PRIMARY_COLOUR_1 : SeasonalUIConfig.PRIMARY_COLOUR_2; + } +} From 8e9377914d96a4d65a96335da0cd169e3721128d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2024 15:04:37 +0900 Subject: [PATCH 269/326] Subclass menu logo visualisation --- .../Screens/Menu/MenuLogoVisualisation.cs | 19 +++++++------------ osu.Game/Screens/Menu/OsuLogo.cs | 16 +++++++++------- osu.Game/Seasonal/OsuLogoChristmas.cs | 2 ++ .../Seasonal/SeasonalMenuLogoVisualisation.cs | 12 ++++++++++++ 4 files changed, 30 insertions(+), 19 deletions(-) create mode 100644 osu.Game/Seasonal/SeasonalMenuLogoVisualisation.cs diff --git a/osu.Game/Screens/Menu/MenuLogoVisualisation.cs b/osu.Game/Screens/Menu/MenuLogoVisualisation.cs index 32b5c706a3..f152c0c93c 100644 --- a/osu.Game/Screens/Menu/MenuLogoVisualisation.cs +++ b/osu.Game/Screens/Menu/MenuLogoVisualisation.cs @@ -1,22 +1,19 @@ // 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.Game.Online.API; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Seasonal; using osu.Game.Skinning; using osuTK.Graphics; namespace osu.Game.Screens.Menu { - internal partial class MenuLogoVisualisation : LogoVisualisation + public partial class MenuLogoVisualisation : LogoVisualisation { - private IBindable user; - private Bindable skin; + private IBindable user = null!; + private Bindable skin = null!; [BackgroundDependencyLoader] private void load(IAPIProvider api, SkinManager skinManager) @@ -24,15 +21,13 @@ namespace osu.Game.Screens.Menu user = api.LocalUser.GetBoundCopy(); skin = skinManager.CurrentSkin.GetBoundCopy(); - user.ValueChanged += _ => updateColour(); - skin.BindValueChanged(_ => updateColour(), true); + user.ValueChanged += _ => UpdateColour(); + skin.BindValueChanged(_ => UpdateColour(), true); } - private void updateColour() + protected virtual void UpdateColour() { - if (SeasonalUIConfig.ENABLED) - Colour = SeasonalUIConfig.AMBIENT_COLOUR_1; - else if (user.Value?.IsSupporter ?? false) + if (user.Value?.IsSupporter ?? false) Colour = skin.Value.GetConfig(GlobalSkinColours.MenuGlow)?.Value ?? Color4.White; else Colour = Color4.White; diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index dc2dfefddb..31f47c1349 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -53,6 +53,8 @@ namespace osu.Game.Screens.Menu private Sample sampleClick; private SampleChannel sampleClickChannel; + protected virtual MenuLogoVisualisation CreateMenuLogoVisualisation() => new MenuLogoVisualisation(); + protected virtual double BeatSampleVariance => 0.1; protected Sample SampleBeat; @@ -153,14 +155,14 @@ namespace osu.Game.Screens.Menu AutoSizeAxes = Axes.Both, Children = new Drawable[] { - visualizer = new MenuLogoVisualisation + visualizer = CreateMenuLogoVisualisation().With(v => { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Alpha = visualizer_default_alpha, - Size = SCALE_ADJUST - }, + v.RelativeSizeAxes = Axes.Both; + v.Origin = Anchor.Centre; + v.Anchor = Anchor.Centre; + v.Alpha = visualizer_default_alpha; + v.Size = SCALE_ADJUST; + }), LogoElements = new Container { AutoSizeAxes = Axes.Both, diff --git a/osu.Game/Seasonal/OsuLogoChristmas.cs b/osu.Game/Seasonal/OsuLogoChristmas.cs index ec9cac94ea..8975a69c32 100644 --- a/osu.Game/Seasonal/OsuLogoChristmas.cs +++ b/osu.Game/Seasonal/OsuLogoChristmas.cs @@ -19,6 +19,8 @@ namespace osu.Game.Seasonal private bool hasHat; + protected override MenuLogoVisualisation CreateMenuLogoVisualisation() => new SeasonalMenuLogoVisualisation(); + [BackgroundDependencyLoader] private void load(TextureStore textures, AudioManager audio) { diff --git a/osu.Game/Seasonal/SeasonalMenuLogoVisualisation.cs b/osu.Game/Seasonal/SeasonalMenuLogoVisualisation.cs new file mode 100644 index 0000000000..f00da3fe7e --- /dev/null +++ b/osu.Game/Seasonal/SeasonalMenuLogoVisualisation.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.Game.Screens.Menu; + +namespace osu.Game.Seasonal +{ + internal partial class SeasonalMenuLogoVisualisation : MenuLogoVisualisation + { + protected override void UpdateColour() => Colour = SeasonalUIConfig.AMBIENT_COLOUR_1; + } +} From 3fc99904113036e4edd0fbd750e17605e900d953 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2024 15:28:33 +0900 Subject: [PATCH 270/326] Fix some failing tests --- .../Editor/TestSceneSliderVelocityAdjust.cs | 3 ++- .../Visual/Menus/TestSceneMainMenuSeasonalLighting.cs | 3 ++- osu.Game/Screens/Menu/IntroScreen.cs | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs index 175cbeca6e..6690d043f8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs @@ -7,6 +7,7 @@ using osu.Framework.Input; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit; @@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private Slider? slider => editorBeatmap.HitObjects.OfType().FirstOrDefault(); - private TimelineHitObjectBlueprint blueprint => editor.ChildrenOfType().FirstOrDefault()!; + private TimelineHitObjectBlueprint blueprint => editor.ChildrenOfType().FirstOrDefault(b => b.Item.GetEndTime() != b.Item.StartTime)!; private DifficultyPointPiece difficultyPointPiece => blueprint.ChildrenOfType().First(); diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs b/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs index 11356f7eeb..46fddf823e 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMainMenuSeasonalLighting.cs @@ -22,7 +22,8 @@ namespace osu.Game.Tests.Visual.Menus { var setInfo = beatmaps.QueryBeatmapSet(b => b.Protected && b.Hash == IntroChristmas.CHRISTMAS_BEATMAP_SET_HASH); - Beatmap.Value = beatmaps.GetWorkingBeatmap(setInfo!.Value.Beatmaps.First()); + if (setInfo != null) + Beatmap.Value = beatmaps.GetWorkingBeatmap(setInfo.Value.Beatmaps.First()); }); AddStep("create lighting", () => Child = new MainMenuSeasonalLighting()); diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 9885c061a9..a5c2497618 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -12,6 +12,7 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Screens; @@ -200,7 +201,7 @@ namespace osu.Game.Screens.Menu PrepareMenuLoad(); LoadMenu(); - if (!Debugger.IsAttached) + if (!Debugger.IsAttached && !DebugUtils.IsNUnitRunning) { notifications.Post(new SimpleErrorNotification { From 7ebc9dd843b0b801bbfb3a1e72c1be669fff197a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2024 15:32:00 +0900 Subject: [PATCH 271/326] Disable seasonal for now --- osu.Game/Seasonal/SeasonalUIConfig.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Seasonal/SeasonalUIConfig.cs b/osu.Game/Seasonal/SeasonalUIConfig.cs index 060913a8bf..b894a42108 100644 --- a/osu.Game/Seasonal/SeasonalUIConfig.cs +++ b/osu.Game/Seasonal/SeasonalUIConfig.cs @@ -11,7 +11,7 @@ namespace osu.Game.Seasonal /// public static class SeasonalUIConfig { - public static readonly bool ENABLED = true; + public static readonly bool ENABLED = false; public static readonly Color4 PRIMARY_COLOUR_1 = Color4Extensions.FromHex(@"D32F2F"); From f5b019807730a4b1d45158939f55299d54ac5cc6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2024 16:02:43 +0900 Subject: [PATCH 272/326] Fix test faiulres when seasonal set to `true` due to non-circles intro --- osu.Game/Screens/Loader.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index dfa5d2c369..9e7ff80f7c 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shaders; @@ -38,7 +39,9 @@ namespace osu.Game.Screens private IntroScreen getIntroSequence() { - if (SeasonalUIConfig.ENABLED) + // Headless tests run too fast to load non-circles intros correctly. + // They will hit the "audio can't play" notification and cause random test failures. + if (SeasonalUIConfig.ENABLED && !DebugUtils.IsNUnitRunning) return new IntroChristmas(createMainMenu); if (introSequence == IntroSequence.Random) From 5d1701469848f89410d84a220e065754f003a42b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2024 16:31:06 +0900 Subject: [PATCH 273/326] Fix mouse wheel disable not working during gameplay --- .../TestSceneMouseWheelVolumeAdjust.cs | 14 +++--- .../Volume/GlobalScrollAdjustsVolume.cs | 3 -- .../Play/GameplayScrollWheelHandling.cs | 44 +++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 18 +------- 4 files changed, 53 insertions(+), 26 deletions(-) create mode 100644 osu.Game/Screens/Play/GameplayScrollWheelHandling.cs diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs b/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs index a89f5fb647..26a37fa211 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneMouseWheelVolumeAdjust.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.Extensions; using osu.Game.Configuration; @@ -58,7 +56,11 @@ namespace osu.Game.Tests.Visual.Navigation // First scroll makes volume controls appear, second adjusts volume. AddRepeatStep("Adjust volume using mouse wheel", () => InputManager.ScrollVerticalBy(5), 10); - AddAssert("Volume is still zero", () => Game.Audio.Volume.Value == 0); + AddAssert("Volume is still zero", () => Game.Audio.Volume.Value, () => Is.Zero); + + AddStep("Pause", () => InputManager.PressKey(Key.Escape)); + AddRepeatStep("Adjust volume using mouse wheel", () => InputManager.ScrollVerticalBy(5), 10); + AddAssert("Volume is above zero", () => Game.Audio.Volume.Value > 0); } [Test] @@ -80,8 +82,8 @@ namespace osu.Game.Tests.Visual.Navigation private void loadToPlayerNonBreakTime() { - Player player = null; - Screens.Select.SongSelect songSelect = null; + Player? player = null; + Screens.Select.SongSelect songSelect = null!; PushAndConfirm(() => songSelect = new TestSceneScreenNavigation.TestPlaySongSelect()); AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); @@ -95,7 +97,7 @@ namespace osu.Game.Tests.Visual.Navigation return (player = Game.ScreenStack.CurrentScreen as Player) != null; }); - AddUntilStep("wait for play time active", () => !player.IsBreakTime.Value); + AddUntilStep("wait for play time active", () => player!.IsBreakTime.Value, () => Is.False); } } } diff --git a/osu.Game/Overlays/Volume/GlobalScrollAdjustsVolume.cs b/osu.Game/Overlays/Volume/GlobalScrollAdjustsVolume.cs index 81be084d22..f1ad88833b 100644 --- a/osu.Game/Overlays/Volume/GlobalScrollAdjustsVolume.cs +++ b/osu.Game/Overlays/Volume/GlobalScrollAdjustsVolume.cs @@ -33,8 +33,5 @@ namespace osu.Game.Overlays.Volume // forward any unhandled mouse scroll events to the volume control. return volumeOverlay?.Adjust(GlobalAction.IncreaseVolume, e.ScrollDelta.Y, e.IsPrecise) ?? false; } - - public bool OnScroll(KeyBindingScrollEvent e) => - volumeOverlay?.Adjust(e.Action, e.ScrollAmount, e.IsPrecise) ?? false; } } diff --git a/osu.Game/Screens/Play/GameplayScrollWheelHandling.cs b/osu.Game/Screens/Play/GameplayScrollWheelHandling.cs new file mode 100644 index 0000000000..73ad9ccb24 --- /dev/null +++ b/osu.Game/Screens/Play/GameplayScrollWheelHandling.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.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Input.Events; +using osu.Game.Configuration; +using osu.Game.Overlays.Volume; + +namespace osu.Game.Screens.Play +{ + /// + /// Primarily handles volume adjustment in gameplay. + /// + /// - If the user has mouse wheel disabled, only allow during break time or when holding alt. Also block scroll from parent handling. + /// - Otherwise always allow, as per implementation. + /// + internal class GameplayScrollWheelHandling : GlobalScrollAdjustsVolume + { + private Bindable mouseWheelDisabled = null!; + + [Resolved] + private IGameplayClock gameplayClock { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); + } + + protected override bool OnScroll(ScrollEvent e) + { + // During pause, allow global volume adjust regardless of settings. + if (gameplayClock.IsPaused.Value) + return base.OnScroll(e); + + // Block any parent handling of scroll if the user has asked for it (special case when holding "Alt"). + if (mouseWheelDisabled.Value && !e.AltPressed) + return true; + + return base.OnScroll(e); + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 1c186485b8..f6b0230714 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -15,7 +15,6 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; @@ -28,7 +27,6 @@ using osu.Game.Graphics.Containers; using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Overlays; -using osu.Game.Overlays.Volume; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -89,8 +87,6 @@ namespace osu.Game.Screens.Play private bool isRestarting; private bool skipExitTransition; - private Bindable mouseWheelDisabled; - private readonly Bindable storyboardReplacesBackground = new Bindable(); public IBindable LocalUserPlaying => localUserPlaying; @@ -229,8 +225,6 @@ namespace osu.Game.Screens.Play return; } - mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); - if (game != null) gameActive.BindTo(game.IsActive); @@ -254,7 +248,6 @@ namespace osu.Game.Screens.Play InternalChildren = new Drawable[] { - new GlobalScrollAdjustsVolume(), GameplayClockContainer = CreateGameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime), }; @@ -271,6 +264,7 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score, ScoreProcessor, HealthProcessor, Beatmap.Value.Storyboard)); var rulesetSkinProvider = new RulesetSkinProvidingContainer(ruleset, playableBeatmap, Beatmap.Value.Skin); + GameplayClockContainer.Add(new GameplayScrollWheelHandling()); // load the skinning hierarchy first. // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. @@ -899,16 +893,6 @@ namespace osu.Game.Screens.Play }); } - protected override bool OnScroll(ScrollEvent e) - { - // During pause, allow global volume adjust regardless of settings. - if (GameplayClockContainer.IsPaused.Value) - return false; - - // Block global volume adjust if the user has asked for it (special case when holding "Alt"). - return mouseWheelDisabled.Value && !e.AltPressed; - } - #region Gameplay leaderboard protected readonly Bindable LeaderboardExpandedState = new BindableBool(); From 48ce68694a4d01cf57171332453d66b1393962cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2024 17:06:47 +0900 Subject: [PATCH 274/326] Add missing partial --- osu.Game/Screens/Play/GameplayScrollWheelHandling.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayScrollWheelHandling.cs b/osu.Game/Screens/Play/GameplayScrollWheelHandling.cs index 73ad9ccb24..059d5a0dd4 100644 --- a/osu.Game/Screens/Play/GameplayScrollWheelHandling.cs +++ b/osu.Game/Screens/Play/GameplayScrollWheelHandling.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Play /// - If the user has mouse wheel disabled, only allow during break time or when holding alt. Also block scroll from parent handling. /// - Otherwise always allow, as per implementation. /// - internal class GameplayScrollWheelHandling : GlobalScrollAdjustsVolume + internal partial class GameplayScrollWheelHandling : GlobalScrollAdjustsVolume { private Bindable mouseWheelDisabled = null!; From 25373c3f9c9b2f4b4b5d6c7d7da1bc2685885320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 20 Dec 2024 09:50:58 +0100 Subject: [PATCH 275/326] Fix backwards repeat 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 60fcd17ac6..244b72edaa 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1433,7 +1433,7 @@ namespace osu.Game case GlobalAction.NextVolumeMeter: case GlobalAction.PreviousVolumeMeter: - if (!e.Repeat) + if (e.Repeat) return true; return volume.Adjust(e.Action); From 3ec63d00cbd32b5ab31dd3b5705f9e0dedb229bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 19 Dec 2024 13:26:52 +0100 Subject: [PATCH 276/326] Silence test that apparently can't work on CI --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 7855c138ab..58fe6e8e56 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -208,6 +208,7 @@ namespace osu.Game.Tests.Visual.Gameplay } [Test] + [Ignore("Fails on github runners if they happen to skip too far forward in time.")] public void TestUserPauseDuringCooldownTooSoon() { AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(0)); From 139fb2cdd3a60faee550be9a9cb816c4943c9141 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2024 19:44:43 +0900 Subject: [PATCH 277/326] Revert and fix some tests still --- .../Editor/TestSceneSliderVelocityAdjust.cs | 3 +-- osu.Game/Screens/Menu/IntroScreen.cs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs index 6690d043f8..175cbeca6e 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderVelocityAdjust.cs @@ -7,7 +7,6 @@ using osu.Framework.Input; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit; @@ -30,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor private Slider? slider => editorBeatmap.HitObjects.OfType().FirstOrDefault(); - private TimelineHitObjectBlueprint blueprint => editor.ChildrenOfType().FirstOrDefault(b => b.Item.GetEndTime() != b.Item.StartTime)!; + private TimelineHitObjectBlueprint blueprint => editor.ChildrenOfType().FirstOrDefault()!; private DifficultyPointPiece difficultyPointPiece => blueprint.ChildrenOfType().First(); diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index a5c2497618..9885c061a9 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -12,7 +12,6 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Bindables; -using osu.Framework.Development; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Screens; @@ -201,7 +200,7 @@ namespace osu.Game.Screens.Menu PrepareMenuLoad(); LoadMenu(); - if (!Debugger.IsAttached && !DebugUtils.IsNUnitRunning) + if (!Debugger.IsAttached) { notifications.Post(new SimpleErrorNotification { From 4551d59f3922a44a9d8424048a34cfdccaa2d711 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 20 Dec 2024 12:06:26 +0100 Subject: [PATCH 278/326] Give skinnable mod display a minimum size Co-authored-by: Dean Herbert --- osu.Game/Screens/Play/HUD/ModDisplay.cs | 4 +++- osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs | 10 +++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 417ce355a5..3ab4c15154 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -22,6 +22,8 @@ namespace osu.Game.Screens.Play.HUD /// public partial class ModDisplay : CompositeDrawable, IHasCurrentValue> { + public const float MOD_ICON_SCALE = 0.6f; + private ExpansionMode expansionMode = ExpansionMode.ExpandOnHover; public ExpansionMode ExpansionMode @@ -93,7 +95,7 @@ namespace osu.Game.Screens.Play.HUD iconsContainer.Clear(); foreach (Mod mod in mods.NewValue.AsOrdered()) - iconsContainer.Add(new ModIcon(mod, showExtendedInformation: showExtendedInformation) { Scale = new Vector2(0.6f) }); + iconsContainer.Add(new ModIcon(mod, showExtendedInformation: showExtendedInformation) { Scale = new Vector2(MOD_ICON_SCALE) }); } private void updateExpansionMode(double duration = 500) diff --git a/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs b/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs index 819484e8ba..ee77e38edd 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs @@ -10,6 +10,8 @@ using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Skinning; using osu.Game.Localisation.SkinComponents; +using osu.Game.Rulesets.UI; +using osuTK; namespace osu.Game.Screens.Play.HUD { @@ -32,7 +34,13 @@ namespace osu.Game.Screens.Play.HUD [BackgroundDependencyLoader] private void load() { - InternalChild = modDisplay = new ModDisplay(); + InternalChildren = new Drawable[] + { + // Provide a minimum autosize. + new Container { Size = ModIcon.MOD_ICON_SIZE * ModDisplay.MOD_ICON_SCALE }, + modDisplay = new ModDisplay(), + }; + modDisplay.Current = mods; AutoSizeAxes = Axes.Both; } From a9cf31f5d8c9f2fc3136201faa22eaa58b35a46e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 20 Dec 2024 21:27:24 +0900 Subject: [PATCH 279/326] Usings --- osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs b/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs index ee77e38edd..59bb1ade41 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableModDisplay.cs @@ -7,11 +7,10 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Configuration; -using osu.Game.Rulesets.Mods; -using osu.Game.Skinning; using osu.Game.Localisation.SkinComponents; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; -using osuTK; +using osu.Game.Skinning; namespace osu.Game.Screens.Play.HUD { From 1fcd953e4a55c8d6576e64c737fa05b19a40a829 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 21 Dec 2024 20:17:27 +0900 Subject: [PATCH 280/326] Fetch ruleset before initialising beatmap the first time --- osu.Game/Seasonal/MainMenuSeasonalLighting.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Seasonal/MainMenuSeasonalLighting.cs b/osu.Game/Seasonal/MainMenuSeasonalLighting.cs index a382785499..718dd38fe7 100644 --- a/osu.Game/Seasonal/MainMenuSeasonalLighting.cs +++ b/osu.Game/Seasonal/MainMenuSeasonalLighting.cs @@ -49,11 +49,11 @@ namespace osu.Game.Seasonal [BackgroundDependencyLoader] private void load(IBindable working, RulesetStore rulesets) { - this.working = working.GetBoundCopy(); - this.working.BindValueChanged(_ => Scheduler.AddOnce(updateBeatmap), true); - // operate in osu! ruleset to keep things simple for now. osuRuleset = rulesets.GetRuleset(0); + + this.working = working.GetBoundCopy(); + this.working.BindValueChanged(_ => Scheduler.AddOnce(updateBeatmap), true); } private void updateBeatmap() From d897a31f0c5b63534f60d165857bd67123a854e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 21 Dec 2024 20:30:00 +0900 Subject: [PATCH 281/326] Add extra safeties against null ref when rulesets are missing --- osu.Game/Seasonal/MainMenuSeasonalLighting.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Seasonal/MainMenuSeasonalLighting.cs b/osu.Game/Seasonal/MainMenuSeasonalLighting.cs index 718dd38fe7..30ad7acefe 100644 --- a/osu.Game/Seasonal/MainMenuSeasonalLighting.cs +++ b/osu.Game/Seasonal/MainMenuSeasonalLighting.cs @@ -27,7 +27,7 @@ namespace osu.Game.Seasonal { private IBindable working = null!; - private InterpolatingFramedClock beatmapClock = null!; + private InterpolatingFramedClock? beatmapClock; private List hitObjects = null!; @@ -82,6 +82,9 @@ namespace osu.Game.Seasonal { base.Update(); + if (osuRuleset == null || beatmapClock == null) + return; + Height = DrawWidth / 16 * 10; beatmapClock.ProcessFrame(); From 5f617e6697aa6e2e4f8be7e411612725a364cc0a Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sat, 21 Dec 2024 20:31:12 +0800 Subject: [PATCH 282/326] Implement rename skin popover and button --- osu.Game/Localisation/SkinSettingsStrings.cs | 5 + .../Overlays/Settings/Sections/SkinSection.cs | 96 +++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/osu.Game/Localisation/SkinSettingsStrings.cs b/osu.Game/Localisation/SkinSettingsStrings.cs index 4b6b0ce1d6..1a812ad04d 100644 --- a/osu.Game/Localisation/SkinSettingsStrings.cs +++ b/osu.Game/Localisation/SkinSettingsStrings.cs @@ -54,6 +54,11 @@ namespace osu.Game.Localisation /// public static LocalisableString BeatmapHitsounds => new TranslatableString(getKey(@"beatmap_hitsounds"), @"Beatmap hitsounds"); + /// + /// "Rename selected skin" + /// + public static LocalisableString RenameSkinButton = new TranslatableString(getKey(@"rename_skin_button"), @"Rename selected skin"); + /// /// "Export selected skin" /// diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 9b04f208a7..c015affcd2 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -9,17 +9,23 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; using osu.Game.Overlays.SkinEditor; using osu.Game.Screens.Select; using osu.Game.Skinning; +using osuTK; using Realms; namespace osu.Game.Overlays.Settings.Sections @@ -69,6 +75,7 @@ namespace osu.Game.Overlays.Settings.Sections Text = SkinSettingsStrings.SkinLayoutEditor, Action = () => skinEditor?.ToggleVisibility(), }, + new RenameSkinButton(), new ExportSkinButton(), new DeleteSkinButton(), }; @@ -136,6 +143,95 @@ namespace osu.Game.Overlays.Settings.Sections } } + public partial class RenameSkinButton : SettingsButton, IHasPopover + { + [Resolved] + private SkinManager skins { get; set; } + + private Bindable currentSkin; + + [BackgroundDependencyLoader] + private void load() + { + Text = SkinSettingsStrings.RenameSkinButton; + Action = this.ShowPopover; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + currentSkin = skins.CurrentSkin.GetBoundCopy(); + currentSkin.BindValueChanged(skin => Enabled.Value = skin.NewValue.SkinInfo.PerformRead(s => !s.Protected), true); + } + + public Popover GetPopover() + { + return new RenameSkinPopover(); + } + + public partial class RenameSkinPopover : OsuPopover + { + [Resolved] + private SkinManager skins { get; set; } + + public Action Rename { get; init; } + + private readonly FocusedTextBox textBox; + private readonly RoundedButton renameButton; + + public RenameSkinPopover() + { + AutoSizeAxes = Axes.Both; + Origin = Anchor.TopCentre; + + Child = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Y, + Width = 250, + Spacing = new Vector2(10f), + Children = new Drawable[] + { + textBox = new FocusedTextBox + { + PlaceholderText = @"Skin name", + FontSize = OsuFont.DEFAULT_FONT_SIZE, + RelativeSizeAxes = Axes.X, + }, + renameButton = new RoundedButton + { + Height = 40, + RelativeSizeAxes = Axes.X, + MatchingFilter = true, + Text = SkinSettingsStrings.RenameSkinButton, + } + } + }; + + renameButton.Action += rename; + textBox.OnCommit += delegate (TextBox _, bool _) { rename(); }; + } + + protected override void PopIn() + { + textBox.Text = skins.CurrentSkinInfo.Value.Value.Name; + textBox.TakeFocus(); + base.PopIn(); + } + + private void rename() + { + skins.CurrentSkinInfo.Value.PerformWrite(skin => + { + skin.Name = textBox.Text; + PopOut(); + }); + } + } + } + + public partial class ExportSkinButton : SettingsButton { [Resolved] From 1174f46656510e7524af30d5218fd48b7d99b0d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 21 Dec 2024 21:41:48 +0900 Subject: [PATCH 283/326] Add menu tip hinting at correct spelling of laser --- osu.Game/Localisation/MenuTipStrings.cs | 5 +++++ osu.Game/Screens/Menu/MenuTip.cs | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Localisation/MenuTipStrings.cs b/osu.Game/Localisation/MenuTipStrings.cs index f97ad5fa2c..9258f5d575 100644 --- a/osu.Game/Localisation/MenuTipStrings.cs +++ b/osu.Game/Localisation/MenuTipStrings.cs @@ -119,6 +119,11 @@ namespace osu.Game.Localisation /// public static LocalisableString AutoplayBeatmapShortcut => new TranslatableString(getKey(@"autoplay_beatmap_shortcut"), @"Ctrl-Enter at song select will start a beatmap in autoplay mode!"); + /// + /// ""Lazer" it not an english word. The correct spelling for the bright light is "laser"." + /// + public static LocalisableString LazerIsNotAWord => new TranslatableString(getKey(@"lazer_is_not_a_word"), @"""Lazer"" it not an english word. The correct spelling for the bright light is ""laser""."); + /// /// "Multithreading support means that even with low "FPS" your input and judgements will be accurate!" /// diff --git a/osu.Game/Screens/Menu/MenuTip.cs b/osu.Game/Screens/Menu/MenuTip.cs index 3fc5fe57fb..af7cfde52b 100644 --- a/osu.Game/Screens/Menu/MenuTip.cs +++ b/osu.Game/Screens/Menu/MenuTip.cs @@ -122,7 +122,8 @@ namespace osu.Game.Screens.Menu MenuTipStrings.RandomSkinShortcut, MenuTipStrings.ToggleReplaySettingsShortcut, MenuTipStrings.CopyModsFromScore, - MenuTipStrings.AutoplayBeatmapShortcut + MenuTipStrings.AutoplayBeatmapShortcut, + MenuTipStrings.LazerIsNotAWord }; return tips[RNG.Next(0, tips.Length)]; From 9a0d9641ab9d608713f2a3588a2c571c8b7b2aa2 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sat, 21 Dec 2024 21:26:56 +0800 Subject: [PATCH 284/326] Select all on focus when popover just open --- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index c015affcd2..5ff8c88756 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -178,13 +178,14 @@ namespace osu.Game.Overlays.Settings.Sections public Action Rename { get; init; } private readonly FocusedTextBox textBox; - private readonly RoundedButton renameButton; public RenameSkinPopover() { AutoSizeAxes = Axes.Both; Origin = Anchor.TopCentre; + RoundedButton renameButton; + Child = new FillFlowContainer { Direction = FillDirection.Vertical, @@ -198,6 +199,7 @@ namespace osu.Game.Overlays.Settings.Sections PlaceholderText = @"Skin name", FontSize = OsuFont.DEFAULT_FONT_SIZE, RelativeSizeAxes = Axes.X, + SelectAllOnFocus = true, }, renameButton = new RoundedButton { @@ -231,7 +233,6 @@ namespace osu.Game.Overlays.Settings.Sections } } - public partial class ExportSkinButton : SettingsButton { [Resolved] From ae7f1a9ef104d8f18d1f1d24c8fe822e5b95bda0 Mon Sep 17 00:00:00 2001 From: jkh675 Date: Sat, 21 Dec 2024 22:27:21 +0800 Subject: [PATCH 285/326] Fix code quality --- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 5ff8c88756..1792c61d48 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -212,7 +212,13 @@ namespace osu.Game.Overlays.Settings.Sections }; renameButton.Action += rename; - textBox.OnCommit += delegate (TextBox _, bool _) { rename(); }; + + void onTextboxCommit(TextBox sender, bool newText) + { + rename(); + } + + textBox.OnCommit += onTextboxCommit; } protected override void PopIn() From 7cd397986687d88fb0c423c920cc40b27e3b5f70 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sat, 21 Dec 2024 12:58:56 -0500 Subject: [PATCH 286/326] Fix typo in main menu tip --- osu.Game/Localisation/MenuTipStrings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/MenuTipStrings.cs b/osu.Game/Localisation/MenuTipStrings.cs index 9258f5d575..3b40d7bff5 100644 --- a/osu.Game/Localisation/MenuTipStrings.cs +++ b/osu.Game/Localisation/MenuTipStrings.cs @@ -120,9 +120,9 @@ namespace osu.Game.Localisation public static LocalisableString AutoplayBeatmapShortcut => new TranslatableString(getKey(@"autoplay_beatmap_shortcut"), @"Ctrl-Enter at song select will start a beatmap in autoplay mode!"); /// - /// ""Lazer" it not an english word. The correct spelling for the bright light is "laser"." + /// ""Lazer" is not an english word. The correct spelling for the bright light is "laser"." /// - public static LocalisableString LazerIsNotAWord => new TranslatableString(getKey(@"lazer_is_not_a_word"), @"""Lazer"" it not an english word. The correct spelling for the bright light is ""laser""."); + public static LocalisableString LazerIsNotAWord => new TranslatableString(getKey(@"lazer_is_not_a_word"), @"""Lazer"" is not an english word. The correct spelling for the bright light is ""laser""."); /// /// "Multithreading support means that even with low "FPS" your input and judgements will be accurate!" From 1c48fdb2350b2389f3d79fdaad9fb32194c9fa48 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sat, 21 Dec 2024 14:03:20 -0500 Subject: [PATCH 287/326] Add `Hidden` cursor state flag on all platforms --- osu.Desktop/OsuGameDesktop.cs | 1 - osu.Game/OsuGame.cs | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 46bd894c07..2d3f4e0ed6 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -134,7 +134,6 @@ namespace osu.Desktop if (iconStream != null) host.Window.SetIconFromStream(iconStream); - host.Window.CursorState |= CursorState.Hidden; host.Window.Title = Name; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 244b72edaa..96899e0ddb 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -319,6 +319,7 @@ namespace osu.Game if (host.Window != null) { + host.Window.CursorState |= CursorState.Hidden; host.Window.DragDrop += path => { // on macOS/iOS, URL associations are handled via SDL_DROPFILE events. From ce5a2059933e48693a27797b9e9919afe191fbe2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 21 Dec 2024 11:37:30 -0800 Subject: [PATCH 288/326] Capitalise English --- osu.Game/Localisation/MenuTipStrings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/MenuTipStrings.cs b/osu.Game/Localisation/MenuTipStrings.cs index 3b40d7bff5..9d398e8e64 100644 --- a/osu.Game/Localisation/MenuTipStrings.cs +++ b/osu.Game/Localisation/MenuTipStrings.cs @@ -120,9 +120,9 @@ namespace osu.Game.Localisation public static LocalisableString AutoplayBeatmapShortcut => new TranslatableString(getKey(@"autoplay_beatmap_shortcut"), @"Ctrl-Enter at song select will start a beatmap in autoplay mode!"); /// - /// ""Lazer" is not an english word. The correct spelling for the bright light is "laser"." + /// ""Lazer" is not an English word. The correct spelling for the bright light is "laser"." /// - public static LocalisableString LazerIsNotAWord => new TranslatableString(getKey(@"lazer_is_not_a_word"), @"""Lazer"" is not an english word. The correct spelling for the bright light is ""laser""."); + public static LocalisableString LazerIsNotAWord => new TranslatableString(getKey(@"lazer_is_not_a_word"), @"""Lazer"" is not an English word. The correct spelling for the bright light is ""laser""."); /// /// "Multithreading support means that even with low "FPS" your input and judgements will be accurate!" From 431d57a8a11671d9fd787ea26a60c7ff414c9eac Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sat, 21 Dec 2024 17:22:07 -0500 Subject: [PATCH 289/326] Make "featured artist" beatmap listing filter persist in config --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ .../BeatmapListing/BeatmapSearchGeneralFilterRow.cs | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index df0a823648..deac1a5128 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -57,6 +57,7 @@ namespace osu.Game.Configuration SetDefault(OsuSetting.ChatDisplayHeight, ChatOverlay.DEFAULT_HEIGHT, 0.2f, 1f, 0.01f); SetDefault(OsuSetting.BeatmapListingCardSize, BeatmapCardSize.Normal); + SetDefault(OsuSetting.BeatmapListingFeaturedArtistFilter, true); SetDefault(OsuSetting.ProfileCoverExpanded, true); @@ -450,5 +451,6 @@ namespace osu.Game.Configuration EditorAdjustExistingObjectsOnTimingChanges, AlwaysRequireHoldingForPause, MultiplayerShowInProgressFilter, + BeatmapListingFeaturedArtistFilter, } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs index 34b7d45a77..c297e4305d 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs @@ -125,6 +125,9 @@ namespace osu.Game.Overlays.BeatmapListing [Resolved] private OsuColour colours { get; set; } = null!; + [Resolved] + private OsuConfigManager config { get; set; } = null!; + [Resolved] private SessionStatics sessionStatics { get; set; } = null!; @@ -135,7 +138,12 @@ namespace osu.Game.Overlays.BeatmapListing { base.LoadComplete(); + config.BindWith(OsuSetting.BeatmapListingFeaturedArtistFilter, Active); disclaimerShown = sessionStatics.GetBindable(Static.FeaturedArtistDisclaimerShownOnce); + + // no need to show the disclaimer if the user already had it toggled off in config. + if (!Active.Value) + disclaimerShown.Value = true; } protected override Color4 ColourNormal => colours.Orange1; From c24f690019fd1871a941abcbb5d70ca386387137 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 22 Dec 2024 07:47:57 -0500 Subject: [PATCH 290/326] Allow disabling filter items in beatmap listing overlay --- ...BeatmapSearchMultipleSelectionFilterRow.cs | 33 +++++++++++++++---- .../Overlays/BeatmapListing/FilterTabItem.cs | 2 ++ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index 958297b559..50e3c0e931 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -73,7 +73,12 @@ namespace osu.Game.Overlays.BeatmapListing private void currentChanged(object? sender, NotifyCollectionChangedEventArgs e) { foreach (var c in Children) - c.Active.Value = Current.Contains(c.Value); + { + if (!c.Active.Disabled) + c.Active.Value = Current.Contains(c.Value); + else if (c.Active.Value != Current.Contains(c.Value)) + throw new InvalidOperationException($"Expected filter {c.Value} to be set to {Current.Contains(c.Value)}, but was {c.Active.Value}"); + } } /// @@ -100,8 +105,9 @@ namespace osu.Game.Overlays.BeatmapListing protected partial class MultipleSelectionFilterTabItem : FilterTabItem { - private Drawable activeContent = null!; + private Container activeContent = null!; private Circle background = null!; + private SpriteIcon icon = null!; public MultipleSelectionFilterTabItem(T value) : base(value) @@ -123,7 +129,6 @@ namespace osu.Game.Overlays.BeatmapListing Alpha = 0, Padding = new MarginPadding { - Left = -16, Right = -4, Vertical = -2 }, @@ -134,8 +139,9 @@ namespace osu.Game.Overlays.BeatmapListing Colour = Color4.White, RelativeSizeAxes = Axes.Both, }, - new SpriteIcon + icon = new SpriteIcon { + Alpha = 0f, Icon = FontAwesome.Solid.TimesCircle, Size = new Vector2(10), Colour = ColourProvider.Background4, @@ -160,13 +166,26 @@ namespace osu.Game.Overlays.BeatmapListing { Color4 colour = Active.Value ? ColourActive : ColourNormal; - if (IsHovered) + if (!Enabled.Value) + colour = colour.Darken(1f); + else if (IsHovered) colour = Active.Value ? colour.Darken(0.2f) : colour.Lighten(0.2f); if (Active.Value) { - // This just allows enough spacing for adjacent tab items to show the "x". - Padding = new MarginPadding { Left = 12 }; + if (Enabled.Value) + { + // This just allows enough spacing for adjacent tab items to show the "x". + Padding = new MarginPadding { Left = 12 }; + activeContent.Padding = activeContent.Padding with { Left = -16 }; + icon.Show(); + } + else + { + Padding = new MarginPadding(); + activeContent.Padding = activeContent.Padding with { Left = -6 }; + icon.Hide(); + } activeContent.FadeIn(200, Easing.OutQuint); background.FadeColour(colour, 200, Easing.OutQuint); diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 8f4ecaa0f5..e357718103 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -57,7 +57,9 @@ namespace osu.Game.Overlays.BeatmapListing { base.LoadComplete(); + Enabled.BindValueChanged(_ => UpdateState()); UpdateState(); + FinishTransforms(true); } From 589e187a80b022b4ce20e265fb4e5af775b2369f Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 22 Dec 2024 07:50:08 -0500 Subject: [PATCH 291/326] Disable ability to toggle "featured artists" beatmap listing filter in iOS --- osu.Game/OsuGame.cs | 6 ++++++ .../BeatmapListing/BeatmapSearchGeneralFilterRow.cs | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 244b72edaa..36f7bcbb1e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -220,6 +220,12 @@ namespace osu.Game private readonly List visibleBlockingOverlays = new List(); + /// + /// Whether the game should be limited from providing access to download non-featured-artist beatmaps. + /// This only affects the "featured artists" filter in the beatmap listing overlay. + /// + public bool LimitedToFeaturedArtists => RuntimeInfo.OS == RuntimeInfo.Platform.iOS && true; + public OsuGame(string[] args = null) { this.args = args; diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs index c297e4305d..d7201d4df8 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs @@ -125,6 +125,9 @@ namespace osu.Game.Overlays.BeatmapListing [Resolved] private OsuColour colours { get; set; } = null!; + [Resolved] + private OsuGame game { get; set; } = null!; + [Resolved] private OsuConfigManager config { get; set; } = null!; @@ -144,6 +147,12 @@ namespace osu.Game.Overlays.BeatmapListing // no need to show the disclaimer if the user already had it toggled off in config. if (!Active.Value) disclaimerShown.Value = true; + + if (game.LimitedToFeaturedArtists) + { + Enabled.Value = false; + Active.Disabled = true; + } } protected override Color4 ColourNormal => colours.Orange1; @@ -151,6 +160,9 @@ namespace osu.Game.Overlays.BeatmapListing protected override bool OnClick(ClickEvent e) { + if (!Enabled.Value) + return true; + if (!disclaimerShown.Value && dialogOverlay != null) { dialogOverlay.Push(new FeaturedArtistConfirmDialog(() => From e716919a07599068556b3f07aab191c9c266bf8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 Dec 2024 22:57:17 +0900 Subject: [PATCH 292/326] Remove redundant `&& true` Co-authored-by: Susko3 --- 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 36f7bcbb1e..17ad67b733 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -224,7 +224,7 @@ namespace osu.Game /// Whether the game should be limited from providing access to download non-featured-artist beatmaps. /// This only affects the "featured artists" filter in the beatmap listing overlay. /// - public bool LimitedToFeaturedArtists => RuntimeInfo.OS == RuntimeInfo.Platform.iOS && true; + public bool LimitedToFeaturedArtists => RuntimeInfo.OS == RuntimeInfo.Platform.iOS; public OsuGame(string[] args = null) { From 0aed625bb8027bea06a98833904b2687c8619650 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 22 Dec 2024 23:58:35 +0900 Subject: [PATCH 293/326] Rename variable and adjust commentary --- osu.Game/OsuGame.cs | 5 ++--- .../Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 17ad67b733..3864c518d2 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -221,10 +221,9 @@ namespace osu.Game private readonly List visibleBlockingOverlays = new List(); /// - /// Whether the game should be limited from providing access to download non-featured-artist beatmaps. - /// This only affects the "featured artists" filter in the beatmap listing overlay. + /// Whether the game should be limited to only display licensed content. /// - public bool LimitedToFeaturedArtists => RuntimeInfo.OS == RuntimeInfo.Platform.iOS; + public bool HideUnlicensedContent => RuntimeInfo.OS == RuntimeInfo.Platform.iOS; public OsuGame(string[] args = null) { diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs index d7201d4df8..b525d8282e 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs @@ -148,7 +148,7 @@ namespace osu.Game.Overlays.BeatmapListing if (!Active.Value) disclaimerShown.Value = true; - if (game.LimitedToFeaturedArtists) + if (game.HideUnlicensedContent) { Enabled.Value = false; Active.Disabled = true; From fcfab9e53c5fdb98e38d84903120611d48fa439e Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Sun, 22 Dec 2024 10:14:52 -0500 Subject: [PATCH 294/326] Fix spacing --- .../BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index 50e3c0e931..27b630d623 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -183,7 +183,7 @@ namespace osu.Game.Overlays.BeatmapListing else { Padding = new MarginPadding(); - activeContent.Padding = activeContent.Padding with { Left = -6 }; + activeContent.Padding = activeContent.Padding with { Left = -4 }; icon.Hide(); } From 1a7feeb4edab01db1ab6c9fa5c501b69456a78da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Dec 2024 14:39:07 +0900 Subject: [PATCH 295/326] Use `virtual` property rather than inline iOS conditional --- osu.Game/OsuGame.cs | 4 ++-- osu.iOS/OsuGameIOS.cs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 3864c518d2..c5c6ef8cc7 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -221,9 +221,9 @@ namespace osu.Game private readonly List visibleBlockingOverlays = new List(); /// - /// Whether the game should be limited to only display licensed content. + /// Whether the game should be limited to only display officially licensed content. /// - public bool HideUnlicensedContent => RuntimeInfo.OS == RuntimeInfo.Platform.iOS; + public virtual bool HideUnlicensedContent => false; public OsuGame(string[] args = null) { diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index c0bd77366e..a9ca1778a0 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -17,6 +17,8 @@ namespace osu.iOS { public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString()); + public override bool HideUnlicensedContent => true; + protected override UpdateManager CreateUpdateManager() => new MobileUpdateNotifier(); protected override BatteryInfo CreateBatteryInfo() => new IOSBatteryInfo(); From f12fffd116eb3488586405de0177ed63e1fffa30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Dec 2024 14:43:36 +0900 Subject: [PATCH 296/326] Fix more than obvious test failure Please run tests please run tests please run tests. --- .../BeatmapListing/BeatmapSearchGeneralFilterRow.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs index b525d8282e..e4c663ee13 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs @@ -125,9 +125,6 @@ namespace osu.Game.Overlays.BeatmapListing [Resolved] private OsuColour colours { get; set; } = null!; - [Resolved] - private OsuGame game { get; set; } = null!; - [Resolved] private OsuConfigManager config { get; set; } = null!; @@ -137,6 +134,9 @@ namespace osu.Game.Overlays.BeatmapListing [Resolved] private IDialogOverlay? dialogOverlay { get; set; } + [Resolved] + private OsuGame? game { get; set; } + protected override void LoadComplete() { base.LoadComplete(); @@ -148,7 +148,7 @@ namespace osu.Game.Overlays.BeatmapListing if (!Active.Value) disclaimerShown.Value = true; - if (game.HideUnlicensedContent) + if (game?.HideUnlicensedContent == true) { Enabled.Value = false; Active.Disabled = true; From 097828ded208d872bf886579741fe72197781f01 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Dec 2024 22:07:42 +0900 Subject: [PATCH 297/326] Fix incorrect mouse wheel mappings --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index c343b4e1e6..35d2465084 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -142,10 +142,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.J }, GlobalAction.EditorFlipVertically), new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.EditorDecreaseDistanceSpacing), new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelUp }, GlobalAction.EditorIncreaseDistanceSpacing), - // Framework automatically converts wheel up/down to left/right when shift is held. - // See https://github.com/ppy/osu-framework/blob/master/osu.Framework/Input/StateChanges/MouseScrollRelativeInput.cs#L37-L38. - new KeyBinding(new[] { InputKey.Control, InputKey.MouseWheelRight }, GlobalAction.EditorCyclePreviousBeatSnapDivisor), - new KeyBinding(new[] { InputKey.Control, InputKey.MouseWheelLeft }, GlobalAction.EditorCycleNextBeatSnapDivisor), + new KeyBinding(new[] { InputKey.Control, InputKey.MouseWheelUp }, GlobalAction.EditorCyclePreviousBeatSnapDivisor), + new KeyBinding(new[] { InputKey.Control, InputKey.MouseWheelDown }, GlobalAction.EditorCycleNextBeatSnapDivisor), new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl), new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl), new KeyBinding(new[] { InputKey.Control, InputKey.Left }, GlobalAction.EditorSeekToPreviousHitObject), From 9ff4a58fa3724904c13f1117c14ab03824963dda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 23 Dec 2024 22:14:03 +0900 Subject: [PATCH 298/326] Add migration to update users which have previous default bindings for beat snap --- osu.Game/Database/RealmAccess.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index a520040ad1..e9fd82c4ff 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -96,7 +96,7 @@ namespace osu.Game.Database /// 43 2024-10-14 Reset keybind for toggling FPS display to avoid conflict with "convert to stream" in the editor, if not already changed by user. /// 44 2024-11-22 Removed several properties from BeatmapInfo which did not need to be persisted to realm. /// - private const int schema_version = 44; + private const int schema_version = 45; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. @@ -1205,6 +1205,22 @@ namespace osu.Game.Database break; } + + case 45: + { + // Cycling beat snap divisors no longer requires holding shift (just control). + var keyBindings = migration.NewRealm.All(); + + var nextBeatSnapBinding = keyBindings.FirstOrDefault(k => k.ActionInt == (int)GlobalAction.EditorCycleNextBeatSnapDivisor); + if (nextBeatSnapBinding != null && nextBeatSnapBinding.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Shift, InputKey.Control, InputKey.MouseWheelLeft })) + migration.NewRealm.Remove(nextBeatSnapBinding); + + var previousBeatSnapBinding = keyBindings.FirstOrDefault(k => k.ActionInt == (int)GlobalAction.EditorCyclePreviousBeatSnapDivisor); + if (previousBeatSnapBinding != null && previousBeatSnapBinding.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Shift, InputKey.Control, InputKey.MouseWheelRight })) + migration.NewRealm.Remove(previousBeatSnapBinding); + + break; + } } Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms"); From 050bf9ec6033b26a4a0cb6878738dc66346ba0b7 Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 23 Dec 2024 10:52:18 -0500 Subject: [PATCH 299/326] Keep 'x' symbol visible even while disabled --- ...BeatmapSearchMultipleSelectionFilterRow.cs | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index 27b630d623..b4940d3aa1 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -107,7 +107,6 @@ namespace osu.Game.Overlays.BeatmapListing { private Container activeContent = null!; private Circle background = null!; - private SpriteIcon icon = null!; public MultipleSelectionFilterTabItem(T value) : base(value) @@ -129,6 +128,7 @@ namespace osu.Game.Overlays.BeatmapListing Alpha = 0, Padding = new MarginPadding { + Left = -16, Right = -4, Vertical = -2 }, @@ -139,9 +139,8 @@ namespace osu.Game.Overlays.BeatmapListing Colour = Color4.White, RelativeSizeAxes = Axes.Both, }, - icon = new SpriteIcon + new SpriteIcon { - Alpha = 0f, Icon = FontAwesome.Solid.TimesCircle, Size = new Vector2(10), Colour = ColourProvider.Background4, @@ -173,19 +172,8 @@ namespace osu.Game.Overlays.BeatmapListing if (Active.Value) { - if (Enabled.Value) - { - // This just allows enough spacing for adjacent tab items to show the "x". - Padding = new MarginPadding { Left = 12 }; - activeContent.Padding = activeContent.Padding with { Left = -16 }; - icon.Show(); - } - else - { - Padding = new MarginPadding(); - activeContent.Padding = activeContent.Padding with { Left = -4 }; - icon.Hide(); - } + // This just allows enough spacing for adjacent tab items to show the "x". + Padding = new MarginPadding { Left = 12 }; activeContent.FadeIn(200, Easing.OutQuint); background.FadeColour(colour, 200, Easing.OutQuint); From 7e3477f4bbfaa9cb1c01dea68b320e7267c5bbda Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 23 Dec 2024 10:54:52 -0500 Subject: [PATCH 300/326] Remove unnecessary guarding --- .../BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index b4940d3aa1..73af62c322 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -76,8 +76,6 @@ namespace osu.Game.Overlays.BeatmapListing { if (!c.Active.Disabled) c.Active.Value = Current.Contains(c.Value); - else if (c.Active.Value != Current.Contains(c.Value)) - throw new InvalidOperationException($"Expected filter {c.Value} to be set to {Current.Contains(c.Value)}, but was {c.Active.Value}"); } } From 6b635d588f16af12bde4340640aee476197795fd Mon Sep 17 00:00:00 2001 From: Salman Alshamrani Date: Mon, 23 Dec 2024 10:59:06 -0500 Subject: [PATCH 301/326] Add tooltip --- osu.Game/Localisation/BeatmapOverlayStrings.cs | 5 +++++ .../Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Localisation/BeatmapOverlayStrings.cs b/osu.Game/Localisation/BeatmapOverlayStrings.cs index fc818f7596..43ffa17d93 100644 --- a/osu.Game/Localisation/BeatmapOverlayStrings.cs +++ b/osu.Game/Localisation/BeatmapOverlayStrings.cs @@ -28,6 +28,11 @@ This includes content that may not be correctly licensed for osu! usage. Browse /// public static LocalisableString UserContentConfirmButtonText => new TranslatableString(getKey(@"understood"), @"I understand"); + /// + /// "Toggling this filter is disabled in this platform." + /// + public static LocalisableString FeaturedArtistsDisabledTooltip => new TranslatableString(getKey(@"featured_artists_disabled_tooltip"), @"Toggling this filter is disabled in this platform."); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs index e4c663ee13..b9720f06e8 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; @@ -113,7 +114,7 @@ namespace osu.Game.Overlays.BeatmapListing } } - private partial class FeaturedArtistsTabItem : MultipleSelectionFilterTabItem + private partial class FeaturedArtistsTabItem : MultipleSelectionFilterTabItem, IHasTooltip { private Bindable disclaimerShown = null!; @@ -137,6 +138,8 @@ namespace osu.Game.Overlays.BeatmapListing [Resolved] private OsuGame? game { get; set; } + public LocalisableString TooltipText => !Enabled.Value ? BeatmapOverlayStrings.FeaturedArtistsDisabledTooltip : string.Empty; + protected override void LoadComplete() { base.LoadComplete(); From 7e8aaa68ff11082ff60a3c8b85d54e21444553a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Dec 2024 11:46:39 +0900 Subject: [PATCH 302/326] Add keywords for intro-related settings --- .../Settings/Sections/UserInterface/MainMenuSettings.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs index 5e42c3035c..c50d56b458 100644 --- a/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/UserInterface/MainMenuSettings.cs @@ -36,11 +36,13 @@ namespace osu.Game.Overlays.Settings.Sections.UserInterface }, new SettingsCheckbox { + Keywords = new[] { "intro", "welcome" }, LabelText = UserInterfaceStrings.InterfaceVoices, Current = config.GetBindable(OsuSetting.MenuVoice) }, new SettingsCheckbox { + Keywords = new[] { "intro", "welcome" }, LabelText = UserInterfaceStrings.OsuMusicTheme, Current = config.GetBindable(OsuSetting.MenuMusic) }, From 282c67d14bf5d4071beb64602d0c5d3420ea864a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Dec 2024 11:59:45 +0900 Subject: [PATCH 303/326] 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 fe3bdbffa3..51bed31afb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From 8762e3fedb5139a70b1914dbb5e797e865a1cd85 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Dec 2024 12:12:49 +0900 Subject: [PATCH 304/326] Always show tooltip, and reword to be always applicable --- osu.Game/Localisation/BeatmapOverlayStrings.cs | 4 ++-- .../Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Localisation/BeatmapOverlayStrings.cs b/osu.Game/Localisation/BeatmapOverlayStrings.cs index 43ffa17d93..f8122c1ef9 100644 --- a/osu.Game/Localisation/BeatmapOverlayStrings.cs +++ b/osu.Game/Localisation/BeatmapOverlayStrings.cs @@ -29,9 +29,9 @@ This includes content that may not be correctly licensed for osu! usage. Browse public static LocalisableString UserContentConfirmButtonText => new TranslatableString(getKey(@"understood"), @"I understand"); /// - /// "Toggling this filter is disabled in this platform." + /// "Featured Artists are music artists who have collaborated with osu! to make a selection of their tracks available for use in beatmaps. For some osu! releases, we showcase only featured artist beatmaps to better support the surrounding ecosystem." /// - public static LocalisableString FeaturedArtistsDisabledTooltip => new TranslatableString(getKey(@"featured_artists_disabled_tooltip"), @"Toggling this filter is disabled in this platform."); + public static LocalisableString FeaturedArtistsTooltip => new TranslatableString(getKey(@"featured_artists_disabled_tooltip"), @"Featured Artists are music artists who have collaborated with osu! to make a selection of their tracks available for use in beatmaps. For some osu! releases, we showcase only featured artist beatmaps to better support the surrounding ecosystem."); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs index b9720f06e8..b62836dfde 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs @@ -138,7 +138,7 @@ namespace osu.Game.Overlays.BeatmapListing [Resolved] private OsuGame? game { get; set; } - public LocalisableString TooltipText => !Enabled.Value ? BeatmapOverlayStrings.FeaturedArtistsDisabledTooltip : string.Empty; + public LocalisableString TooltipText => BeatmapOverlayStrings.FeaturedArtistsTooltip; protected override void LoadComplete() { From ae9c7e1b354c43fc606a75031514ea56ec648723 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Dec 2024 13:17:58 +0900 Subject: [PATCH 305/326] Adjust layout and remove localisable strings for temporary buttons --- osu.Game/Localisation/SkinSettingsStrings.cs | 15 -- .../Overlays/Settings/Sections/SkinSection.cs | 150 +++++++++--------- 2 files changed, 76 insertions(+), 89 deletions(-) diff --git a/osu.Game/Localisation/SkinSettingsStrings.cs b/osu.Game/Localisation/SkinSettingsStrings.cs index 1a812ad04d..16dca7fd87 100644 --- a/osu.Game/Localisation/SkinSettingsStrings.cs +++ b/osu.Game/Localisation/SkinSettingsStrings.cs @@ -54,21 +54,6 @@ namespace osu.Game.Localisation /// public static LocalisableString BeatmapHitsounds => new TranslatableString(getKey(@"beatmap_hitsounds"), @"Beatmap hitsounds"); - /// - /// "Rename selected skin" - /// - public static LocalisableString RenameSkinButton = new TranslatableString(getKey(@"rename_skin_button"), @"Rename selected skin"); - - /// - /// "Export selected skin" - /// - public static LocalisableString ExportSkinButton => new TranslatableString(getKey(@"export_skin_button"), @"Export selected skin"); - - /// - /// "Delete selected skin" - /// - public static LocalisableString DeleteSkinButton => new TranslatableString(getKey(@"delete_skin_button"), @"Delete selected skin"); - private static string getKey(string key) => $"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 1792c61d48..7fffd3693c 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -75,9 +75,21 @@ namespace osu.Game.Overlays.Settings.Sections Text = SkinSettingsStrings.SkinLayoutEditor, Action = () => skinEditor?.ToggleVisibility(), }, - new RenameSkinButton(), - new ExportSkinButton(), - new DeleteSkinButton(), + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Padding = new MarginPadding { Left = SettingsPanel.CONTENT_MARGINS, Right = SettingsPanel.CONTENT_MARGINS }, + Children = new Drawable[] + { + // This is all super-temporary until we move skin settings to their own panel / overlay. + new RenameSkinButton { Padding = new MarginPadding(), RelativeSizeAxes = Axes.None, Width = 120 }, + new ExportSkinButton { Padding = new MarginPadding(), RelativeSizeAxes = Axes.None, Width = 120 }, + new DeleteSkinButton { Padding = new MarginPadding(), RelativeSizeAxes = Axes.None, Width = 110 }, + } + }, }; } @@ -153,7 +165,7 @@ namespace osu.Game.Overlays.Settings.Sections [BackgroundDependencyLoader] private void load() { - Text = SkinSettingsStrings.RenameSkinButton; + Text = "Rename"; Action = this.ShowPopover; } @@ -169,74 +181,6 @@ namespace osu.Game.Overlays.Settings.Sections { return new RenameSkinPopover(); } - - public partial class RenameSkinPopover : OsuPopover - { - [Resolved] - private SkinManager skins { get; set; } - - public Action Rename { get; init; } - - private readonly FocusedTextBox textBox; - - public RenameSkinPopover() - { - AutoSizeAxes = Axes.Both; - Origin = Anchor.TopCentre; - - RoundedButton renameButton; - - Child = new FillFlowContainer - { - Direction = FillDirection.Vertical, - AutoSizeAxes = Axes.Y, - Width = 250, - Spacing = new Vector2(10f), - Children = new Drawable[] - { - textBox = new FocusedTextBox - { - PlaceholderText = @"Skin name", - FontSize = OsuFont.DEFAULT_FONT_SIZE, - RelativeSizeAxes = Axes.X, - SelectAllOnFocus = true, - }, - renameButton = new RoundedButton - { - Height = 40, - RelativeSizeAxes = Axes.X, - MatchingFilter = true, - Text = SkinSettingsStrings.RenameSkinButton, - } - } - }; - - renameButton.Action += rename; - - void onTextboxCommit(TextBox sender, bool newText) - { - rename(); - } - - textBox.OnCommit += onTextboxCommit; - } - - protected override void PopIn() - { - textBox.Text = skins.CurrentSkinInfo.Value.Value.Name; - textBox.TakeFocus(); - base.PopIn(); - } - - private void rename() - { - skins.CurrentSkinInfo.Value.PerformWrite(skin => - { - skin.Name = textBox.Text; - PopOut(); - }); - } - } } public partial class ExportSkinButton : SettingsButton @@ -249,7 +193,7 @@ namespace osu.Game.Overlays.Settings.Sections [BackgroundDependencyLoader] private void load() { - Text = SkinSettingsStrings.ExportSkinButton; + Text = "Export"; Action = export; } @@ -287,7 +231,7 @@ namespace osu.Game.Overlays.Settings.Sections [BackgroundDependencyLoader] private void load() { - Text = SkinSettingsStrings.DeleteSkinButton; + Text = "Delete"; Action = delete; } @@ -304,5 +248,63 @@ namespace osu.Game.Overlays.Settings.Sections dialogOverlay?.Push(new SkinDeleteDialog(currentSkin.Value)); } } + + public partial class RenameSkinPopover : OsuPopover + { + [Resolved] + private SkinManager skins { get; set; } + + private readonly FocusedTextBox textBox; + + public RenameSkinPopover() + { + AutoSizeAxes = Axes.Both; + Origin = Anchor.TopCentre; + + RoundedButton renameButton; + + Child = new FillFlowContainer + { + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Y, + Width = 250, + Spacing = new Vector2(10f), + Children = new Drawable[] + { + textBox = new FocusedTextBox + { + PlaceholderText = @"Skin name", + FontSize = OsuFont.DEFAULT_FONT_SIZE, + RelativeSizeAxes = Axes.X, + SelectAllOnFocus = true, + }, + renameButton = new RoundedButton + { + Height = 40, + RelativeSizeAxes = Axes.X, + MatchingFilter = true, + Text = "Save", + } + } + }; + + renameButton.Action += rename; + textBox.OnCommit += (_, _) => rename(); + } + + protected override void PopIn() + { + textBox.Text = skins.CurrentSkinInfo.Value.Value.Name; + textBox.TakeFocus(); + + base.PopIn(); + } + + private void rename() => skins.CurrentSkinInfo.Value.PerformWrite(skin => + { + skin.Name = textBox.Text; + PopOut(); + }); + } } } From 378bef34efab9980bbb6de9d62726a3349ae3a6c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Dec 2024 13:42:18 +0900 Subject: [PATCH 306/326] Change order of skin layout editor button for better visual balance --- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 7fffd3693c..a89d5e2f4a 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -70,11 +70,6 @@ namespace osu.Game.Overlays.Settings.Sections Current = skins.CurrentSkinInfo, Keywords = new[] { @"skins" }, }, - new SettingsButton - { - Text = SkinSettingsStrings.SkinLayoutEditor, - Action = () => skinEditor?.ToggleVisibility(), - }, new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -90,6 +85,11 @@ namespace osu.Game.Overlays.Settings.Sections new DeleteSkinButton { Padding = new MarginPadding(), RelativeSizeAxes = Axes.None, Width = 110 }, } }, + new SettingsButton + { + Text = SkinSettingsStrings.SkinLayoutEditor, + Action = () => skinEditor?.ToggleVisibility(), + }, }; } From a5d354d753302c318ade8cb56fbe1d884e20942a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Dec 2024 15:17:10 +0900 Subject: [PATCH 307/326] 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 f13760bd21..84827ce76b 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 3e618a3a74..349d6fa1d7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,6 +17,6 @@ -all - + From b8d6bba03924ed96328d04e6c9ce7fe5041afa59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Dec 2024 16:05:44 +0900 Subject: [PATCH 308/326] Fix legacy hitcircle fallback logic being broken with recent fix I was a bit too eager to replace all calls with the new `provider` in https://github.com/ppy/osu/commit/dae380b7fa927c351e2e413c5b23834f717908d9, while it doesn't actually make sense. To handle the case that was trying to be fixed, using the `provider` to check whether the *prefix* version of the circle sprite is available is enough alone. Closes https://github.com/ppy/osu/issues/31200 --- .../Skinning/Legacy/LegacyMainCirclePiece.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index 0dc0f065d4..e74ffaac0c 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -61,13 +61,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy var drawableOsuObject = (DrawableOsuHitObject?)drawableObject; - // As a precondition, ensure that any prefix lookups are run against the skin which is providing "hitcircle". + // As a precondition, prefer that any *prefix* lookups are run against the skin which is providing "hitcircle". // This is to correctly handle a case such as: // // - Beatmap provides `hitcircle` // - User skin provides `sliderstartcircle` // // In such a case, the `hitcircle` should be used for slider start circles rather than the user's skin override. + // + // Of note, this consideration should only be used to decide whether to continue looking up the prefixed name or not. + // The final lookups must still run on the full skin hierarchy as per usual in order to correctly handle fallback cases. var provider = skin.FindProvider(s => s.GetTexture(base_lookup) != null) ?? skin; // if a base texture for the specified prefix exists, continue using it for subsequent lookups. @@ -81,7 +84,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 = provider.GetTexture(circleName)?.WithMaximumSize(maxSize) }) + CircleSprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(circleName)?.WithMaximumSize(maxSize) }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -90,7 +93,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = provider.GetTexture(@$"{circleName}overlay")?.WithMaximumSize(maxSize) }) + Child = OverlaySprite = new LegacyKiaiFlashingDrawable(() => new Sprite { Texture = skin.GetTexture(@$"{circleName}overlay")?.WithMaximumSize(maxSize) }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, From d9be172647c81972247075c5eae14608ace9f99d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 24 Dec 2024 08:17:25 +0100 Subject: [PATCH 309/326] Add explanatory comment for schema version bump --- osu.Game/Database/RealmAccess.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index e9fd82c4ff..b412348595 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -95,6 +95,7 @@ namespace osu.Game.Database /// 42 2024-08-07 Update mania key bindings to reflect changes to ManiaAction /// 43 2024-10-14 Reset keybind for toggling FPS display to avoid conflict with "convert to stream" in the editor, if not already changed by user. /// 44 2024-11-22 Removed several properties from BeatmapInfo which did not need to be persisted to realm. + /// 45 2024-12-23 Change beat snap divisor adjust defaults to be Ctrl+Scroll instead of Ctrl+Shift+Scroll, if not already changed by user. /// private const int schema_version = 45; From d8686f55f7178bbdbee3d85a60c3f3e5c36431c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Dec 2024 17:10:48 +0900 Subject: [PATCH 310/326] Slightly reduce background brightness at main menu when seasonal lighting is active --- osu.Game/Screens/Menu/MainMenu.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index a5acc6a1c2..99bc1825f5 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -202,18 +202,20 @@ namespace osu.Game.Screens.Menu holdToExitGameOverlay?.CreateProxy() ?? Empty() }); + float baseDim = SeasonalUIConfig.ENABLED ? 0.84f : 1; + Buttons.StateChanged += state => { switch (state) { case ButtonSystemState.Initial: case ButtonSystemState.Exit: - ApplyToBackground(b => b.FadeColour(Color4.White, 500, Easing.OutSine)); + ApplyToBackground(b => b.FadeColour(OsuColour.Gray(baseDim), 500, Easing.OutSine)); onlineMenuBanner.State.Value = Visibility.Hidden; break; default: - ApplyToBackground(b => b.FadeColour(OsuColour.Gray(0.8f), 500, Easing.OutSine)); + ApplyToBackground(b => b.FadeColour(OsuColour.Gray(baseDim * 0.8f), 500, Easing.OutSine)); onlineMenuBanner.State.Value = Visibility.Visible; break; } From ce1eda7e54516921bc25d1a3ed6ee0c7e307ade9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 24 Dec 2024 17:11:21 +0900 Subject: [PATCH 311/326] Fix adjusting volume using scroll wheel not working during intro --- osu.Game/Screens/Menu/IntroScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 9885c061a9..c110c53df8 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -24,6 +24,7 @@ using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; +using osu.Game.Overlays.Volume; using osu.Game.Rulesets; using osu.Game.Screens.Backgrounds; using osu.Game.Skinning; @@ -174,6 +175,8 @@ namespace osu.Game.Screens.Menu return UsingThemedIntro = initialBeatmap != null; } + + AddInternal(new GlobalScrollAdjustsVolume()); } public override void OnEntering(ScreenTransitionEvent e) From 1f60adbaf144ab77dbc211f14c1a2ede46e6bf74 Mon Sep 17 00:00:00 2001 From: kongehund <63306696+kongehund@users.noreply.github.com> Date: Thu, 26 Dec 2024 00:35:21 +0100 Subject: [PATCH 312/326] Switch scroll direction for beat snap Matches stable better --- 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 35d2465084..2666b24be9 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -142,8 +142,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.J }, GlobalAction.EditorFlipVertically), new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.EditorDecreaseDistanceSpacing), new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelUp }, GlobalAction.EditorIncreaseDistanceSpacing), - new KeyBinding(new[] { InputKey.Control, InputKey.MouseWheelUp }, GlobalAction.EditorCyclePreviousBeatSnapDivisor), - new KeyBinding(new[] { InputKey.Control, InputKey.MouseWheelDown }, GlobalAction.EditorCycleNextBeatSnapDivisor), + new KeyBinding(new[] { InputKey.Control, InputKey.MouseWheelDown }, GlobalAction.EditorCyclePreviousBeatSnapDivisor), + new KeyBinding(new[] { InputKey.Control, InputKey.MouseWheelUp }, GlobalAction.EditorCycleNextBeatSnapDivisor), new KeyBinding(new[] { InputKey.Control, InputKey.R }, GlobalAction.EditorToggleRotateControl), new KeyBinding(new[] { InputKey.Control, InputKey.E }, GlobalAction.EditorToggleScaleControl), new KeyBinding(new[] { InputKey.Control, InputKey.Left }, GlobalAction.EditorSeekToPreviousHitObject), From e752531aec5dea9401b55afc312c8f625673dba6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Dec 2024 15:05:59 +0900 Subject: [PATCH 313/326] Fix volume adjust key repeat not working as expected Regressed in https://github.com/ppy/osu/pull/31146. Closes part of https://github.com/ppy/osu/issues/31267. --- osu.Game/OsuGame.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 06e30e3fab..6812cd87cf 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1428,9 +1428,18 @@ namespace osu.Game public bool OnPressed(KeyBindingPressEvent e) { + switch (e.Action) + { + case GlobalAction.DecreaseVolume: + case GlobalAction.IncreaseVolume: + return volume.Adjust(e.Action); + } + + // All actions below this point don't allow key repeat. if (e.Repeat) return false; + // Wait until we're loaded at least to the intro before allowing various interactions. if (introScreen == null) return false; switch (e.Action) @@ -1442,10 +1451,6 @@ namespace osu.Game case GlobalAction.ToggleMute: case GlobalAction.NextVolumeMeter: case GlobalAction.PreviousVolumeMeter: - - if (e.Repeat) - return true; - return volume.Adjust(e.Action); case GlobalAction.ToggleFPSDisplay: From 2a374c06958d7a2ac0640e8dd506d91f236bbf17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 26 Dec 2024 15:42:34 +0900 Subject: [PATCH 314/326] Add migration --- osu.Game/Database/RealmAccess.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index b412348595..e1b8de89fa 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -96,8 +96,9 @@ namespace osu.Game.Database /// 43 2024-10-14 Reset keybind for toggling FPS display to avoid conflict with "convert to stream" in the editor, if not already changed by user. /// 44 2024-11-22 Removed several properties from BeatmapInfo which did not need to be persisted to realm. /// 45 2024-12-23 Change beat snap divisor adjust defaults to be Ctrl+Scroll instead of Ctrl+Shift+Scroll, if not already changed by user. + /// 46 2024-12-26 Change beat snap divisor bindings to match stable directionality ¯\_(ツ)_/¯. /// - private const int schema_version = 45; + private const int schema_version = 46; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. @@ -1222,6 +1223,22 @@ namespace osu.Game.Database break; } + + case 46: + { + // Stable direction didn't match. + var keyBindings = migration.NewRealm.All(); + + var nextBeatSnapBinding = keyBindings.FirstOrDefault(k => k.ActionInt == (int)GlobalAction.EditorCycleNextBeatSnapDivisor); + if (nextBeatSnapBinding != null && nextBeatSnapBinding.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Control, InputKey.MouseWheelDown })) + migration.NewRealm.Remove(nextBeatSnapBinding); + + var previousBeatSnapBinding = keyBindings.FirstOrDefault(k => k.ActionInt == (int)GlobalAction.EditorCyclePreviousBeatSnapDivisor); + if (previousBeatSnapBinding != null && previousBeatSnapBinding.KeyCombination.Keys.SequenceEqual(new[] { InputKey.Control, InputKey.MouseWheelUp })) + migration.NewRealm.Remove(previousBeatSnapBinding); + + break; + } } Logger.Log($"Migration completed in {stopwatch.ElapsedMilliseconds}ms"); From 94d56d3584c8c1021e11d00a71469d90bc4991b6 Mon Sep 17 00:00:00 2001 From: StanR Date: Thu, 26 Dec 2024 18:13:09 +0500 Subject: [PATCH 315/326] Change `OsuModRelax` hit leniency to be the same as in stable --- .../Mods/TestSceneOsuModRelax.cs | 100 ++++++++++++++++++ osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 4 +- 2 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRelax.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRelax.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRelax.cs new file mode 100644 index 0000000000..1bb2f24c1c --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRelax.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.Collections.Generic; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Replays; +using osu.Game.Rulesets.Mods; +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.Osu.Scoring; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public partial class TestSceneOsuModRelax : OsuModTestScene + { + private readonly HitCircle hitObject; + private readonly HitWindows hitWindows = new OsuHitWindows(); + + public TestSceneOsuModRelax() + { + hitWindows.SetDifficulty(9); + + hitObject = new HitCircle + { + StartTime = 1000, + Position = new Vector2(100, 100), + HitWindows = hitWindows + }; + } + + protected override TestPlayer CreateModPlayer(Ruleset ruleset) => new ModRelaxTestPlayer(CurrentTestData, AllowFail); + + [Test] + public void TestRelax() => CreateModTest(new ModTestData + { + Mod = new OsuModRelax(), + Autoplay = false, + CreateBeatmap = () => new Beatmap + { + HitObjects = new List { hitObject } + }, + ReplayFrames = new List + { + new OsuReplayFrame(0, new Vector2()), + new OsuReplayFrame(hitObject.StartTime, hitObject.Position), + }, + PassCondition = () => Player.ScoreProcessor.Combo.Value == 1 + }); + + [Test] + public void TestRelaxLeniency() => CreateModTest(new ModTestData + { + Mod = new OsuModRelax(), + Autoplay = false, + CreateBeatmap = () => new Beatmap + { + HitObjects = new List { hitObject } + }, + ReplayFrames = new List + { + new OsuReplayFrame(0, new Vector2(hitObject.X - 22, hitObject.Y - 22)), // must be an edge hit for the cursor to not stay on the object for too long + new OsuReplayFrame(hitObject.StartTime - OsuModRelax.RELAX_LENIENCY, new Vector2(hitObject.X - 22, hitObject.Y - 22)), + new OsuReplayFrame(hitObject.StartTime, new Vector2(0)), + }, + PassCondition = () => Player.ScoreProcessor.Combo.Value == 1 + }); + + protected partial class ModRelaxTestPlayer : ModTestPlayer + { + private readonly ModTestData currentTestData; + + public ModRelaxTestPlayer(ModTestData data, bool allowFail) + : base(data, allowFail) + { + currentTestData = data; + } + + protected override void PrepareReplay() + { + // We need to set IsLegacyScore to true otherwise the mod assumes that presses are already embedded into the replay + DrawableRuleset?.SetReplayScore(new Score + { + Replay = new Replay { Frames = currentTestData.ReplayFrames! }, + ScoreInfo = new ScoreInfo { User = new APIUser { Username = @"Test" }, IsLegacyScore = true, Mods = new Mod[] { new OsuModRelax() } }, + }); + + DrawableRuleset?.SetRecordTarget(Score); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 31511c01b8..71de3c269b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// /// How early before a hitobject's start time to trigger a hit. /// - private const float relax_leniency = 3; + public const float RELAX_LENIENCY = 12; private bool isDownState; private bool wasLeft; @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.Mods foreach (var h in playfield.HitObjectContainer.AliveObjects.OfType()) { // we are not yet close enough to the object. - if (time < h.HitObject.StartTime - relax_leniency) + if (time < h.HitObject.StartTime - RELAX_LENIENCY) break; // already hit or beyond the hittable end time. From ed397c8feef6a49d5df7eb3ae977791dbc351551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Dec 2024 09:02:59 +0100 Subject: [PATCH 316/326] Add failing assertions --- osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index 23efb40d3f..765ffb4549 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -177,6 +177,7 @@ namespace osu.Game.Tests.Visual.Editing // bit of a hack to ensure this test can be ran multiple times without running into UNIQUE constraint failures AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = Guid.NewGuid().ToString()); + AddStep("start playing track", () => InputManager.Key(Key.Space)); AddStep("click test gameplay button", () => { var button = Editor.ChildrenOfType().Single(); @@ -185,11 +186,13 @@ namespace osu.Game.Tests.Visual.Editing InputManager.Click(MouseButton.Left); }); AddUntilStep("save prompt shown", () => DialogOverlay.CurrentDialog is SaveRequiredPopupDialog); + AddAssert("track stopped", () => !Beatmap.Value.Track.IsRunning); AddStep("save changes", () => DialogOverlay.CurrentDialog!.PerformOkAction()); EditorPlayer editorPlayer = null; AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null); + AddUntilStep("track playing", () => Beatmap.Value.Track.IsRunning); AddAssert("beatmap has 1 object", () => editorPlayer.Beatmap.Value.Beatmap.HitObjects.Count == 1); AddUntilStep("wait for return to editor", () => Stack.CurrentScreen is Editor); From 5abad0741265097cfaa53eceb375a0540d7a4aab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Dec 2024 09:08:16 +0100 Subject: [PATCH 317/326] Pause playback when entering gameplay test from editor Closes https://github.com/ppy/osu/issues/31290. Tend to agree that this is a good idea for gameplay test at least. Not sure about other similar interactions like exiting - I don't think it matters what's done in those cases, because for exiting timing is in no way key, so I just applied this locally to gameplay test. --- osu.Game/Screens/Edit/Editor.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d031eb84c6..f6875a7aa4 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -523,6 +523,8 @@ namespace osu.Game.Screens.Edit public void TestGameplay() { + clock.Stop(); + if (HasUnsavedChanges) { dialogOverlay.Push(new SaveRequiredPopupDialog(() => attemptMutationOperation(() => From a9a5bb2c6a172bd8dcd4d2f84bc425e903a47231 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 27 Dec 2024 21:36:07 +0900 Subject: [PATCH 318/326] Remove duplicated block --- osu.Game/OsuGame.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 6812cd87cf..c20536a1ec 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1444,10 +1444,6 @@ namespace osu.Game switch (e.Action) { - case GlobalAction.DecreaseVolume: - case GlobalAction.IncreaseVolume: - return volume.Adjust(e.Action); - case GlobalAction.ToggleMute: case GlobalAction.NextVolumeMeter: case GlobalAction.PreviousVolumeMeter: From 6a6db5a22bb355130ccb189e3540320573e7f29b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 27 Dec 2024 15:07:24 +0100 Subject: [PATCH 319/326] Populate metadata from ID3 tags when changing beatmap audio track in editor - Closes https://github.com/ppy/osu/issues/21189 - Supersedes / closes https://github.com/ppy/osu-framework/pull/5627 - Supersedes / closes https://github.com/ppy/osu/pull/22235 The reason why I opted for a complete rewrite rather than a revival of that aforementioned pull series is that it always felt quite gross to me to be pulling framework's audio subsystem into the task of reading ID3 tags, and I also partially don't believe that BASS is *good* at reading ID3 tags. Meanwhile, we already have another library pulled in that is *explicitly* intended for reading multimedia metadata, and using it does not require framework changes. (And it was pulled in explicitly for use in the editor verify tab as well.) The hard and dumb part of this diff is hacking the gibson such that the metadata section on setup screen actually *updates itself* after the resources section is done doing its thing. After significant gnashing of teeth I just did the bare minimum to make work by caching a common parent and exposing an `Action?` on it. If anyone has better ideas, I'm all ears. --- .../Screens/Edit/Setup/MetadataSection.cs | 53 ++++++++++++------- .../Screens/Edit/Setup/ResourcesSection.cs | 36 ++++++++++--- osu.Game/Screens/Edit/Setup/SetupScreen.cs | 4 ++ 3 files changed, 67 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index 20c0a74d84..6926b6631f 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -28,33 +28,29 @@ namespace osu.Game.Screens.Edit.Setup public override LocalisableString Title => EditorSetupStrings.MetadataHeader; [BackgroundDependencyLoader] - private void load() + private void load(SetupScreen setupScreen) { - var metadata = Beatmap.Metadata; - Children = new[] { - ArtistTextBox = createTextBox(EditorSetupStrings.Artist, - !string.IsNullOrEmpty(metadata.ArtistUnicode) ? metadata.ArtistUnicode : metadata.Artist), - RomanisedArtistTextBox = createTextBox(EditorSetupStrings.RomanisedArtist, - !string.IsNullOrEmpty(metadata.Artist) ? metadata.Artist : MetadataUtils.StripNonRomanisedCharacters(metadata.ArtistUnicode)), - TitleTextBox = createTextBox(EditorSetupStrings.Title, - !string.IsNullOrEmpty(metadata.TitleUnicode) ? metadata.TitleUnicode : metadata.Title), - RomanisedTitleTextBox = createTextBox(EditorSetupStrings.RomanisedTitle, - !string.IsNullOrEmpty(metadata.Title) ? metadata.Title : MetadataUtils.StripNonRomanisedCharacters(metadata.ArtistUnicode)), - creatorTextBox = createTextBox(EditorSetupStrings.Creator, metadata.Author.Username), - difficultyTextBox = createTextBox(EditorSetupStrings.DifficultyName, Beatmap.BeatmapInfo.DifficultyName), - sourceTextBox = createTextBox(BeatmapsetsStrings.ShowInfoSource, metadata.Source), - tagsTextBox = createTextBox(BeatmapsetsStrings.ShowInfoTags, metadata.Tags) + ArtistTextBox = createTextBox(EditorSetupStrings.Artist), + RomanisedArtistTextBox = createTextBox(EditorSetupStrings.RomanisedArtist), + TitleTextBox = createTextBox(EditorSetupStrings.Title), + RomanisedTitleTextBox = createTextBox(EditorSetupStrings.RomanisedTitle), + creatorTextBox = createTextBox(EditorSetupStrings.Creator), + difficultyTextBox = createTextBox(EditorSetupStrings.DifficultyName), + sourceTextBox = createTextBox(BeatmapsetsStrings.ShowInfoSource), + tagsTextBox = createTextBox(BeatmapsetsStrings.ShowInfoTags) }; + + setupScreen.MetadataChanged += reloadMetadata; + reloadMetadata(); } - private TTextBox createTextBox(LocalisableString label, string initialValue) + private TTextBox createTextBox(LocalisableString label) where TTextBox : FormTextBox, new() => new TTextBox { Caption = label, - Current = { Value = initialValue }, TabbableContentContainer = this }; @@ -94,10 +90,29 @@ 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. - updateMetadata(); + setMetadata(); } - private void updateMetadata() + private void reloadMetadata() + { + var metadata = Beatmap.Metadata; + + RomanisedArtistTextBox.ReadOnly = false; + RomanisedTitleTextBox.ReadOnly = false; + + ArtistTextBox.Current.Value = !string.IsNullOrEmpty(metadata.ArtistUnicode) ? metadata.ArtistUnicode : metadata.Artist; + RomanisedArtistTextBox.Current.Value = !string.IsNullOrEmpty(metadata.Artist) ? metadata.Artist : MetadataUtils.StripNonRomanisedCharacters(metadata.ArtistUnicode); + TitleTextBox.Current.Value = !string.IsNullOrEmpty(metadata.TitleUnicode) ? metadata.TitleUnicode : metadata.Title; + RomanisedTitleTextBox.Current.Value = !string.IsNullOrEmpty(metadata.Title) ? metadata.Title : MetadataUtils.StripNonRomanisedCharacters(metadata.ArtistUnicode); + creatorTextBox.Current.Value = metadata.Author.Username; + difficultyTextBox.Current.Value = Beatmap.BeatmapInfo.DifficultyName; + sourceTextBox.Current.Value = metadata.Source; + tagsTextBox.Current.Value = metadata.Tags; + + updateReadOnlyState(); + } + + private void setMetadata() { Beatmap.Metadata.ArtistUnicode = ArtistTextBox.Current.Value; Beatmap.Metadata.Artist = RomanisedArtistTextBox.Current.Value; diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 7fcd09d7e7..5bc95dd824 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -35,6 +35,9 @@ namespace osu.Game.Screens.Edit.Setup [Resolved] private Editor? editor { get; set; } + [Resolved] + private SetupScreen setupScreen { get; set; } = null!; + private SetupScreenHeaderBackground headerBackground = null!; [BackgroundDependencyLoader] @@ -93,15 +96,37 @@ namespace osu.Game.Screens.Edit.Setup if (!source.Exists) return false; + var tagSource = TagLib.File.Create(source.FullName); + changeResource(source, applyToAllDifficulties, @"audio", metadata => metadata.AudioFile, - (metadata, name) => metadata.AudioFile = name); + (metadata, name) => + { + metadata.AudioFile = name; + + string artist = tagSource.Tag.JoinedAlbumArtists; + + if (!string.IsNullOrWhiteSpace(artist)) + { + metadata.ArtistUnicode = artist; + metadata.Artist = MetadataUtils.StripNonRomanisedCharacters(metadata.ArtistUnicode); + } + + string title = tagSource.Tag.Title; + + if (!string.IsNullOrEmpty(title)) + { + metadata.TitleUnicode = title; + metadata.Title = MetadataUtils.StripNonRomanisedCharacters(metadata.TitleUnicode); + } + }); music.ReloadCurrentTrack(); + setupScreen.MetadataChanged?.Invoke(); return true; } - private void changeResource(FileInfo source, bool applyToAllDifficulties, string baseFilename, Func readFilename, Action writeFilename) + private void changeResource(FileInfo source, bool applyToAllDifficulties, string baseFilename, Func readFilename, Action writeMetadata) { var set = working.Value.BeatmapSetInfo; var beatmap = working.Value.BeatmapInfo; @@ -148,10 +173,7 @@ namespace osu.Game.Screens.Edit.Setup { foreach (var b in otherBeatmaps) { - // This operation is quite expensive, so only perform it if required. - if (readFilename(b.Metadata) == newFilename) continue; - - writeFilename(b.Metadata, newFilename); + writeMetadata(b.Metadata, newFilename); // save the difficulty to re-encode the .osu file, updating any reference of the old filename. // @@ -162,7 +184,7 @@ namespace osu.Game.Screens.Edit.Setup } } - writeFilename(beatmap.Metadata, newFilename); + writeMetadata(beatmap.Metadata, newFilename); // editor change handler cannot be aware of any file changes or other difficulties having their metadata modified. // for simplicity's sake, trigger a save when changing any resource to ensure the change is correctly saved. diff --git a/osu.Game/Screens/Edit/Setup/SetupScreen.cs b/osu.Game/Screens/Edit/Setup/SetupScreen.cs index f8c4998263..97e12ae096 100644 --- a/osu.Game/Screens/Edit/Setup/SetupScreen.cs +++ b/osu.Game/Screens/Edit/Setup/SetupScreen.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.Graphics; @@ -13,12 +14,15 @@ using osuTK; namespace osu.Game.Screens.Edit.Setup { + [Cached] public partial class SetupScreen : EditorScreen { public const float COLUMN_WIDTH = 450; public const float SPACING = 28; public const float MAX_WIDTH = 2 * COLUMN_WIDTH + SPACING; + public Action? MetadataChanged { get; set; } + public SetupScreen() : base(EditorScreenMode.SongSetup) { From 1b2a223a5f5c3cc3523d0b7446cd2a1cea04e510 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 28 Dec 2024 01:02:15 +0900 Subject: [PATCH 320/326] Fix failing test scene due to new dependency --- osu.Game/Screens/Edit/Setup/MetadataSection.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/MetadataSection.cs b/osu.Game/Screens/Edit/Setup/MetadataSection.cs index 6926b6631f..7b74aa7642 100644 --- a/osu.Game/Screens/Edit/Setup/MetadataSection.cs +++ b/osu.Game/Screens/Edit/Setup/MetadataSection.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Edit.Setup public override LocalisableString Title => EditorSetupStrings.MetadataHeader; [BackgroundDependencyLoader] - private void load(SetupScreen setupScreen) + private void load(SetupScreen? setupScreen) { Children = new[] { @@ -42,7 +42,9 @@ namespace osu.Game.Screens.Edit.Setup tagsTextBox = createTextBox(BeatmapsetsStrings.ShowInfoTags) }; - setupScreen.MetadataChanged += reloadMetadata; + if (setupScreen != null) + setupScreen.MetadataChanged += reloadMetadata; + reloadMetadata(); } From aa67f87fe95af769c66e5329b30212d07b8e3ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Dec 2024 09:42:24 +0100 Subject: [PATCH 321/326] Add failing test coverage --- .../Editor/TestSceneOsuComposerSelection.cs | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs index 345965b912..5aa7d6865f 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs @@ -10,6 +10,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; @@ -261,6 +262,90 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddAssert("selection preserved", () => EditorBeatmap.SelectedHitObjects.Count, () => Is.EqualTo(1)); } + [Test] + public void TestQuickDeleteOnUnselectedControlPointOnlyRemovesThatControlPoint() + { + var slider = new Slider + { + StartTime = 0, + Position = new Vector2(100, 100), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint { Type = PathType.LINEAR }, + new PathControlPoint(new Vector2(100, 0)), + new PathControlPoint(new Vector2(100)), + new PathControlPoint(new Vector2(0, 100)) + } + } + }; + AddStep("add slider", () => EditorBeatmap.Add(slider)); + AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); + + AddStep("select second node", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType>().ElementAt(1)); + InputManager.Click(MouseButton.Left); + }); + AddStep("also select third node", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.MoveMouseTo(this.ChildrenOfType>().ElementAt(2)); + InputManager.Click(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddStep("quick-delete fourth node", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType>().ElementAt(3)); + InputManager.Click(MouseButton.Middle); + }); + AddUntilStep("slider not deleted", () => EditorBeatmap.HitObjects.OfType().Count(), () => Is.EqualTo(1)); + AddUntilStep("slider path has 3 nodes", () => EditorBeatmap.HitObjects.OfType().Single().Path.ControlPoints.Count, () => Is.EqualTo(3)); + } + + [Test] + public void TestQuickDeleteOnSelectedControlPointRemovesEntireSelection() + { + var slider = new Slider + { + StartTime = 0, + Position = new Vector2(100, 100), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint { Type = PathType.LINEAR }, + new PathControlPoint(new Vector2(100, 0)), + new PathControlPoint(new Vector2(100)), + new PathControlPoint(new Vector2(0, 100)) + } + } + }; + AddStep("add slider", () => EditorBeatmap.Add(slider)); + AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); + + AddStep("select second node", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType>().ElementAt(1)); + InputManager.Click(MouseButton.Left); + }); + AddStep("also select third node", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.MoveMouseTo(this.ChildrenOfType>().ElementAt(2)); + InputManager.Click(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); + AddStep("quick-delete second node", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType>().ElementAt(1)); + InputManager.Click(MouseButton.Middle); + }); + AddUntilStep("slider not deleted", () => EditorBeatmap.HitObjects.OfType().Count(), () => Is.EqualTo(1)); + AddUntilStep("slider path has 2 nodes", () => EditorBeatmap.HitObjects.OfType().Single().Path.ControlPoints.Count, () => Is.EqualTo(2)); + } + private ComposeBlueprintContainer blueprintContainer => Editor.ChildrenOfType().First(); From 182f998f9b9069e52ab2b76e70bc47d4f4a0101c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Dec 2024 09:42:48 +0100 Subject: [PATCH 322/326] Fix quick-deleting unselected slider path control point also deleting all selected control points Closes https://github.com/ppy/osu/issues/31308. Logic matches corresponding quick-delete logic in https://github.com/ppy/osu/blob/130802e48048c134c6c8f19c77e3e032834acf72/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs#L307-L316. --- .../Components/PathControlPointVisualiser.cs | 23 ++++++++++++++----- .../Sliders/SliderSelectionBlueprint.cs | 7 ++++-- 2 files changed, 22 insertions(+), 8 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 f114516300..f98117c0fa 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -137,11 +137,27 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components /// /// Delete all visually selected s. /// - /// + /// Whether any change actually took place. public bool DeleteSelected() { List toRemove = Pieces.Where(p => p.IsSelected.Value).Select(p => p.ControlPoint).ToList(); + if (!Delete(toRemove)) + return false; + + // Since pieces are re-used, they will not point to the deleted control points while remaining selected + foreach (var piece in Pieces) + piece.IsSelected.Value = false; + + return true; + } + + /// + /// Delete the specified s. + /// + /// Whether any change actually took place. + public bool Delete(List toRemove) + { // Ensure that there are any points to be deleted if (toRemove.Count == 0) return false; @@ -149,11 +165,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components changeHandler?.BeginChange(); RemoveControlPointsRequested?.Invoke(toRemove); changeHandler?.EndChange(); - - // Since pieces are re-used, they will not point to the deleted control points while remaining selected - foreach (var piece in Pieces) - piece.IsSelected.Value = false; - return true; } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index 02f76b51b0..3504954bec 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -140,8 +140,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders if (hoveredControlPoint == null) return false; - hoveredControlPoint.IsSelected.Value = true; - ControlPointVisualiser?.DeleteSelected(); + if (hoveredControlPoint.IsSelected.Value) + ControlPointVisualiser?.DeleteSelected(); + else + ControlPointVisualiser?.Delete([hoveredControlPoint.ControlPoint]); + return true; } From 2a758bc3df34d1fe309720e0f5eae56f8ac5f856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Dec 2024 10:47:55 +0100 Subject: [PATCH 323/326] Add failing test case --- .../Editor/TestSceneOsuComposerSelection.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs index 5aa7d6865f..f3e76da9c9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs @@ -346,6 +346,36 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddUntilStep("slider path has 2 nodes", () => EditorBeatmap.HitObjects.OfType().Single().Path.ControlPoints.Count, () => Is.EqualTo(2)); } + [Test] + public void TestSliderDragMarkerDoesNotBlockControlPointContextMenu() + { + var slider = new Slider + { + StartTime = 0, + Position = new Vector2(100, 100), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint { Type = PathType.LINEAR }, + new PathControlPoint(new Vector2(50, 100)), + new PathControlPoint(new Vector2(145, 100)), + }, + ExpectedDistance = { Value = 162.62 } + }, + }; + AddStep("add slider", () => EditorBeatmap.Add(slider)); + AddStep("select slider", () => EditorBeatmap.SelectedHitObjects.Add(slider)); + + AddStep("select last node", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType>().Last()); + InputManager.Click(MouseButton.Left); + }); + AddStep("right click node", () => InputManager.Click(MouseButton.Right)); + AddUntilStep("context menu open", () => this.ChildrenOfType().Single().ChildrenOfType().All(m => m.State == MenuState.Open)); + } + private ComposeBlueprintContainer blueprintContainer => Editor.ChildrenOfType().First(); From a4c6f221c2ecfabf8d970969f7200da2c2bee7c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Dec 2024 10:56:42 +0100 Subject: [PATCH 324/326] Add extra test coverage to prevent regressions Covers scenario described in https://github.com/ppy/osu/issues/31176 and fixed in https://github.com/ppy/osu/pull/31184. --- .../Editor/TestSceneOsuComposerSelection.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs index f3e76da9c9..4e6cad1dca 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuComposerSelection.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; +using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; @@ -376,6 +377,49 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor AddUntilStep("context menu open", () => this.ChildrenOfType().Single().ChildrenOfType().All(m => m.State == MenuState.Open)); } + [Test] + public void TestSliderDragMarkerBlocksSelectionOfObjectsUnderneath() + { + var firstSlider = new Slider + { + StartTime = 0, + Position = new Vector2(10, 50), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(100)) + } + } + }; + var secondSlider = new Slider + { + StartTime = 500, + Position = new Vector2(200, 0), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(), + new PathControlPoint(new Vector2(-100, 100)) + } + } + }; + + AddStep("add objects", () => EditorBeatmap.AddRange(new HitObject[] { firstSlider, secondSlider })); + AddStep("select second slider", () => EditorBeatmap.SelectedHitObjects.Add(secondSlider)); + + AddStep("move to marker", () => + { + var marker = this.ChildrenOfType().First(); + var position = (marker.ScreenSpaceDrawQuad.TopRight + marker.ScreenSpaceDrawQuad.BottomRight) / 2; + InputManager.MoveMouseTo(position); + }); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddAssert("second slider still selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondSlider)); + } + private ComposeBlueprintContainer blueprintContainer => Editor.ChildrenOfType().First(); From 4d326ec31f06068a85a83dfe08fe7f3e67c45d01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Dec 2024 10:57:57 +0100 Subject: [PATCH 325/326] Fix slider end drag marker blocking open of control point piece context menus Closes https://github.com/ppy/osu/issues/31323. --- .../Edit/Blueprints/Sliders/SliderEndDragMarker.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderEndDragMarker.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderEndDragMarker.cs index 326dd82fc6..9cc5394191 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderEndDragMarker.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderEndDragMarker.cs @@ -10,6 +10,7 @@ using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Rulesets.Osu.Objects; using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { @@ -76,9 +77,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders base.OnDragEnd(e); } - protected override bool OnMouseDown(MouseDownEvent e) => true; + protected override bool OnMouseDown(MouseDownEvent e) => e.Button == MouseButton.Left; - protected override bool OnClick(ClickEvent e) => true; + protected override bool OnClick(ClickEvent e) => e.Button == MouseButton.Left; private void updateState() { From 693db097ee7dc90e2fda6d4d5cdcbc27a1191064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 30 Dec 2024 12:04:41 +0100 Subject: [PATCH 326/326] Take custom bank name length into account when collapsing sample point indicators Would close https://github.com/ppy/osu/issues/31312. Not super happy with the performance overhead of this, but this is already a heuristic-based implementation to avoid every-frame `.ChildrenOfType<>()` calls or similar, so not super sure how to do better. The `Array.Contains()` check stands out in profiling, but without it the indicators can collapse *too* eagerly sometimes. --- .../Timeline/TimelineBlueprintContainer.cs | 20 +++++++++++++++++-- 1 file changed, 18 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 a4083f58b6..578e945c64 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -16,6 +16,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Audio; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -131,7 +132,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateSamplePointContractedState() { - const double minimum_gap = 28; + const double absolute_minimum_gap = 31; // assumes single letter bank name for default banks + double minimumGap = absolute_minimum_gap; if (timeline == null || editorClock == null) return; @@ -153,9 +155,23 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (hitObject.GetEndTime() < editorClock.CurrentTime - timeline.VisibleRange / 2) break; + foreach (var sample in hitObject.Samples) + { + if (!HitSampleInfo.AllBanks.Contains(sample.Bank)) + minimumGap = Math.Max(minimumGap, absolute_minimum_gap + sample.Bank.Length * 3); + } + if (hitObject is IHasRepeats hasRepeats) + { smallestTimeGap = Math.Min(smallestTimeGap, hasRepeats.Duration / hasRepeats.SpanCount() / 2); + foreach (var sample in hasRepeats.NodeSamples.SelectMany(s => s)) + { + if (!HitSampleInfo.AllBanks.Contains(sample.Bank)) + minimumGap = Math.Max(minimumGap, absolute_minimum_gap + sample.Bank.Length * 3); + } + } + double gap = lastTime - hitObject.GetEndTime(); // If the gap is less than 1ms, we can assume that the objects are stacked on top of each other @@ -167,7 +183,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } double smallestAbsoluteGap = ((TimelineSelectionBlueprintContainer)SelectionBlueprints).ContentRelativeToAbsoluteFactor.X * smallestTimeGap; - SamplePointContracted.Value = smallestAbsoluteGap < minimum_gap; + SamplePointContracted.Value = smallestAbsoluteGap < minimumGap; } private readonly Stack currentConcurrentObjects = new Stack();