From 10287e01505880f236667a636968c814f250c137 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 26 May 2022 00:08:00 -0400 Subject: [PATCH 001/145] initial implementation --- .../Mods/OsuEaseHitObjectPositionsMod.cs | 75 +++++++++++++++++++ .../Mods/OsuModMagnetised.cs | 59 ++------------- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 39 ++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- 4 files changed, 121 insertions(+), 54 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs new file mode 100644 index 0000000000..d81d86dc65 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs @@ -0,0 +1,75 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Bindables; +using osu.Framework.Utils; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public abstract class OsuEaseHitObjectPositionsMod : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset + { + public override ModType Type => ModType.Fun; + public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; + + protected BindableFloat EasementStrength = new BindableFloat(0.5f); + protected Vector2 CursorPosition; + protected DrawableHitObject WorkingHitObject; + protected abstract Vector2 DestinationVector { get; } + + private IFrameStableClock gameplayClock; + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + gameplayClock = drawableRuleset.FrameStableClock; + + // Hide judgment displays and follow points as they won't make any sense. + // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. + drawableRuleset.Playfield.DisplayJudgements.Value = false; + (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); + } + + public void Update(Playfield playfield) + { + CursorPosition = playfield.Cursor.ActiveCursor.DrawPosition; + + foreach (var drawable in playfield.HitObjectContainer.AliveObjects) + { + WorkingHitObject = drawable; + switch (drawable) + { + case DrawableHitCircle circle: + easeHitObjectPositionToVector(circle, DestinationVector); + break; + + case DrawableSlider slider: + + if (!slider.HeadCircle.Result.HasResult) + easeHitObjectPositionToVector(slider, DestinationVector); + else + easeHitObjectPositionToVector(slider, DestinationVector - slider.Ball.DrawPosition); + + break; + } + } + } + + private void easeHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) + { + double dampLength = Interpolation.Lerp(3000, 40, EasementStrength.Value); + + float x = (float)Interpolation.DampContinuously(hitObject.X, Math.Clamp(destination.X, 0, OsuPlayfield.BASE_SIZE.X), dampLength, gameplayClock.ElapsedFrameTime); + float y = (float)Interpolation.DampContinuously(hitObject.Y, Math.Clamp(destination.Y, 0, OsuPlayfield.BASE_SIZE.Y), dampLength, gameplayClock.ElapsedFrameTime); + + hitObject.Position = new Vector2(x, y); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index ca6e9cfb1d..ca69085e31 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -2,31 +2,23 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; -using osu.Framework.Utils; using osu.Game.Configuration; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModMagnetised : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset + internal class OsuModMagnetised : OsuEaseHitObjectPositionsMod { public override string Name => "Magnetised"; public override string Acronym => "MG"; public override IconUsage? Icon => FontAwesome.Solid.Magnet; - public override ModType Type => ModType.Fun; public override string Description => "No need to chase the circles – your cursor is a magnet!"; - public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax) }; + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModRelax), typeof(OsuModRepel) }).ToArray(); - private IFrameStableClock gameplayClock; + protected override Vector2 DestinationVector => CursorPosition; [SettingSource("Attraction strength", "How strong the pull is.", 0)] public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f) @@ -36,48 +28,9 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 1.0f, }; - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + public OsuModMagnetised() { - gameplayClock = drawableRuleset.FrameStableClock; - - // Hide judgment displays and follow points as they won't make any sense. - // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. - drawableRuleset.Playfield.DisplayJudgements.Value = false; - (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); - } - - public void Update(Playfield playfield) - { - var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; - - foreach (var drawable in playfield.HitObjectContainer.AliveObjects) - { - switch (drawable) - { - case DrawableHitCircle circle: - easeTo(circle, cursorPos); - break; - - case DrawableSlider slider: - - if (!slider.HeadCircle.Result.HasResult) - easeTo(slider, cursorPos); - else - easeTo(slider, cursorPos - slider.Ball.DrawPosition); - - break; - } - } - } - - private void easeTo(DrawableHitObject hitObject, Vector2 destination) - { - double dampLength = Interpolation.Lerp(3000, 40, AttractionStrength.Value); - - float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); - float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); - - hitObject.Position = new Vector2(x, y); + EasementStrength.BindTo(AttractionStrength); } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs new file mode 100644 index 0000000000..35f369d9c5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Mods +{ + internal class OsuModRepel : OsuEaseHitObjectPositionsMod + { + public override string Name => "Repel"; + public override string Acronym => "RP"; + public override IconUsage? Icon => FontAwesome.Solid.ExpandArrowsAlt; + public override string Description => "Run away!"; + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModMagnetised)).ToArray(); + + [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] + public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f) + { + Precision = 0.05f, + MinValue = 0.05f, + MaxValue = 1.0f, + }; + + protected override Vector2 DestinationVector => new Vector2( + 2 * WorkingHitObject.X - CursorPosition.X, + 2 * WorkingHitObject.Y - CursorPosition.Y + ); + + public OsuModRepel() + { + EasementStrength.BindTo(RepulsionStrength); + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 207e7a4ab0..09b85726ca 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -195,7 +195,7 @@ namespace osu.Game.Rulesets.Osu new OsuModApproachDifferent(), new OsuModMuted(), new OsuModNoScope(), - new OsuModMagnetised(), + new MultiMod(new OsuModMagnetised(), new OsuModRepel()), new ModAdaptiveSpeed() }; From 5d838628d7e49df3b3ce3e1768e6c19c68e366fe Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Fri, 27 May 2022 23:15:19 -0400 Subject: [PATCH 002/145] add test, fix formatting, expose easing function --- .../Mods/TestSceneOsuModRepel.cs | 27 +++++++++++++++++++ .../Mods/OsuEaseHitObjectPositionsMod.cs | 12 ++++----- 2 files changed, 33 insertions(+), 6 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs new file mode 100644 index 0000000000..6bd41e2fa5 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.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 NUnit.Framework; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class TestSceneOsuModRepel : OsuModTestScene + { + [TestCase(0.1f)] + [TestCase(0.5f)] + [TestCase(1)] + public void TestRepel(float strength) + { + CreateModTest(new ModTestData + { + Mod = new OsuModRepel + { + RepulsionStrength = { Value = strength }, + }, + PassCondition = () => true, + Autoplay = false, + }); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs index d81d86dc65..2c344b7a68 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs @@ -23,8 +23,8 @@ namespace osu.Game.Rulesets.Osu.Mods protected BindableFloat EasementStrength = new BindableFloat(0.5f); protected Vector2 CursorPosition; protected DrawableHitObject WorkingHitObject; - protected abstract Vector2 DestinationVector { get; } - + protected virtual Vector2 DestinationVector => WorkingHitObject.Position; + private IFrameStableClock gameplayClock; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) @@ -47,22 +47,22 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { case DrawableHitCircle circle: - easeHitObjectPositionToVector(circle, DestinationVector); + EaseHitObjectPositionToVector(circle, DestinationVector); break; case DrawableSlider slider: if (!slider.HeadCircle.Result.HasResult) - easeHitObjectPositionToVector(slider, DestinationVector); + EaseHitObjectPositionToVector(slider, DestinationVector); else - easeHitObjectPositionToVector(slider, DestinationVector - slider.Ball.DrawPosition); + EaseHitObjectPositionToVector(slider, DestinationVector - slider.Ball.DrawPosition); break; } } } - private void easeHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) + protected void EaseHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) { double dampLength = Interpolation.Lerp(3000, 40, EasementStrength.Value); From de224e79c79863f268367392574df539d6ddac80 Mon Sep 17 00:00:00 2001 From: Henry Lin Date: Tue, 7 Jun 2022 10:32:51 +0800 Subject: [PATCH 003/145] Limit slider rotation when the slider is too large --- .../OsuHitObjectGenerationUtils_Reposition.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index a77d1f8b0f..477ef2d55d 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -198,6 +198,27 @@ namespace osu.Game.Rulesets.Osu.Utils var slider = (Slider)workingObject.HitObject; var possibleMovementBounds = calculatePossibleMovementBounds(slider); + // The slider rotation applied in computeModifiedPosition might make it impossible to fit the slider into the playfield + // For example, a long horizontal slider will be off-screen when rotated by 90 degrees + // In this case, limit the rotation to either 0 or 180 degrees + if (possibleMovementBounds.Width < 0 || possibleMovementBounds.Height < 0) + { + float currentRotation = getSliderRotation(slider); + float diff1 = getAngleDifference(workingObject.RotationOriginal, currentRotation); + float diff2 = getAngleDifference(workingObject.RotationOriginal + MathF.PI, currentRotation); + + if (diff1 < diff2) + { + RotateSlider(slider, workingObject.RotationOriginal - getSliderRotation(slider)); + } + else + { + RotateSlider(slider, workingObject.RotationOriginal + MathF.PI - getSliderRotation(slider)); + } + + possibleMovementBounds = calculatePossibleMovementBounds(slider); + } + var previousPosition = workingObject.PositionModified; // Clamp slider position to the placement area @@ -355,6 +376,18 @@ namespace osu.Game.Rulesets.Osu.Utils return MathF.Atan2(endPositionVector.Y, endPositionVector.X); } + /// + /// Get the absolute difference between 2 angles measured in Radians. + /// + /// The first angle + /// The second angle + /// The absolute difference with interval [0, MathF.PI) + private static float getAngleDifference(float angle1, float angle2) + { + float diff = MathF.Abs(angle1 - angle2) % (MathF.PI * 2); + return MathF.Min(diff, MathF.PI * 2 - diff); + } + public class ObjectPositionInfo { /// @@ -397,6 +430,7 @@ namespace osu.Game.Rulesets.Osu.Utils private class WorkingObject { + public float RotationOriginal { get; } public Vector2 PositionOriginal { get; } public Vector2 PositionModified { get; set; } public Vector2 EndPositionModified { get; set; } @@ -407,6 +441,7 @@ namespace osu.Game.Rulesets.Osu.Utils public WorkingObject(ObjectPositionInfo positionInfo) { PositionInfo = positionInfo; + RotationOriginal = HitObject is Slider slider ? getSliderRotation(slider) : 0; PositionModified = PositionOriginal = HitObject.Position; EndPositionModified = HitObject.EndPosition; } From b7bdad407486266b5c5446fde71c7108c0adc225 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:36:44 -0400 Subject: [PATCH 004/145] clamp sliders, expose slider bounds function --- .../Mods/OsuEaseHitObjectPositionsMod.cs | 16 ++++----- .../Mods/OsuModMagnetised.cs | 7 +--- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 33 ++++++++++++++----- .../OsuHitObjectGenerationUtils_Reposition.cs | 22 ++++++++----- 4 files changed, 48 insertions(+), 30 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs index 2c344b7a68..74391e53a2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs @@ -20,10 +20,10 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - protected BindableFloat EasementStrength = new BindableFloat(0.5f); + public abstract BindableFloat EasementStrength { get; } protected Vector2 CursorPosition; protected DrawableHitObject WorkingHitObject; - protected virtual Vector2 DestinationVector => WorkingHitObject.Position; + protected abstract Vector2 DestinationVector { get; } private IFrameStableClock gameplayClock; @@ -47,27 +47,27 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { case DrawableHitCircle circle: - EaseHitObjectPositionToVector(circle, DestinationVector); + easeHitObjectPositionToVector(circle, DestinationVector); break; case DrawableSlider slider: if (!slider.HeadCircle.Result.HasResult) - EaseHitObjectPositionToVector(slider, DestinationVector); + easeHitObjectPositionToVector(slider, DestinationVector); else - EaseHitObjectPositionToVector(slider, DestinationVector - slider.Ball.DrawPosition); + easeHitObjectPositionToVector(slider, DestinationVector - slider.Ball.DrawPosition); break; } } } - protected void EaseHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) + private void easeHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) { double dampLength = Interpolation.Lerp(3000, 40, EasementStrength.Value); - float x = (float)Interpolation.DampContinuously(hitObject.X, Math.Clamp(destination.X, 0, OsuPlayfield.BASE_SIZE.X), dampLength, gameplayClock.ElapsedFrameTime); - float y = (float)Interpolation.DampContinuously(hitObject.Y, Math.Clamp(destination.Y, 0, OsuPlayfield.BASE_SIZE.Y), dampLength, gameplayClock.ElapsedFrameTime); + float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); + float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); hitObject.Position = new Vector2(x, y); } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index ca69085e31..1c10d61c99 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -21,16 +21,11 @@ namespace osu.Game.Rulesets.Osu.Mods protected override Vector2 DestinationVector => CursorPosition; [SettingSource("Attraction strength", "How strong the pull is.", 0)] - public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f) + public override BindableFloat EasementStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, MinValue = 0.05f, MaxValue = 1.0f, }; - - public OsuModMagnetised() - { - EasementStrength.BindTo(AttractionStrength); - } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 35f369d9c5..fec66e3ef1 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -6,6 +6,9 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; +using osu.Game.Rulesets.Osu.Utils; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods @@ -19,21 +22,35 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModMagnetised)).ToArray(); [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] - public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f) + public override BindableFloat EasementStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, MinValue = 0.05f, MaxValue = 1.0f, }; - protected override Vector2 DestinationVector => new Vector2( - 2 * WorkingHitObject.X - CursorPosition.X, - 2 * WorkingHitObject.Y - CursorPosition.Y - ); - - public OsuModRepel() + protected override Vector2 DestinationVector { - EasementStrength.BindTo(RepulsionStrength); + get + { + float x = Math.Clamp(2 * WorkingHitObject.X - CursorPosition.X, 0, OsuPlayfield.BASE_SIZE.X); + float y = Math.Clamp(2 * WorkingHitObject.Y - CursorPosition.Y, 0, OsuPlayfield.BASE_SIZE.Y); + + if (WorkingHitObject.HitObject is Slider slider) + { + var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(slider, false); + + x = possibleMovementBounds.Width < 0 + ? x + : Math.Clamp(x, possibleMovementBounds.Left, possibleMovementBounds.Right); + + y = possibleMovementBounds.Height < 0 + ? y + : Math.Clamp(y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); + } + + return new Vector2(x, y); + } } } } diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index d1bc3b45df..765477fba2 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -167,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.Utils private static Vector2 clampSliderToPlayfield(WorkingObject workingObject) { var slider = (Slider)workingObject.HitObject; - var possibleMovementBounds = calculatePossibleMovementBounds(slider); + var possibleMovementBounds = CalculatePossibleMovementBounds(slider); var previousPosition = workingObject.PositionModified; @@ -212,10 +212,13 @@ namespace osu.Game.Rulesets.Osu.Utils /// Calculates a which contains all of the possible movements of the slider (in relative X/Y coordinates) /// such that the entire slider is inside the playfield. /// + /// The for which to calculate a movement bounding box. + /// Whether the movement bounding box should account for the slider's follow circle. Defaults to true. + /// A which contains all of the possible movements of the slider such that the entire slider is inside the playfield. /// /// If the slider is larger than the playfield, the returned may have negative width/height. /// - private static RectangleF calculatePossibleMovementBounds(Slider slider) + public static RectangleF CalculatePossibleMovementBounds(Slider slider, bool accountForFollowCircleRadius = true) { var pathPositions = new List(); slider.Path.GetPathToProgress(pathPositions, 0, 1); @@ -236,14 +239,17 @@ namespace osu.Game.Rulesets.Osu.Utils maxY = MathF.Max(maxY, pos.Y); } - // Take the circle radius into account. - float radius = (float)slider.Radius; + if (accountForFollowCircleRadius) + { + // Take the circle radius into account. + float radius = (float)slider.Radius; - minX -= radius; - minY -= radius; + minX -= radius; + minY -= radius; - maxX += radius; - maxY += radius; + maxX += radius; + maxY += radius; + } // Given the bounding box of the slider (via min/max X/Y), // the amount that the slider can move to the left is minX (with the sign flipped, since positive X is to the right), From e5171aaebf60a7ab17b31f7295a71f4278efde4b Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Tue, 7 Jun 2022 09:48:53 -0400 Subject: [PATCH 005/145] update tests to match new bindable names --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs | 2 +- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs index 9b49e60363..29ec91cda8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModMagnetised { - AttractionStrength = { Value = strength }, + EasementStrength = { Value = strength }, }, PassCondition = () => true, Autoplay = false, diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs index 6bd41e2fa5..bdd4ed7fb9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModRepel { - RepulsionStrength = { Value = strength }, + EasementStrength = { Value = strength }, }, PassCondition = () => true, Autoplay = false, From f21c9fb520190d51a9e499b57f5410ab16978564 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Tue, 7 Jun 2022 12:05:53 -0400 Subject: [PATCH 006/145] add newline --- osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs index 74391e53a2..d0f115def3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs @@ -44,6 +44,7 @@ namespace osu.Game.Rulesets.Osu.Mods foreach (var drawable in playfield.HitObjectContainer.AliveObjects) { WorkingHitObject = drawable; + switch (drawable) { case DrawableHitCircle circle: From f54a68f6cae2ff193c6ad94b23d11f918ec3197f Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 8 Jun 2022 01:00:47 -0400 Subject: [PATCH 007/145] scale down repulsion strength --- osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs | 3 ++- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs index d0f115def3..e93863fa96 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs @@ -21,6 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; public abstract BindableFloat EasementStrength { get; } + protected virtual float EasementStrengthMultiplier => 1.0f; protected Vector2 CursorPosition; protected DrawableHitObject WorkingHitObject; protected abstract Vector2 DestinationVector { get; } @@ -65,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void easeHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) { - double dampLength = Interpolation.Lerp(3000, 40, EasementStrength.Value); + double dampLength = Interpolation.Lerp(3000, 40, EasementStrength.Value * EasementStrengthMultiplier); float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index fec66e3ef1..95242808af 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -22,13 +22,15 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModMagnetised)).ToArray(); [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] - public override BindableFloat EasementStrength { get; } = new BindableFloat(0.5f) + public override BindableFloat EasementStrength { get; } = new BindableFloat(0.6f) { Precision = 0.05f, MinValue = 0.05f, MaxValue = 1.0f, }; + protected override float EasementStrengthMultiplier => 0.8f; + protected override Vector2 DestinationVector { get From 6e883a69d9e98947053646d6f166afe0bf4a277a Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 9 Jun 2022 18:07:37 -0400 Subject: [PATCH 008/145] revert slider radius parameter addition --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 +- .../OsuHitObjectGenerationUtils_Reposition.cs | 18 +++++++----------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 95242808af..3261076084 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (WorkingHitObject.HitObject is Slider slider) { - var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(slider, false); + var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(slider); x = possibleMovementBounds.Width < 0 ? x diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 765477fba2..5e11ede91f 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -213,12 +213,11 @@ namespace osu.Game.Rulesets.Osu.Utils /// such that the entire slider is inside the playfield. /// /// The for which to calculate a movement bounding box. - /// Whether the movement bounding box should account for the slider's follow circle. Defaults to true. /// A which contains all of the possible movements of the slider such that the entire slider is inside the playfield. /// /// If the slider is larger than the playfield, the returned may have negative width/height. /// - public static RectangleF CalculatePossibleMovementBounds(Slider slider, bool accountForFollowCircleRadius = true) + public static RectangleF CalculatePossibleMovementBounds(Slider slider) { var pathPositions = new List(); slider.Path.GetPathToProgress(pathPositions, 0, 1); @@ -239,17 +238,14 @@ namespace osu.Game.Rulesets.Osu.Utils maxY = MathF.Max(maxY, pos.Y); } - if (accountForFollowCircleRadius) - { - // Take the circle radius into account. - float radius = (float)slider.Radius; + // Take the circle radius into account. + float radius = (float)slider.Radius; - minX -= radius; - minY -= radius; + minX -= radius; + minY -= radius; - maxX += radius; - maxY += radius; - } + maxX += radius; + maxY += radius; // Given the bounding box of the slider (via min/max X/Y), // the amount that the slider can move to the left is minX (with the sign flipped, since positive X is to the right), From 4e01db03bb3a6002590dac58e04005cf9c528193 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 9 Jun 2022 18:25:04 -0400 Subject: [PATCH 009/145] don't specify icon --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 3261076084..da6686d314 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Rulesets.Osu.Utils; using osu.Game.Rulesets.Osu.Objects; @@ -17,7 +16,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Name => "Repel"; public override string Acronym => "RP"; - public override IconUsage? Icon => FontAwesome.Solid.ExpandArrowsAlt; public override string Description => "Run away!"; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModMagnetised)).ToArray(); From 569c39942a5b74ae51847c64e0f230f8973ec994 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 9 Jun 2022 18:26:18 -0400 Subject: [PATCH 010/145] replace easement with easing --- .../Mods/TestSceneOsuModMagnetised.cs | 2 +- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs | 6 +++--- osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs index 29ec91cda8..bab47fa851 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModMagnetised { - EasementStrength = { Value = strength }, + EasingStrength = { Value = strength }, }, PassCondition = () => true, Autoplay = false, diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs index bdd4ed7fb9..0462f60991 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModRepel { - EasementStrength = { Value = strength }, + EasingStrength = { Value = strength }, }, PassCondition = () => true, Autoplay = false, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs index e93863fa96..0ba3a204f9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs @@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - public abstract BindableFloat EasementStrength { get; } - protected virtual float EasementStrengthMultiplier => 1.0f; + public abstract BindableFloat EasingStrength { get; } + protected virtual float EasingStrengthMultiplier => 1.0f; protected Vector2 CursorPosition; protected DrawableHitObject WorkingHitObject; protected abstract Vector2 DestinationVector { get; } @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void easeHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) { - double dampLength = Interpolation.Lerp(3000, 40, EasementStrength.Value * EasementStrengthMultiplier); + double dampLength = Interpolation.Lerp(3000, 40, EasingStrength.Value * EasingStrengthMultiplier); float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index 1c10d61c99..b8c976cf9f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods protected override Vector2 DestinationVector => CursorPosition; [SettingSource("Attraction strength", "How strong the pull is.", 0)] - public override BindableFloat EasementStrength { get; } = new BindableFloat(0.5f) + public override BindableFloat EasingStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, MinValue = 0.05f, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index da6686d314..1622400eac 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -20,14 +20,14 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModMagnetised)).ToArray(); [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] - public override BindableFloat EasementStrength { get; } = new BindableFloat(0.6f) + public override BindableFloat EasingStrength { get; } = new BindableFloat(0.6f) { Precision = 0.05f, MinValue = 0.05f, MaxValue = 1.0f, }; - protected override float EasementStrengthMultiplier => 0.8f; + protected override float EasingStrengthMultiplier => 0.8f; protected override Vector2 DestinationVector { From 2fe34f188f4c6d4294cd98f2f4d18a921f5949a1 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Thu, 9 Jun 2022 18:52:10 -0400 Subject: [PATCH 011/145] shamelessly copy osumodmagnetised --- .../Mods/TestSceneOsuModMagnetised.cs | 2 +- .../Mods/TestSceneOsuModRepel.cs | 2 +- .../Mods/OsuEaseHitObjectPositionsMod.cs | 77 ------------------- .../Mods/OsuModMagnetised.cs | 62 +++++++++++++-- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 74 ++++++++++++++---- 5 files changed, 118 insertions(+), 99 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs index bab47fa851..9b49e60363 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModMagnetised { - EasingStrength = { Value = strength }, + AttractionStrength = { Value = strength }, }, PassCondition = () => true, Autoplay = false, diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs index 0462f60991..6bd41e2fa5 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModRepel.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { Mod = new OsuModRepel { - EasingStrength = { Value = strength }, + RepulsionStrength = { Value = strength }, }, PassCondition = () => true, Autoplay = false, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs b/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs deleted file mode 100644 index 0ba3a204f9..0000000000 --- a/osu.Game.Rulesets.Osu/Mods/OsuEaseHitObjectPositionsMod.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Bindables; -using osu.Framework.Utils; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.UI; -using osu.Game.Rulesets.UI; -using osuTK; - -namespace osu.Game.Rulesets.Osu.Mods -{ - public abstract class OsuEaseHitObjectPositionsMod : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset - { - public override ModType Type => ModType.Fun; - public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay) }; - - public abstract BindableFloat EasingStrength { get; } - protected virtual float EasingStrengthMultiplier => 1.0f; - protected Vector2 CursorPosition; - protected DrawableHitObject WorkingHitObject; - protected abstract Vector2 DestinationVector { get; } - - private IFrameStableClock gameplayClock; - - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) - { - gameplayClock = drawableRuleset.FrameStableClock; - - // Hide judgment displays and follow points as they won't make any sense. - // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. - drawableRuleset.Playfield.DisplayJudgements.Value = false; - (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); - } - - public void Update(Playfield playfield) - { - CursorPosition = playfield.Cursor.ActiveCursor.DrawPosition; - - foreach (var drawable in playfield.HitObjectContainer.AliveObjects) - { - WorkingHitObject = drawable; - - switch (drawable) - { - case DrawableHitCircle circle: - easeHitObjectPositionToVector(circle, DestinationVector); - break; - - case DrawableSlider slider: - - if (!slider.HeadCircle.Result.HasResult) - easeHitObjectPositionToVector(slider, DestinationVector); - else - easeHitObjectPositionToVector(slider, DestinationVector - slider.Ball.DrawPosition); - - break; - } - } - } - - private void easeHitObjectPositionToVector(DrawableHitObject hitObject, Vector2 destination) - { - double dampLength = Interpolation.Lerp(3000, 40, EasingStrength.Value * EasingStrengthMultiplier); - - float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); - float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); - - hitObject.Position = new Vector2(x, y); - } - } -} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index b8c976cf9f..97a573f1b4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -2,30 +2,82 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModMagnetised : OsuEaseHitObjectPositionsMod + internal class OsuModMagnetised : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Name => "Magnetised"; public override string Acronym => "MG"; public override IconUsage? Icon => FontAwesome.Solid.Magnet; + public override ModType Type => ModType.Fun; public override string Description => "No need to chase the circles – your cursor is a magnet!"; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModRelax), typeof(OsuModRepel) }).ToArray(); + public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel) }; - protected override Vector2 DestinationVector => CursorPosition; + private IFrameStableClock gameplayClock; [SettingSource("Attraction strength", "How strong the pull is.", 0)] - public override BindableFloat EasingStrength { get; } = new BindableFloat(0.5f) + public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, MinValue = 0.05f, MaxValue = 1.0f, }; + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + gameplayClock = drawableRuleset.FrameStableClock; + + // Hide judgment displays and follow points as they won't make any sense. + // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. + drawableRuleset.Playfield.DisplayJudgements.Value = false; + (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); + } + + public void Update(Playfield playfield) + { + var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; + + foreach (var drawable in playfield.HitObjectContainer.AliveObjects) + { + switch (drawable) + { + case DrawableHitCircle circle: + easeTo(circle, cursorPos); + break; + + case DrawableSlider slider: + + if (!slider.HeadCircle.Result.HasResult) + easeTo(slider, cursorPos); + else + easeTo(slider, cursorPos - slider.Ball.DrawPosition); + + break; + } + } + } + + private void easeTo(DrawableHitObject hitObject, Vector2 destination) + { + double dampLength = Interpolation.Lerp(3000, 40, AttractionStrength.Value); + + float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); + float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); + + hitObject.Position = new Vector2(x, y); + } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 1622400eac..807be997db 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -2,43 +2,61 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Utils; using osu.Game.Configuration; -using osu.Game.Rulesets.Osu.Utils; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Osu.Utils; +using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Osu.Mods { - internal class OsuModRepel : OsuEaseHitObjectPositionsMod + internal class OsuModRepel : Mod, IUpdatableByPlayfield, IApplicableToDrawableRuleset { public override string Name => "Repel"; public override string Acronym => "RP"; + public override ModType Type => ModType.Fun; public override string Description => "Run away!"; - public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModMagnetised)).ToArray(); + public override double ScoreMultiplier => 1; + public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised) }; + + private IFrameStableClock gameplayClock; [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] - public override BindableFloat EasingStrength { get; } = new BindableFloat(0.6f) + public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.6f) { Precision = 0.05f, MinValue = 0.05f, MaxValue = 1.0f, }; - protected override float EasingStrengthMultiplier => 0.8f; - - protected override Vector2 DestinationVector + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - get - { - float x = Math.Clamp(2 * WorkingHitObject.X - CursorPosition.X, 0, OsuPlayfield.BASE_SIZE.X); - float y = Math.Clamp(2 * WorkingHitObject.Y - CursorPosition.Y, 0, OsuPlayfield.BASE_SIZE.Y); + gameplayClock = drawableRuleset.FrameStableClock; - if (WorkingHitObject.HitObject is Slider slider) + // Hide judgment displays and follow points as they won't make any sense. + // Judgements can potentially be turned on in a future where they display at a position relative to their drawable counterpart. + drawableRuleset.Playfield.DisplayJudgements.Value = false; + (drawableRuleset.Playfield as OsuPlayfield)?.FollowPoints.Hide(); + } + + public void Update(Playfield playfield) + { + var cursorPos = playfield.Cursor.ActiveCursor.DrawPosition; + + foreach (var drawable in playfield.HitObjectContainer.AliveObjects) + { + float x = Math.Clamp(2 * drawable.X - cursorPos.X, 0, OsuPlayfield.BASE_SIZE.X); + float y = Math.Clamp(2 * drawable.Y - cursorPos.Y, 0, OsuPlayfield.BASE_SIZE.Y); + + if (drawable.HitObject is Slider thisSlider) { - var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(slider); + var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(thisSlider); x = possibleMovementBounds.Width < 0 ? x @@ -49,8 +67,34 @@ namespace osu.Game.Rulesets.Osu.Mods : Math.Clamp(y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); } - return new Vector2(x, y); + var destination = new Vector2(x, y); + + switch (drawable) + { + case DrawableHitCircle circle: + easeTo(circle, destination); + break; + + case DrawableSlider slider: + + if (!slider.HeadCircle.Result.HasResult) + easeTo(slider, destination); + else + easeTo(slider, destination - slider.Ball.DrawPosition); + + break; + } } } + + private void easeTo(DrawableHitObject hitObject, Vector2 destination) + { + double dampLength = Interpolation.Lerp(3000, 40, 0.8 * RepulsionStrength.Value); + + float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); + float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); + + hitObject.Position = new Vector2(x, y); + } } } From a17e18103f983649af80c9a15e1f14b3e6e52774 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Jul 2022 18:19:31 +0900 Subject: [PATCH 012/145] Improve description --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 807be997db..052c7128f6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Name => "Repel"; public override string Acronym => "RP"; public override ModType Type => ModType.Fun; - public override string Description => "Run away!"; + public override string Description => "Hit objects run away!"; public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised) }; From e6a05ce3e2d8312581dd660ec9518bebee4b230f Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Sun, 3 Jul 2022 13:51:30 -0700 Subject: [PATCH 013/145] Slow down legacy followcircle animations --- .../Objects/Drawables/DrawableSlider.cs | 4 -- .../Objects/Drawables/DrawableSliderBall.cs | 11 ++-- .../Skinning/Default/DefaultFollowCircle.cs | 50 ++++++++++++++ .../Skinning/Default/DefaultSliderBall.cs | 63 ++++++++++++++++-- .../Skinning/Legacy/LegacyFollowCircle.cs | 65 +++++++++++++++++++ 5 files changed, 175 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 91bb7f95f6..d83f5df7a3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -319,13 +319,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables const float fade_out_time = 450; - // intentionally pile on an extra FadeOut to make it happen much faster. - Ball.FadeOut(fade_out_time / 4, Easing.Out); - switch (state) { case ArmedState.Hit: - Ball.ScaleTo(HitObject.Scale * 1.4f, fade_out_time, Easing.Out); if (SliderBody?.SnakingOut.Value == true) Body.FadeOut(40); // short fade to allow for any body colour to smoothly disappear. break; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs index 7bde60b39d..6bfb4e8aae 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderBall.cs @@ -23,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { public class DrawableSliderBall : CircularContainer, ISliderProgress, IRequireHighFrequencyMousePosition, IHasAccentColour { + public const float FOLLOW_AREA = 2.4f; + public Func GetInitialHitAction; public Color4 AccentColour @@ -31,7 +33,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables set => ball.Colour = value; } - private Drawable followCircle; private Drawable followCircleReceptor; private DrawableSlider drawableSlider; private Drawable ball; @@ -47,12 +48,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Children = new[] { - followCircle = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()) + new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.SliderFollowCircle), _ => new DefaultFollowCircle()) { Origin = Anchor.Centre, Anchor = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Alpha = 0, }, followCircleReceptor = new CircularContainer { @@ -103,10 +103,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables tracking = value; - followCircleReceptor.Scale = new Vector2(tracking ? 2.4f : 1f); - - followCircle.ScaleTo(tracking ? 2.4f : 1f, 300, Easing.OutQuint); - followCircle.FadeTo(tracking ? 1f : 0, 300, Easing.OutQuint); + followCircleReceptor.Scale = new Vector2(tracking ? FOLLOW_AREA : 1f); } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 8211448705..c39a07da4e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -1,17 +1,25 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Default { public class DefaultFollowCircle : CompositeDrawable { + [Resolved(canBeNull: true)] + private DrawableHitObject? parentObject { get; set; } + public DefaultFollowCircle() { + Alpha = 0f; RelativeSizeAxes = Axes.Both; InternalChild = new CircularContainer @@ -29,5 +37,47 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default } }; } + + [BackgroundDependencyLoader] + private void load() + { + if (parentObject != null) + { + var slider = (DrawableSlider)parentObject; + slider.Tracking.BindValueChanged(trackingChanged, true); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (parentObject != null) + { + parentObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(parentObject, parentObject.State.Value); + } + } + + private void trackingChanged(ValueChangedEvent tracking) + { + const float scale_duration = 300f; + const float fade_duration = 300f; + + this.ScaleTo(tracking.NewValue ? DrawableSliderBall.FOLLOW_AREA : 1f, scale_duration, Easing.OutQuint) + .FadeTo(tracking.NewValue ? 1f : 0, fade_duration, Easing.OutQuint); + } + + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) + { + if (drawableObject is not DrawableSlider) + return; + + const float fade_time = 450f; + + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + // intentionally pile on an extra FadeOut to make it happen much faster. + this.FadeOut(fade_time / 4, Easing.Out); + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs index 47308375e6..3c7dd7ba5d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -19,13 +17,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default { public class DefaultSliderBall : CompositeDrawable { - private Box box; + private Box box = null!; + + [Resolved(canBeNull: true)] + private DrawableHitObject? parentObject { get; set; } [BackgroundDependencyLoader] - private void load(DrawableHitObject drawableObject, ISkinSource skin) + private void load(ISkinSource skin) { - var slider = (DrawableSlider)drawableObject; - RelativeSizeAxes = Axes.Both; float radius = skin.GetConfig(OsuSkinConfiguration.SliderPathRadius)?.Value ?? OsuHitObject.OBJECT_RADIUS; @@ -51,10 +50,60 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default } }; - slider.Tracking.BindValueChanged(trackingChanged, true); + if (parentObject != null) + { + var slider = (DrawableSlider)parentObject; + slider.Tracking.BindValueChanged(trackingChanged, true); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (parentObject != null) + { + parentObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(parentObject, parentObject.State.Value); + } } private void trackingChanged(ValueChangedEvent tracking) => box.FadeTo(tracking.NewValue ? 0.3f : 0.05f, 200, Easing.OutQuint); + + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) + { + if (drawableObject is not DrawableSlider) + return; + + const float fade_out_time = 450f; + + using (BeginAbsoluteSequence(drawableObject.StateUpdateTime)) + { + this.ScaleTo(1f) + .FadeIn(); + } + + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + { + // intentionally pile on an extra FadeOut to make it happen much faster + this.FadeOut(fade_out_time / 4, Easing.Out); + + switch (state) + { + case ArmedState.Hit: + this.ScaleTo(1.4f, fade_out_time, Easing.Out); + break; + } + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (parentObject != null) + parentObject.ApplyCustomUpdateState -= updateStateTransforms; + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index b8a559ce07..df33180c7b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -1,13 +1,21 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { public class LegacyFollowCircle : CompositeDrawable { + [Resolved(canBeNull: true)] + private DrawableHitObject? parentObject { get; set; } + public LegacyFollowCircle(Drawable animationContent) { // follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x @@ -15,8 +23,65 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy animationContent.Anchor = Anchor.Centre; animationContent.Origin = Anchor.Centre; + Alpha = 0f; RelativeSizeAxes = Axes.Both; InternalChild = animationContent; } + + [BackgroundDependencyLoader] + private void load() + { + if (parentObject != null) + { + var slider = (DrawableSlider)parentObject; + slider.Tracking.BindValueChanged(trackingChanged, true); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (parentObject != null) + { + parentObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(parentObject, parentObject.State.Value); + } + } + + private void trackingChanged(ValueChangedEvent tracking) + { + Debug.Assert(parentObject != null); + + if (parentObject.Judged) + return; + + const float scale_duration = 180f; + const float fade_duration = 90f; + + double maxScaleDuration = parentObject.HitStateUpdateTime - Time.Current; + double realScaleDuration = scale_duration; + if (tracking.NewValue && maxScaleDuration < realScaleDuration) + realScaleDuration = maxScaleDuration; + double realFadeDuration = fade_duration * realScaleDuration / fade_duration; + + this.ScaleTo(tracking.NewValue ? DrawableSliderBall.FOLLOW_AREA : 1f, realScaleDuration, Easing.OutQuad) + .FadeTo(tracking.NewValue ? 1f : 0f, realFadeDuration, Easing.OutQuad); + } + + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) + { + if (drawableObject is not DrawableSlider) + return; + + const float shrink_duration = 200f; + const float fade_duration = 240f; + + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + { + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 0.8f, shrink_duration, Easing.OutQuint) + .FadeOut(fade_duration, Easing.InQuint); + } + } } } From 72fb1ae892e71424a45b383b684f791802444fdf Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Tue, 5 Jul 2022 21:04:13 -0700 Subject: [PATCH 014/145] Add forgotten unsubscribes --- .../Skinning/Default/DefaultFollowCircle.cs | 17 ++++++++-- .../Skinning/Default/DefaultSliderBall.cs | 11 +++--- .../Skinning/Legacy/LegacyFollowCircle.cs | 34 ++++++++++++++++--- 3 files changed, 50 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index c39a07da4e..7dd45c295d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -1,11 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Logging; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK.Graphics; @@ -70,14 +72,23 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { + // see comment in LegacySliderBall.updateStateTransforms if (drawableObject is not DrawableSlider) return; - const float fade_time = 450f; + const float fade_duration = 450f; + // intentionally pile on an extra FadeOut to make it happen much faster using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) - // intentionally pile on an extra FadeOut to make it happen much faster. - this.FadeOut(fade_time / 4, Easing.Out); + this.FadeOut(fade_duration / 4, Easing.Out); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (parentObject != null) + parentObject.ApplyCustomUpdateState -= updateStateTransforms; } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs index 3c7dd7ba5d..37e5e150bd 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs @@ -73,26 +73,27 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { + // see comment in LegacySliderBall.updateStateTransforms if (drawableObject is not DrawableSlider) return; - const float fade_out_time = 450f; + const float fade_duration = 450f; using (BeginAbsoluteSequence(drawableObject.StateUpdateTime)) { - this.ScaleTo(1f) - .FadeIn(); + this.FadeIn() + .ScaleTo(1f); } using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) { // intentionally pile on an extra FadeOut to make it happen much faster - this.FadeOut(fade_out_time / 4, Easing.Out); + this.FadeOut(fade_duration / 4, Easing.Out); switch (state) { case ArmedState.Hit: - this.ScaleTo(1.4f, fade_out_time, Easing.Out); + this.ScaleTo(1.4f, fade_duration, Easing.Out); break; } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index df33180c7b..dc44c86de0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -1,13 +1,17 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Transforms; +using osu.Framework.Logging; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -23,7 +27,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy animationContent.Anchor = Anchor.Centre; animationContent.Origin = Anchor.Centre; - Alpha = 0f; RelativeSizeAxes = Axes.Both; InternalChild = animationContent; } @@ -44,6 +47,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (parentObject != null) { + parentObject.HitObjectApplied += onHitObjectApplied; + onHitObjectApplied(parentObject); + parentObject.ApplyCustomUpdateState += updateStateTransforms; updateStateTransforms(parentObject, parentObject.State.Value); } @@ -69,18 +75,38 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy .FadeTo(tracking.NewValue ? 1f : 0f, realFadeDuration, Easing.OutQuad); } + private void onHitObjectApplied(DrawableHitObject drawableObject) + { + this.ScaleTo(1f) + .FadeOut(); + } + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { + // see comment in LegacySliderBall.updateStateTransforms if (drawableObject is not DrawableSlider) return; const float shrink_duration = 200f; - const float fade_duration = 240f; + const float fade_delay = 175f; + const float fade_duration = 35f; using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) { - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 0.8f, shrink_duration, Easing.OutQuint) - .FadeOut(fade_duration, Easing.InQuint); + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 0.75f, shrink_duration, Easing.OutQuad) + .Delay(fade_delay) + .FadeOut(fade_duration); + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (parentObject != null) + { + parentObject.HitObjectApplied -= onHitObjectApplied; + parentObject.ApplyCustomUpdateState -= updateStateTransforms; } } } From 0281bf672c42d5cedf8a7ac607dec280ad4acbdd Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 6 Jul 2022 15:58:25 -0400 Subject: [PATCH 015/145] operate on vectors instead of vector components --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 052c7128f6..ad3a54c630 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -51,24 +51,19 @@ namespace osu.Game.Rulesets.Osu.Mods foreach (var drawable in playfield.HitObjectContainer.AliveObjects) { - float x = Math.Clamp(2 * drawable.X - cursorPos.X, 0, OsuPlayfield.BASE_SIZE.X); - float y = Math.Clamp(2 * drawable.Y - cursorPos.Y, 0, OsuPlayfield.BASE_SIZE.Y); + var destination = Vector2.Clamp(2 * drawable.Position - cursorPos, Vector2.Zero, OsuPlayfield.BASE_SIZE); if (drawable.HitObject is Slider thisSlider) { var possibleMovementBounds = OsuHitObjectGenerationUtils.CalculatePossibleMovementBounds(thisSlider); - x = possibleMovementBounds.Width < 0 - ? x - : Math.Clamp(x, possibleMovementBounds.Left, possibleMovementBounds.Right); - - y = possibleMovementBounds.Height < 0 - ? y - : Math.Clamp(y, possibleMovementBounds.Top, possibleMovementBounds.Bottom); + destination = Vector2.Clamp( + destination, + new Vector2(possibleMovementBounds.Left, possibleMovementBounds.Top), + new Vector2(possibleMovementBounds.Right, possibleMovementBounds.Bottom) + ); } - var destination = new Vector2(x, y); - switch (drawable) { case DrawableHitCircle circle: From 40e98f84f304c6288c2decbecda614f5d8b1042d Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 6 Jul 2022 16:01:08 -0400 Subject: [PATCH 016/145] change default strength back to 0.5 --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index ad3a54c630..c53ac58752 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Mods private IFrameStableClock gameplayClock; [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] - public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.6f) + public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f) { Precision = 0.05f, MinValue = 0.05f, From d5b4d146708a5d712924d02a479f8063e152ab5a Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:01:14 -0400 Subject: [PATCH 017/145] modify damp length to effectively invert repulsion strength --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index c53ac58752..0aab019d73 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Mods private void easeTo(DrawableHitObject hitObject, Vector2 destination) { - double dampLength = Interpolation.Lerp(3000, 40, 0.8 * RepulsionStrength.Value); + double dampLength = Vector2.Distance(hitObject.Position, destination) / (0.04 * RepulsionStrength.Value + 0.04); float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); From 62beae4063846c0022930925506d48435985d756 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Wed, 6 Jul 2022 17:18:21 -0400 Subject: [PATCH 018/145] add nullable directive --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 0aab019d73..1cb5bf31a4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable disable + using System; using osu.Framework.Bindables; using osu.Framework.Utils; From 9d730f84400b236e0cabd8a8f01dc4fd6e78d09c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 14:49:22 +0900 Subject: [PATCH 019/145] Fix custom rulesets not importing scores at all Replaces the error with the ability to import, minus replays. Closes https://github.com/ppy/osu/issues/17350 (arguably, but let's go with it for now). --- osu.Game/Screens/Play/Player.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4040adc48d..b4016fc1cf 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -22,6 +22,7 @@ using osu.Framework.Threading; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Extensions; using osu.Game.Graphics.Containers; using osu.Game.IO.Archives; using osu.Game.Online.API; @@ -1064,12 +1065,15 @@ namespace osu.Game.Screens.Play if (DrawableRuleset.ReplayScore != null) return Task.CompletedTask; - LegacyByteArrayReader replayReader; + LegacyByteArrayReader replayReader = null; - using (var stream = new MemoryStream()) + if (score.ScoreInfo.Ruleset.IsLegacyRuleset()) { - new LegacyScoreEncoder(score, GameplayState.Beatmap).Encode(stream); - replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr"); + using (var stream = new MemoryStream()) + { + new LegacyScoreEncoder(score, GameplayState.Beatmap).Encode(stream); + replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr"); + } } // the import process will re-attach managed beatmap/rulesets to this score. we don't want this for now, so create a temporary copy to import. From f88e2aa025e676ad2f3d82c459fd3b25420f1a24 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 15:02:03 +0900 Subject: [PATCH 020/145] Remove EF test workaround --- .../Gameplay/TestScenePlayerScoreSubmission.cs | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index e0c8989389..501ad9bd28 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -363,23 +363,9 @@ namespace osu.Game.Tests.Visual.Gameplay await AllowImportCompletion.WaitAsync().ConfigureAwait(false); - ImportedScore = score; + await base.ImportScore(score); - // It was discovered that Score members could sometimes be half-populated. - // In particular, the RulesetID property could be set to 0 even on non-osu! maps. - // We want to test that the state of that property is consistent in this test. - // EF makes this impossible. - // - // First off, because of the EF navigational property-explicit foreign key field duality, - // it can happen that - for example - the Ruleset navigational property is correctly initialised to mania, - // but the RulesetID foreign key property is not initialised and remains 0. - // EF silently bypasses this by prioritising the Ruleset navigational property over the RulesetID foreign key one. - // - // Additionally, adding an entity to an EF DbSet CAUSES SIDE EFFECTS with regard to the foreign key property. - // In the above instance, if a ScoreInfo with Ruleset = {mania} and RulesetID = 0 is attached to an EF context, - // RulesetID WILL BE SILENTLY SET TO THE CORRECT VALUE of 3. - // - // For the above reasons, actual importing is disabled in this test. + ImportedScore = score; } } } From 1a41d3ef209302df0976302c15f96b5604272615 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 16:42:36 +0900 Subject: [PATCH 021/145] Allow `PlayerTestScene` to import the beatmap it's using --- osu.Game/Tests/Visual/PlayerTestScene.cs | 27 +++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index e1714299b6..036ac8a639 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -4,12 +4,15 @@ #nullable disable using System; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -22,6 +25,11 @@ namespace osu.Game.Tests.Visual /// protected virtual bool HasCustomSteps => false; + /// + /// WARNING: ONLY WORKS IF RUN HEADLESS because reasons. + /// + protected virtual bool ImportBeatmapToDatabase => false; + protected TestPlayer Player; protected OsuConfigManager LocalConfig; @@ -49,7 +57,7 @@ namespace osu.Game.Tests.Visual action?.Invoke(); - AddStep(CreatePlayerRuleset().Description, LoadPlayer); + AddStep($"Load player for {CreatePlayerRuleset().Description}", LoadPlayer); AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); } @@ -57,6 +65,9 @@ namespace osu.Game.Tests.Visual protected virtual bool Autoplay => false; + [Resolved] + private BeatmapManager beatmaps { get; set; } + protected void LoadPlayer() { var ruleset = CreatePlayerRuleset(); @@ -64,6 +75,20 @@ namespace osu.Game.Tests.Visual var beatmap = CreateBeatmap(ruleset.RulesetInfo); + if (ImportBeatmapToDatabase) + { + Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null); + + var imported = beatmaps.Import(beatmap.BeatmapInfo.BeatmapSet); + + Debug.Assert(imported != null); + + beatmap.BeatmapInfo = null; + beatmap.Difficulty = null; + + beatmap.BeatmapInfo = imported.Value.Detach().Beatmaps.First(); + } + Beatmap.Value = CreateWorkingBeatmap(beatmap); SelectedMods.Value = Array.Empty(); From 461d133c1f0a9a736bfa265e5c7dd060d6dd68b8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 16:42:48 +0900 Subject: [PATCH 022/145] Add test coverage of score importing --- .../TestScenePlayerLocalScoreImport.cs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs new file mode 100644 index 0000000000..61f0483c86 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -0,0 +1,72 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking; + +namespace osu.Game.Tests.Visual.Gameplay +{ + [HeadlessTest] // Importing rulesets doesn't work in interactive flows. + public class TestScenePlayerLocalScoreImport : PlayerTestScene + { + private Ruleset? customRuleset; + + protected override bool ImportBeatmapToDatabase => true; + + protected override Ruleset CreatePlayerRuleset() => customRuleset ?? new OsuRuleset(); + + protected override TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false); + + protected override bool HasCustomSteps => true; + + protected override bool AllowFail => false; + + [Test] + public void TestScoreStoredLocally() + { + AddStep("set no custom ruleset", () => customRuleset = null); + + CreateTest(); + + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + + AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); + + AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); + AddUntilStep("score in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); + } + + [Test] + public void TestScoreStoredLocallyCustomRuleset() + { + Ruleset createCustomRuleset() => new OsuRuleset + { + RulesetInfo = + { + Name = "custom", + ShortName = "custom", + OnlineID = -1 + } + }; + + AddStep("import custom ruleset", () => Realm.Write(r => r.Add(createCustomRuleset().RulesetInfo))); + AddStep("set custom ruleset", () => customRuleset = createCustomRuleset()); + + CreateTest(); + + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + + AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); + + AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); + AddUntilStep("score in database", () => Realm.Run(r => r.All().Count() == 1)); + } + } +} From b663986b9fd2922ca3a29e4de95af012c04f30a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Apr 2022 16:12:06 +0900 Subject: [PATCH 023/145] Add test coverage of locally available without replay button handling --- .../Gameplay/TestSceneReplayDownloadButton.cs | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 10a6b196b0..c259d5f0a8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -142,6 +142,28 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value); } + [Test] + public void TestLocallyAvailableWithoutReplay() + { + Live imported = null; + + AddStep("import score", () => imported = scoreManager.Import(getScoreInfo(false, false))); + + AddStep("create button without replay", () => + { + Child = downloadButton = new TestReplayDownloadButton(imported.Value) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + }); + + AddUntilStep("wait for load", () => downloadButton.IsLoaded); + + AddUntilStep("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); + AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value); + } + [Test] public void TestScoreImportThenDelete() { @@ -189,11 +211,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value); } - private ScoreInfo getScoreInfo(bool replayAvailable) + private ScoreInfo getScoreInfo(bool replayAvailable, bool hasOnlineId = true) { return new APIScore { - OnlineID = online_score_id, + OnlineID = hasOnlineId ? online_score_id : 0, RulesetID = 0, Beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo).Beatmaps.First(), HasReplay = replayAvailable, From 57b2f8189c4bdd3dd91bf00b035c3810ec44cb19 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Jul 2022 17:40:47 +0900 Subject: [PATCH 024/145] Add back test workaround for `TestScenePlayerScoreSubmission` with updated explanation This reverts commit f88e2aa025e676ad2f3d82c459fd3b25420f1a24. --- .../Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 501ad9bd28..96efca6b65 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -363,9 +363,11 @@ namespace osu.Game.Tests.Visual.Gameplay await AllowImportCompletion.WaitAsync().ConfigureAwait(false); - await base.ImportScore(score); - ImportedScore = score; + + // Calling base.ImportScore is omitted as it will fail for the test method which uses a custom ruleset. + // This can be resolved by doing something similar to what TestScenePlayerLocalScoreImport is doing, + // but requires a bit of restructuring. } } } From a94fb62be380cdfc2e6af8475f1baf4ee881c482 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Jul 2022 01:39:43 +0300 Subject: [PATCH 025/145] Split collection toggle menu item to own class --- .../Collections/CollectionToggleMenuItem.cs | 20 +++++++++++++++++++ .../Carousel/DrawableCarouselBeatmap.cs | 16 +-------------- 2 files changed, 21 insertions(+), 15 deletions(-) create mode 100644 osu.Game/Collections/CollectionToggleMenuItem.cs diff --git a/osu.Game/Collections/CollectionToggleMenuItem.cs b/osu.Game/Collections/CollectionToggleMenuItem.cs new file mode 100644 index 0000000000..34f749c9f8 --- /dev/null +++ b/osu.Game/Collections/CollectionToggleMenuItem.cs @@ -0,0 +1,20 @@ +using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Collections +{ + public class CollectionToggleMenuItem : ToggleMenuItem + { + public CollectionToggleMenuItem(BeatmapCollection collection, IBeatmapInfo beatmap) + : base(collection.Name.Value, MenuItemType.Standard, s => + { + if (s) + collection.BeatmapHashes.Add(beatmap.MD5Hash); + else + collection.BeatmapHashes.Remove(beatmap.MD5Hash); + }) + { + State.Value = collection.BeatmapHashes.Contains(beatmap.MD5Hash); + } + } +} diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 1b3cab20e8..a6532ee145 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -244,7 +244,7 @@ namespace osu.Game.Screens.Select.Carousel if (collectionManager != null) { - var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); + var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmapInfo)).Cast().ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); @@ -258,20 +258,6 @@ namespace osu.Game.Screens.Select.Carousel } } - private MenuItem createCollectionMenuItem(BeatmapCollection collection) - { - return new ToggleMenuItem(collection.Name.Value, MenuItemType.Standard, s => - { - if (s) - collection.BeatmapHashes.Add(beatmapInfo.MD5Hash); - else - collection.BeatmapHashes.Remove(beatmapInfo.MD5Hash); - }) - { - State = { Value = collection.BeatmapHashes.Contains(beatmapInfo.MD5Hash) } - }; - } - protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 1d0f2e359a5925b4e672202266a99ff32f4e265d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Jul 2022 01:40:11 +0300 Subject: [PATCH 026/145] Add collection context menu to room playlist items --- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 48 ++++++++++++++++++- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index a3a176477e..455e1f3481 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -15,11 +16,13 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Collections; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -38,7 +41,7 @@ using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay { - public class DrawableRoomPlaylistItem : OsuRearrangeableListItem + public class DrawableRoomPlaylistItem : OsuRearrangeableListItem, IHasContextMenu { public const float HEIGHT = 50; @@ -90,6 +93,8 @@ namespace osu.Game.Screens.OnlinePlay private PanelBackground panelBackground; private FillFlowContainer mainFillFlow; + private BeatmapDownloadTracker downloadTracker; + [Resolved] private RulesetStore rulesets { get; set; } @@ -102,6 +107,12 @@ namespace osu.Game.Screens.OnlinePlay [Resolved] private BeatmapLookupCache beatmapLookupCache { get; set; } + [Resolved(CanBeNull = true)] + private CollectionManager collectionManager { get; set; } + + [Resolved(CanBeNull = true)] + private ManageCollectionsDialog manageCollectionsDialog { get; set; } + protected override bool ShouldBeConsideredForInput(Drawable child) => AllowReordering || AllowDeletion || !AllowSelection || SelectedItem.Value == Model; public DrawableRoomPlaylistItem(PlaylistItem item) @@ -304,6 +315,15 @@ namespace osu.Game.Screens.OnlinePlay difficultyIconContainer.FadeInFromZero(500, Easing.OutQuint); mainFillFlow.FadeInFromZero(500, Easing.OutQuint); + + downloadTracker?.RemoveAndDisposeImmediately(); + + if (beatmap != null) + { + Debug.Assert(beatmap.BeatmapSet != null); + downloadTracker = new BeatmapDownloadTracker(beatmap.BeatmapSet); + AddInternal(downloadTracker); + } } protected override Drawable CreateContent() @@ -433,7 +453,7 @@ namespace osu.Game.Screens.OnlinePlay } } }, - } + }, }; } @@ -470,6 +490,30 @@ namespace osu.Game.Screens.OnlinePlay return true; } + public MenuItem[] ContextMenuItems + { + get + { + List items = new List(); + + if (beatmap != null && collectionManager != null) + { + if (downloadTracker.State.Value == DownloadState.LocallyAvailable) + { + var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + } + else + items.Add(new OsuMenuItem("Download to add to collection") { Action = { Disabled = true } }); + } + + return items.ToArray(); + } + } + public class PlaylistEditButton : GrayButton { public PlaylistEditButton() From 7b08501eafb31c76cec7842a4f87e35f28c81cee Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Jul 2022 01:42:11 +0300 Subject: [PATCH 027/145] Cover online-play room screens with context menu containers --- .../Multiplayer/MultiplayerMatchSubScreen.cs | 227 ++++++++-------- .../Playlists/PlaylistsRoomSubScreen.cs | 247 +++++++++--------- 2 files changed, 242 insertions(+), 232 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 869548c948..4eb16a854b 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -15,6 +15,7 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Graphics.Cursor; using osu.Game.Online; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -81,134 +82,138 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Horizontal = 5, Vertical = 10 }, - Child = new GridContainer + Child = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + Child = new GridContainer { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 10), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 10), - new Dimension(), - }, - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] { - // Participants column - new GridContainer + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] + // Participants column + new GridContainer { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new Drawable[] { new ParticipantsListHeader() }, - new Drawable[] + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - new ParticipantsList - { - RelativeSizeAxes = Axes.Both - }, - } - } - }, - // Spacer - null, - // Beatmap column - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] { new OverlinedHeader("Beatmap") }, - new Drawable[] - { - addItemButton = new AddItemButton - { - RelativeSizeAxes = Axes.X, - Height = 40, - Text = "Add item", - Action = () => OpenSongSelection() - }, + new Dimension(GridSizeMode.AutoSize) }, - null, - new Drawable[] + Content = new[] { - new MultiplayerPlaylist + new Drawable[] { new ParticipantsListHeader() }, + new Drawable[] { - RelativeSizeAxes = Axes.Both, - RequestEdit = item => OpenSongSelection(item.ID) - } - }, - new[] - { - UserModsSection = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 10 }, - Alpha = 0, - Children = new Drawable[] + new ParticipantsList { - new OverlinedHeader("Extra mods"), - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Children = new Drawable[] - { - new UserModSelectButton - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Width = 90, - Text = "Select", - Action = ShowUserModSelect, - }, - new ModDisplay - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Current = UserMods, - Scale = new Vector2(0.8f), - }, - } - }, + RelativeSizeAxes = Axes.Both + }, + } + } + }, + // Spacer + null, + // Beatmap column + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { new OverlinedHeader("Beatmap") }, + new Drawable[] + { + addItemButton = new AddItemButton + { + RelativeSizeAxes = Axes.X, + Height = 40, + Text = "Add item", + Action = () => OpenSongSelection() + }, + }, + null, + new Drawable[] + { + new MultiplayerPlaylist + { + RelativeSizeAxes = Axes.Both, + RequestEdit = item => OpenSongSelection(item.ID) } }, + new[] + { + UserModsSection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 10 }, + Alpha = 0, + Children = new Drawable[] + { + new OverlinedHeader("Extra mods"), + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new UserModSelectButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 90, + Text = "Select", + Action = ShowUserModSelect, + }, + new ModDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Current = UserMods, + Scale = new Vector2(0.8f), + }, + } + }, + } + }, + }, }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, 5), + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + } }, - RowDimensions = new[] + // Spacer + null, + // Main right column + new GridContainer { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Absolute, 5), - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - } - }, - // Spacer - null, - // Main right column - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] { new OverlinedHeader("Chat") }, - new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } } + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { new OverlinedHeader("Chat") }, + new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } } + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + } }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - } - }, + } } } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 8a9c4db6ad..228ecd4bf3 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Screens; +using osu.Game.Graphics.Cursor; using osu.Game.Input; using osu.Game.Online.Rooms; using osu.Game.Screens.OnlinePlay.Components; @@ -75,151 +76,155 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Horizontal = 5, Vertical = 10 }, - Child = new GridContainer + Child = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, - ColumnDimensions = new[] + Child = new GridContainer { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 10), - new Dimension(), - new Dimension(GridSizeMode.Absolute, 10), - new Dimension(), - }, - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] { - // Playlist items column - new Container + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + new Dimension(GridSizeMode.Absolute, 10), + new Dimension(), + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 5 }, - Child = new GridContainer + // Playlist items column + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 5 }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { new OverlinedPlaylistHeader(), }, + new Drawable[] + { + new DrawableRoomPlaylist + { + RelativeSizeAxes = Axes.Both, + Items = { BindTarget = Room.Playlist }, + SelectedItem = { BindTarget = SelectedItem }, + AllowSelection = true, + AllowShowingResults = true, + RequestResults = item => + { + Debug.Assert(RoomId.Value != null); + ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false)); + } + } + }, + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + } + } + }, + // Spacer + null, + // Middle column (mods and leaderboard) + new GridContainer { RelativeSizeAxes = Axes.Both, Content = new[] { - new Drawable[] { new OverlinedPlaylistHeader(), }, + new[] + { + UserModsSection = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Margin = new MarginPadding { Bottom = 10 }, + Children = new Drawable[] + { + new OverlinedHeader("Extra mods"), + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new UserModSelectButton + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Width = 90, + Text = "Select", + Action = ShowUserModSelect, + }, + new ModDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Current = UserMods, + Scale = new Vector2(0.8f), + }, + } + } + } + }, + }, new Drawable[] { - new DrawableRoomPlaylist + progressSection = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Items = { BindTarget = Room.Playlist }, - SelectedItem = { BindTarget = SelectedItem }, - AllowSelection = true, - AllowShowingResults = true, - RequestResults = item => + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Alpha = 0, + Margin = new MarginPadding { Bottom = 10 }, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Debug.Assert(RoomId.Value != null); - ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false)); + new OverlinedHeader("Progress"), + new RoomLocalUserInfo(), } - } + }, }, + new Drawable[] + { + new OverlinedHeader("Leaderboard") + }, + new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, }, + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + } + }, + // Spacer + null, + // Main right column + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { new OverlinedHeader("Chat") }, + new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } } }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(), } - } - }, - // Spacer - null, - // Middle column (mods and leaderboard) - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new[] - { - UserModsSection = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Alpha = 0, - Margin = new MarginPadding { Bottom = 10 }, - Children = new Drawable[] - { - new OverlinedHeader("Extra mods"), - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Children = new Drawable[] - { - new UserModSelectButton - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Width = 90, - Text = "Select", - Action = ShowUserModSelect, - }, - new ModDisplay - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Current = UserMods, - Scale = new Vector2(0.8f), - }, - } - } - } - }, - }, - new Drawable[] - { - progressSection = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Alpha = 0, - Margin = new MarginPadding { Bottom = 10 }, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new OverlinedHeader("Progress"), - new RoomLocalUserInfo(), - } - }, - }, - new Drawable[] - { - new OverlinedHeader("Leaderboard") - }, - new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, }, }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - } - }, - // Spacer - null, - // Main right column - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] { new OverlinedHeader("Chat") }, - new Drawable[] { new MatchChatDisplay(Room) { RelativeSizeAxes = Axes.Both } } - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - } }, }, - }, + } } }; From 67fa15f231d543b342bdd8be22c53b85ce8c8b1a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Jul 2022 01:42:55 +0300 Subject: [PATCH 028/145] Remove no longer required context menu container in `ParticipantsList` --- .../TestSceneMultiplayerParticipantsList.cs | 15 ++++++++++----- .../Participants/ParticipantsList.cs | 19 +++++++------------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index 7db18d1127..7a64e75644 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; @@ -370,12 +371,16 @@ namespace osu.Game.Tests.Visual.Multiplayer { ParticipantsList participantsList = null; - AddStep("create new list", () => Child = participantsList = new ParticipantsList + AddStep("create new list", () => Child = new OsuContextMenuContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Size = new Vector2(380, 0.7f) + RelativeSizeAxes = Axes.Both, + Child = participantsList = new ParticipantsList + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Size = new Vector2(380, 0.7f) + } }); AddUntilStep("wait for list to load", () => participantsList.IsLoaded); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs index cda86c74bf..7c93c6084e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs @@ -9,7 +9,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Cursor; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants @@ -24,20 +23,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants [BackgroundDependencyLoader] private void load() { - InternalChild = new OsuContextMenuContainer + InternalChild = new OsuScrollContainer { RelativeSizeAxes = Axes.Both, - Child = new OsuScrollContainer + ScrollbarVisible = false, + Child = panels = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = panels = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 2) - } + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 2) } }; } From 63a06afab220c116928299a18582e4117c7be3e2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Jul 2022 01:59:27 +0300 Subject: [PATCH 029/145] Add missing license header --- osu.Game/Collections/CollectionToggleMenuItem.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Collections/CollectionToggleMenuItem.cs b/osu.Game/Collections/CollectionToggleMenuItem.cs index 34f749c9f8..fbc935d19a 100644 --- a/osu.Game/Collections/CollectionToggleMenuItem.cs +++ b/osu.Game/Collections/CollectionToggleMenuItem.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; From 89f1c75f7ad0cdea5e23240eeb4580c1cc0a066b Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Thu, 7 Jul 2022 21:57:18 -0500 Subject: [PATCH 030/145] Update mod icon colors --- osu.Game/Rulesets/UI/ModIcon.cs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 0f21a497b0..f32c298571 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -127,33 +127,33 @@ namespace osu.Game.Rulesets.UI { default: case ModType.DifficultyIncrease: - backgroundColour = colours.Yellow; - highlightedColour = colours.YellowLight; + backgroundColour = colours.Red1; + highlightedColour = colours.Red0; break; case ModType.DifficultyReduction: - backgroundColour = colours.Green; - highlightedColour = colours.GreenLight; + backgroundColour = colours.Lime1; + highlightedColour = colours.Lime0; break; case ModType.Automation: - backgroundColour = colours.Blue; - highlightedColour = colours.BlueLight; + backgroundColour = colours.Blue1; + highlightedColour = colours.Blue0; break; case ModType.Conversion: - backgroundColour = colours.Purple; - highlightedColour = colours.PurpleLight; + backgroundColour = colours.Purple1; + highlightedColour = colours.Purple0; break; case ModType.Fun: - backgroundColour = colours.Pink; - highlightedColour = colours.PinkLight; + backgroundColour = colours.Pink1; + highlightedColour = colours.Pink0; break; case ModType.System: - backgroundColour = colours.Gray6; - highlightedColour = colours.Gray7; + backgroundColour = colours.Gray7; + highlightedColour = colours.Gray8; modIcon.Colour = colours.Yellow; break; } From 84dcd042f42267acae6271996235c76eda670521 Mon Sep 17 00:00:00 2001 From: goodtrailer Date: Thu, 7 Jul 2022 20:30:31 -0700 Subject: [PATCH 031/145] Protect duration calculations against unstable fps --- .../Skinning/Default/DefaultFollowCircle.cs | 2 -- .../Skinning/Legacy/LegacyFollowCircle.cs | 8 +++----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 7dd45c295d..8bb36f8c39 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -1,13 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Logging; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK.Graphics; diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index dc44c86de0..26d3f053ff 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -1,17 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Transforms; -using osu.Framework.Logging; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -67,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy double maxScaleDuration = parentObject.HitStateUpdateTime - Time.Current; double realScaleDuration = scale_duration; - if (tracking.NewValue && maxScaleDuration < realScaleDuration) + if (tracking.NewValue && maxScaleDuration < realScaleDuration && maxScaleDuration >= 0) realScaleDuration = maxScaleDuration; double realFadeDuration = fade_duration * realScaleDuration / fade_duration; @@ -77,6 +73,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void onHitObjectApplied(DrawableHitObject drawableObject) { + ClearTransformsAfter(double.MinValue); + this.ScaleTo(1f) .FadeOut(); } From e64b2b1682602020a3bd68f3af3f45c44f95d832 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 15:45:01 +0900 Subject: [PATCH 032/145] Assert that test is run headless when required --- osu.Game/Tests/Visual/PlayerTestScene.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 036ac8a639..5b4d4d5c2c 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -8,6 +8,7 @@ using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Development; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -26,8 +27,12 @@ namespace osu.Game.Tests.Visual protected virtual bool HasCustomSteps => false; /// - /// WARNING: ONLY WORKS IF RUN HEADLESS because reasons. + /// Import the beatmap to the database before starting gameplay. Handy for testing local score storage and the likes. /// + /// + /// Only works under headless operation currently due to realm isolation difficulties. + /// If this is ever needed to change, consideration needs to be given to the fact that BeatmapManager is attached to the global realm. + /// protected virtual bool ImportBeatmapToDatabase => false; protected TestPlayer Player; @@ -77,6 +82,8 @@ namespace osu.Game.Tests.Visual if (ImportBeatmapToDatabase) { + Debug.Assert(DebugUtils.IsNUnitRunning, $@"Importing beatmaps in {nameof(PlayerTestScene)} requires headless environment."); + Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null); var imported = beatmaps.Import(beatmap.BeatmapInfo.BeatmapSet); From 7ced84b7ef1ab6735ef42ab2c9a8f066008b8d8f Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Fri, 8 Jul 2022 03:23:58 -0500 Subject: [PATCH 033/145] Replace switch statement with ForModType In order to make `highlightedColour` dependent on the mod type color, the color is converted to an `osu.Framework.Graphics.Colour4` and calls `Lighten`. --- osu.Game/Rulesets/UI/ModIcon.cs | 38 +++++---------------------------- 1 file changed, 5 insertions(+), 33 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index f32c298571..308122b71c 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -123,40 +123,12 @@ namespace osu.Game.Rulesets.UI modAcronym.FadeOut(); } - switch (value.Type) - { - default: - case ModType.DifficultyIncrease: - backgroundColour = colours.Red1; - highlightedColour = colours.Red0; - break; + Color4 typeColour = colours.ForModType(value.Type); + backgroundColour = typeColour; + highlightedColour = ((Colour4)typeColour).Lighten(.2f); - case ModType.DifficultyReduction: - backgroundColour = colours.Lime1; - highlightedColour = colours.Lime0; - break; - - case ModType.Automation: - backgroundColour = colours.Blue1; - highlightedColour = colours.Blue0; - break; - - case ModType.Conversion: - backgroundColour = colours.Purple1; - highlightedColour = colours.Purple0; - break; - - case ModType.Fun: - backgroundColour = colours.Pink1; - highlightedColour = colours.Pink0; - break; - - case ModType.System: - backgroundColour = colours.Gray7; - highlightedColour = colours.Gray8; - modIcon.Colour = colours.Yellow; - break; - } + if (value.Type == ModType.System) + modIcon.Colour = colours.Yellow; updateColour(); } From 13e16530a6d90c4fa341a2b3a3b7bec6e055eac4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 18:44:50 +0900 Subject: [PATCH 034/145] Revert most changes to `PlayerTestScene` --- osu.Game/Tests/Visual/PlayerTestScene.cs | 32 ------------------------ 1 file changed, 32 deletions(-) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 5b4d4d5c2c..a9decbae57 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -4,16 +4,12 @@ #nullable disable using System; -using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; -using osu.Framework.Development; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; -using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Database; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -26,15 +22,6 @@ namespace osu.Game.Tests.Visual /// protected virtual bool HasCustomSteps => false; - /// - /// Import the beatmap to the database before starting gameplay. Handy for testing local score storage and the likes. - /// - /// - /// Only works under headless operation currently due to realm isolation difficulties. - /// If this is ever needed to change, consideration needs to be given to the fact that BeatmapManager is attached to the global realm. - /// - protected virtual bool ImportBeatmapToDatabase => false; - protected TestPlayer Player; protected OsuConfigManager LocalConfig; @@ -70,9 +57,6 @@ namespace osu.Game.Tests.Visual protected virtual bool Autoplay => false; - [Resolved] - private BeatmapManager beatmaps { get; set; } - protected void LoadPlayer() { var ruleset = CreatePlayerRuleset(); @@ -80,22 +64,6 @@ namespace osu.Game.Tests.Visual var beatmap = CreateBeatmap(ruleset.RulesetInfo); - if (ImportBeatmapToDatabase) - { - Debug.Assert(DebugUtils.IsNUnitRunning, $@"Importing beatmaps in {nameof(PlayerTestScene)} requires headless environment."); - - Debug.Assert(beatmap.BeatmapInfo.BeatmapSet != null); - - var imported = beatmaps.Import(beatmap.BeatmapInfo.BeatmapSet); - - Debug.Assert(imported != null); - - beatmap.BeatmapInfo = null; - beatmap.Difficulty = null; - - beatmap.BeatmapInfo = imported.Value.Detach().Beatmaps.First(); - } - Beatmap.Value = CreateWorkingBeatmap(beatmap); SelectedMods.Value = Array.Empty(); From 3c8f06403cbe814f3f356e24f3bf055e2b275807 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 18:59:08 +0900 Subject: [PATCH 035/145] Make test work properly --- .../TestScenePlayerLocalScoreImport.cs | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 61f0483c86..a75d527a69 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -3,22 +3,51 @@ 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.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Gameplay { - [HeadlessTest] // Importing rulesets doesn't work in interactive flows. public class TestScenePlayerLocalScoreImport : PlayerTestScene { - private Ruleset? customRuleset; + private BeatmapManager beatmaps = null!; + private RulesetStore rulesets = null!; - protected override bool ImportBeatmapToDatabase => true; + private BeatmapSetInfo? importedSet; + + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, Scheduler)); + Dependencies.Cache(Realm); + } + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("import beatmap", () => + { + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); + importedSet = beatmaps.GetAllUsableBeatmapSets().First(); + }); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => beatmaps.GetWorkingBeatmap(importedSet?.Beatmaps.First()).Beatmap; + + private Ruleset? customRuleset; protected override Ruleset CreatePlayerRuleset() => customRuleset ?? new OsuRuleset(); @@ -66,7 +95,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); - AddUntilStep("score in database", () => Realm.Run(r => r.All().Count() == 1)); + AddUntilStep("score in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); } } } From ac3cdf103a61bb3dced7b49c6c9f5dd0c357537c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Jul 2022 19:17:51 +0900 Subject: [PATCH 036/145] Fix test not failing against `master` --- .../TestScenePlayerLocalScoreImport.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index a75d527a69..96b7da9c9c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Tests.Resources; @@ -75,15 +76,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestScoreStoredLocallyCustomRuleset() { - Ruleset createCustomRuleset() => new OsuRuleset - { - RulesetInfo = - { - Name = "custom", - ShortName = "custom", - OnlineID = -1 - } - }; + Ruleset createCustomRuleset() => new CustomRuleset(); AddStep("import custom ruleset", () => Realm.Write(r => r.Add(createCustomRuleset().RulesetInfo))); AddStep("set custom ruleset", () => customRuleset = createCustomRuleset()); @@ -97,5 +90,15 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("results displayed", () => Player.GetChildScreen() is ResultsScreen); AddUntilStep("score in database", () => Realm.Run(r => r.Find(Player.Score.ScoreInfo.ID) != null)); } + + private class CustomRuleset : OsuRuleset, ILegacyRuleset + { + public override string Description => "custom"; + public override string ShortName => "custom"; + + public new int LegacyID => -1; + + public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); + } } } From a606d545c193d2da6ac187b71fa0d461144d7468 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Fri, 8 Jul 2022 12:00:07 -0400 Subject: [PATCH 037/145] update new usage of CalculatePossibleMovementBounds --- .../Utils/OsuHitObjectGenerationUtils_Reposition.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs index 436d71fda2..a9ae313a31 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils_Reposition.cs @@ -214,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Utils RotateSlider(slider, workingObject.RotationOriginal + MathF.PI - getSliderRotation(slider)); } - possibleMovementBounds = calculatePossibleMovementBounds(slider); + possibleMovementBounds = CalculatePossibleMovementBounds(slider); } var previousPosition = workingObject.PositionModified; From 8c7aabccb074070961b1d84a129277add1126eb2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Jul 2022 19:01:21 +0300 Subject: [PATCH 038/145] Fix custom legacy ID in test scene not overriding base value --- .../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 96b7da9c9c..dc0a14ad9b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Gameplay public override string Description => "custom"; public override string ShortName => "custom"; - public new int LegacyID => -1; + int ILegacyRuleset.LegacyID => -1; public override ScoreProcessor CreateScoreProcessor() => new ScoreProcessor(this); } From 667b1d795d6a7f9a58b9123ec397c60127a9de3a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 8 Jul 2022 19:02:46 +0300 Subject: [PATCH 039/145] Ensure score has custom ruleset --- .../Visual/Gameplay/TestScenePlayerLocalScoreImport.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index dc0a14ad9b..3011515b62 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Beatmaps; @@ -83,6 +84,8 @@ namespace osu.Game.Tests.Visual.Gameplay CreateTest(); + AddAssert("score has custom ruleset", () => Player.Score.ScoreInfo.Ruleset.Equals(customRuleset.AsNonNull().RulesetInfo)); + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); AddStep("seek to completion", () => Player.GameplayClockContainer.Seek(Player.DrawableRuleset.Objects.Last().GetEndTime())); From 66f314915dc3f2ed4a5d47857af5ee133b72d255 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 9 Jul 2022 06:01:22 +0900 Subject: [PATCH 040/145] Fix crash on mobile releases when attempting to read any file --- osu.Game/IO/LineBufferedReader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/LineBufferedReader.cs b/osu.Game/IO/LineBufferedReader.cs index 93e554b43d..da1cdba73b 100644 --- a/osu.Game/IO/LineBufferedReader.cs +++ b/osu.Game/IO/LineBufferedReader.cs @@ -19,7 +19,7 @@ namespace osu.Game.IO public LineBufferedReader(Stream stream, bool leaveOpen = false) { - streamReader = new StreamReader(stream, Encoding.UTF8, true, -1, leaveOpen); + streamReader = new StreamReader(stream, Encoding.UTF8, true, 1024, leaveOpen); } /// From 086388ec4ea2723fbd7e40719d9fa517503631c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sat, 9 Jul 2022 21:06:47 +0800 Subject: [PATCH 041/145] Remove nullable disable annotation in the benchmark project. --- osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs | 2 -- osu.Game.Benchmarks/BenchmarkMod.cs | 2 -- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 2 -- osu.Game.Benchmarks/BenchmarkRuleset.cs | 2 -- osu.Game.Benchmarks/BenchmarkTest.cs | 2 -- osu.Game.Benchmarks/Program.cs | 2 -- 6 files changed, 12 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs index 07ffda4030..1d207d04c7 100644 --- a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs +++ b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.IO; using BenchmarkDotNet.Attributes; using osu.Framework.IO.Stores; diff --git a/osu.Game.Benchmarks/BenchmarkMod.cs b/osu.Game.Benchmarks/BenchmarkMod.cs index a1d92d9a67..c5375e9f09 100644 --- a/osu.Game.Benchmarks/BenchmarkMod.cs +++ b/osu.Game.Benchmarks/BenchmarkMod.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 BenchmarkDotNet.Attributes; using osu.Game.Rulesets.Osu.Mods; diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index 5ffda6504e..2277f4e85c 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using System.Threading; using BenchmarkDotNet.Attributes; diff --git a/osu.Game.Benchmarks/BenchmarkRuleset.cs b/osu.Game.Benchmarks/BenchmarkRuleset.cs index de8cb13773..2835ec9499 100644 --- a/osu.Game.Benchmarks/BenchmarkRuleset.cs +++ b/osu.Game.Benchmarks/BenchmarkRuleset.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 BenchmarkDotNet.Attributes; using BenchmarkDotNet.Engines; using osu.Game.Online.API; diff --git a/osu.Game.Benchmarks/BenchmarkTest.cs b/osu.Game.Benchmarks/BenchmarkTest.cs index 140696e4a4..34f5edd084 100644 --- a/osu.Game.Benchmarks/BenchmarkTest.cs +++ b/osu.Game.Benchmarks/BenchmarkTest.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 BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; using NUnit.Framework; diff --git a/osu.Game.Benchmarks/Program.cs b/osu.Game.Benchmarks/Program.cs index 603d8aa1b9..439ced53ab 100644 --- a/osu.Game.Benchmarks/Program.cs +++ b/osu.Game.Benchmarks/Program.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 BenchmarkDotNet.Configs; using BenchmarkDotNet.Running; From 8fcc33936c799b49ac4c3350ff84757a1c5d32a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sat, 9 Jul 2022 21:07:47 +0800 Subject: [PATCH 042/145] Mark the parameter as non-nullable. --- osu.Game.Benchmarks/BenchmarkMod.cs | 2 +- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 6 +++--- osu.Game.Benchmarks/BenchmarkRuleset.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkMod.cs b/osu.Game.Benchmarks/BenchmarkMod.cs index c5375e9f09..994300df36 100644 --- a/osu.Game.Benchmarks/BenchmarkMod.cs +++ b/osu.Game.Benchmarks/BenchmarkMod.cs @@ -9,7 +9,7 @@ namespace osu.Game.Benchmarks { public class BenchmarkMod : BenchmarkTest { - private OsuModDoubleTime mod; + private OsuModDoubleTime mod = null!; [Params(1, 10, 100)] public int Times { get; set; } diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index 2277f4e85c..082eb5e51b 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -15,9 +15,9 @@ namespace osu.Game.Benchmarks { public class BenchmarkRealmReads : BenchmarkTest { - private TemporaryNativeStorage storage; - private RealmAccess realm; - private UpdateThread updateThread; + private TemporaryNativeStorage storage = null!; + private RealmAccess realm = null!; + private UpdateThread updateThread = null!; [Params(1, 100, 1000)] public int ReadsPerFetch { get; set; } diff --git a/osu.Game.Benchmarks/BenchmarkRuleset.cs b/osu.Game.Benchmarks/BenchmarkRuleset.cs index 2835ec9499..7d318e043b 100644 --- a/osu.Game.Benchmarks/BenchmarkRuleset.cs +++ b/osu.Game.Benchmarks/BenchmarkRuleset.cs @@ -11,9 +11,9 @@ namespace osu.Game.Benchmarks { public class BenchmarkRuleset : BenchmarkTest { - private OsuRuleset ruleset; - private APIMod apiModDoubleTime; - private APIMod apiModDifficultyAdjust; + private OsuRuleset ruleset = null!; + private APIMod apiModDoubleTime = null!; + private APIMod apiModDifficultyAdjust = null!; public override void SetUp() { From 378be99fe1cc9d2608ed4e0901e8bfd3164518e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sat, 9 Jul 2022 21:12:35 +0800 Subject: [PATCH 043/145] Remove un-need null check. --- osu.Game.Benchmarks/BenchmarkRealmReads.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkRealmReads.cs b/osu.Game.Benchmarks/BenchmarkRealmReads.cs index 082eb5e51b..1df77320d2 100644 --- a/osu.Game.Benchmarks/BenchmarkRealmReads.cs +++ b/osu.Game.Benchmarks/BenchmarkRealmReads.cs @@ -133,9 +133,9 @@ namespace osu.Game.Benchmarks [GlobalCleanup] public void Cleanup() { - realm?.Dispose(); - storage?.Dispose(); - updateThread?.Exit(); + realm.Dispose(); + storage.Dispose(); + updateThread.Exit(); } } } From 5fcc4bf713937e1edbcae416532b3a29f43844dd Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 9 Jul 2022 12:10:18 -0700 Subject: [PATCH 044/145] Add failing sample playback disabled check --- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index 8e325838ff..ff985de70e 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -24,6 +24,7 @@ namespace osu.Game.Tests.Visual.Editing public void TestCantExitWithoutSaving() { AddRepeatStep("Exit", () => InputManager.Key(Key.Escape), 10); + AddAssert("Sample playback disabled", () => Editor.SamplePlaybackDisabled.Value); AddAssert("Editor is still active screen", () => Game.ScreenStack.CurrentScreen is Editor); } From 834bb1f187a268f25be9b031cfc2319b7c56e13c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 9 Jul 2022 12:14:39 -0700 Subject: [PATCH 045/145] Fix editor playing object samples while paused after cancelling exit --- osu.Game/Screens/Edit/Editor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 6ec9ff4e89..2b763415cd 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -921,7 +921,7 @@ namespace osu.Game.Screens.Edit private void cancelExit() { - samplePlaybackDisabled.Value = false; + updateSampleDisabledState(); loader?.CancelPendingDifficultySwitch(); } From 52aef09cd6528bc8541f3d77305053dbce3da769 Mon Sep 17 00:00:00 2001 From: Ludio235 <94078268+Ludio235@users.noreply.github.com> Date: Sun, 10 Jul 2022 02:05:40 +0000 Subject: [PATCH 046/145] Update PlaylistsRoomSettingsOverlay.cs --- .../OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 1e565e298e..9c6a2a5e0b 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -140,9 +140,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists }, new Section("Duration") { - Child = DurationField = new DurationDropdown + Child = new Container { RelativeSizeAxes = Axes.X, + Height = 40, + Child = DurationField = new DurationDropdown + { + RelativeSizeAxes = Axes.X + } } }, new Section("Allowed attempts (across all playlist items)") From 6443338251e5c392ae71b3d4ee5410ddc6b36463 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Sun, 10 Jul 2022 01:22:22 -0400 Subject: [PATCH 047/145] use cursor position instead of destination for dampLength calculation the destination vector is clamped within playfield borders, we want dampLength to be based on distance from the cursor. --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 1cb5bf31a4..17bbba4ba7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs @@ -69,24 +69,24 @@ namespace osu.Game.Rulesets.Osu.Mods switch (drawable) { case DrawableHitCircle circle: - easeTo(circle, destination); + easeTo(circle, destination, cursorPos); break; case DrawableSlider slider: if (!slider.HeadCircle.Result.HasResult) - easeTo(slider, destination); + easeTo(slider, destination, cursorPos); else - easeTo(slider, destination - slider.Ball.DrawPosition); + easeTo(slider, destination - slider.Ball.DrawPosition, cursorPos); break; } } } - private void easeTo(DrawableHitObject hitObject, Vector2 destination) + private void easeTo(DrawableHitObject hitObject, Vector2 destination, Vector2 cursorPos) { - double dampLength = Vector2.Distance(hitObject.Position, destination) / (0.04 * RepulsionStrength.Value + 0.04); + double dampLength = Vector2.Distance(hitObject.Position, cursorPos) / (0.04 * RepulsionStrength.Value + 0.04); float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); From 8116a4b6f6a2779d74172c168afeee2f593d0bed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 10 Jul 2022 23:53:06 +0900 Subject: [PATCH 048/145] Fix multiplayer spectator crash due to track potentially not being loaded in time --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index bae25dc9f8..f5af110372 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -314,6 +314,9 @@ namespace osu.Game.Screens.OnlinePlay.Match public override void OnSuspending(ScreenTransitionEvent e) { + // Should be a noop in most cases, but let's ensure beyond doubt that the beatmap is in a correct state. + updateWorkingBeatmap(); + onLeaving(); base.OnSuspending(e); } From 8b6665cb5b211c06e24f9d3e9bf210480bc08760 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 02:51:54 +0900 Subject: [PATCH 049/145] Ensure initial beatmap processing is done inside the import transaction --- osu.Game/Database/RealmArchiveModelImporter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 6cea92f1d9..aa7fac07a8 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -338,11 +338,11 @@ namespace osu.Game.Database // import to store realm.Add(item); + PostImport(item, realm); + transaction.Commit(); } - PostImport(item, realm); - LogForModel(item, @"Import successfully completed!"); } catch (Exception e) @@ -479,7 +479,7 @@ namespace osu.Game.Database } /// - /// Perform any final actions after the import has been committed to the database. + /// Perform any final actions before the import has been committed to the database. /// /// The model prepared for import. /// The current realm context. From 0434c1091472e1582aa144e9f1ec77059acad4e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 02:57:44 +0900 Subject: [PATCH 050/145] Use global `WorkingBeatmap` in `PlayerArea` for the time being --- .../Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 5bae4f9ea5..302d04b531 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -53,9 +53,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate [CanBeNull] public Score Score { get; private set; } - [Resolved] - private BeatmapManager beatmapManager { get; set; } - private readonly BindableDouble volumeAdjustment = new BindableDouble(); private readonly Container gameplayContent; private readonly LoadingLayer loadingLayer; @@ -84,6 +81,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate GameplayClock.Source = masterClock; } + [Resolved] + private IBindable beatmap { get; set; } + public void LoadScore([NotNull] Score score) { if (Score != null) @@ -91,7 +91,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Score = score; - gameplayContent.Child = new PlayerIsolationContainer(beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.BeatmapInfo), Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) + gameplayContent.Child = new PlayerIsolationContainer(beatmap.Value, Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) { RelativeSizeAxes = Axes.Both, Child = stack = new OsuScreenStack From 48911b956af8bbb1835fbd73de06572846b4f2bf Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Sun, 10 Jul 2022 17:07:21 -0700 Subject: [PATCH 051/145] Remove ClearTransformsAfter call A bit weird only having one call on its own; probably deserves an entire PR dedicated to adding ClearTransformsAfter calls --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index 26d3f053ff..f18c4529ab 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -73,8 +73,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void onHitObjectApplied(DrawableHitObject drawableObject) { - ClearTransformsAfter(double.MinValue); - this.ScaleTo(1f) .FadeOut(); } From 958c0fb390ea04ac37f11e6547b8bb95ce9b31a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 15:01:16 +0900 Subject: [PATCH 052/145] Remove `Appveyor.TestLogger` --- .../osu.Game.Rulesets.EmptyFreeform.Tests.csproj | 1 - .../osu.Game.Rulesets.Pippidon.Tests.csproj | 1 - .../osu.Game.Rulesets.EmptyScrolling.Tests.csproj | 1 - .../osu.Game.Rulesets.Pippidon.Tests.csproj | 1 - .../osu.Game.Rulesets.Catch.Tests.csproj | 1 - .../osu.Game.Rulesets.Mania.Tests.csproj | 1 - osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 1 - .../osu.Game.Rulesets.Taiko.Tests.csproj | 1 - osu.Game.Tests/osu.Game.Tests.csproj | 1 - osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 1 - 10 files changed, 10 deletions(-) diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index bc285dbe11..011a37cbdc 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -9,7 +9,6 @@ false - diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 718ada1905..c04f6132f3 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -9,7 +9,6 @@ false - diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index 6b9c3f4d63..529054fd4f 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -9,7 +9,6 @@ false - diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 718ada1905..c04f6132f3 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -9,7 +9,6 @@ false - diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index b957ade952..3ac1491946 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -1,7 +1,6 @@  - diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index d3b4b378c0..d07df75864 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -1,7 +1,6 @@  - diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 2c0d3fd937..402cf3ad41 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -1,7 +1,6 @@  - diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index ce468d399b..51d4bbc630 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -1,7 +1,6 @@  - diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index a1eef4ce47..02fd6ccd16 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -1,7 +1,6 @@  - diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 6fd53d923b..5512b26863 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -4,7 +4,6 @@ osu.Game.Tournament.Tests.TournamentTestRunner - From 22a51fdc50f8444034049eca370c0aaaf19fc83f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 15:35:00 +0900 Subject: [PATCH 053/145] Add support for a drawings screen video background --- osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 32da4d1b36..0df6386dae 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -97,6 +97,11 @@ namespace osu.Game.Tournament.Screens.Drawings FillMode = FillMode.Fill, Texture = textures.Get(@"Backgrounds/Drawings/background.png") }, + new TourneyVideo("drawings") + { + Loop = true, + RelativeSizeAxes = Axes.Both, + }, // Visualiser new VisualiserContainer { From 10d6027c8921937c2b8e100239174d87b0bee188 Mon Sep 17 00:00:00 2001 From: Andrew Hong <35881688+novialriptide@users.noreply.github.com> Date: Thu, 7 Jul 2022 23:16:06 -0400 Subject: [PATCH 054/145] Assign missing UserID to RealmUser --- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Scoring/ScoreImporter.cs | 28 +++++++++++++++++++++++++++- osu.Game/Scoring/ScoreManager.cs | 5 +++-- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index a16252ac89..4b5c9c0815 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -272,7 +272,7 @@ namespace osu.Game dependencies.Cache(difficultyCache = new BeatmapDifficultyCache()); // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() - dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, difficultyCache, LocalConfig)); + dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, API, difficultyCache, LocalConfig)); dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true)); diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index f59ffc7c94..7494914625 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -8,11 +8,15 @@ using System.Threading; using Newtonsoft.Json; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Game.Models; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Rulesets; using osu.Game.Scoring.Legacy; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using Realms; namespace osu.Game.Scoring @@ -26,11 +30,14 @@ namespace osu.Game.Scoring private readonly RulesetStore rulesets; private readonly Func beatmaps; - public ScoreImporter(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm) + private readonly IAPIProvider api; + + public ScoreImporter(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, IAPIProvider api) : base(storage, realm) { this.rulesets = rulesets; this.beatmaps = beatmaps; + this.api = api; } protected override ScoreInfo? CreateModel(ArchiveReader archive) @@ -68,5 +75,24 @@ namespace osu.Game.Scoring if (string.IsNullOrEmpty(model.StatisticsJson)) model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics); } + + protected override void PostImport(ScoreInfo model, Realm realm) + { + base.PostImport(model, realm); + + var userRequest = new GetUserRequest(model.User.Username); + api.Perform(userRequest); + APIUser userReq = userRequest.Response; + + if (!(userReq is null)) { + Logger.Log($"Assignning UserID to RealmUser"); + var user = new RealmUser + { + OnlineID = userReq.Id, + Username = model.User.Username + }; + model.RealmUser = user; + } + } } } diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 6ee1d11f83..9aed8904e6 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -21,6 +21,7 @@ using osu.Game.IO.Archives; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; +using osu.Game.Online.API; namespace osu.Game.Scoring { @@ -31,7 +32,7 @@ namespace osu.Game.Scoring private readonly OsuConfigManager configManager; private readonly ScoreImporter scoreImporter; - public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler, + public ScoreManager(RulesetStore rulesets, Func beatmaps, Storage storage, RealmAccess realm, Scheduler scheduler, IAPIProvider api, BeatmapDifficultyCache difficultyCache = null, OsuConfigManager configManager = null) : base(storage, realm) { @@ -39,7 +40,7 @@ namespace osu.Game.Scoring this.difficultyCache = difficultyCache; this.configManager = configManager; - scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm) + scoreImporter = new ScoreImporter(rulesets, beatmaps, storage, realm, api) { PostNotification = obj => PostNotification?.Invoke(obj) }; From 56896e8b415a7b9622a99561a98d48d001d52602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrew=20Hong=20=28=ED=99=8D=EC=A4=80=EC=9B=90=29?= <35881688+novialriptide@users.noreply.github.com> Date: Mon, 11 Jul 2022 02:20:39 -0400 Subject: [PATCH 055/145] Move PostImport() --- osu.Game/Database/RealmArchiveModelImporter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 6cea92f1d9..c143c51c6d 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -338,11 +338,11 @@ namespace osu.Game.Database // import to store realm.Add(item); + PostImport(item, realm); + transaction.Commit(); } - PostImport(item, realm); - LogForModel(item, @"Import successfully completed!"); } catch (Exception e) From 6220650ea3228930ebd27a46b9e4c8c3824abc73 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 11 Jul 2022 02:29:56 -0700 Subject: [PATCH 056/145] Fix dialog overlay not loading in time for headless test --- osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index ff985de70e..d7e9cc1bc0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -6,11 +6,13 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Overlays; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osu.Game.Screens.Select; @@ -23,6 +25,7 @@ namespace osu.Game.Tests.Visual.Editing [Test] public void TestCantExitWithoutSaving() { + AddUntilStep("Wait for dialog overlay load", () => ((Drawable)Game.Dependencies.Get()).IsLoaded); AddRepeatStep("Exit", () => InputManager.Key(Key.Escape), 10); AddAssert("Sample playback disabled", () => Editor.SamplePlaybackDisabled.Value); AddAssert("Editor is still active screen", () => Game.ScreenStack.CurrentScreen is Editor); From 44d2e001ed4d80674beb281e32a69241294364fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 20:15:29 +0900 Subject: [PATCH 057/145] Update various dependencies --- osu.Desktop/osu.Desktop.csproj | 2 +- .../osu.Game.Rulesets.Osu.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game/osu.Game.csproj | 14 +++++++------- osu.iOS.props | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index a4f9e2671b..c67017f175 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 402cf3ad41..4349d25cb3 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 02fd6ccd16..7615b3e8be 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 06b022ea44..1251ab800b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -20,12 +20,12 @@ - + - - - - + + + + @@ -38,8 +38,8 @@ - - + + diff --git a/osu.iOS.props b/osu.iOS.props index 9085205bbc..c38bb548bf 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -85,7 +85,7 @@ - + From 00c7101f540329333442390719113c3fedb25628 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 20:36:05 +0900 Subject: [PATCH 058/145] Remove `DrawingsScreen` world map completely --- .../Screens/Drawings/DrawingsScreen.cs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 0df6386dae..85afb9b2d0 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -12,8 +12,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Graphics; @@ -26,7 +24,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Drawings { - public class DrawingsScreen : TournamentScreen + public class DrawingsScreen : TournamentScreen, IProvideVideo { private const string results_filename = "drawings_results.txt"; @@ -45,7 +43,7 @@ namespace osu.Game.Tournament.Screens.Drawings public ITeamList TeamList; [BackgroundDependencyLoader] - private void load(TextureStore textures, Storage storage) + private void load(Storage storage) { RelativeSizeAxes = Axes.Both; @@ -91,12 +89,6 @@ namespace osu.Game.Tournament.Screens.Drawings RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new Sprite - { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fill, - Texture = textures.Get(@"Backgrounds/Drawings/background.png") - }, new TourneyVideo("drawings") { Loop = true, From 73e924479fb6db7b5e57413ef08663c068805f3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 11 Jul 2022 20:42:04 +0900 Subject: [PATCH 059/145] Find video by recursive check rather than marker interface Seems a lot more reliable, and allows falling back to the "main" video in cases which didn't support this previously. A next step may be to allow every screen to support a video based on its screen name, rather than specifying the local `TourneyVideo` every time. --- osu.Game.Tournament/Components/TourneyVideo.cs | 2 ++ .../Screens/Drawings/DrawingsScreen.cs | 2 +- .../Screens/Editors/TournamentEditorScreen.cs | 2 +- .../Screens/Gameplay/GameplayScreen.cs | 2 +- osu.Game.Tournament/Screens/IProvideVideo.cs | 14 -------------- osu.Game.Tournament/Screens/Ladder/LadderScreen.cs | 2 +- .../Screens/Schedule/ScheduleScreen.cs | 2 +- osu.Game.Tournament/Screens/Setup/SetupScreen.cs | 2 +- .../Screens/Showcase/ShowcaseScreen.cs | 2 +- .../Screens/TeamIntro/SeedingScreen.cs | 2 +- .../Screens/TeamIntro/TeamIntroScreen.cs | 2 +- .../Screens/TeamWin/TeamWinScreen.cs | 2 +- osu.Game.Tournament/TournamentSceneManager.cs | 3 ++- 13 files changed, 14 insertions(+), 25 deletions(-) delete mode 100644 osu.Game.Tournament/Screens/IProvideVideo.cs diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index c6bbb54f9a..2e79998e66 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -22,6 +22,8 @@ namespace osu.Game.Tournament.Components private Video video; private ManualClock manualClock; + public bool VideoAvailable => video != null; + public TourneyVideo(string filename, bool drawFallbackGradient = false) { this.filename = filename; diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 85afb9b2d0..5ac25f97b5 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -24,7 +24,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Drawings { - public class DrawingsScreen : TournamentScreen, IProvideVideo + public class DrawingsScreen : TournamentScreen { private const string results_filename = "drawings_results.txt"; diff --git a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs index 8af5bbe513..0fefe6f780 100644 --- a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs @@ -20,7 +20,7 @@ using osuTK; namespace osu.Game.Tournament.Screens.Editors { - public abstract class TournamentEditorScreen : TournamentScreen, IProvideVideo + public abstract class TournamentEditorScreen : TournamentScreen where TDrawable : Drawable, IModelBacked where TModel : class, new() { diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index 86b2c2a4e9..54ae4c0366 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -21,7 +21,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Gameplay { - public class GameplayScreen : BeatmapInfoScreen, IProvideVideo + public class GameplayScreen : BeatmapInfoScreen { private readonly BindableBool warmup = new BindableBool(); diff --git a/osu.Game.Tournament/Screens/IProvideVideo.cs b/osu.Game.Tournament/Screens/IProvideVideo.cs deleted file mode 100644 index aa67a5211f..0000000000 --- a/osu.Game.Tournament/Screens/IProvideVideo.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. - -#nullable disable - -namespace osu.Game.Tournament.Screens -{ - /// - /// Marker interface for a screen which provides its own local video background. - /// - public interface IProvideVideo - { - } -} diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs index 23bfa84afc..7ad7e76a1f 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Ladder { - public class LadderScreen : TournamentScreen, IProvideVideo + public class LadderScreen : TournamentScreen { protected Container MatchesContainer; private Container paths; diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index 7a11e26794..0827cbae69 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Schedule { - public class ScheduleScreen : TournamentScreen // IProvidesVideo + public class ScheduleScreen : TournamentScreen { private readonly Bindable currentMatch = new Bindable(); private Container mainContainer; diff --git a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs index 42eff3565f..f472541d59 100644 --- a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs @@ -19,7 +19,7 @@ using osuTK; namespace osu.Game.Tournament.Screens.Setup { - public class SetupScreen : TournamentScreen, IProvideVideo + public class SetupScreen : TournamentScreen { private FillFlowContainer fillFlow; diff --git a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs index 082aa99b0e..a7a175ceba 100644 --- a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs +++ b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs @@ -14,7 +14,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Showcase { - public class ShowcaseScreen : BeatmapInfoScreen // IProvideVideo + public class ShowcaseScreen : BeatmapInfoScreen { [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index 925c697346..719e0384d3 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -19,7 +19,7 @@ using osuTK; namespace osu.Game.Tournament.Screens.TeamIntro { - public class SeedingScreen : TournamentMatchScreen, IProvideVideo + public class SeedingScreen : TournamentMatchScreen { private Container mainContainer; diff --git a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs index 98dfaa7487..08c9a7a897 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs @@ -13,7 +13,7 @@ using osuTK; namespace osu.Game.Tournament.Screens.TeamIntro { - public class TeamIntroScreen : TournamentMatchScreen, IProvideVideo + public class TeamIntroScreen : TournamentMatchScreen { private Container mainContainer; diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index 50207547cd..07557674e8 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -14,7 +14,7 @@ using osuTK; namespace osu.Game.Tournament.Screens.TeamWin { - public class TeamWinScreen : TournamentMatchScreen, IProvideVideo + public class TeamWinScreen : TournamentMatchScreen { private Container mainContainer; diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 296b259d72..a12dbb4740 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -186,7 +187,7 @@ namespace osu.Game.Tournament var lastScreen = currentScreen; currentScreen = target; - if (currentScreen is IProvideVideo) + if (currentScreen.ChildrenOfType().FirstOrDefault()?.VideoAvailable == true) { video.FadeOut(200); From 09bfca4e4ad59da7574c8212c08f729b404aef10 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 11 Jul 2022 21:45:39 +0300 Subject: [PATCH 060/145] Fix build failing on tests --- .../Visual/Gameplay/TestScenePlayerLocalScoreImport.cs | 2 +- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs | 2 +- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 3011515b62..bfc06c0ee0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay { Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, Scheduler)); + Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 1b9b59676b..ef0c7d7d4d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.SongSelect dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, Scheduler)); + dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); return dependencies; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 8f890b2383..05b5c5c0cd 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); - Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Realm, Scheduler)); + Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index cee917f6cf..e59914f69a 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); - dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Realm, Scheduler)); + dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); return dependencies; From 4f009419b8d8402550a40664ae5dead774a6bd2c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 11 Jul 2022 21:46:42 +0300 Subject: [PATCH 061/145] Simplify population logic and match code style --- osu.Game/Scoring/ScoreImporter.cs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 7494914625..53dd511d57 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -8,7 +8,6 @@ using System.Threading; using Newtonsoft.Json; using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Game.Models; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO.Archives; @@ -80,19 +79,12 @@ namespace osu.Game.Scoring { base.PostImport(model, realm); - var userRequest = new GetUserRequest(model.User.Username); - api.Perform(userRequest); - APIUser userReq = userRequest.Response; + var userRequest = new GetUserRequest(model.RealmUser.Username); - if (!(userReq is null)) { - Logger.Log($"Assignning UserID to RealmUser"); - var user = new RealmUser - { - OnlineID = userReq.Id, - Username = model.User.Username - }; - model.RealmUser = user; - } + api.Perform(userRequest); + + if (userRequest.Response is APIUser user) + model.RealmUser.OnlineID = user.Id; } } } From 54fe84350cfba996d83e81b150bd19df0e63d0e5 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Mon, 11 Jul 2022 17:23:32 -0400 Subject: [PATCH 062/145] reciprocate mod incompatibility --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 4c9418726c..a3f6448457 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Automation; public override string Description => @"Automatic cursor movement - just follow the rhythm."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAutoplay), typeof(OsuModMagnetised), typeof(OsuModRepel) }; public bool PerformFail() => false; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index 5a08df3803..84906f6eed 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override string Description => "Everything rotates. EVERYTHING."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModWiggle), typeof(OsuModMagnetised), typeof(OsuModRepel) }; private float theta; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 3fba2cefd2..8acd4fc422 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override ModType Type => ModType.Fun; public override string Description => "They just won't stay still..."; public override double ScoreMultiplier => 1; - public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised) }; + public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel) }; private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles private const int wiggle_strength = 10; // Higher = stronger wiggles From 28278e2554246fa49764457bfdb09f33799f9289 Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Mon, 11 Jul 2022 17:27:25 -0400 Subject: [PATCH 063/145] enable NRT again --- osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs index 17bbba4ba7..211987ee32 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRepel.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRepel.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.Bindables; using osu.Framework.Utils; using osu.Game.Configuration; @@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModMagnetised) }; - private IFrameStableClock gameplayClock; + private IFrameStableClock? gameplayClock; [SettingSource("Repulsion strength", "How strong the repulsion is.", 0)] public BindableFloat RepulsionStrength { get; } = new BindableFloat(0.5f) @@ -86,6 +85,8 @@ namespace osu.Game.Rulesets.Osu.Mods private void easeTo(DrawableHitObject hitObject, Vector2 destination, Vector2 cursorPos) { + Debug.Assert(gameplayClock != null); + double dampLength = Vector2.Distance(hitObject.Position, cursorPos) / (0.04 * RepulsionStrength.Value + 0.04); float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); From 76be9a829c3df4a39c1070203dab99e13216e4a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 12:59:55 +0900 Subject: [PATCH 064/145] Fix mutation after disposal in `TeamEditorScreen` --- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 11db37c8b7..111893d18c 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -298,10 +298,10 @@ namespace osu.Game.Tournament.Screens.Editors }, true); } - private void updatePanel() + private void updatePanel() => Scheduler.AddOnce(() => { drawableContainer.Child = new UserGridPanel(user.ToAPIUser()) { Width = 300 }; - } + }); } } } From bae314a254b1c93aac01f9889bb0c34b8ce443b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 13:03:43 +0900 Subject: [PATCH 065/145] Add background on `SetupScreen` to hide video --- .../Screens/Setup/SetupScreen.cs | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs index f472541d59..2b2dce3664 100644 --- a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs @@ -9,6 +9,8 @@ using osu.Framework.Bindables; using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Online.API; using osu.Game.Overlays; @@ -48,13 +50,21 @@ namespace osu.Game.Tournament.Screens.Setup { windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); - InternalChild = fillFlow = new FillFlowContainer + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding(10), - Spacing = new Vector2(10), + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.Gray(0.2f), + }, + fillFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(10), + Spacing = new Vector2(10), + } }; api.LocalUser.BindValueChanged(_ => Schedule(reload)); @@ -74,7 +84,8 @@ namespace osu.Game.Tournament.Screens.Setup Action = () => sceneManager?.SetScreen(new StablePathSelectScreen()), Value = fileBasedIpc?.IPCStorage?.GetFullPath(string.Empty) ?? "Not found", Failing = fileBasedIpc?.IPCStorage == null, - Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation." + Description = + "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation." }, new ActionableInfo { From 90fecbc9c7435ff5c2f3077b8fdebb96ff3d6c52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 14:32:39 +0900 Subject: [PATCH 066/145] Add test showing all mod icons for reference --- .../Visual/UserInterface/TestSceneModIcon.cs | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs index 0a0415789a..ce9aa682d1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModIcon.cs @@ -1,10 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; @@ -13,10 +13,24 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneModIcon : OsuTestScene { + [Test] + public void TestShowAllMods() + { + AddStep("create mod icons", () => + { + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Full, + ChildrenEnumerable = Ruleset.Value.CreateInstance().CreateAllMods().Select(m => new ModIcon(m)), + }; + }); + } + [Test] public void TestChangeModType() { - ModIcon icon = null; + ModIcon icon = null!; AddStep("create mod icon", () => Child = icon = new ModIcon(new OsuModDoubleTime())); AddStep("change mod", () => icon.Mod = new OsuModEasy()); @@ -25,7 +39,7 @@ namespace osu.Game.Tests.Visual.UserInterface [Test] public void TestInterfaceModType() { - ModIcon icon = null; + ModIcon icon = null!; var ruleset = new OsuRuleset(); From 8dbe24fd7c05d8f30cf500b8d77dfa17094c2320 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 14:36:50 +0900 Subject: [PATCH 067/145] Simplify colour assigning logic and remove system mod colour for now --- osu.Game/Rulesets/UI/ModIcon.cs | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/UI/ModIcon.cs b/osu.Game/Rulesets/UI/ModIcon.cs index 308122b71c..b1f355a789 100644 --- a/osu.Game/Rulesets/UI/ModIcon.cs +++ b/osu.Game/Rulesets/UI/ModIcon.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Mods; using osuTK; using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Localisation; namespace osu.Game.Rulesets.UI @@ -53,7 +54,6 @@ namespace osu.Game.Rulesets.UI private OsuColour colours { get; set; } private Color4 backgroundColour; - private Color4 highlightedColour; /// /// Construct a new instance. @@ -123,19 +123,13 @@ namespace osu.Game.Rulesets.UI modAcronym.FadeOut(); } - Color4 typeColour = colours.ForModType(value.Type); - backgroundColour = typeColour; - highlightedColour = ((Colour4)typeColour).Lighten(.2f); - - if (value.Type == ModType.System) - modIcon.Colour = colours.Yellow; - + backgroundColour = colours.ForModType(value.Type); updateColour(); } private void updateColour() { - background.Colour = Selected.Value ? highlightedColour : backgroundColour; + background.Colour = Selected.Value ? backgroundColour.Lighten(0.2f) : backgroundColour; } } } From b52ea161336ce148e1d511dbb9111289ad9f2972 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 15:10:59 +0900 Subject: [PATCH 068/145] Show basic error message when score submission fails --- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 1cc0d1853d..c916791eaa 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -168,7 +168,7 @@ namespace osu.Game.Screens.Play request.Failure += e => { - Logger.Error(e, "Failed to submit score"); + Logger.Error(e, $"Failed to submit score ({e.Message})"); scoreSubmissionSource.SetResult(false); }; From fa626a82b3448b8da0130cadff09e240e236ba39 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 15:18:52 +0900 Subject: [PATCH 069/145] Add missed incompatilibity rules --- osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index d562c37541..490b5b7a9d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModAutoplay : ModAutoplay { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray(); public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) => new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" }); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 2cf8c278ca..5c1de83972 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset, IApplicableToPlayer { public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAlternate) }).ToArray(); /// /// How early before a hitobject's start time to trigger a hit. From cafe30fc4dc0d21dc885dd224568b88f6b4f545a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 14:54:45 +0900 Subject: [PATCH 070/145] Fix potential crash during shutdown sequence if intro playback was aborted Fixes one of the audio related `ObjectDisposedException`s (https://sentry.ppy.sh/organizations/ppy/issues/92/events/12f282f048cb4a4fae85810e8a70b68d/?project=2&query=is%3Aunresolved&sort=freq&statsPeriod=7d). Ran into this while testing locally. See `IntroScreen.ensureEventuallyArrivingAtMenu` for the related cause of this happening (forced continuing to next screen if the intro doesn't load in time). --- osu.Game/Screens/Menu/IntroTriangles.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 3cdf51a87c..c228faae58 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -72,9 +72,16 @@ namespace osu.Game.Screens.Menu RelativeSizeAxes = Axes.Both, Clock = decoupledClock, LoadMenu = LoadMenu - }, t => + }, _ => { - AddInternal(t); + 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 the game may already be in a disposing state + // which will trigger errors during attempted audio playback. + if (DidLoadMenu) + return; + if (!UsingThemedIntro) welcome?.Play(); From 10a14f39ed3ead07664dcf24edc0b70dc469c717 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 15:42:20 +0900 Subject: [PATCH 071/145] Show an error message on startup when attempting to run on an unsupported version of windows A lot of sentry error reports are coming from realm / EF failures due to the host operating system being too old. Let's give the user some proper feedback rather than a silent crash and error report hitting our logging. --- osu.Desktop/Program.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 712f300671..da2a580086 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -14,6 +14,7 @@ using osu.Framework.Platform; using osu.Game; using osu.Game.IPC; using osu.Game.Tournament; +using SDL2; using Squirrel; namespace osu.Desktop @@ -29,7 +30,19 @@ namespace osu.Desktop { // run Squirrel first, as the app may exit after these run if (OperatingSystem.IsWindows()) + { + var windowsVersion = Environment.OSVersion.Version; + + if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 1)) + { + SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, + "Your operating system is too old to run osu!", + "This version of osu! requires at least Windows 8 to run.\nPlease upgrade your operating system or consider using an older version of osu!.", IntPtr.Zero); + return; + } + setupSquirrel(); + } // Back up the cwd before DesktopGameHost changes it string cwd = Environment.CurrentDirectory; From a36f7867250558ceebcb947ee489564401601b81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 16:16:46 +0900 Subject: [PATCH 072/145] Change minimum version to Windows 8.1 instead of Windows 8 --- osu.Desktop/Program.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index da2a580086..66b361cb73 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -33,7 +33,9 @@ namespace osu.Desktop { var windowsVersion = Environment.OSVersion.Version; - if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 1)) + // While .NET 6 still supports Windows 7 and above, we are limited by realm currently, as they choose to only support 8.1 and higher. + // See https://www.mongodb.com/docs/realm/sdk/dotnet/#supported-platforms + if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2)) { SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, "Your operating system is too old to run osu!", From cad18ebc5844277bad61c50cac8a40c429c5da80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 16:46:19 +0900 Subject: [PATCH 073/145] Reword comment to better explain what we are guarding against --- osu.Game/Screens/Menu/IntroTriangles.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index c228faae58..6ad0350e43 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -77,8 +77,9 @@ namespace osu.Game.Screens.Menu 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 the game may already be in a disposing state - // which will trigger errors during attempted audio playback. + // 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; From 1bef2d7b3946d6856da6574aa1cbaed244547755 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 04:04:21 +0900 Subject: [PATCH 074/145] Add and consume `SoloScoreInfo` --- osu.Game/Online/API/APIAccess.cs | 2 +- .../Responses/APIScoreWithPosition.cs | 4 +- .../Requests/Responses/APIScoresCollection.cs | 2 +- .../API/Requests/Responses/SoloScoreInfo.cs | 124 ++++++++++++++++++ .../BeatmapSet/Scores/ScoresContainer.cs | 2 +- 5 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 088dc56701..43cea7fb97 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -38,7 +38,7 @@ namespace osu.Game.Online.API public string WebsiteRootUrl { get; } - public int APIVersion => 20220217; // We may want to pull this from the game version eventually. + public int APIVersion => 20220705; // We may want to pull this from the game version eventually. public Exception LastLoginError { get; private set; } diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs index 8bd54f889d..494826f534 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs @@ -16,11 +16,11 @@ namespace osu.Game.Online.API.Requests.Responses public int? Position; [JsonProperty(@"score")] - public APIScore Score; + public SoloScoreInfo Score; public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) { - var score = Score.CreateScoreInfo(rulesets, beatmap); + var score = Score.ToScoreInfo(rulesets, beatmap); score.Position = Position; return score; } diff --git a/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs b/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs index 9c8a38c63a..38c67d92f4 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs @@ -11,7 +11,7 @@ namespace osu.Game.Online.API.Requests.Responses public class APIScoresCollection { [JsonProperty(@"scores")] - public List Scores; + public List Scores; [JsonProperty(@"userScore")] public APIScoreWithPosition UserScore; diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs new file mode 100644 index 0000000000..71479d2867 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -0,0 +1,124 @@ +// 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.CodeAnalysis; +using System.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; + +namespace osu.Game.Online.API.Requests.Responses +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + [Serializable] + public class SoloScoreInfo : IHasOnlineID + { + [JsonIgnore] + public long id { get; set; } + + public int user_id { get; set; } + + public int beatmap_id { get; set; } + + public int ruleset_id { get; set; } + + public int? build_id { get; set; } + + public bool passed { get; set; } + + public int total_score { get; set; } + + public double accuracy { get; set; } + + public APIUser user { get; set; } + + // TODO: probably want to update this column to match user stats (short)? + public int max_combo { get; set; } + + [JsonConverter(typeof(StringEnumConverter))] + public ScoreRank rank { get; set; } + + public DateTimeOffset? started_at { get; set; } + + public DateTimeOffset? ended_at { get; set; } + + public List mods { get; set; } = new List(); + + [JsonProperty("statistics")] + public Dictionary Statistics { get; set; } = new Dictionary(); + + public override string ToString() => $"score_id: {id} user_id: {user_id}"; + + [JsonIgnore] + public DateTimeOffset created_at { get; set; } + + [JsonIgnore] + public DateTimeOffset updated_at { get; set; } + + [JsonIgnore] + public DateTimeOffset? deleted_at { get; set; } + + /// + /// Create a from an API score instance. + /// + /// A ruleset store, used to populate a ruleset instance in the returned score. + /// An optional beatmap, copied into the returned score (for cases where the API does not populate the beatmap). + /// + public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) + { + var ruleset = rulesets.GetRuleset(ruleset_id) ?? throw new InvalidOperationException($"Ruleset with ID of {ruleset_id} not found locally"); + + var rulesetInstance = ruleset.CreateInstance(); + + var modInstances = mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray(); + + // all API scores provided by this class are considered to be legacy. + modInstances = modInstances.Append(rulesetInstance.CreateMod()).ToArray(); + + var scoreInfo = new ScoreInfo + { + User = user ?? new APIUser { Id = user_id }, + BeatmapInfo = beatmap ?? new BeatmapInfo { OnlineID = beatmap_id }, + Passed = passed, + TotalScore = total_score, + Accuracy = accuracy, + MaxCombo = max_combo, + Rank = rank, + Statistics = Statistics, + OnlineID = OnlineID, + Date = ended_at ?? DateTimeOffset.Now, + // PP = + Hash = "online", // TODO: temporary? + Ruleset = ruleset, + Mods = modInstances, + }; + + return scoreInfo; + } + + public ScoreInfo CreateScoreInfo(Mod[] mods) => new ScoreInfo + { + OnlineID = id, + User = new APIUser { Id = user_id }, + BeatmapInfo = new BeatmapInfo { OnlineID = beatmap_id }, + Ruleset = new RulesetInfo { OnlineID = ruleset_id }, + Passed = passed, + TotalScore = total_score, + Accuracy = accuracy, + MaxCombo = max_combo, + Rank = rank, + Mods = mods, + Statistics = Statistics + }; + + public long OnlineID => id; + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index c2e54d0d7b..98202ba7c0 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores scoreTable.Show(); var userScore = value.UserScore; - var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets, beatmapInfo); + var userScoreInfo = userScore?.Score.ToScoreInfo(rulesets, beatmapInfo); topScoresContainer.Add(new DrawableTopScore(topScore)); From 900e0ace8ed24edf99b47748b00217e3dfe642ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 16:59:56 +0900 Subject: [PATCH 075/145] Standardise naming and enable NRT --- .../API/Requests/Responses/SoloScoreInfo.cs | 124 ++++++++++-------- 1 file changed, 71 insertions(+), 53 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 71479d2867..5dce2db6ed 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -1,10 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -17,54 +15,73 @@ using osu.Game.Scoring; namespace osu.Game.Online.API.Requests.Responses { - [SuppressMessage("ReSharper", "InconsistentNaming")] [Serializable] public class SoloScoreInfo : IHasOnlineID { [JsonIgnore] - public long id { get; set; } + [JsonProperty(" ")] + public long ID { get; set; } - public int user_id { get; set; } + [JsonProperty("beatmap_id")] + public int BeatmapID { get; set; } - public int beatmap_id { get; set; } + [JsonProperty("ruleset_id")] + public int RulesetID { get; set; } - public int ruleset_id { get; set; } + [JsonProperty("build_id")] + public int? BuildID { get; set; } - public int? build_id { get; set; } + [JsonProperty("passed")] + public bool Passed { get; set; } - public bool passed { get; set; } + [JsonProperty("total_score")] + public int TotalScore { get; set; } - public int total_score { get; set; } + [JsonProperty("accuracy")] + public double Accuracy { get; set; } - public double accuracy { get; set; } + [JsonProperty("user_id")] + public int UserID { get; set; } - public APIUser user { get; set; } + /// + /// User may be provided via an osu-web response, but will not be present from raw database json. + /// + [JsonProperty("user")] + public APIUser? User { get; set; } // TODO: probably want to update this column to match user stats (short)? - public int max_combo { get; set; } + [JsonProperty("max_combo")] + public int MaxCombo { get; set; } [JsonConverter(typeof(StringEnumConverter))] - public ScoreRank rank { get; set; } + [JsonProperty("rank")] + public ScoreRank Rank { get; set; } - public DateTimeOffset? started_at { get; set; } + [JsonProperty("started_at")] + public DateTimeOffset? StartedAt { get; set; } - public DateTimeOffset? ended_at { get; set; } + [JsonProperty("ended_at")] + public DateTimeOffset? EndedAt { get; set; } - public List mods { get; set; } = new List(); + [JsonProperty("mods")] + public List Mods { get; set; } = new List(); + + [JsonIgnore] + [JsonProperty("created_at")] + public DateTimeOffset CreatedAt { get; set; } + + [JsonIgnore] + [JsonProperty("updated_at")] + public DateTimeOffset UpdatedAt { get; set; } + + [JsonIgnore] + [JsonProperty("deleted_at")] + public DateTimeOffset? DeletedAt { get; set; } [JsonProperty("statistics")] public Dictionary Statistics { get; set; } = new Dictionary(); - public override string ToString() => $"score_id: {id} user_id: {user_id}"; - - [JsonIgnore] - public DateTimeOffset created_at { get; set; } - - [JsonIgnore] - public DateTimeOffset updated_at { get; set; } - - [JsonIgnore] - public DateTimeOffset? deleted_at { get; set; } + public override string ToString() => $"score_id: {ID} user_id: {UserID}"; /// /// Create a from an API score instance. @@ -72,53 +89,54 @@ namespace osu.Game.Online.API.Requests.Responses /// A ruleset store, used to populate a ruleset instance in the returned score. /// An optional beatmap, copied into the returned score (for cases where the API does not populate the beatmap). /// - public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) + public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo? beatmap = null) { - var ruleset = rulesets.GetRuleset(ruleset_id) ?? throw new InvalidOperationException($"Ruleset with ID of {ruleset_id} not found locally"); + var ruleset = rulesets.GetRuleset(RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {RulesetID} not found locally"); var rulesetInstance = ruleset.CreateInstance(); - var modInstances = mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray(); + var modInstances = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray(); // all API scores provided by this class are considered to be legacy. modInstances = modInstances.Append(rulesetInstance.CreateMod()).ToArray(); var scoreInfo = new ScoreInfo { - User = user ?? new APIUser { Id = user_id }, - BeatmapInfo = beatmap ?? new BeatmapInfo { OnlineID = beatmap_id }, - Passed = passed, - TotalScore = total_score, - Accuracy = accuracy, - MaxCombo = max_combo, - Rank = rank, - Statistics = Statistics, OnlineID = OnlineID, - Date = ended_at ?? DateTimeOffset.Now, - // PP = - Hash = "online", // TODO: temporary? + User = User ?? new APIUser { Id = UserID }, + BeatmapInfo = beatmap ?? new BeatmapInfo { OnlineID = BeatmapID }, Ruleset = ruleset, + Passed = Passed, + TotalScore = TotalScore, + Accuracy = Accuracy, + MaxCombo = MaxCombo, + Rank = Rank, + Statistics = Statistics, + Date = EndedAt ?? DateTimeOffset.Now, + Hash = "online", // TODO: temporary? Mods = modInstances, }; return scoreInfo; } - public ScoreInfo CreateScoreInfo(Mod[] mods) => new ScoreInfo + public ScoreInfo ToScoreInfo(Mod[] mods) => new ScoreInfo { - OnlineID = id, - User = new APIUser { Id = user_id }, - BeatmapInfo = new BeatmapInfo { OnlineID = beatmap_id }, - Ruleset = new RulesetInfo { OnlineID = ruleset_id }, - Passed = passed, - TotalScore = total_score, - Accuracy = accuracy, - MaxCombo = max_combo, - Rank = rank, + OnlineID = ID, + User = new APIUser { Id = UserID }, + BeatmapInfo = new BeatmapInfo { OnlineID = BeatmapID }, + Ruleset = new RulesetInfo { OnlineID = RulesetID }, + Passed = Passed, + TotalScore = TotalScore, + Accuracy = Accuracy, + MaxCombo = MaxCombo, + Rank = Rank, + Statistics = Statistics, + Date = EndedAt ?? DateTimeOffset.Now, + Hash = "online", // TODO: temporary? Mods = mods, - Statistics = Statistics }; - public long OnlineID => id; + public long OnlineID => ID; } } From f956955d4d66a6955cafdf066d1c4034b46c64ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 17:14:31 +0900 Subject: [PATCH 076/145] Combine `ScoreInfo` construction helper methods --- .../API/Requests/Responses/SoloScoreInfo.cs | 33 ++++++++----------- .../BeatmapSet/Scores/ScoresContainer.cs | 2 +- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 2 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 5dce2db6ed..71c6c3adab 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -89,40 +89,33 @@ namespace osu.Game.Online.API.Requests.Responses /// A ruleset store, used to populate a ruleset instance in the returned score. /// An optional beatmap, copied into the returned score (for cases where the API does not populate the beatmap). /// - public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo? beatmap = null) + public ScoreInfo ToScoreInfo(RulesetStore rulesets, BeatmapInfo? beatmap = null) { var ruleset = rulesets.GetRuleset(RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {RulesetID} not found locally"); var rulesetInstance = ruleset.CreateInstance(); - var modInstances = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray(); + var mods = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray(); // all API scores provided by this class are considered to be legacy. - modInstances = modInstances.Append(rulesetInstance.CreateMod()).ToArray(); + mods = mods.Append(rulesetInstance.CreateMod()).ToArray(); - var scoreInfo = new ScoreInfo - { - OnlineID = OnlineID, - User = User ?? new APIUser { Id = UserID }, - BeatmapInfo = beatmap ?? new BeatmapInfo { OnlineID = BeatmapID }, - Ruleset = ruleset, - Passed = Passed, - TotalScore = TotalScore, - Accuracy = Accuracy, - MaxCombo = MaxCombo, - Rank = Rank, - Statistics = Statistics, - Date = EndedAt ?? DateTimeOffset.Now, - Hash = "online", // TODO: temporary? - Mods = modInstances, - }; + var scoreInfo = ToScoreInfo(mods); + + scoreInfo.Ruleset = ruleset; + if (beatmap != null) scoreInfo.BeatmapInfo = beatmap; return scoreInfo; } + /// + /// Create a from an API score instance. + /// + /// The mod instances, resolved from a ruleset. + /// public ScoreInfo ToScoreInfo(Mod[] mods) => new ScoreInfo { - OnlineID = ID, + OnlineID = OnlineID, User = new APIUser { Id = UserID }, BeatmapInfo = new BeatmapInfo { OnlineID = BeatmapID }, Ruleset = new RulesetInfo { OnlineID = RulesetID }, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 98202ba7c0..e50fc356eb 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -87,7 +87,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores MD5Hash = apiBeatmap.MD5Hash }; - scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets, beatmapInfo)).ToArray(), loadCancellationSource.Token) + scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.ToScoreInfo(rulesets, beatmapInfo)).ToArray(), loadCancellationSource.Token) .ContinueWith(task => Schedule(() => { if (loadCancellationSource.IsCancellationRequested) diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 4813cd65db..4312c9528f 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -33,7 +33,7 @@ namespace osu.Game.Screens.Ranking return null; getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset); - getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineID != Score.OnlineID).Select(s => s.CreateScoreInfo(rulesets, Beatmap.Value.BeatmapInfo))); + getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineID != Score.OnlineID).Select(s => s.ToScoreInfo(rulesets, Beatmap.Value.BeatmapInfo))); return getScoreRequest; } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 43eaff56b3..b497943dfa 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Select.Leaderboards req.Success += r => { - scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.CreateScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), cancellationToken) + scoreManager.OrderByTotalScoreAsync(r.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo)).ToArray(), cancellationToken) .ContinueWith(task => Schedule(() => { if (cancellationToken.IsCancellationRequested) From 12a56e36bd69d5ad5a4a90db476cab75f5eefacb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 17:46:58 +0900 Subject: [PATCH 077/145] Fix `ID` mapping and move osu-web additions to region to identify them clearly --- .../API/Requests/Responses/SoloScoreInfo.cs | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 71c6c3adab..dea0de4608 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -18,9 +18,8 @@ namespace osu.Game.Online.API.Requests.Responses [Serializable] public class SoloScoreInfo : IHasOnlineID { - [JsonIgnore] - [JsonProperty(" ")] - public long ID { get; set; } + [JsonProperty("replay")] + public bool HasReplay { get; set; } [JsonProperty("beatmap_id")] public int BeatmapID { get; set; } @@ -43,12 +42,6 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("user_id")] public int UserID { get; set; } - /// - /// User may be provided via an osu-web response, but will not be present from raw database json. - /// - [JsonProperty("user")] - public APIUser? User { get; set; } - // TODO: probably want to update this column to match user stats (short)? [JsonProperty("max_combo")] public int MaxCombo { get; set; } @@ -81,6 +74,19 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("statistics")] public Dictionary Statistics { get; set; } = new Dictionary(); + #region osu-web API additions (not stored to database). + + [JsonProperty("id")] + public long? ID { get; set; } + + [JsonProperty("user")] + public APIUser? User { get; set; } + + [JsonProperty("pp")] + public double? PP { get; set; } + + #endregion + public override string ToString() => $"score_id: {ID} user_id: {UserID}"; /// @@ -116,7 +122,7 @@ namespace osu.Game.Online.API.Requests.Responses public ScoreInfo ToScoreInfo(Mod[] mods) => new ScoreInfo { OnlineID = OnlineID, - User = new APIUser { Id = UserID }, + User = User ?? new APIUser { Id = UserID }, BeatmapInfo = new BeatmapInfo { OnlineID = BeatmapID }, Ruleset = new RulesetInfo { OnlineID = RulesetID }, Passed = Passed, @@ -127,9 +133,11 @@ namespace osu.Game.Online.API.Requests.Responses Statistics = Statistics, Date = EndedAt ?? DateTimeOffset.Now, Hash = "online", // TODO: temporary? + HasReplay = HasReplay, Mods = mods, + PP = PP, }; - public long OnlineID => ID; + public long OnlineID => ID ?? -1; } } From 0fe3bac1739b2f5961489292a5702783d39dad38 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 12 Jul 2022 17:51:20 +0900 Subject: [PATCH 078/145] Store mods to array and update test scenes --- .../Visual/Online/TestSceneScoresContainer.cs | 49 ++++++++++--------- .../API/Requests/Responses/SoloScoreInfo.cs | 2 +- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 16a34e996f..b772a28194 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -18,6 +18,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Users; using osuTK.Graphics; @@ -146,12 +147,12 @@ namespace osu.Game.Tests.Visual.Online { var scores = new APIScoresCollection { - Scores = new List + Scores = new List { - new APIScore + new SoloScoreInfo { - Date = DateTimeOffset.Now, - OnlineID = onlineID++, + EndedAt = DateTimeOffset.Now, + ID = onlineID++, User = new APIUser { Id = 6602580, @@ -175,10 +176,10 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 1234567890, Accuracy = 1, }, - new APIScore + new SoloScoreInfo { - Date = DateTimeOffset.Now, - OnlineID = onlineID++, + EndedAt = DateTimeOffset.Now, + ID = onlineID++, User = new APIUser { Id = 4608074, @@ -201,10 +202,10 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 1234789, Accuracy = 0.9997, }, - new APIScore + new SoloScoreInfo { - Date = DateTimeOffset.Now, - OnlineID = onlineID++, + EndedAt = DateTimeOffset.Now, + ID = onlineID++, User = new APIUser { Id = 1014222, @@ -226,10 +227,10 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 12345678, Accuracy = 0.9854, }, - new APIScore + new SoloScoreInfo { - Date = DateTimeOffset.Now, - OnlineID = onlineID++, + EndedAt = DateTimeOffset.Now, + ID = onlineID++, User = new APIUser { Id = 1541390, @@ -250,10 +251,10 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 1234567, Accuracy = 0.8765, }, - new APIScore + new SoloScoreInfo { - Date = DateTimeOffset.Now, - OnlineID = onlineID++, + EndedAt = DateTimeOffset.Now, + ID = onlineID++, User = new APIUser { Id = 7151382, @@ -275,12 +276,12 @@ namespace osu.Game.Tests.Visual.Online foreach (var s in scores.Scores) { - s.Statistics = new Dictionary + s.Statistics = new Dictionary { - { "count_300", RNG.Next(2000) }, - { "count_100", RNG.Next(2000) }, - { "count_50", RNG.Next(2000) }, - { "count_miss", RNG.Next(2000) } + { HitResult.Great, RNG.Next(2000) }, + { HitResult.Good, RNG.Next(2000) }, + { HitResult.Meh, RNG.Next(2000) }, + { HitResult.Miss, RNG.Next(2000) } }; } @@ -289,10 +290,10 @@ namespace osu.Game.Tests.Visual.Online private APIScoreWithPosition createUserBest() => new APIScoreWithPosition { - Score = new APIScore + Score = new SoloScoreInfo { - Date = DateTimeOffset.Now, - OnlineID = onlineID++, + EndedAt = DateTimeOffset.Now, + ID = onlineID++, User = new APIUser { Id = 7151382, diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index dea0de4608..b70da194a5 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -57,7 +57,7 @@ namespace osu.Game.Online.API.Requests.Responses public DateTimeOffset? EndedAt { get; set; } [JsonProperty("mods")] - public List Mods { get; set; } = new List(); + public APIMod[] Mods { get; set; } = Array.Empty(); [JsonIgnore] [JsonProperty("created_at")] From 363e23c2518b9c8bf21f99beec65ddbbc894e062 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 12 Jul 2022 18:47:43 +0900 Subject: [PATCH 079/145] Use correct HitResult in test --- osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index b772a28194..be03328caa 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -279,7 +279,7 @@ namespace osu.Game.Tests.Visual.Online s.Statistics = new Dictionary { { HitResult.Great, RNG.Next(2000) }, - { HitResult.Good, RNG.Next(2000) }, + { HitResult.Ok, RNG.Next(2000) }, { HitResult.Meh, RNG.Next(2000) }, { HitResult.Miss, RNG.Next(2000) } }; From b96734e31a9184b2a636b5435d122d7d5b3d32df Mon Sep 17 00:00:00 2001 From: Gabe Livengood <47010459+ggliv@users.noreply.github.com> Date: Tue, 12 Jul 2022 08:43:48 -0400 Subject: [PATCH 080/145] fix mod incompatibility between repel and relax --- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 5c1de83972..2cf8c278ca 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset, IApplicableToPlayer { public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAlternate) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate) }).ToArray(); /// /// How early before a hitobject's start time to trigger a hit. From c04658584285fb302d8df96e56279ba1fe59a806 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 12 Jul 2022 18:29:17 +0300 Subject: [PATCH 081/145] Fix unsupported OS message stating Windows 8 to be supported --- osu.Desktop/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 66b361cb73..cebbcb40b7 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -39,7 +39,7 @@ namespace osu.Desktop { SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, "Your operating system is too old to run osu!", - "This version of osu! requires at least Windows 8 to run.\nPlease upgrade your operating system or consider using an older version of osu!.", IntPtr.Zero); + "This version of osu! requires at least Windows 8.1 to run.\nPlease upgrade your operating system or consider using an older version of osu!.", IntPtr.Zero); return; } From f90f93a43cb8110f1b4555af4416ff165cf0bdde Mon Sep 17 00:00:00 2001 From: James <51536154+tsunyoku@users.noreply.github.com> Date: Tue, 12 Jul 2022 23:07:10 +0100 Subject: [PATCH 082/145] abstract OsuModAlternate into InputBlockingMod --- .../Mods/InputBlockingMod.cs | 106 ++++++++++++++++++ osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 97 +--------------- 2 files changed, 112 insertions(+), 91 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs new file mode 100644 index 0000000000..b86e06ec5e --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs @@ -0,0 +1,106 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; +using osu.Game.Utils; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public abstract class InputBlockingMod : Mod, IApplicableToDrawableRuleset + { + public override double ScoreMultiplier => 1.0; + public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) }; + public override ModType Type => ModType.Conversion; + + protected const double FLASH_DURATION = 1000; + + /// + /// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods). + /// + /// + /// This is different from in that the periods here end strictly at the first object after the break, rather than the break's end time. + /// + protected PeriodTracker NonGameplayPeriods; + + protected OsuAction? LastActionPressed; + protected DrawableRuleset Ruleset; + + protected IFrameStableClock GameplayClock; + + public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + { + Ruleset = drawableRuleset; + drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); + + var periods = new List(); + + if (drawableRuleset.Objects.Any()) + { + periods.Add(new Period(int.MinValue, getValidJudgementTime(Ruleset.Objects.First()) - 1)); + + foreach (BreakPeriod b in drawableRuleset.Beatmap.Breaks) + periods.Add(new Period(b.StartTime, getValidJudgementTime(Ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1)); + + static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh); + } + + NonGameplayPeriods = new PeriodTracker(periods); + + GameplayClock = drawableRuleset.FrameStableClock; + } + + protected virtual bool CheckCorrectAction(OsuAction action) + { + if (NonGameplayPeriods.IsInAny(GameplayClock.CurrentTime)) + { + LastActionPressed = null; + return true; + } + + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + break; + + // Any action which is not left or right button should be ignored. + default: + return true; + } + + return false; + } + + private class InputInterceptor : Component, IKeyBindingHandler + { + private readonly InputBlockingMod mod; + + public InputInterceptor(InputBlockingMod mod) + { + this.mod = mod; + } + + public bool OnPressed(KeyBindingPressEvent e) + // if the pressed action is incorrect, block it from reaching gameplay. + => !mod.CheckCorrectAction(e.Action); + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 622d2df432..2ad9789799 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -3,117 +3,32 @@ #nullable disable -using System; -using System.Collections.Generic; -using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Beatmaps.Timing; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Scoring; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; -using osu.Game.Utils; namespace osu.Game.Rulesets.Osu.Mods { - public class OsuModAlternate : Mod, IApplicableToDrawableRuleset + public class OsuModAlternate : InputBlockingMod { public override string Name => @"Alternate"; public override string Acronym => @"AL"; public override string Description => @"Don't use the same key twice in a row!"; - public override double ScoreMultiplier => 1.0; - public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) }; - public override ModType Type => ModType.Conversion; public override IconUsage? Icon => FontAwesome.Solid.Keyboard; - private const double flash_duration = 1000; - - /// - /// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods). - /// - /// - /// This is different from in that the periods here end strictly at the first object after the break, rather than the break's end time. - /// - private PeriodTracker nonGameplayPeriods; - - private OsuAction? lastActionPressed; - private DrawableRuleset ruleset; - - private IFrameStableClock gameplayClock; - - public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) + protected override bool CheckCorrectAction(OsuAction action) { - ruleset = drawableRuleset; - drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); - - var periods = new List(); - - if (drawableRuleset.Objects.Any()) - { - periods.Add(new Period(int.MinValue, getValidJudgementTime(ruleset.Objects.First()) - 1)); - - foreach (BreakPeriod b in drawableRuleset.Beatmap.Breaks) - periods.Add(new Period(b.StartTime, getValidJudgementTime(ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1)); - - static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh); - } - - nonGameplayPeriods = new PeriodTracker(periods); - - gameplayClock = drawableRuleset.FrameStableClock; - } - - private bool checkCorrectAction(OsuAction action) - { - if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime)) - { - lastActionPressed = null; + if (base.CheckCorrectAction(action)) return true; - } - switch (action) - { - case OsuAction.LeftButton: - case OsuAction.RightButton: - break; - - // Any action which is not left or right button should be ignored. - default: - return true; - } - - if (lastActionPressed != action) + if (LastActionPressed != action) { // User alternated correctly. - lastActionPressed = action; + LastActionPressed = action; return true; } - ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint); + Ruleset.Cursor.FlashColour(Colour4.Red, FLASH_DURATION, Easing.OutQuint); return false; } - - private class InputInterceptor : Component, IKeyBindingHandler - { - private readonly OsuModAlternate mod; - - public InputInterceptor(OsuModAlternate mod) - { - this.mod = mod; - } - - public bool OnPressed(KeyBindingPressEvent e) - // if the pressed action is incorrect, block it from reaching gameplay. - => !mod.checkCorrectAction(e.Action); - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - } } } From c05263c3c39e5828d320ba5d11bac9b2ec1436d1 Mon Sep 17 00:00:00 2001 From: James <51536154+tsunyoku@users.noreply.github.com> Date: Tue, 12 Jul 2022 23:07:26 +0100 Subject: [PATCH 083/145] add Single Tap mod --- osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs | 38 +++++++++++++++++++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs new file mode 100644 index 0000000000..22211b70c1 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using osu.Framework.Graphics; + +namespace osu.Game.Rulesets.Osu.Mods +{ + public class OsuModSingleTap : InputBlockingMod + { + public override string Name => @"Single Tap"; + public override string Acronym => @"ST"; + public override string Description => @"You must only use one key!"; + + protected override bool CheckCorrectAction(OsuAction action) + { + if (base.CheckCorrectAction(action)) + return true; + + if (LastActionPressed == null) + { + // First keypress, store the expected action. + LastActionPressed = action; + return true; + } + + if (LastActionPressed == action) + { + // User singletapped correctly. + return true; + } + + Ruleset.Cursor.FlashColour(Colour4.Red, FLASH_DURATION, Easing.OutQuint); + return false; + } + } +} diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index ba0ef9ec3a..302194e91a 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Osu new OsuModClassic(), new OsuModRandom(), new OsuModMirror(), - new OsuModAlternate(), + new MultiMod(new OsuModAlternate(), new OsuModSingleTap()) }; case ModType.Automation: From 20d2b8619327632c76fc8fc7b654a0b2a3e12c27 Mon Sep 17 00:00:00 2001 From: James <51536154+tsunyoku@users.noreply.github.com> Date: Tue, 12 Jul 2022 23:07:41 +0100 Subject: [PATCH 084/145] make Single Tap incompatible with Autoplay, Cinema and Relax --- osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index 490b5b7a9d..c4de77b8a3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModAutoplay : ModAutoplay { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModRepel), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray(); public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) => new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" }); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs index 656cf95e77..704b922ee5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModCinema : ModCinema { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray(); public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) => new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" }); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 2cf8c278ca..2030156f2e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods public class OsuModRelax : ModRelax, IUpdatableByPlayfield, IApplicableToDrawableRuleset, IApplicableToPlayer { public override string Description => @"You don't need to click. Give your clicking/tapping fingers a break from the heat of things."; - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot), typeof(OsuModMagnetised), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray(); /// /// How early before a hitobject's start time to trigger a hit. From 886efbcbdfb06cde32eedcd277dca18b11a874bf Mon Sep 17 00:00:00 2001 From: James <51536154+tsunyoku@users.noreply.github.com> Date: Tue, 12 Jul 2022 23:08:00 +0100 Subject: [PATCH 085/145] add test scene for Single Tap mod --- .../Mods/TestSceneOsuModSingleTap.cs | 177 ++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs new file mode 100644 index 0000000000..0fe35fac0b --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs @@ -0,0 +1,177 @@ +// 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 NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Replays; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class TestSceneOsuModSingleTap : OsuModTestScene + { + [Test] + public void TestInputSingular() => CreateModTest(new ModTestData + { + Mod = new OsuModSingleTap(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 2, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 500, + Position = new Vector2(100), + }, + new HitCircle + { + StartTime = 1000, + Position = new Vector2(200, 100), + }, + new HitCircle + { + StartTime = 1500, + Position = new Vector2(300, 100), + }, + new HitCircle + { + StartTime = 2000, + Position = new Vector2(400, 100), + }, + }, + }, + ReplayFrames = new List + { + new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(100)), + new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.LeftButton), + } + }); + + [Test] + public void TestInputAlternating() => CreateModTest(new ModTestData + { + Mod = new OsuModSingleTap(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 1, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 500, + Position = new Vector2(100), + }, + new HitCircle + { + StartTime = 1000, + Position = new Vector2(200, 100), + }, + }, + }, + ReplayFrames = new List + { + new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(100)), + new OsuReplayFrame(1000, new Vector2(200, 100), OsuAction.RightButton), + new OsuReplayFrame(1001, new Vector2(200, 100)), + new OsuReplayFrame(1500, new Vector2(300, 100), OsuAction.LeftButton), + new OsuReplayFrame(1501, new Vector2(300, 100)), + new OsuReplayFrame(2000, new Vector2(400, 100), OsuAction.RightButton), + new OsuReplayFrame(2001, new Vector2(400, 100)), + } + }); + + /// + /// Ensures singletapping is reset before the first hitobject after intro. + /// + [Test] + public void TestInputAlternatingAtIntro() => CreateModTest(new ModTestData + { + Mod = new OsuModSingleTap(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 1, + Autoplay = false, + Beatmap = new Beatmap + { + HitObjects = new List + { + new HitCircle + { + StartTime = 1000, + Position = new Vector2(100), + }, + }, + }, + ReplayFrames = new List + { + // first press during intro. + new OsuReplayFrame(500, new Vector2(200), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(200)), + // press different key at hitobject and ensure it has been hit. + new OsuReplayFrame(1000, new Vector2(100), OsuAction.RightButton), + } + }); + + /// + /// Ensures singletapping is reset before the first hitobject after a break. + /// + [Test] + public void TestInputAlternatingWithBreak() => CreateModTest(new ModTestData + { + Mod = new OsuModSingleTap(), + PassCondition = () => Player.ScoreProcessor.Combo.Value == 0 && Player.ScoreProcessor.HighestCombo.Value == 2, + Autoplay = false, + Beatmap = new Beatmap + { + Breaks = new List + { + new BreakPeriod(500, 2000), + }, + HitObjects = new List + { + new HitCircle + { + StartTime = 500, + Position = new Vector2(100), + }, + new HitCircle + { + StartTime = 2500, + Position = new Vector2(500, 100), + }, + new HitCircle + { + StartTime = 3000, + Position = new Vector2(500, 100), + }, + } + }, + ReplayFrames = new List + { + // first press to start singletap lock. + new OsuReplayFrame(500, new Vector2(100), OsuAction.LeftButton), + new OsuReplayFrame(501, new Vector2(100)), + // press different key after break but before hit object. + new OsuReplayFrame(2250, new Vector2(300, 100), OsuAction.RightButton), + new OsuReplayFrame(2251, new Vector2(300, 100)), + // press same key at second hitobject and ensure it has been hit. + new OsuReplayFrame(2500, new Vector2(500, 100), OsuAction.LeftButton), + new OsuReplayFrame(2501, new Vector2(500, 100)), + // press different key at third hitobject and ensure it has been missed. + new OsuReplayFrame(3000, new Vector2(500, 100), OsuAction.RightButton), + new OsuReplayFrame(3001, new Vector2(500, 100)), + } + }); + } +} From e9b0a3e4fafeb42d3dc45afc07b8afc5fc7a8d75 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Wed, 13 Jul 2022 07:35:53 +0100 Subject: [PATCH 086/145] make alternate and singletap incompatible with eachother --- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 3 +++ osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 2ad9789799..cc4591562e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -3,6 +3,8 @@ #nullable disable +using System; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; @@ -14,6 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Acronym => @"AL"; public override string Description => @"Don't use the same key twice in a row!"; public override IconUsage? Icon => FontAwesome.Solid.Keyboard; + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray(); protected override bool CheckCorrectAction(OsuAction action) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs index 22211b70c1..b2b6e11f48 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs @@ -3,6 +3,8 @@ #nullable disable +using System; +using System.Linq; using osu.Framework.Graphics; namespace osu.Game.Rulesets.Osu.Mods @@ -12,6 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Name => @"Single Tap"; public override string Acronym => @"ST"; public override string Description => @"You must only use one key!"; + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray(); protected override bool CheckCorrectAction(OsuAction action) { From 6755a771b4adab14885aa9e2cf3fc1ffcaaf59a5 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Wed, 13 Jul 2022 07:49:08 +0100 Subject: [PATCH 087/145] make Cinema incompatible with InputBlockingMod --- osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs index b86e06ec5e..ee6f072c4b 100644 --- a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Mods public abstract class InputBlockingMod : Mod, IApplicableToDrawableRuleset { public override double ScoreMultiplier => 1.0; - public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax) }; + public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(OsuModCinema) }; public override ModType Type => ModType.Conversion; protected const double FLASH_DURATION = 1000; From 27ef7fc78eda72a56fda70003f5f60e7d51430b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 16:22:50 +0900 Subject: [PATCH 088/145] Add log output for custom storage usage Sometimes I am not sure where my osu! is reading files from. This should help somewhat. ```csharp /Users/dean/Projects/osu/osu.Desktop/bin/Debug/net6.0/osu! [runtime] 2022-07-13 07:22:03 [verbose]: Starting legacy IPC provider... [runtime] 2022-07-13 07:22:03 [verbose]: Attempting to use custom storage location /Users/dean/Games/osu-lazer-2 [runtime] 2022-07-13 07:22:03 [verbose]: Storage successfully changed to /Users/dean/Games/osu-lazer-2. [runtime] 2022-07-13 07:22:05 [verbose]: GL Initialized ``` --- osu.Game/IO/OsuStorage.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 89bdd09f0d..368ac56850 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -94,6 +94,8 @@ namespace osu.Game.IO error = OsuStorageError.None; Storage lastStorage = UnderlyingStorage; + Logger.Log($"Attempting to use custom storage location {CustomStoragePath}"); + try { Storage userStorage = host.GetStorage(CustomStoragePath); @@ -102,6 +104,7 @@ namespace osu.Game.IO error = OsuStorageError.AccessibleButEmpty; ChangeTargetStorage(userStorage); + Logger.Log($"Storage successfully changed to {CustomStoragePath}."); } catch { @@ -109,6 +112,9 @@ namespace osu.Game.IO ChangeTargetStorage(lastStorage); } + if (error != OsuStorageError.None) + Logger.Log($"Custom storage location could not be used ({error})."); + return error == OsuStorageError.None; } From 8820ea4006b1bd2119071a51fa5d1c786ff26014 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 16:36:43 +0900 Subject: [PATCH 089/145] Add last played date to `BeatmapInfo` --- osu.Game/Beatmaps/BeatmapInfo.cs | 5 +++++ osu.Game/Database/RealmAccess.cs | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 346bf86818..3b851e05c0 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -110,6 +110,11 @@ namespace osu.Game.Beatmaps public bool SamplesMatchPlaybackRate { get; set; } = true; + /// + /// The time at which this beatmap was last played by the local user. + /// + 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 ). diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 8cf57b802b..02b5a51f1f 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -58,8 +58,9 @@ namespace osu.Game.Database /// 12 2021-11-24 Add Status to RealmBeatmapSet. /// 13 2022-01-13 Final migration of beatmaps and scores to realm (multiple new storage fields). /// 14 2022-03-01 Added BeatmapUserSettings to BeatmapInfo. + /// 15 2022-07-13 Added LastPlayed to BeatmapInfo. /// - private const int schema_version = 14; + private const int schema_version = 15; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. From 4b96d74b0cd7d800b0e6cdbe9caffc00a8e3ec6c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 16:43:39 +0900 Subject: [PATCH 090/145] Add test coverage of `LastPlayed` updating --- .../Gameplay/TestScenePlayerLocalScoreImport.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index bfc06c0ee0..5ec9e88728 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -59,6 +60,20 @@ namespace osu.Game.Tests.Visual.Gameplay protected override bool AllowFail => false; + [Test] + public void TestLastPlayedUpdated() + { + DateTimeOffset? getLastPlayed() => Realm.Run(r => r.Find(Beatmap.Value.BeatmapInfo.ID)?.LastPlayed); + + AddStep("set no custom ruleset", () => customRuleset = null); + AddAssert("last played is null", () => getLastPlayed() == null); + + CreateTest(); + + AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); + AddUntilStep("wait for last played to update", () => getLastPlayed() != null); + } + [Test] public void TestScoreStoredLocally() { From ab3ec80159f6b20e9eb0c470287fb1562267f971 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 16:36:55 +0900 Subject: [PATCH 091/145] Update `LastPlayed` on gameplay starting in a `SubmittingPlayer` --- osu.Game/Screens/Play/SubmittingPlayer.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index c916791eaa..f3bc0ea798 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -11,6 +11,8 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Scoring; @@ -117,6 +119,23 @@ namespace osu.Game.Screens.Play await submitScore(score).ConfigureAwait(false); } + [Resolved] + private RealmAccess realm { get; set; } + + protected override void StartGameplay() + { + base.StartGameplay(); + + // User expectation is that last played should be updated when entering the gameplay loop + // from multiplayer / playlists / solo, even when using autoplay mod. + realm.WriteAsync(r => + { + var realmBeatmap = r.Find(Beatmap.Value.BeatmapInfo.ID); + if (realmBeatmap != null) + realmBeatmap.LastPlayed = DateTimeOffset.Now; + }); + } + public override bool OnExiting(ScreenExitEvent e) { bool exiting = base.OnExiting(e); From fc274629f8e574ac70dcc0895e6762b67a4a418a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 16:37:05 +0900 Subject: [PATCH 092/145] Add "last played" sort mode to song select Note that this will consider the most recent play of any beatmap in beatmap set groups for now, similar to other sort methods. --- osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 3 +++ osu.Game/Screens/Select/Filter/SortMode.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 36ec536780..94d911692c 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -81,6 +81,9 @@ namespace osu.Game.Screens.Select.Carousel case SortMode.DateAdded: return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded); + case SortMode.LastPlayed: + return -compareUsingAggregateMax(otherSet, b => (b.LastPlayed ?? DateTimeOffset.MinValue).ToUnixTimeSeconds()); + case SortMode.BPM: return compareUsingAggregateMax(otherSet, b => b.BPM); diff --git a/osu.Game/Screens/Select/Filter/SortMode.cs b/osu.Game/Screens/Select/Filter/SortMode.cs index 48f774393e..4227114618 100644 --- a/osu.Game/Screens/Select/Filter/SortMode.cs +++ b/osu.Game/Screens/Select/Filter/SortMode.cs @@ -23,6 +23,9 @@ namespace osu.Game.Screens.Select.Filter [Description("Date Added")] DateAdded, + [Description("Last Played")] + LastPlayed, + [LocalisableDescription(typeof(BeatmapsStrings), nameof(BeatmapsStrings.ListingSearchSortingDifficulty))] Difficulty, From 11c8a2c16e1e79737308731a519e44b0c978cd67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 17:34:33 +0900 Subject: [PATCH 093/145] Disable tournament client "save changes" button when there's no changes to save --- osu.Game.Tournament/SaveChangesOverlay.cs | 101 ++++++++++++++++++++++ osu.Game.Tournament/TournamentGame.cs | 37 +------- osu.Game.Tournament/TournamentGameBase.cs | 17 ++-- osu.Game.Tournament/TourneyButton.cs | 3 + 4 files changed, 117 insertions(+), 41 deletions(-) create mode 100644 osu.Game.Tournament/SaveChangesOverlay.cs diff --git a/osu.Game.Tournament/SaveChangesOverlay.cs b/osu.Game.Tournament/SaveChangesOverlay.cs new file mode 100644 index 0000000000..03a3f2d3a4 --- /dev/null +++ b/osu.Game.Tournament/SaveChangesOverlay.cs @@ -0,0 +1,101 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Online.Multiplayer; +using osuTK; + +namespace osu.Game.Tournament +{ + internal class SaveChangesOverlay : CompositeDrawable + { + [Resolved] + private TournamentGame tournamentGame { get; set; } + + private string lastSerialisedLadder; + private readonly TourneyButton saveChangesButton; + + public SaveChangesOverlay() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = new Container + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Position = new Vector2(5), + CornerRadius = 10, + Masking = true, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = OsuColour.Gray(0.2f), + RelativeSizeAxes = Axes.Both, + }, + saveChangesButton = new TourneyButton + { + Text = "Save Changes", + Width = 140, + Height = 50, + Padding = new MarginPadding + { + Top = 10, + Left = 10, + }, + Margin = new MarginPadding + { + Right = 10, + Bottom = 10, + }, + Action = saveChanges, + }, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + scheduleNextCheck(); + } + + private async Task checkForChanges() + { + string serialisedLadder = await Task.Run(() => tournamentGame.GetSerialisedLadder()); + + // If a save hasn't been triggered by the user yet, populate the initial value + lastSerialisedLadder ??= serialisedLadder; + + if (lastSerialisedLadder != serialisedLadder && !saveChangesButton.Enabled.Value) + { + saveChangesButton.Enabled.Value = true; + saveChangesButton.Background + .FadeColour(saveChangesButton.BackgroundColour.Lighten(0.5f), 500, Easing.In).Then() + .FadeColour(saveChangesButton.BackgroundColour, 500, Easing.Out) + .Loop(); + } + + scheduleNextCheck(); + } + + private void scheduleNextCheck() => Scheduler.AddDelayed(() => checkForChanges().FireAndForget(), 1000); + + private void saveChanges() + { + tournamentGame.SaveChanges(); + lastSerialisedLadder = tournamentGame.GetSerialisedLadder(); + + saveChangesButton.Enabled.Value = false; + saveChangesButton.Background.FadeColour(saveChangesButton.BackgroundColour, 500); + } + } +} diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 537fbfc038..7d67bfa759 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -11,8 +11,6 @@ using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Handlers.Mouse; using osu.Framework.Logging; using osu.Framework.Platform; @@ -20,11 +18,11 @@ using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Tournament.Models; -using osuTK; using osuTK.Graphics; namespace osu.Game.Tournament { + [Cached] public class TournamentGame : TournamentGameBase { public static ColourInfo GetTeamColour(TeamColour teamColour) => teamColour == TeamColour.Red ? COLOUR_RED : COLOUR_BLUE; @@ -78,40 +76,9 @@ namespace osu.Game.Tournament LoadComponentsAsync(new[] { - new Container + new SaveChangesOverlay { - CornerRadius = 10, Depth = float.MinValue, - Position = new Vector2(5), - Masking = true, - AutoSizeAxes = Axes.Both, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Children = new Drawable[] - { - new Box - { - Colour = OsuColour.Gray(0.2f), - RelativeSizeAxes = Axes.Both, - }, - new TourneyButton - { - Text = "Save Changes", - Width = 140, - Height = 50, - Padding = new MarginPadding - { - Top = 10, - Left = 10, - }, - Margin = new MarginPadding - { - Right = 10, - Bottom = 10, - }, - Action = SaveChanges, - }, - } }, heightWarning = new WarningBox("Please make the window wider") { diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 75c9f17d4c..f2a35ea5b3 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -295,7 +295,7 @@ namespace osu.Game.Tournament } } - protected virtual void SaveChanges() + public void SaveChanges() { if (!bracketLoadTaskCompletionSource.Task.IsCompletedSuccessfully) { @@ -311,7 +311,16 @@ namespace osu.Game.Tournament .ToList(); // Serialise before opening stream for writing, so if there's a failure it will leave the file in the previous state. - string serialisedLadder = JsonConvert.SerializeObject(ladder, + string serialisedLadder = GetSerialisedLadder(); + + using (var stream = storage.CreateFileSafely(BRACKET_FILENAME)) + using (var sw = new StreamWriter(stream)) + sw.Write(serialisedLadder); + } + + public string GetSerialisedLadder() + { + return JsonConvert.SerializeObject(ladder, new JsonSerializerSettings { Formatting = Formatting.Indented, @@ -319,10 +328,6 @@ namespace osu.Game.Tournament DefaultValueHandling = DefaultValueHandling.Ignore, Converters = new JsonConverter[] { new JsonPointConverter() } }); - - using (var stream = storage.CreateFileSafely(BRACKET_FILENAME)) - using (var sw = new StreamWriter(stream)) - sw.Write(serialisedLadder); } protected override UserInputManager CreateUserInputManager() => new TournamentInputManager(); diff --git a/osu.Game.Tournament/TourneyButton.cs b/osu.Game.Tournament/TourneyButton.cs index f5a82771f5..f1b14df783 100644 --- a/osu.Game.Tournament/TourneyButton.cs +++ b/osu.Game.Tournament/TourneyButton.cs @@ -3,12 +3,15 @@ #nullable disable +using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.UserInterface; namespace osu.Game.Tournament { public class TourneyButton : OsuButton { + public new Box Background => base.Background; + public TourneyButton() : base(null) { From b9ad90ce54ecf92709de51df29642d38a0bc6783 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 17:57:45 +0900 Subject: [PATCH 094/145] Switch `TeamWinScreen` scheduling to `AddOnce` --- osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index 07557674e8..ac54ff58f5 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tournament.Screens.TeamWin private bool firstDisplay = true; - private void update() => Schedule(() => + private void update() => Scheduler.AddOnce(() => { var match = CurrentMatch.Value; From 4dff999ce665b20cd87b1fdb7064863fe8da6286 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 18:09:54 +0900 Subject: [PATCH 095/145] Fix potential null referenced in `SeedingScreen` Also ensure that any update operations only occur when the seeding screen is displayed. They were running in the background until now. --- osu.Game.Tournament/Models/SeedingBeatmap.cs | 4 +--- .../Screens/TeamIntro/SeedingScreen.cs | 13 ++++++++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tournament/Models/SeedingBeatmap.cs b/osu.Game.Tournament/Models/SeedingBeatmap.cs index 03beb7ca9a..fb0e20556c 100644 --- a/osu.Game.Tournament/Models/SeedingBeatmap.cs +++ b/osu.Game.Tournament/Models/SeedingBeatmap.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 Newtonsoft.Json; using osu.Framework.Bindables; @@ -13,7 +11,7 @@ namespace osu.Game.Tournament.Models public int ID; [JsonProperty("BeatmapInfo")] - public TournamentBeatmap Beatmap; + public TournamentBeatmap? Beatmap; public long Score; diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index 719e0384d3..4970d520af 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro currentTeam.BindValueChanged(teamChanged, true); } - private void teamChanged(ValueChangedEvent team) + private void teamChanged(ValueChangedEvent team) => Scheduler.AddOnce(() => { if (team.NewValue == null) { @@ -78,7 +78,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro } showTeam(team.NewValue); - } + }); protected override void CurrentMatchChanged(ValueChangedEvent match) { @@ -120,8 +120,14 @@ namespace osu.Game.Tournament.Screens.TeamIntro foreach (var seeding in team.SeedingResults) { fill.Add(new ModRow(seeding.Mod.Value, seeding.Seed.Value)); + foreach (var beatmap in seeding.Beatmaps) + { + if (beatmap.Beatmap == null) + continue; + fill.Add(new BeatmapScoreRow(beatmap)); + } } } @@ -157,7 +163,8 @@ namespace osu.Game.Tournament.Screens.TeamIntro Children = new Drawable[] { new TournamentSpriteText { Text = beatmap.Score.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Width = 80 }, - new TournamentSpriteText { Text = "#" + beatmap.Seed.Value.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.Regular) }, + new TournamentSpriteText + { Text = "#" + beatmap.Seed.Value.ToString("#,0"), Colour = TournamentGame.TEXT_COLOUR, Font = OsuFont.Torus.With(weight: FontWeight.Regular) }, } }, }; From 1516756d8bfa3de7a26966be4ad083b5b800768c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 18:10:27 +0900 Subject: [PATCH 096/145] Fix team name not updating on `TeamDisplay` immediately --- .../Components/TournamentSpriteTextWithBackground.cs | 3 ++- .../Screens/Gameplay/Components/TeamDisplay.cs | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs b/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs index 0fc3646585..b088670caa 100644 --- a/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs +++ b/osu.Game.Tournament/Components/TournamentSpriteTextWithBackground.cs @@ -12,7 +12,8 @@ namespace osu.Game.Tournament.Components { public class TournamentSpriteTextWithBackground : CompositeDrawable { - protected readonly TournamentSpriteText Text; + public readonly TournamentSpriteText Text; + protected readonly Box Background; public TournamentSpriteTextWithBackground(string text = "") diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs index bb187c9e67..1eceddd871 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs @@ -16,6 +16,10 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components { private readonly TeamScore score; + private readonly TournamentSpriteTextWithBackground teamText; + + private readonly Bindable teamName = new Bindable("???"); + private bool showScore; public bool ShowScore @@ -93,7 +97,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components } } }, - new TournamentSpriteTextWithBackground(team?.FullName.Value ?? "???") + teamText = new TournamentSpriteTextWithBackground { Scale = new Vector2(0.5f), Origin = anchor, @@ -113,6 +117,11 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components updateDisplay(); FinishTransforms(true); + + if (Team != null) + teamName.BindTo(Team.FullName); + + teamName.BindValueChanged(name => teamText.Text.Text = name.NewValue, true); } private void updateDisplay() From 5c6fa2341f8fe3bcfdc02df08652538fd82fd3d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 18:15:59 +0900 Subject: [PATCH 097/145] Fix `TeamScoreDisplay` not tracking team changes properly --- .../Screens/Gameplay/Components/TeamScoreDisplay.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs index ed11f097ed..5ee57e9271 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs @@ -42,6 +42,8 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components currentMatch.BindTo(ladder.CurrentMatch); currentMatch.BindValueChanged(matchChanged); + currentTeam.BindValueChanged(teamChanged); + updateMatch(); } @@ -67,7 +69,7 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components // team may change to same team, which means score is not in a good state. // thus we handle this manually. - teamChanged(currentTeam.Value); + currentTeam.TriggerChange(); } protected override bool OnMouseDown(MouseDownEvent e) @@ -88,11 +90,11 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components return base.OnMouseDown(e); } - private void teamChanged(TournamentTeam team) + private void teamChanged(ValueChangedEvent team) { InternalChildren = new Drawable[] { - teamDisplay = new TeamDisplay(team, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0), + teamDisplay = new TeamDisplay(team.NewValue, teamColour, currentTeamScore, currentMatch.Value?.PointsToWin ?? 0), }; } } From 214351a87e3022eed3e929cd866ef4e1511ac553 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 18:32:47 +0900 Subject: [PATCH 098/145] Ensure any changes are committed before changing `LadderEditorSettings`'s target match --- .../Screens/Ladder/Components/LadderEditorSettings.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs index f0eda5672a..1fdf616e34 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -53,6 +53,9 @@ namespace osu.Game.Tournament.Screens.Ladder.Components editorInfo.Selected.ValueChanged += selection => { + // ensure any ongoing edits are committed out to the *current* selection before changing to a new one. + GetContainingInputManager().TriggerFocusContention(null); + roundDropdown.Current = selection.NewValue?.Round; losersCheckbox.Current = selection.NewValue?.Losers; dateTimeBox.Current = selection.NewValue?.Date; From 467f83b603dcdba943c3d2f9e8341eaa55127186 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 18:48:12 +0900 Subject: [PATCH 099/145] Add non-null assertion missing in `BeatmapScoreRow` --- osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index 4970d520af..9262cab098 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -3,6 +3,7 @@ #nullable disable +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -135,6 +136,8 @@ namespace osu.Game.Tournament.Screens.TeamIntro { public BeatmapScoreRow(SeedingBeatmap beatmap) { + Debug.Assert(beatmap.Beatmap != null); + RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; From 952d97c66e98de0f5e90cc285b329b2abf26463b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 19:02:21 +0900 Subject: [PATCH 100/145] Update comment regarding `LoadTrack` safety --- osu.Game/Screens/Edit/Editor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 2b763415cd..48576b81e2 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -186,7 +186,7 @@ namespace osu.Game.Screens.Edit loadableBeatmap = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value); // required so we can get the track length in EditorClock. - // this is safe as nothing has yet got a reference to this new beatmap. + // this is ONLY safe because the track being provided is a `TrackVirtual` which we don't really care about disposing. loadableBeatmap.LoadTrack(); // this is a bit haphazard, but guards against setting the lease Beatmap bindable if From 6950223a7dd12a3ce84daf2ef48d9ee1e9a07784 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 19:18:38 +0900 Subject: [PATCH 101/145] Fix drawable mutation from disposal thread --- osu.Game/Screens/Select/FooterButtonMods.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 2732b5baa8..c938f58984 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Select Current.BindValueChanged(_ => updateMultiplierText(), true); } - private void updateMultiplierText() + private void updateMultiplierText() => Schedule(() => { double multiplier = Current.Value?.Aggregate(1.0, (current, mod) => current * mod.ScoreMultiplier) ?? 1; @@ -85,6 +85,6 @@ namespace osu.Game.Screens.Select modDisplay.FadeIn(); else modDisplay.FadeOut(); - } + }); } } From c6b6f41b717be33f1595b9effe4a6a4f8845d038 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 19:40:57 +0900 Subject: [PATCH 102/145] Add test coverage of `AudioEquals` --- .../NonVisual/BeatmapSetInfoEqualityTest.cs | 44 +++++++++++++++++++ osu.Game.Tests/Resources/TestResources.cs | 1 + 2 files changed, 45 insertions(+) diff --git a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs index de23b012c1..c887105da6 100644 --- a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs +++ b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs @@ -4,9 +4,12 @@ #nullable disable using System; +using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Extensions; +using osu.Game.Models; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.NonVisual { @@ -23,6 +26,47 @@ namespace osu.Game.Tests.NonVisual Assert.IsTrue(ourInfo.MatchesOnlineID(otherInfo)); } + [Test] + public void TestAudioEqualityNoFile() + { + var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1); + var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1); + + Assert.AreNotEqual(beatmapSetA, beatmapSetB); + Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single())); + } + + [Test] + public void TestAudioEqualitySameHash() + { + var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1); + var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1); + + addAudioFile(beatmapSetA, "abc"); + addAudioFile(beatmapSetB, "abc"); + + Assert.AreNotEqual(beatmapSetA, beatmapSetB); + Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single())); + } + + [Test] + public void TestAudioEqualityDifferentHash() + { + var beatmapSetA = TestResources.CreateTestBeatmapSetInfo(1); + var beatmapSetB = TestResources.CreateTestBeatmapSetInfo(1); + + addAudioFile(beatmapSetA); + addAudioFile(beatmapSetB); + + Assert.AreNotEqual(beatmapSetA, beatmapSetB); + Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single())); + } + + private static void addAudioFile(BeatmapSetInfo beatmapSetInfo, string hash = null) + { + beatmapSetInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = hash ?? Guid.NewGuid().ToString() }, "audio.mp3")); + } + [Test] public void TestDatabasedWithDatabased() { diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 91d4eb70e8..41404b2636 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -134,6 +134,7 @@ namespace osu.Game.Tests.Resources DifficultyName = $"{version} {beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", StarRating = diff, Length = length, + BeatmapSet = beatmapSet, BPM = bpm, Hash = Guid.NewGuid().ToString().ComputeMD5Hash(), Ruleset = rulesetInfo, From 1cfdea911b7aec4ce7a354480b1818d61a5cbd33 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 19:13:18 +0900 Subject: [PATCH 103/145] Fix audio and background file equality incorrectly comparing `BeatmapSet.Hash` --- osu.Game/Beatmaps/BeatmapInfo.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 346bf86818..531dc7deca 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using Newtonsoft.Json; @@ -151,14 +152,23 @@ namespace osu.Game.Beatmaps public bool AudioEquals(BeatmapInfo? other) => other != null && BeatmapSet != null && other.BeatmapSet != null - && BeatmapSet.Hash == other.BeatmapSet.Hash - && Metadata.AudioFile == other.Metadata.AudioFile; + && compareFiles(this, other, m => m.AudioFile); public bool BackgroundEquals(BeatmapInfo? other) => other != null && BeatmapSet != null && other.BeatmapSet != null - && BeatmapSet.Hash == other.BeatmapSet.Hash - && Metadata.BackgroundFile == other.Metadata.BackgroundFile; + && compareFiles(this, other, m => m.BackgroundFile); + + private static bool compareFiles(BeatmapInfo x, BeatmapInfo y, Func getFilename) + { + Debug.Assert(x.BeatmapSet != null); + Debug.Assert(y.BeatmapSet != null); + + string? fileHashX = x.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(x.BeatmapSet.Metadata))?.File.Hash; + string? fileHashY = y.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(y.BeatmapSet.Metadata))?.File.Hash; + + return fileHashX == fileHashY; + } IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata; IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet; From 2e86e7ccee2e802c01e17182f15145ec436988e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 19:31:53 +0900 Subject: [PATCH 104/145] Add extra steps to `TestExitWithoutSave` to guarantee track type --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index f565ca3ef4..6ad6f0b299 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Screens; @@ -103,6 +104,8 @@ namespace osu.Game.Tests.Visual.Editing */ public void TestAddAudioTrack() { + AddAssert("track is virtual", () => Beatmap.Value.Track is TrackVirtual); + AddAssert("switch track to real track", () => { var setup = Editor.ChildrenOfType().First(); @@ -131,6 +134,7 @@ namespace osu.Game.Tests.Visual.Editing } }); + AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual); AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000); } From 5e6b9b96b01944becc8ae25210fd4348a8d31d4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 22:02:46 +0900 Subject: [PATCH 105/145] Apply NRT to new `InputBlockingMod` class --- osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs index ee6f072c4b..40c621a4be 100644 --- a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -34,12 +32,13 @@ namespace osu.Game.Rulesets.Osu.Mods /// /// This is different from in that the periods here end strictly at the first object after the break, rather than the break's end time. /// - protected PeriodTracker NonGameplayPeriods; + protected PeriodTracker NonGameplayPeriods = null!; + + protected DrawableRuleset Ruleset = null!; + + protected IFrameStableClock GameplayClock = null!; protected OsuAction? LastActionPressed; - protected DrawableRuleset Ruleset; - - protected IFrameStableClock GameplayClock; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { From 33dd9562cc1fe03f3f0b625f6b62a9a630072044 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 22:04:57 +0900 Subject: [PATCH 106/145] Privatise some fields --- osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs index 40c621a4be..4541d33579 100644 --- a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs @@ -26,19 +26,19 @@ namespace osu.Game.Rulesets.Osu.Mods protected const double FLASH_DURATION = 1000; + protected DrawableRuleset Ruleset = null!; + + protected OsuAction? LastActionPressed; + /// /// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods). /// /// /// This is different from in that the periods here end strictly at the first object after the break, rather than the break's end time. /// - protected PeriodTracker NonGameplayPeriods = null!; + private PeriodTracker nonGameplayPeriods = null!; - protected DrawableRuleset Ruleset = null!; - - protected IFrameStableClock GameplayClock = null!; - - protected OsuAction? LastActionPressed; + private IFrameStableClock gameplayClock = null!; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { @@ -57,14 +57,14 @@ namespace osu.Game.Rulesets.Osu.Mods static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh); } - NonGameplayPeriods = new PeriodTracker(periods); + nonGameplayPeriods = new PeriodTracker(periods); - GameplayClock = drawableRuleset.FrameStableClock; + gameplayClock = drawableRuleset.FrameStableClock; } protected virtual bool CheckCorrectAction(OsuAction action) { - if (NonGameplayPeriods.IsInAny(GameplayClock.CurrentTime)) + if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime)) { LastActionPressed = null; return true; From be3187c3a44282e0e9f5ce0a705739b1e1c9fa76 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 22:05:56 +0900 Subject: [PATCH 107/145] Remove remnant nullable disables --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs | 2 -- 3 files changed, 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs index 0fe35fac0b..1aed84be10 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSingleTap.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using NUnit.Framework; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index cc4591562e..e1be8288e3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Graphics; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs index b2b6e11f48..2000adc7d9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Graphics; From 0da1bd393c48b4514071df474e57de6a90dc369b Mon Sep 17 00:00:00 2001 From: James <51536154+tsunyoku@users.noreply.github.com> Date: Wed, 13 Jul 2022 14:26:44 +0100 Subject: [PATCH 108/145] privatise checkCorrectAction, add abstract CheckValidNewAction function --- .../Mods/InputBlockingMod.cs | 13 +++++++++-- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 17 +------------- osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs | 23 +------------------ 3 files changed, 13 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs index 4541d33579..40d05b9475 100644 --- a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs @@ -62,7 +62,9 @@ namespace osu.Game.Rulesets.Osu.Mods gameplayClock = drawableRuleset.FrameStableClock; } - protected virtual bool CheckCorrectAction(OsuAction action) + protected abstract bool CheckValidNewAction(OsuAction action); + + private bool checkCorrectAction(OsuAction action) { if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime)) { @@ -81,6 +83,13 @@ namespace osu.Game.Rulesets.Osu.Mods return true; } + if (CheckValidNewAction(action)) + { + LastActionPressed = action; + return true; + } + + ruleset.Cursor.FlashColour(Colour4.Red, flash_duration, Easing.OutQuint); return false; } @@ -95,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Mods public bool OnPressed(KeyBindingPressEvent e) // if the pressed action is incorrect, block it from reaching gameplay. - => !mod.CheckCorrectAction(e.Action); + => !mod.checkCorrectAction(e.Action); public void OnReleased(KeyBindingReleaseEvent e) { diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index e1be8288e3..9bc401de67 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; namespace osu.Game.Rulesets.Osu.Mods @@ -16,20 +15,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override IconUsage? Icon => FontAwesome.Solid.Keyboard; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray(); - protected override bool CheckCorrectAction(OsuAction action) - { - if (base.CheckCorrectAction(action)) - return true; - - if (LastActionPressed != action) - { - // User alternated correctly. - LastActionPressed = action; - return true; - } - - Ruleset.Cursor.FlashColour(Colour4.Red, FLASH_DURATION, Easing.OutQuint); - return false; - } + protected override bool CheckValidNewAction(OsuAction action) => LastActionPressed != action; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs index 2000adc7d9..95b798c39e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs @@ -3,7 +3,6 @@ using System; using System.Linq; -using osu.Framework.Graphics; namespace osu.Game.Rulesets.Osu.Mods { @@ -14,26 +13,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => @"You must only use one key!"; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray(); - protected override bool CheckCorrectAction(OsuAction action) - { - if (base.CheckCorrectAction(action)) - return true; - - if (LastActionPressed == null) - { - // First keypress, store the expected action. - LastActionPressed = action; - return true; - } - - if (LastActionPressed == action) - { - // User singletapped correctly. - return true; - } - - Ruleset.Cursor.FlashColour(Colour4.Red, FLASH_DURATION, Easing.OutQuint); - return false; - } + protected override bool CheckValidNewAction(OsuAction action) => LastActionPressed == null || LastActionPressed == action; } } From af0300249590b2ef395375b567cfd8e2b88489e5 Mon Sep 17 00:00:00 2001 From: James <51536154+tsunyoku@users.noreply.github.com> Date: Wed, 13 Jul 2022 14:31:09 +0100 Subject: [PATCH 109/145] make flash duration and ruleset private --- osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs index 40d05b9475..e3bb5f17e9 100644 --- a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs @@ -24,9 +24,9 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(ModAutoplay), typeof(ModRelax), typeof(OsuModCinema) }; public override ModType Type => ModType.Conversion; - protected const double FLASH_DURATION = 1000; + private const double flash_duration = 1000; - protected DrawableRuleset Ruleset = null!; + private DrawableRuleset ruleset = null!; protected OsuAction? LastActionPressed; @@ -42,17 +42,17 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { - Ruleset = drawableRuleset; + ruleset = drawableRuleset; drawableRuleset.KeyBindingInputManager.Add(new InputInterceptor(this)); var periods = new List(); if (drawableRuleset.Objects.Any()) { - periods.Add(new Period(int.MinValue, getValidJudgementTime(Ruleset.Objects.First()) - 1)); + periods.Add(new Period(int.MinValue, getValidJudgementTime(ruleset.Objects.First()) - 1)); foreach (BreakPeriod b in drawableRuleset.Beatmap.Breaks) - periods.Add(new Period(b.StartTime, getValidJudgementTime(Ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1)); + periods.Add(new Period(b.StartTime, getValidJudgementTime(ruleset.Objects.First(h => h.StartTime >= b.EndTime)) - 1)); static double getValidJudgementTime(HitObject hitObject) => hitObject.StartTime - hitObject.HitWindows.WindowFor(HitResult.Meh); } From 937692604edbc919e6a55d6499ca6a0e72bc69c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 22:37:20 +0900 Subject: [PATCH 110/145] Remove mention of autoplay mod for now --- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index f3bc0ea798..ad63925b93 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Play base.StartGameplay(); // User expectation is that last played should be updated when entering the gameplay loop - // from multiplayer / playlists / solo, even when using autoplay mod. + // from multiplayer / playlists / solo. realm.WriteAsync(r => { var realmBeatmap = r.Find(Beatmap.Value.BeatmapInfo.ID); From 4d9494d3b317a50ec50ed045845bce55203fd560 Mon Sep 17 00:00:00 2001 From: James <51536154+tsunyoku@users.noreply.github.com> Date: Wed, 13 Jul 2022 14:42:45 +0100 Subject: [PATCH 111/145] change LastPressedAction to have a private setter --- osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs index e3bb5f17e9..daae67c3a0 100644 --- a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Mods private DrawableRuleset ruleset = null!; - protected OsuAction? LastActionPressed; + protected OsuAction? LastActionPressed { get; private set; } /// /// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods). From 0db1caf591e53b275cd14df4d83cd0ea663a9ca0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 23:15:25 +0900 Subject: [PATCH 112/145] Add language selection to first run overlay --- .../Overlays/FirstRunSetup/ScreenWelcome.cs | 137 +++++++++++++++++- 1 file changed, 135 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs index 20ff8f21c8..ca1c02d53e 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs @@ -1,14 +1,23 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Configuration; +using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; using osu.Game.Localisation; +using osuTK; namespace osu.Game.Overlays.FirstRunSetup { @@ -26,7 +35,131 @@ namespace osu.Game.Overlays.FirstRunSetup RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y }, + new LanguageSelectionFlow + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } }; } + + private class LanguageSelectionFlow : FillFlowContainer + { + private Bindable frameworkLocale = null!; + + [BackgroundDependencyLoader] + private void load(FrameworkConfigManager frameworkConfig) + { + Direction = FillDirection.Full; + Spacing = new Vector2(5); + + ChildrenEnumerable = Enum.GetValues(typeof(Language)) + .Cast() + .Select(l => new LanguageButton(l) + { + Action = () => frameworkLocale.Value = l.ToCultureCode() + }); + + frameworkLocale = frameworkConfig.GetBindable(FrameworkSetting.Locale); + frameworkLocale.BindValueChanged(locale => + { + if (!LanguageExtensions.TryParseCultureCode(frameworkLocale.Value, out var language)) + language = Language.en; + + foreach (var c in Children.OfType()) + c.Selected = c.Language == language; + }, true); + } + + private class LanguageButton : OsuClickableContainer + { + public readonly Language Language; + + private Box backgroundBox = null!; + + private OsuSpriteText text = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + private bool selected; + + public bool Selected + { + get => selected; + set + { + if (selected == value) + return; + + selected = value; + + updateState(); + } + } + + public LanguageButton(Language language) + { + Language = language; + + Size = new Vector2(160, 50); + Masking = true; + CornerRadius = 10; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + backgroundBox = new Box + { + Alpha = 0, + Colour = colourProvider.Background5, + RelativeSizeAxes = Axes.Both, + }, + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = colourProvider.Light1, + Text = Language.GetDescription(), + } + }; + } + + protected override bool OnHover(HoverEvent e) + { + if (!selected) + updateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + if (!selected) + updateState(); + base.OnHoverLost(e); + } + + private void updateState() + { + const double duration = 1000; + + if (selected) + { + backgroundBox.FadeTo(1, duration, Easing.OutQuint); + text.FadeColour(colourProvider.Content1, duration, Easing.OutQuint); + text.ScaleTo(1.2f, duration, Easing.OutQuint); + } + else + { + backgroundBox.FadeTo(IsHovered ? 0.4f : 0, duration / 2, Easing.OutQuint); + text.ScaleTo(1, duration / 2, Easing.OutQuint); + text.FadeColour(colourProvider.Light1, duration / 2, Easing.OutQuint); + } + } + } + } } } From 3b554140db559d9c1a1eab8ad01d7f7aab2eb04c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 23:22:50 +0900 Subject: [PATCH 113/145] Use grid container to avoid layout changes when changing language --- .../Overlays/FirstRunSetup/ScreenWelcome.cs | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs index ca1c02d53e..ec074bcd04 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs @@ -29,11 +29,27 @@ namespace osu.Game.Overlays.FirstRunSetup { Content.Children = new Drawable[] { - new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) + new GridContainer { - Text = FirstRunSetupOverlayStrings.WelcomeDescription, RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y + AutoSizeAxes = Axes.Y, + RowDimensions = new[] + { + // Avoid height changes when changing language. + new Dimension(GridSizeMode.AutoSize, minSize: 100), + }, + Content = new[] + { + new Drawable[] + { + new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) + { + Text = FirstRunSetupOverlayStrings.WelcomeDescription, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }, + }, + } }, new LanguageSelectionFlow { From 31e1e963642b9ea4ee5e2c06cd4ba4fbefa7b4b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 13 Jul 2022 23:25:32 +0900 Subject: [PATCH 114/145] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 8c15ed7949..caaa83bff4 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1251ab800b..355fd5f458 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index c38bb548bf..fcf71f3ab0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 7ac04d04782837946bf6247434d8303d99e1760a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jul 2022 00:58:32 +0900 Subject: [PATCH 115/145] Fix potential crash when exiting editor test mode --- osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs index 87e640badc..fd230a97bc 100644 --- a/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs +++ b/osu.Game/Screens/Edit/GameplayTest/EditorPlayer.cs @@ -35,7 +35,13 @@ namespace osu.Game.Screens.Edit.GameplayTest ScoreProcessor.HasCompleted.BindValueChanged(completed => { if (completed.NewValue) - Scheduler.AddDelayed(this.Exit, RESULTS_DISPLAY_DELAY); + { + Scheduler.AddDelayed(() => + { + if (this.IsCurrentScreen()) + this.Exit(); + }, RESULTS_DISPLAY_DELAY); + } }); } From e2f2d5f79469f0a06ae7fd3e839f68d2f698fb29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jul 2022 01:40:44 +0900 Subject: [PATCH 116/145] Rename last action to better represent that it is only captured actions --- osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs | 6 +++--- osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs index daae67c3a0..a7aca8257b 100644 --- a/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs +++ b/osu.Game.Rulesets.Osu/Mods/InputBlockingMod.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Mods private DrawableRuleset ruleset = null!; - protected OsuAction? LastActionPressed { get; private set; } + protected OsuAction? LastAcceptedAction { get; private set; } /// /// A tracker for periods where alternate should not be forced (i.e. non-gameplay periods). @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Osu.Mods { if (nonGameplayPeriods.IsInAny(gameplayClock.CurrentTime)) { - LastActionPressed = null; + LastAcceptedAction = null; return true; } @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (CheckValidNewAction(action)) { - LastActionPressed = action; + LastAcceptedAction = action; return true; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs index 9bc401de67..d88cb17e84 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAlternate.cs @@ -15,6 +15,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override IconUsage? Icon => FontAwesome.Solid.Keyboard; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModSingleTap) }).ToArray(); - protected override bool CheckValidNewAction(OsuAction action) => LastActionPressed != action; + protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction != action; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs index 95b798c39e..051ceb968c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSingleTap.cs @@ -13,6 +13,6 @@ namespace osu.Game.Rulesets.Osu.Mods public override string Description => @"You must only use one key!"; public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAlternate) }).ToArray(); - protected override bool CheckValidNewAction(OsuAction action) => LastActionPressed == null || LastActionPressed == action; + protected override bool CheckValidNewAction(OsuAction action) => LastAcceptedAction == null || LastAcceptedAction == action; } } From 099a7e90d624ec69192aafd242334b109b0033f6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 02:19:23 +0300 Subject: [PATCH 117/145] Centralise creation of playlist in test scene --- .../TestSceneDrawableRoomPlaylist.cs | 107 ++++++++---------- 1 file changed, 46 insertions(+), 61 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 757dfff2b7..542762af97 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -245,40 +245,35 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestExpiredItems() { - AddStep("create playlist", () => + createPlaylist(p => { - Child = playlist = new TestPlaylist + p.Items.Clear(); + p.Items.AddRange(new[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500, 300), - Items = + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) { - new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + ID = 0, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + Expired = true, + RequiredMods = new[] { - ID = 0, - RulesetID = new OsuRuleset().RulesetInfo.OnlineID, - Expired = true, - RequiredMods = new[] - { - new APIMod(new OsuModHardRock()), - new APIMod(new OsuModDoubleTime()), - new APIMod(new OsuModAutoplay()) - } - }, - new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + new APIMod(new OsuModHardRock()), + new APIMod(new OsuModDoubleTime()), + new APIMod(new OsuModAutoplay()) + } + }, + new PlaylistItem(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo) + { + ID = 1, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + RequiredMods = new[] { - ID = 1, - RulesetID = new OsuRuleset().RulesetInfo.OnlineID, - RequiredMods = new[] - { - new APIMod(new OsuModHardRock()), - new APIMod(new OsuModDoubleTime()), - new APIMod(new OsuModAutoplay()) - } + new APIMod(new OsuModHardRock()), + new APIMod(new OsuModDoubleTime()), + new APIMod(new OsuModAutoplay()) } } - }; + }); }); AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); @@ -321,6 +316,29 @@ namespace osu.Game.Tests.Visual.Multiplayer => AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).Alpha > 0) == visible); + private void createPlaylistWithBeatmaps(Func> beatmaps) => createPlaylist(p => + { + int index = 0; + + p.Items.Clear(); + + foreach (var b in beatmaps()) + { + p.Items.Add(new PlaylistItem(b) + { + ID = index++, + OwnerID = 2, + RulesetID = new OsuRuleset().RulesetInfo.OnlineID, + RequiredMods = new[] + { + new APIMod(new OsuModHardRock()), + new APIMod(new OsuModDoubleTime()), + new APIMod(new OsuModAutoplay()) + } + }); + } + }); + private void createPlaylist(Action setupPlaylist = null) { AddStep("create playlist", () => @@ -332,8 +350,6 @@ namespace osu.Game.Tests.Visual.Multiplayer Size = new Vector2(500, 300) }; - setupPlaylist?.Invoke(playlist); - for (int i = 0; i < 20; i++) { playlist.Items.Add(new PlaylistItem(i % 2 == 1 @@ -360,39 +376,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); } - }); - AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); - } - - private void createPlaylistWithBeatmaps(Func> beatmaps) - { - AddStep("create playlist", () => - { - Child = playlist = new TestPlaylist - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500, 300) - }; - - int index = 0; - - foreach (var b in beatmaps()) - { - playlist.Items.Add(new PlaylistItem(b) - { - ID = index++, - OwnerID = 2, - RulesetID = new OsuRuleset().RulesetInfo.OnlineID, - RequiredMods = new[] - { - new APIMod(new OsuModHardRock()), - new APIMod(new OsuModDoubleTime()), - new APIMod(new OsuModAutoplay()) - } - }); - } + setupPlaylist?.Invoke(playlist); }); AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); From 728487b7fbd58c743e9d94d271af6a68d66650a2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 02:31:37 +0300 Subject: [PATCH 118/145] Handle `GetBeatmapSetRequest` on test room requests handler Required for `BeatmapSetOverlay` lookups to work under dummy API. Not 100% sure about it, but works for now. --- .../OnlinePlay/TestRoomRequestsHandler.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index e7c83ca1f9..fa7ade2c07 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -136,6 +136,7 @@ namespace osu.Game.Tests.Visual.OnlinePlay return true; case GetBeatmapsRequest getBeatmapsRequest: + { var result = new List(); foreach (int id in getBeatmapsRequest.BeatmapIds) @@ -154,6 +155,24 @@ namespace osu.Game.Tests.Visual.OnlinePlay getBeatmapsRequest.TriggerSuccess(new GetBeatmapsResponse { Beatmaps = result }); return true; + } + + case GetBeatmapSetRequest getBeatmapSetRequest: + { + var baseBeatmap = getBeatmapSetRequest.Type == BeatmapSetLookupType.BeatmapId + ? beatmapManager.QueryBeatmap(b => b.OnlineID == getBeatmapSetRequest.ID) + : beatmapManager.QueryBeatmap(b => b.BeatmapSet.OnlineID == getBeatmapSetRequest.ID); + + if (baseBeatmap == null) + { + baseBeatmap = new TestBeatmap(new RulesetInfo { OnlineID = 0 }).BeatmapInfo; + baseBeatmap.OnlineID = getBeatmapSetRequest.ID; + baseBeatmap.BeatmapSet!.OnlineID = getBeatmapSetRequest.ID; + } + + getBeatmapSetRequest.TriggerSuccess(OsuTestScene.CreateAPIBeatmapSet(baseBeatmap)); + return true; + } } return false; From 036e64382fced24200435df4c8a519086b0fd61a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 02:45:31 +0300 Subject: [PATCH 119/145] Add beatmap details menu item to playlist items --- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 455e1f3481..e68634f4c6 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -30,6 +30,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.Chat; using osu.Game.Online.Rooms; +using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; @@ -107,6 +108,9 @@ namespace osu.Game.Screens.OnlinePlay [Resolved] private BeatmapLookupCache beatmapLookupCache { get; set; } + [Resolved(CanBeNull = true)] + private BeatmapSetOverlay beatmapOverlay { get; set; } + [Resolved(CanBeNull = true)] private CollectionManager collectionManager { get; set; } @@ -496,18 +500,16 @@ namespace osu.Game.Screens.OnlinePlay { List items = new List(); - if (beatmap != null && collectionManager != null) - { - if (downloadTracker.State.Value == DownloadState.LocallyAvailable) - { - var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); - if (manageCollectionsDialog != null) - collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + if (beatmapOverlay != null) + items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(Item.Beatmap.OnlineID))); - items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); - } - else - items.Add(new OsuMenuItem("Download to add to collection") { Action = { Disabled = true } }); + if (beatmap != null && collectionManager != null && downloadTracker.State.Value == DownloadState.LocallyAvailable) + { + var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); } return items.ToArray(); From 9ec4fbb86dfb8a4446733ca0ac676719cfeb724b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 02:45:38 +0300 Subject: [PATCH 120/145] Add test coverage for details item --- .../TestSceneDrawableRoomPlaylist.cs | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 542762af97..6d8aaa6f6f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -12,15 +12,19 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Database; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Cursor; +using osu.Game.Graphics.UserInterface; using osu.Game.Models; using osu.Game.Online.API; using osu.Game.Online.Rooms; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -299,6 +303,25 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } + [Test] + public void TestContextMenuDetails() + { + OsuContextMenu contextMenu = null; + + createPlaylist(); + + moveToItem(0); + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddAssert("context menu open", () => (contextMenu = this.ChildrenOfType().SingleOrDefault())?.State == MenuState.Open); + + AddStep("click details", () => + { + InputManager.MoveMouseTo(contextMenu.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + AddAssert("beatmap overlay visible", () => this.ChildrenOfType().Single().State.Value == Visibility.Visible); + } + private void moveToItem(int index, Vector2? offset = null) => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(index), offset)); @@ -343,11 +366,29 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create playlist", () => { - Child = playlist = new TestPlaylist + BeatmapSetOverlay beatmapOverlay; + + Child = new DependencyProvidingContainer { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500, 300) + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] + { + (typeof(BeatmapSetOverlay), beatmapOverlay = new BeatmapSetOverlay()), + }, + Children = new Drawable[] + { + new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.Both, + Child = playlist = new TestPlaylist + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500, 300), + }, + }, + beatmapOverlay, + }, }; for (int i = 0; i < 20; i++) From cb2f0b8c67c57e8231661424bcc232d91cbf4254 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 03:12:48 +0300 Subject: [PATCH 121/145] Add test coverage for collection items --- .../TestSceneDrawableRoomPlaylist.cs | 108 +++++++++++++++++- 1 file changed, 102 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 6d8aaa6f6f..ce36ae7827 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -17,6 +17,7 @@ using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; +using osu.Game.Collections; using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; @@ -42,6 +43,10 @@ namespace osu.Game.Tests.Visual.Multiplayer private BeatmapManager manager; private RulesetStore rulesets; + private CollectionManager collections; + + private BeatmapSetOverlay beatmapOverlay; + private ManageCollectionsDialog manageCollectionsDialog; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -199,12 +204,15 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestDownloadButtonHiddenWhenBeatmapExists() { - var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; Live imported = null; - Debug.Assert(beatmap.BeatmapSet != null); + AddStep("import beatmap", () => + { + var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; - AddStep("import beatmap", () => imported = manager.Import(beatmap.BeatmapSet)); + Debug.Assert(beatmap.BeatmapSet != null); + imported = manager.Import(beatmap.BeatmapSet); + }); createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach())); @@ -310,6 +318,8 @@ namespace osu.Game.Tests.Visual.Multiplayer createPlaylist(); + AddAssert("beatmap overlay hidden", () => beatmapOverlay.State.Value == Visibility.Hidden); + moveToItem(0); AddStep("right click", () => InputManager.Click(MouseButton.Right)); AddAssert("context menu open", () => (contextMenu = this.ChildrenOfType().SingleOrDefault())?.State == MenuState.Open); @@ -319,7 +329,91 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.MoveMouseTo(contextMenu.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); }); - AddAssert("beatmap overlay visible", () => this.ChildrenOfType().Single().State.Value == Visibility.Visible); + AddAssert("beatmap overlay visible", () => beatmapOverlay.State.Value == Visibility.Visible); + } + + [Test] + public void TestContextMenuCollection() + { + OsuContextMenu contextMenu = null; + BeatmapInfo beatmap = null; + Live imported = null; + + AddStep("import beatmap", () => + { + beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; + + Debug.Assert(beatmap.BeatmapSet != null); + imported = manager.Import(beatmap.BeatmapSet); + }); + + createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach())); + + AddStep("add two collections", () => + { + collections.Collections.Clear(); + collections.Collections.AddRange(new[] + { + new BeatmapCollection { Name = { Value = "Collection #1" }, BeatmapHashes = { beatmap.MD5Hash } }, + new BeatmapCollection { Name = { Value = "Collection #2" } }, + }); + }); + + moveToItem(0); + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddAssert("context menu open", () => (contextMenu = this.ChildrenOfType().SingleOrDefault())?.State == MenuState.Open); + + AddStep("select collections", () => InputManager.MoveMouseTo(contextMenu.ChildrenOfType().ElementAt(1))); + AddAssert("collection 1 present and beatmap added", () => + { + var item = (ToggleMenuItem)contextMenu.Items[1].Items[0]; + return item.Text.Value == "Collection #1" && item.State.Value; + }); + AddAssert("collection 2 present", () => + { + var item = (ToggleMenuItem)contextMenu.Items[1].Items[1]; + return item.Text.Value == "Collection #2" && !item.State.Value; + }); + + AddStep("select second collection", () => + { + InputManager.MoveMouseTo(contextMenu.ChildrenOfType().ElementAt(1)); + InputManager.Click(MouseButton.Left); + }); + AddAssert("beatmap added to second collection", () => collections.Collections[1].BeatmapHashes.Contains(beatmap.MD5Hash)); + AddAssert("item state updated", () => ((ToggleMenuItem)contextMenu.Items[1].Items[1]).State.Value); + } + + [Test] + public void TestContextMenuManageCollections() + { + OsuContextMenu contextMenu = null; + Live imported = null; + + AddStep("import beatmap", () => + { + var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; + + Debug.Assert(beatmap.BeatmapSet != null); + imported = manager.Import(beatmap.BeatmapSet); + }); + + createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach())); + + AddStep("clear collections", () => collections.Collections.Clear()); + AddAssert("manage collections dialog hidden", () => manageCollectionsDialog.State.Value == Visibility.Hidden); + + moveToItem(0); + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + AddAssert("context menu open", () => (contextMenu = this.ChildrenOfType().SingleOrDefault())?.State == MenuState.Open); + + AddStep("select collections", () => InputManager.MoveMouseTo(contextMenu.ChildrenOfType().ElementAt(1))); + AddStep("click manage", () => + { + InputManager.MoveMouseTo(contextMenu.ChildrenOfType().ElementAt(1).ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + AddAssert("manage collections dialog open", () => manageCollectionsDialog.State.Value == Visibility.Visible); } private void moveToItem(int index, Vector2? offset = null) @@ -366,17 +460,18 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create playlist", () => { - BeatmapSetOverlay beatmapOverlay; - Child = new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, CachedDependencies = new (Type, object)[] { (typeof(BeatmapSetOverlay), beatmapOverlay = new BeatmapSetOverlay()), + (typeof(CollectionManager), collections = new CollectionManager(LocalStorage)), + (typeof(ManageCollectionsDialog), manageCollectionsDialog = new ManageCollectionsDialog()), }, Children = new Drawable[] { + collections, new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, @@ -388,6 +483,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }, }, beatmapOverlay, + manageCollectionsDialog, }, }; From 776d9551e2d93745653609fbfd6b4461a7cc8569 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 04:16:25 +0300 Subject: [PATCH 122/145] Disable "save changes" button by default --- osu.Game.Tournament/SaveChangesOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tournament/SaveChangesOverlay.cs b/osu.Game.Tournament/SaveChangesOverlay.cs index 03a3f2d3a4..c03afb2eab 100644 --- a/osu.Game.Tournament/SaveChangesOverlay.cs +++ b/osu.Game.Tournament/SaveChangesOverlay.cs @@ -57,6 +57,7 @@ namespace osu.Game.Tournament Bottom = 10, }, Action = saveChanges, + Enabled = { Value = false }, }, } }; From 24df8f6a0dfe41ec28a8562077ed94a7afba7754 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 04:33:07 +0300 Subject: [PATCH 123/145] Enable NRT on save changes button --- osu.Game.Tournament/SaveChangesOverlay.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tournament/SaveChangesOverlay.cs b/osu.Game.Tournament/SaveChangesOverlay.cs index c03afb2eab..b5e08fc005 100644 --- a/osu.Game.Tournament/SaveChangesOverlay.cs +++ b/osu.Game.Tournament/SaveChangesOverlay.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -17,9 +16,9 @@ namespace osu.Game.Tournament internal class SaveChangesOverlay : CompositeDrawable { [Resolved] - private TournamentGame tournamentGame { get; set; } + private TournamentGame tournamentGame { get; set; } = null!; - private string lastSerialisedLadder; + private string? lastSerialisedLadder; private readonly TourneyButton saveChangesButton; public SaveChangesOverlay() @@ -57,7 +56,7 @@ namespace osu.Game.Tournament Bottom = 10, }, Action = saveChanges, - Enabled = { Value = false }, + // Enabled = { Value = false }, }, } }; From a85a70c47249eb9685ea8072cc1885fccb3b5337 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 05:01:18 +0300 Subject: [PATCH 124/145] Fix potential nullref in `ContextMenuItems` --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index e68634f4c6..d89816d3c7 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -94,6 +95,7 @@ namespace osu.Game.Screens.OnlinePlay private PanelBackground panelBackground; private FillFlowContainer mainFillFlow; + [CanBeNull] private BeatmapDownloadTracker downloadTracker; [Resolved] @@ -503,7 +505,7 @@ namespace osu.Game.Screens.OnlinePlay if (beatmapOverlay != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(Item.Beatmap.OnlineID))); - if (beatmap != null && collectionManager != null && downloadTracker.State.Value == DownloadState.LocallyAvailable) + if (beatmap != null && collectionManager != null && downloadTracker?.State.Value == DownloadState.LocallyAvailable) { var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); if (manageCollectionsDialog != null) From f83d413b33d000a3cbdaf3783dc96df3478a1ca4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 06:05:03 +0300 Subject: [PATCH 125/145] Fix dialog overlay potentially pushing dialog while not loaded --- osu.Game/Overlays/DialogOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index a07cf1608d..7f2db9f03f 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -65,7 +65,7 @@ namespace osu.Game.Overlays dialogContainer.Add(dialog); Show(); - }, false); + }, !IsLoaded); } public override bool IsPresent => Scheduler.HasPendingTasks || dialogContainer.Children.Count > 0; From 3def8428aa368b13387da6c026270e77802f2a42 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 06:06:56 +0300 Subject: [PATCH 126/145] Make scheduling more legible --- osu.Game/Overlays/DialogOverlay.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 7f2db9f03f..259c6068e0 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -57,7 +57,12 @@ namespace osu.Game.Overlays // a DialogOverlay instance has finished loading. CurrentDialog = dialog; - Scheduler.Add(() => + if (IsLoaded) + Scheduler.Add(pushDialog, false); + else + Schedule(pushDialog); + + void pushDialog() { // if any existing dialog is being displayed, dismiss it before showing a new one. lastDialog?.Hide(); @@ -65,7 +70,7 @@ namespace osu.Game.Overlays dialogContainer.Add(dialog); Show(); - }, !IsLoaded); + } } public override bool IsPresent => Scheduler.HasPendingTasks || dialogContainer.Children.Count > 0; From be69514002b62a6d4fd0728923577a5a639d7664 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 06:21:03 +0300 Subject: [PATCH 127/145] Fix `CollectionManager` opening file multiple times across test scene --- .../Multiplayer/TestSceneDrawableRoomPlaylist.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index ce36ae7827..599a28f913 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -39,6 +39,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneDrawableRoomPlaylist : MultiplayerTestScene { + protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; + private TestPlaylist playlist; private BeatmapManager manager; @@ -54,6 +56,14 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); + + base.Content.AddRange(new Drawable[] + { + collections = new CollectionManager(LocalStorage), + Content + }); + + Dependencies.Cache(collections); } [Test] @@ -466,12 +476,10 @@ namespace osu.Game.Tests.Visual.Multiplayer CachedDependencies = new (Type, object)[] { (typeof(BeatmapSetOverlay), beatmapOverlay = new BeatmapSetOverlay()), - (typeof(CollectionManager), collections = new CollectionManager(LocalStorage)), (typeof(ManageCollectionsDialog), manageCollectionsDialog = new ManageCollectionsDialog()), }, Children = new Drawable[] { - collections, new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, From 12221235413416a956b5630ed32362c2e2321e3b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 07:04:46 +0300 Subject: [PATCH 128/145] Rename method and parameter --- osu.Game/Overlays/DialogOverlay.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 259c6068e0..83a563beba 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -66,7 +66,8 @@ namespace osu.Game.Overlays { // if any existing dialog is being displayed, dismiss it before showing a new one. lastDialog?.Hide(); - dialog.State.ValueChanged += state => onDialogOnStateChanged(dialog, state.NewValue); + + dialog.State.ValueChanged += state => onDialogStateChanged(dialog, state.NewValue), true); dialogContainer.Add(dialog); Show(); @@ -77,9 +78,9 @@ namespace osu.Game.Overlays protected override bool BlockNonPositionalInput => true; - private void onDialogOnStateChanged(VisibilityContainer dialog, Visibility v) + private void onDialogStateChanged(VisibilityContainer dialog, Visibility newState) { - if (v != Visibility.Hidden) return; + if (newState != Visibility.Hidden) return; // handle the dialog being dismissed. dialog.Delay(PopupDialog.EXIT_DURATION).Expire(); From c59784c49f31265d96f53caf04cdfd5794b776fc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 07:06:03 +0300 Subject: [PATCH 129/145] Always schedule popup dialog push --- osu.Game/Overlays/DialogOverlay.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 83a563beba..5e082acb23 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -57,12 +57,7 @@ namespace osu.Game.Overlays // a DialogOverlay instance has finished loading. CurrentDialog = dialog; - if (IsLoaded) - Scheduler.Add(pushDialog, false); - else - Schedule(pushDialog); - - void pushDialog() + Schedule(() => { // if any existing dialog is being displayed, dismiss it before showing a new one. lastDialog?.Hide(); @@ -71,7 +66,7 @@ namespace osu.Game.Overlays dialogContainer.Add(dialog); Show(); - } + }); } public override bool IsPresent => Scheduler.HasPendingTasks || dialogContainer.Children.Count > 0; From b96faedbe6de41b984a4725c18d64b45e736fb4b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 07:06:57 +0300 Subject: [PATCH 130/145] Fix dialog overlay hiding early-pushed dialog on initial `PopOut` call --- osu.Game/Overlays/DialogOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 5e082acb23..6f88564119 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -98,7 +98,8 @@ namespace osu.Game.Overlays base.PopOut(); lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 100, Easing.InCubic); - if (CurrentDialog?.State.Value == Visibility.Visible) + // PopOut gets called initially, but we only want to hide dialog when we have been loaded and are present. + if (IsLoaded && CurrentDialog?.State.Value == Visibility.Visible) CurrentDialog.Hide(); } From dccd81dbc7dad3fdc308bff7aa2dd07b6ef92c9c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 14 Jul 2022 07:07:32 +0300 Subject: [PATCH 131/145] Use `BindValueChanged` to handle changes between push time and schedule execution --- osu.Game/Overlays/DialogOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 6f88564119..2024eca707 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -62,7 +62,7 @@ namespace osu.Game.Overlays // if any existing dialog is being displayed, dismiss it before showing a new one. lastDialog?.Hide(); - dialog.State.ValueChanged += state => onDialogStateChanged(dialog, state.NewValue), true); + dialog.State.BindValueChanged(state => onDialogStateChanged(dialog, state.NewValue), true); dialogContainer.Add(dialog); Show(); From 227871e8dffa1a1be9ca3669f6e829fc0d0a163f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jul 2022 14:00:33 +0900 Subject: [PATCH 132/145] Refactor hide logic a touch for better readability --- osu.Game/Overlays/DialogOverlay.cs | 48 ++++++++++++++++++------------ 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 2024eca707..dfc6a0010a 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -49,12 +49,11 @@ namespace osu.Game.Overlays public void Push(PopupDialog dialog) { - if (dialog == CurrentDialog || dialog.State.Value != Visibility.Visible) return; - - var lastDialog = CurrentDialog; + if (dialog == CurrentDialog || dialog.State.Value == Visibility.Hidden) return; // Immediately update the externally accessible property as this may be used for checks even before // a DialogOverlay instance has finished loading. + var lastDialog = CurrentDialog; CurrentDialog = dialog; Schedule(() => @@ -62,31 +61,42 @@ namespace osu.Game.Overlays // if any existing dialog is being displayed, dismiss it before showing a new one. lastDialog?.Hide(); - dialog.State.BindValueChanged(state => onDialogStateChanged(dialog, state.NewValue), true); - dialogContainer.Add(dialog); + // is the new dialog is hidden before added to the dialogContainer, bypass any further operations. + if (dialog.State.Value == Visibility.Hidden) + { + dismiss(); + return; + } + dialogContainer.Add(dialog); Show(); + + dialog.State.BindValueChanged(state => + { + if (state.NewValue != Visibility.Hidden) return; + + // Trigger the demise of the dialog as soon as it hides. + dialog.Delay(PopupDialog.EXIT_DURATION).Expire(); + + dismiss(); + }); }); + + void dismiss() + { + if (dialog != CurrentDialog) return; + + // Handle the case where the dialog is the currently displayed dialog. + // In this scenario, the overlay itself should also be hidden. + Hide(); + CurrentDialog = null; + } } public override bool IsPresent => Scheduler.HasPendingTasks || dialogContainer.Children.Count > 0; protected override bool BlockNonPositionalInput => true; - private void onDialogStateChanged(VisibilityContainer dialog, Visibility newState) - { - if (newState != Visibility.Hidden) return; - - // handle the dialog being dismissed. - dialog.Delay(PopupDialog.EXIT_DURATION).Expire(); - - if (dialog == CurrentDialog) - { - Hide(); - CurrentDialog = null; - } - } - protected override void PopIn() { base.PopIn(); From 5c6b4e498dfda0910beea0847cc19ff586d9ad07 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jul 2022 14:31:59 +0900 Subject: [PATCH 133/145] Protect against a potential early call to `LanguageButton.Selected` --- osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs index ec074bcd04..f195fa82c0 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs @@ -79,7 +79,7 @@ namespace osu.Game.Overlays.FirstRunSetup frameworkLocale = frameworkConfig.GetBindable(FrameworkSetting.Locale); frameworkLocale.BindValueChanged(locale => { - if (!LanguageExtensions.TryParseCultureCode(frameworkLocale.Value, out var language)) + if (!LanguageExtensions.TryParseCultureCode(locale.NewValue, out var language)) language = Language.en; foreach (var c in Children.OfType()) @@ -110,7 +110,8 @@ namespace osu.Game.Overlays.FirstRunSetup selected = value; - updateState(); + if (IsLoaded) + updateState(); } } @@ -144,6 +145,12 @@ namespace osu.Game.Overlays.FirstRunSetup }; } + protected override void LoadComplete() + { + base.LoadComplete(); + updateState(); + } + protected override bool OnHover(HoverEvent e) { if (!selected) From 5dff48a1e02a4ed260d6709e46d084b41fef38a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jul 2022 14:40:25 +0900 Subject: [PATCH 134/145] Fix button selection animation not playing smoothly when new glyphs are loaded --- osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs index f195fa82c0..29aa23ebab 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Framework.Threading; using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -63,6 +64,8 @@ namespace osu.Game.Overlays.FirstRunSetup { private Bindable frameworkLocale = null!; + private ScheduledDelegate? updateSelectedDelegate; + [BackgroundDependencyLoader] private void load(FrameworkConfigManager frameworkConfig) { @@ -82,11 +85,20 @@ namespace osu.Game.Overlays.FirstRunSetup if (!LanguageExtensions.TryParseCultureCode(locale.NewValue, out var language)) language = Language.en; - foreach (var c in Children.OfType()) - c.Selected = c.Language == language; + // Changing language may cause a short period of blocking the UI thread while the new glyphs are loaded. + // Scheduling ensures the button animation plays smoothly after any blocking operation completes. + // Note that a delay is required (the alternative would be a double-schedule; delay feels better). + updateSelectedDelegate?.Cancel(); + updateSelectedDelegate = Scheduler.AddDelayed(() => updateSelectedStates(language), 50); }, true); } + private void updateSelectedStates(Language language) + { + foreach (var c in Children.OfType()) + c.Selected = c.Language == language; + } + private class LanguageButton : OsuClickableContainer { public readonly Language Language; From 08396ba48631a23b04f0aeb4d58bbfa3ba35d519 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jul 2022 14:47:25 +0900 Subject: [PATCH 135/145] Adjust colouring to avoid weird banding during transition period --- .../Overlays/FirstRunSetup/ScreenWelcome.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs index 29aa23ebab..cb1e96d2f2 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs @@ -179,19 +179,23 @@ namespace osu.Game.Overlays.FirstRunSetup private void updateState() { - const double duration = 1000; - if (selected) { - backgroundBox.FadeTo(1, duration, Easing.OutQuint); - text.FadeColour(colourProvider.Content1, duration, Easing.OutQuint); - text.ScaleTo(1.2f, duration, Easing.OutQuint); + const double selected_duration = 1000; + + backgroundBox.FadeTo(1, selected_duration, Easing.OutQuint); + backgroundBox.FadeColour(colourProvider.Background2, selected_duration, Easing.OutQuint); + text.FadeColour(colourProvider.Content1, selected_duration, Easing.OutQuint); + text.ScaleTo(1.2f, selected_duration, Easing.OutQuint); } else { - backgroundBox.FadeTo(IsHovered ? 0.4f : 0, duration / 2, Easing.OutQuint); - text.ScaleTo(1, duration / 2, Easing.OutQuint); - text.FadeColour(colourProvider.Light1, duration / 2, Easing.OutQuint); + const double duration = 500; + + backgroundBox.FadeTo(IsHovered ? 1 : 0, duration, Easing.OutQuint); + backgroundBox.FadeColour(colourProvider.Background5, duration, Easing.OutQuint); + text.FadeColour(colourProvider.Light1, duration, Easing.OutQuint); + text.ScaleTo(1, duration, Easing.OutQuint); } } } From ebe0cfefd87acbae75789d721c1ff612761f0029 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jul 2022 17:04:00 +0900 Subject: [PATCH 136/145] Ensure that multiple `BeatmapSetInfo` already in realm don't cause import failures Really this shouldn't happen but I managed to make it happen. Until this comes up again in a way that matters, let's just fix the LINQ crash from `SingleOrDefault`. I've tested this to work as expected in the broken scenario. --- osu.Game/Beatmaps/BeatmapImporter.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 92f1fc17d5..3e4d01a9a3 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -80,9 +80,8 @@ namespace osu.Game.Beatmaps if (beatmapSet.OnlineID > 0) { - var existingSetWithSameOnlineID = realm.All().SingleOrDefault(b => b.OnlineID == beatmapSet.OnlineID); - - if (existingSetWithSameOnlineID != null) + // OnlineID should really be unique, but to avoid catastrophic failure let's iterate just to be sure. + foreach (var existingSetWithSameOnlineID in realm.All().Where(b => b.OnlineID == beatmapSet.OnlineID)) { existingSetWithSameOnlineID.DeletePending = true; existingSetWithSameOnlineID.OnlineID = -1; @@ -90,7 +89,7 @@ namespace osu.Game.Beatmaps foreach (var b in existingSetWithSameOnlineID.Beatmaps) b.OnlineID = -1; - LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineID ({beatmapSet.OnlineID}). It will be deleted."); + LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineID ({beatmapSet.OnlineID}). It will be disassociated and marked for deletion."); } } } From 66932f1af6979ddd254231b27a27b981eeef97d3 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Thu, 14 Jul 2022 16:52:45 -0700 Subject: [PATCH 137/145] Move shared followcircle code into abstract base class --- .../Skinning/Default/DefaultFollowCircle.cs | 50 +----------- .../Skinning/FollowCircle.cs | 79 +++++++++++++++++++ .../Skinning/Legacy/LegacyFollowCircle.cs | 72 +++-------------- 3 files changed, 92 insertions(+), 109 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 8bb36f8c39..254e220996 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -1,27 +1,19 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Default { - public class DefaultFollowCircle : CompositeDrawable + public class DefaultFollowCircle : FollowCircle { - [Resolved(canBeNull: true)] - private DrawableHitObject? parentObject { get; set; } - public DefaultFollowCircle() { - Alpha = 0f; - RelativeSizeAxes = Axes.Both; - InternalChild = new CircularContainer { RelativeSizeAxes = Axes.Both, @@ -38,28 +30,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default }; } - [BackgroundDependencyLoader] - private void load() - { - if (parentObject != null) - { - var slider = (DrawableSlider)parentObject; - slider.Tracking.BindValueChanged(trackingChanged, true); - } - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - if (parentObject != null) - { - parentObject.ApplyCustomUpdateState += updateStateTransforms; - updateStateTransforms(parentObject, parentObject.State.Value); - } - } - - private void trackingChanged(ValueChangedEvent tracking) + protected override void OnTrackingChanged(ValueChangedEvent tracking) { const float scale_duration = 300f; const float fade_duration = 300f; @@ -68,25 +39,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default .FadeTo(tracking.NewValue ? 1f : 0, fade_duration, Easing.OutQuint); } - private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) + protected override void OnSliderEnd() { - // see comment in LegacySliderBall.updateStateTransforms - if (drawableObject is not DrawableSlider) - return; - const float fade_duration = 450f; // intentionally pile on an extra FadeOut to make it happen much faster - using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) - this.FadeOut(fade_duration / 4, Easing.Out); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (parentObject != null) - parentObject.ApplyCustomUpdateState -= updateStateTransforms; + this.FadeOut(fade_duration / 4, Easing.Out); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs new file mode 100644 index 0000000000..e1e792b89e --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -0,0 +1,79 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public abstract class FollowCircle : CompositeDrawable + { + [Resolved(canBeNull: true)] + protected DrawableHitObject? ParentObject { get; private set; } + + public FollowCircle() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + if (ParentObject != null) + { + var slider = (DrawableSlider)ParentObject; + slider.Tracking.BindValueChanged(OnTrackingChanged, true); + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (ParentObject != null) + { + ParentObject.HitObjectApplied += onHitObjectApplied; + onHitObjectApplied(ParentObject); + + ParentObject.ApplyCustomUpdateState += updateStateTransforms; + updateStateTransforms(ParentObject, ParentObject.State.Value); + } + } + + private void onHitObjectApplied(DrawableHitObject drawableObject) + { + this.ScaleTo(1f) + .FadeOut(); + } + + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) + { + // Gets called by slider ticks, tails, etc., leading to duplicated + // animations which may negatively affect performance + if (drawableObject is not DrawableSlider) + return; + + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + OnSliderEnd(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (ParentObject != null) + { + ParentObject.HitObjectApplied -= onHitObjectApplied; + ParentObject.ApplyCustomUpdateState -= updateStateTransforms; + } + } + + protected abstract void OnTrackingChanged(ValueChangedEvent tracking); + + protected abstract void OnSliderEnd(); + } +} diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index f18c4529ab..38e2e74349 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -2,20 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System.Diagnostics; -using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public class LegacyFollowCircle : CompositeDrawable + public class LegacyFollowCircle : FollowCircle { - [Resolved(canBeNull: true)] - private DrawableHitObject? parentObject { get; set; } - public LegacyFollowCircle(Drawable animationContent) { // follow circles are 2x the hitcircle resolution in legacy skins (since they are scaled down from >1x @@ -27,41 +21,17 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy InternalChild = animationContent; } - [BackgroundDependencyLoader] - private void load() + protected override void OnTrackingChanged(ValueChangedEvent tracking) { - if (parentObject != null) - { - var slider = (DrawableSlider)parentObject; - slider.Tracking.BindValueChanged(trackingChanged, true); - } - } + Debug.Assert(ParentObject != null); - protected override void LoadComplete() - { - base.LoadComplete(); - - if (parentObject != null) - { - parentObject.HitObjectApplied += onHitObjectApplied; - onHitObjectApplied(parentObject); - - parentObject.ApplyCustomUpdateState += updateStateTransforms; - updateStateTransforms(parentObject, parentObject.State.Value); - } - } - - private void trackingChanged(ValueChangedEvent tracking) - { - Debug.Assert(parentObject != null); - - if (parentObject.Judged) + if (ParentObject.Judged) return; const float scale_duration = 180f; const float fade_duration = 90f; - double maxScaleDuration = parentObject.HitStateUpdateTime - Time.Current; + double maxScaleDuration = ParentObject.HitStateUpdateTime - Time.Current; double realScaleDuration = scale_duration; if (tracking.NewValue && maxScaleDuration < realScaleDuration && maxScaleDuration >= 0) realScaleDuration = maxScaleDuration; @@ -71,39 +41,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy .FadeTo(tracking.NewValue ? 1f : 0f, realFadeDuration, Easing.OutQuad); } - private void onHitObjectApplied(DrawableHitObject drawableObject) + protected override void OnSliderEnd() { - this.ScaleTo(1f) - .FadeOut(); - } - - private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) - { - // see comment in LegacySliderBall.updateStateTransforms - if (drawableObject is not DrawableSlider) - return; - const float shrink_duration = 200f; const float fade_delay = 175f; const float fade_duration = 35f; - using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) - { - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 0.75f, shrink_duration, Easing.OutQuad) - .Delay(fade_delay) - .FadeOut(fade_duration); - } - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (parentObject != null) - { - parentObject.HitObjectApplied -= onHitObjectApplied; - parentObject.ApplyCustomUpdateState -= updateStateTransforms; - } + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 0.75f, shrink_duration, Easing.OutQuad) + .Delay(fade_delay) + .FadeOut(fade_duration); } } } From 4453b0b3e831a9619213aa33a42376eea0f43598 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Thu, 14 Jul 2022 16:53:51 -0700 Subject: [PATCH 138/145] Replace comment pointer with actual comment --- osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs index 37e5e150bd..97bb4a3697 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultSliderBall.cs @@ -73,7 +73,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { - // see comment in LegacySliderBall.updateStateTransforms + // Gets called by slider ticks, tails, etc., leading to duplicated + // animations which may negatively affect performance if (drawableObject is not DrawableSlider) return; From 1581f1a0ff8a0570d684a2e02bc58417003aeded Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Thu, 14 Jul 2022 17:08:52 -0700 Subject: [PATCH 139/145] Convert constructor in abstract class to protected --- osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs index e1e792b89e..943b7d3b4f 100644 --- a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [Resolved(canBeNull: true)] protected DrawableHitObject? ParentObject { get; private set; } - public FollowCircle() + protected FollowCircle() { RelativeSizeAxes = Axes.Both; } From 0bafafd63b4ab42462158af4f67d68cc7eb2fbbe Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 15 Jul 2022 03:19:50 +0300 Subject: [PATCH 140/145] Remove unnecessary test coverage RIP hours. --- .../TestSceneDrawableRoomPlaylist.cs | 149 +----------------- 1 file changed, 6 insertions(+), 143 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 599a28f913..1797c82fb9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -12,20 +12,16 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; -using osu.Game.Collections; using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; -using osu.Game.Graphics.UserInterface; using osu.Game.Models; using osu.Game.Online.API; using osu.Game.Online.Rooms; -using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -39,16 +35,10 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneDrawableRoomPlaylist : MultiplayerTestScene { - protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; - private TestPlaylist playlist; private BeatmapManager manager; private RulesetStore rulesets; - private CollectionManager collections; - - private BeatmapSetOverlay beatmapOverlay; - private ManageCollectionsDialog manageCollectionsDialog; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) @@ -56,14 +46,6 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); - - base.Content.AddRange(new Drawable[] - { - collections = new CollectionManager(LocalStorage), - Content - }); - - Dependencies.Cache(collections); } [Test] @@ -321,111 +303,6 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } - [Test] - public void TestContextMenuDetails() - { - OsuContextMenu contextMenu = null; - - createPlaylist(); - - AddAssert("beatmap overlay hidden", () => beatmapOverlay.State.Value == Visibility.Hidden); - - moveToItem(0); - AddStep("right click", () => InputManager.Click(MouseButton.Right)); - AddAssert("context menu open", () => (contextMenu = this.ChildrenOfType().SingleOrDefault())?.State == MenuState.Open); - - AddStep("click details", () => - { - InputManager.MoveMouseTo(contextMenu.ChildrenOfType().First()); - InputManager.Click(MouseButton.Left); - }); - AddAssert("beatmap overlay visible", () => beatmapOverlay.State.Value == Visibility.Visible); - } - - [Test] - public void TestContextMenuCollection() - { - OsuContextMenu contextMenu = null; - BeatmapInfo beatmap = null; - Live imported = null; - - AddStep("import beatmap", () => - { - beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; - - Debug.Assert(beatmap.BeatmapSet != null); - imported = manager.Import(beatmap.BeatmapSet); - }); - - createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach())); - - AddStep("add two collections", () => - { - collections.Collections.Clear(); - collections.Collections.AddRange(new[] - { - new BeatmapCollection { Name = { Value = "Collection #1" }, BeatmapHashes = { beatmap.MD5Hash } }, - new BeatmapCollection { Name = { Value = "Collection #2" } }, - }); - }); - - moveToItem(0); - AddStep("right click", () => InputManager.Click(MouseButton.Right)); - AddAssert("context menu open", () => (contextMenu = this.ChildrenOfType().SingleOrDefault())?.State == MenuState.Open); - - AddStep("select collections", () => InputManager.MoveMouseTo(contextMenu.ChildrenOfType().ElementAt(1))); - AddAssert("collection 1 present and beatmap added", () => - { - var item = (ToggleMenuItem)contextMenu.Items[1].Items[0]; - return item.Text.Value == "Collection #1" && item.State.Value; - }); - AddAssert("collection 2 present", () => - { - var item = (ToggleMenuItem)contextMenu.Items[1].Items[1]; - return item.Text.Value == "Collection #2" && !item.State.Value; - }); - - AddStep("select second collection", () => - { - InputManager.MoveMouseTo(contextMenu.ChildrenOfType().ElementAt(1)); - InputManager.Click(MouseButton.Left); - }); - AddAssert("beatmap added to second collection", () => collections.Collections[1].BeatmapHashes.Contains(beatmap.MD5Hash)); - AddAssert("item state updated", () => ((ToggleMenuItem)contextMenu.Items[1].Items[1]).State.Value); - } - - [Test] - public void TestContextMenuManageCollections() - { - OsuContextMenu contextMenu = null; - Live imported = null; - - AddStep("import beatmap", () => - { - var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; - - Debug.Assert(beatmap.BeatmapSet != null); - imported = manager.Import(beatmap.BeatmapSet); - }); - - createPlaylistWithBeatmaps(() => imported.PerformRead(s => s.Beatmaps.Detach())); - - AddStep("clear collections", () => collections.Collections.Clear()); - AddAssert("manage collections dialog hidden", () => manageCollectionsDialog.State.Value == Visibility.Hidden); - - moveToItem(0); - AddStep("right click", () => InputManager.Click(MouseButton.Right)); - AddAssert("context menu open", () => (contextMenu = this.ChildrenOfType().SingleOrDefault())?.State == MenuState.Open); - - AddStep("select collections", () => InputManager.MoveMouseTo(contextMenu.ChildrenOfType().ElementAt(1))); - AddStep("click manage", () => - { - InputManager.MoveMouseTo(contextMenu.ChildrenOfType().ElementAt(1).ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); - AddAssert("manage collections dialog open", () => manageCollectionsDialog.State.Value == Visibility.Visible); - } - private void moveToItem(int index, Vector2? offset = null) => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(index), offset)); @@ -470,29 +347,15 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create playlist", () => { - Child = new DependencyProvidingContainer + Child = new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, - CachedDependencies = new (Type, object)[] + Child = playlist = new TestPlaylist { - (typeof(BeatmapSetOverlay), beatmapOverlay = new BeatmapSetOverlay()), - (typeof(ManageCollectionsDialog), manageCollectionsDialog = new ManageCollectionsDialog()), - }, - Children = new Drawable[] - { - new OsuContextMenuContainer - { - RelativeSizeAxes = Axes.Both, - Child = playlist = new TestPlaylist - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500, 300), - }, - }, - beatmapOverlay, - manageCollectionsDialog, - }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500, 300) + } }; for (int i = 0; i < 20; i++) From aea786ea0c2f5e7df04911a37dac0f0f855316db Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 15 Jul 2022 05:56:49 +0300 Subject: [PATCH 141/145] Fix minor typo --- osu.Game/Overlays/DialogOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index dfc6a0010a..493cd66258 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays // if any existing dialog is being displayed, dismiss it before showing a new one. lastDialog?.Hide(); - // is the new dialog is hidden before added to the dialogContainer, bypass any further operations. + // if the new dialog is hidden before added to the dialogContainer, bypass any further operations. if (dialog.State.Value == Visibility.Hidden) { dismiss(); From 254d22de1c94ec53347dc597a3cdca70f8c219e5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 15 Jul 2022 07:44:56 +0300 Subject: [PATCH 142/145] Use proper variable name --- osu.Game/Collections/CollectionToggleMenuItem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Collections/CollectionToggleMenuItem.cs b/osu.Game/Collections/CollectionToggleMenuItem.cs index fbc935d19a..f2b10305b8 100644 --- a/osu.Game/Collections/CollectionToggleMenuItem.cs +++ b/osu.Game/Collections/CollectionToggleMenuItem.cs @@ -9,9 +9,9 @@ namespace osu.Game.Collections public class CollectionToggleMenuItem : ToggleMenuItem { public CollectionToggleMenuItem(BeatmapCollection collection, IBeatmapInfo beatmap) - : base(collection.Name.Value, MenuItemType.Standard, s => + : base(collection.Name.Value, MenuItemType.Standard, state => { - if (s) + if (state) collection.BeatmapHashes.Add(beatmap.MD5Hash); else collection.BeatmapHashes.Remove(beatmap.MD5Hash); From 7e80a710204074a1011eb66f664b7061f734e9fa Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 15 Jul 2022 08:16:36 +0300 Subject: [PATCH 143/145] Replace download tracker with local querying --- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 31 +++++++------------ 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index d89816d3c7..f38077a9a7 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -5,12 +5,11 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading.Tasks; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -95,12 +94,12 @@ namespace osu.Game.Screens.OnlinePlay private PanelBackground panelBackground; private FillFlowContainer mainFillFlow; - [CanBeNull] - private BeatmapDownloadTracker downloadTracker; - [Resolved] private RulesetStore rulesets { get; set; } + [Resolved] + private BeatmapManager beatmaps { get; set; } + [Resolved] private OsuColour colours { get; set; } @@ -321,15 +320,6 @@ namespace osu.Game.Screens.OnlinePlay difficultyIconContainer.FadeInFromZero(500, Easing.OutQuint); mainFillFlow.FadeInFromZero(500, Easing.OutQuint); - - downloadTracker?.RemoveAndDisposeImmediately(); - - if (beatmap != null) - { - Debug.Assert(beatmap.BeatmapSet != null); - downloadTracker = new BeatmapDownloadTracker(beatmap.BeatmapSet); - AddInternal(downloadTracker); - } } protected override Drawable CreateContent() @@ -505,13 +495,16 @@ namespace osu.Game.Screens.OnlinePlay if (beatmapOverlay != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(Item.Beatmap.OnlineID))); - if (beatmap != null && collectionManager != null && downloadTracker?.State.Value == DownloadState.LocallyAvailable) + if (collectionManager != null && beatmap != null) { - var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); - if (manageCollectionsDialog != null) - collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + if (beatmaps.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID) is BeatmapInfo local && !local.BeatmapSet.AsNonNull().DeletePending) + { + var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); - items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); + } } return items.ToArray(); From 0ade8db550cf193b7093d5d42658f690bbba2a38 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 16:40:48 +0900 Subject: [PATCH 144/145] Tidy up nullability and casting --- osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs index 943b7d3b4f..321705d25e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Osu.Skinning { public abstract class FollowCircle : CompositeDrawable { - [Resolved(canBeNull: true)] + [Resolved] protected DrawableHitObject? ParentObject { get; private set; } protected FollowCircle() @@ -23,11 +23,7 @@ namespace osu.Game.Rulesets.Osu.Skinning [BackgroundDependencyLoader] private void load() { - if (ParentObject != null) - { - var slider = (DrawableSlider)ParentObject; - slider.Tracking.BindValueChanged(OnTrackingChanged, true); - } + ((DrawableSlider?)ParentObject)?.Tracking.BindValueChanged(OnTrackingChanged, true); } protected override void LoadComplete() From 7ed4eb5815cc682567da35177f5d7f774a406cad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 17:01:01 +0900 Subject: [PATCH 145/145] Adjust transform logic to match osu-stable (and add TODOs for remaining oversights) --- .../Skinning/Legacy/LegacyFollowCircle.cs | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index 38e2e74349..324f2525bc 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -1,10 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Rulesets.Osu.Objects.Drawables; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { @@ -28,28 +28,28 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy if (ParentObject.Judged) return; - const float scale_duration = 180f; - const float fade_duration = 90f; + double remainingTime = ParentObject.HitStateUpdateTime - Time.Current; - double maxScaleDuration = ParentObject.HitStateUpdateTime - Time.Current; - double realScaleDuration = scale_duration; - if (tracking.NewValue && maxScaleDuration < realScaleDuration && maxScaleDuration >= 0) - realScaleDuration = maxScaleDuration; - double realFadeDuration = fade_duration * realScaleDuration / fade_duration; - - this.ScaleTo(tracking.NewValue ? DrawableSliderBall.FOLLOW_AREA : 1f, realScaleDuration, Easing.OutQuad) - .FadeTo(tracking.NewValue ? 1f : 0f, realFadeDuration, Easing.OutQuad); + // Note that the scale adjust here is 2 instead of DrawableSliderBall.FOLLOW_AREA to match legacy behaviour. + // This means the actual tracking area for gameplay purposes is larger than the sprite (but skins may be accounting for this). + if (tracking.NewValue) + { + // TODO: Follow circle should bounce on each slider tick. + this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out) + .FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime)); + } + else + { + // TODO: Should animate only at the next slider tick if we want to match stable perfectly. + this.ScaleTo(4f, 100) + .FadeTo(0f, 100); + } } protected override void OnSliderEnd() { - const float shrink_duration = 200f; - const float fade_delay = 175f; - const float fade_duration = 35f; - - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 0.75f, shrink_duration, Easing.OutQuad) - .Delay(fade_delay) - .FadeOut(fade_duration); + this.ScaleTo(1.6f, 200, Easing.Out) + .FadeOut(200, Easing.In); } } }