From 5d5e6a5ab750f380393a9c6ffd9cd3028e486f1f Mon Sep 17 00:00:00 2001 From: Hydria Date: Mon, 3 Jul 2023 17:45:30 +0100 Subject: [PATCH 001/207] Finalised LN Adjustment Values Spent a couple days discussing this on the pp rework server about values that were the most acceptable, these seemed to be the best from the community standpoint of top players. Note: This is more to fix issues with the current system, not to be a final solution. Related Google Sheets Page: https://docs.google.com/spreadsheets/d/1P0AxfdKvMHwWBQder4ZkFGO1fC9eADSGCryA5-UGriU/edit?usp=sharing --- osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs index 06c825e37d77..0a4fec3a70d9 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs @@ -14,7 +14,7 @@ public class Strain : StrainDecaySkill { private const double individual_decay_base = 0.125; private const double overall_decay_base = 0.30; - private const double release_threshold = 24; + private const double release_threshold = 30; protected override double SkillMultiplier => 1; protected override double StrainDecayBase => 1; @@ -70,7 +70,7 @@ protected override double StrainValueOf(DifficultyHitObject current) // 0.0 +--------+-+---------------> Release Difference / ms // release_threshold if (isOverlapping) - holdAddition = 1 / (1 + Math.Exp(0.5 * (release_threshold - closestEndTime))); + holdAddition = 1 / (1 + Math.Exp(0.27 * (release_threshold - closestEndTime))); // Decay and increase individualStrains in own column individualStrains[column] = applyDecay(individualStrains[column], startTime - startTimes[column], individual_decay_base); From ef2134a92a32f0607d0f69bc04c8dbfd2a477acd Mon Sep 17 00:00:00 2001 From: Hydria Date: Sat, 22 Jul 2023 10:45:48 +0100 Subject: [PATCH 002/207] Fix issue with processing LN orders Related Issue: https://github.com/ppy/osu/issues/22756 The trigger in question happens when (1) in a chord: a longer LN, then a shorter LN is processed respectively. (2) in a chord: a long LN, then a note is processed respectively. however, given the opposite processing step, it will fail to trigger. We observe that both situations have the same pattern, however has undeterministic results, which only depends on the order the mapper placed each note. --- osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs index 0a4fec3a70d9..7d8d010da0d0 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs @@ -53,7 +53,8 @@ protected override double StrainValueOf(DifficultyHitObject current) isOverlapping |= Precision.DefinitelyBigger(endTimes[i], startTime, 1) && Precision.DefinitelyBigger(endTime, endTimes[i], 1); // We give a slight bonus to everything if something is held meanwhile - if (Precision.DefinitelyBigger(endTimes[i], endTime, 1)) + if (Precision.DefinitelyBigger(endTimes[i], endTime, 1) && + Precision.DefinitelyBigger(startTime, startTimes[i], 1)) holdFactor = 1.25; closestEndTime = Math.Min(closestEndTime, Math.Abs(endTime - endTimes[i])); From 1e19def1538d718421e4d7c6802045011616d21e Mon Sep 17 00:00:00 2001 From: Hydria Date: Sat, 22 Jul 2023 16:44:01 +0100 Subject: [PATCH 003/207] 2nd fix to cover all scenarios --- osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs index 7d8d010da0d0..a24fcaad8d44 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/Skills/Strain.cs @@ -50,7 +50,9 @@ protected override double StrainValueOf(DifficultyHitObject current) for (int i = 0; i < endTimes.Length; ++i) { // The current note is overlapped if a previous note or end is overlapping the current note body - isOverlapping |= Precision.DefinitelyBigger(endTimes[i], startTime, 1) && Precision.DefinitelyBigger(endTime, endTimes[i], 1); + isOverlapping |= Precision.DefinitelyBigger(endTimes[i], startTime, 1) && + Precision.DefinitelyBigger(endTime, endTimes[i], 1) && + Precision.DefinitelyBigger(startTime, startTimes[i], 1); // We give a slight bonus to everything if something is held meanwhile if (Precision.DefinitelyBigger(endTimes[i], endTime, 1) && From 4622255cc7a3d20487fce6378ce4d6dc0f487a00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jul 2023 18:24:15 +0200 Subject: [PATCH 004/207] Move out helper methods to static class --- .../Edit/OsuSelectionHandler.cs | 45 ++----- .../SkinEditor/SkinSelectionHandler.cs | 7 +- .../Compose/Components/SelectionHandler.cs | 94 ------------- osu.Game/Utils/GeometryUtils.cs | 126 ++++++++++++++++++ 4 files changed, 143 insertions(+), 129 deletions(-) create mode 100644 osu.Game/Utils/GeometryUtils.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 2a6d6ce4c3a4..468d8ae9f572 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Utils; using osuTK; using osuTK.Input; @@ -42,7 +43,7 @@ protected override void OnSelectionChanged() { base.OnSelectionChanged(); - Quad quad = selectedMovableObjects.Length > 0 ? getSurroundingQuad(selectedMovableObjects) : new Quad(); + Quad quad = selectedMovableObjects.Length > 0 ? GeometryUtils.GetSurroundingQuad(selectedMovableObjects) : new Quad(); SelectionBox.CanRotate = quad.Width > 0 || quad.Height > 0; SelectionBox.CanFlipX = SelectionBox.CanScaleX = quad.Width > 0; @@ -109,13 +110,13 @@ public override bool HandleFlip(Direction direction, bool flipOverOrigin) { var hitObjects = selectedMovableObjects; - var flipQuad = flipOverOrigin ? new Quad(0, 0, OsuPlayfield.BASE_SIZE.X, OsuPlayfield.BASE_SIZE.Y) : getSurroundingQuad(hitObjects); + var flipQuad = flipOverOrigin ? new Quad(0, 0, OsuPlayfield.BASE_SIZE.X, OsuPlayfield.BASE_SIZE.Y) : GeometryUtils.GetSurroundingQuad(hitObjects); bool didFlip = false; foreach (var h in hitObjects) { - var flippedPosition = GetFlippedPosition(direction, flipQuad, h.Position); + var flippedPosition = GeometryUtils.GetFlippedPosition(direction, flipQuad, h.Position); if (!Precision.AlmostEquals(flippedPosition, h.Position)) { @@ -173,18 +174,18 @@ public override bool HandleRotation(float delta) { var hitObjects = selectedMovableObjects; - Quad quad = getSurroundingQuad(hitObjects); + Quad quad = GeometryUtils.GetSurroundingQuad(hitObjects); referenceOrigin ??= quad.Centre; foreach (var h in hitObjects) { - h.Position = RotatePointAroundOrigin(h.Position, referenceOrigin.Value, delta); + h.Position = GeometryUtils.RotatePointAroundOrigin(h.Position, referenceOrigin.Value, delta); if (h is IHasPath path) { foreach (PathControlPoint cp in path.Path.ControlPoints) - cp.Position = RotatePointAroundOrigin(cp.Position, Vector2.Zero, delta); + cp.Position = GeometryUtils.RotatePointAroundOrigin(cp.Position, Vector2.Zero, delta); } } @@ -196,7 +197,7 @@ private void scaleSlider(Slider slider, Vector2 scale) { referencePathTypes ??= slider.Path.ControlPoints.Select(p => p.Type).ToList(); - Quad sliderQuad = GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position)); + Quad sliderQuad = GeometryUtils.GetSurroundingQuad(slider.Path.ControlPoints.Select(p => p.Position)); // Limit minimum distance between control points after scaling to almost 0. Less than 0 causes the slider to flip, exactly 0 causes a crash through division by 0. scale = Vector2.ComponentMax(new Vector2(Precision.FLOAT_EPSILON), sliderQuad.Size + scale) - sliderQuad.Size; @@ -222,7 +223,7 @@ private void scaleSlider(Slider slider, Vector2 scale) slider.SnapTo(snapProvider); //if sliderhead or sliderend end up outside playfield, revert scaling. - Quad scaledQuad = getSurroundingQuad(new OsuHitObject[] { slider }); + Quad scaledQuad = GeometryUtils.GetSurroundingQuad(new OsuHitObject[] { slider }); (bool xInBounds, bool yInBounds) = isQuadInBounds(scaledQuad); if (xInBounds && yInBounds && slider.Path.HasValidLength) @@ -238,10 +239,10 @@ private void scaleSlider(Slider slider, Vector2 scale) private void scaleHitObjects(OsuHitObject[] hitObjects, Anchor reference, Vector2 scale) { scale = getClampedScale(hitObjects, reference, scale); - Quad selectionQuad = getSurroundingQuad(hitObjects); + Quad selectionQuad = GeometryUtils.GetSurroundingQuad(hitObjects); foreach (var h in hitObjects) - h.Position = GetScaledPosition(reference, scale, selectionQuad, h.Position); + h.Position = GeometryUtils.GetScaledPosition(reference, scale, selectionQuad, h.Position); } private (bool X, bool Y) isQuadInBounds(Quad quad) @@ -256,7 +257,7 @@ private void moveSelectionInBounds() { var hitObjects = selectedMovableObjects; - Quad quad = getSurroundingQuad(hitObjects); + Quad quad = GeometryUtils.GetSurroundingQuad(hitObjects); Vector2 delta = Vector2.Zero; @@ -286,7 +287,7 @@ private Vector2 getClampedScale(OsuHitObject[] hitObjects, Anchor reference, Vec float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0; float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0; - Quad selectionQuad = getSurroundingQuad(hitObjects); + Quad selectionQuad = GeometryUtils.GetSurroundingQuad(hitObjects); //todo: this is not always correct for selections involving sliders. This approximation assumes each point is scaled independently, but sliderends move with the sliderhead. Quad scaledQuad = new Quad(selectionQuad.TopLeft.X + xOffset, selectionQuad.TopLeft.Y + yOffset, selectionQuad.Width + scale.X, selectionQuad.Height + scale.Y); @@ -311,26 +312,6 @@ private Vector2 getClampedScale(OsuHitObject[] hitObjects, Anchor reference, Vec return scale; } - /// - /// Returns a gamefield-space quad surrounding the provided hit objects. - /// - /// The hit objects to calculate a quad for. - private Quad getSurroundingQuad(OsuHitObject[] hitObjects) => - GetSurroundingQuad(hitObjects.SelectMany(h => - { - if (h is IHasPath path) - { - return new[] - { - h.Position, - // can't use EndPosition for reverse slider cases. - h.Position + path.Path.PositionAt(1) - }; - } - - return new[] { h.Position }; - })); - /// /// All osu! hitobjects which can be moved/rotated/scaled. /// diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index b43f4eeb0096..4a1ddd9d698d 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -16,6 +16,7 @@ using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose.Components; using osu.Game.Skinning; +using osu.Game.Utils; using osuTK; namespace osu.Game.Overlays.SkinEditor @@ -40,7 +41,7 @@ public override bool HandleRotation(float angle) { var drawableItem = (Drawable)b.Item; - var rotatedPosition = RotatePointAroundOrigin(b.ScreenSpaceSelectionPoint, selectionQuad.Centre, angle); + var rotatedPosition = GeometryUtils.RotatePointAroundOrigin(b.ScreenSpaceSelectionPoint, selectionQuad.Centre, angle); updateDrawablePosition(drawableItem, rotatedPosition); drawableItem.Rotation += angle; @@ -137,7 +138,7 @@ public override bool HandleFlip(Direction direction, bool flipOverOrigin) { var drawableItem = (Drawable)b.Item; - var flippedPosition = GetFlippedPosition(direction, flipOverOrigin ? drawableItem.Parent.ScreenSpaceDrawQuad : selectionQuad, b.ScreenSpaceSelectionPoint); + var flippedPosition = GeometryUtils.GetFlippedPosition(direction, flipOverOrigin ? drawableItem.Parent.ScreenSpaceDrawQuad : selectionQuad, b.ScreenSpaceSelectionPoint); updateDrawablePosition(drawableItem, flippedPosition); @@ -275,7 +276,7 @@ private void applyOrigins(Anchor origin) /// /// private Quad getSelectionQuad() => - GetSurroundingQuad(SelectedBlueprints.SelectMany(b => b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())); + GeometryUtils.GetSurroundingQuad(SelectedBlueprints.SelectMany(b => b.Item.ScreenSpaceDrawQuad.GetVertices().ToArray())); private void applyFixedAnchors(Anchor anchor) { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 052cb18a5da9..9b44b15fe4bb 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -16,7 +16,6 @@ using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; using osu.Game.Resources.Localisation.Web; @@ -401,98 +400,5 @@ protected virtual IEnumerable GetContextMenuItemsForSelection(IEnumera => Enumerable.Empty(); #endregion - - #region Helper Methods - - /// - /// Rotate a point around an arbitrary origin. - /// - /// The point. - /// The centre origin to rotate around. - /// The angle to rotate (in degrees). - protected static Vector2 RotatePointAroundOrigin(Vector2 point, Vector2 origin, float angle) - { - angle = -angle; - - point.X -= origin.X; - point.Y -= origin.Y; - - Vector2 ret; - ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(MathUtils.DegreesToRadians(angle)); - ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(MathUtils.DegreesToRadians(angle)); - - ret.X += origin.X; - ret.Y += origin.Y; - - return ret; - } - - /// - /// Given a flip direction, a surrounding quad for all selected objects, and a position, - /// will return the flipped position in screen space coordinates. - /// - protected static Vector2 GetFlippedPosition(Direction direction, Quad quad, Vector2 position) - { - var centre = quad.Centre; - - switch (direction) - { - case Direction.Horizontal: - position.X = centre.X - (position.X - centre.X); - break; - - case Direction.Vertical: - position.Y = centre.Y - (position.Y - centre.Y); - break; - } - - return position; - } - - /// - /// Given a scale vector, a surrounding quad for all selected objects, and a position, - /// will return the scaled position in screen space coordinates. - /// - protected static Vector2 GetScaledPosition(Anchor reference, Vector2 scale, Quad selectionQuad, Vector2 position) - { - // adjust the direction of scale depending on which side the user is dragging. - float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0; - float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0; - - // guard against no-ops and NaN. - if (scale.X != 0 && selectionQuad.Width > 0) - position.X = selectionQuad.TopLeft.X + xOffset + (position.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X); - - if (scale.Y != 0 && selectionQuad.Height > 0) - position.Y = selectionQuad.TopLeft.Y + yOffset + (position.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y); - - return position; - } - - /// - /// Returns a quad surrounding the provided points. - /// - /// The points to calculate a quad for. - protected static Quad GetSurroundingQuad(IEnumerable points) - { - if (!points.Any()) - return new Quad(); - - Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); - Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); - - // Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted - foreach (var p in points) - { - minPosition = Vector2.ComponentMin(minPosition, p); - maxPosition = Vector2.ComponentMax(maxPosition, p); - } - - Vector2 size = maxPosition - minPosition; - - return new Quad(minPosition.X, minPosition.Y, size.X, size.Y); - } - - #endregion } } diff --git a/osu.Game/Utils/GeometryUtils.cs b/osu.Game/Utils/GeometryUtils.cs new file mode 100644 index 000000000000..725e93d0986f --- /dev/null +++ b/osu.Game/Utils/GeometryUtils.cs @@ -0,0 +1,126 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Utils; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Utils +{ + public static class GeometryUtils + { + /// + /// Rotate a point around an arbitrary origin. + /// + /// The point. + /// The centre origin to rotate around. + /// The angle to rotate (in degrees). + public static Vector2 RotatePointAroundOrigin(Vector2 point, Vector2 origin, float angle) + { + angle = -angle; + + point.X -= origin.X; + point.Y -= origin.Y; + + Vector2 ret; + ret.X = point.X * MathF.Cos(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Sin(MathUtils.DegreesToRadians(angle)); + ret.Y = point.X * -MathF.Sin(MathUtils.DegreesToRadians(angle)) + point.Y * MathF.Cos(MathUtils.DegreesToRadians(angle)); + + ret.X += origin.X; + ret.Y += origin.Y; + + return ret; + } + + /// + /// Given a flip direction, a surrounding quad for all selected objects, and a position, + /// will return the flipped position in screen space coordinates. + /// + public static Vector2 GetFlippedPosition(Direction direction, Quad quad, Vector2 position) + { + var centre = quad.Centre; + + switch (direction) + { + case Direction.Horizontal: + position.X = centre.X - (position.X - centre.X); + break; + + case Direction.Vertical: + position.Y = centre.Y - (position.Y - centre.Y); + break; + } + + return position; + } + + /// + /// Given a scale vector, a surrounding quad for all selected objects, and a position, + /// will return the scaled position in screen space coordinates. + /// + public static Vector2 GetScaledPosition(Anchor reference, Vector2 scale, Quad selectionQuad, Vector2 position) + { + // adjust the direction of scale depending on which side the user is dragging. + float xOffset = ((reference & Anchor.x0) > 0) ? -scale.X : 0; + float yOffset = ((reference & Anchor.y0) > 0) ? -scale.Y : 0; + + // guard against no-ops and NaN. + if (scale.X != 0 && selectionQuad.Width > 0) + position.X = selectionQuad.TopLeft.X + xOffset + (position.X - selectionQuad.TopLeft.X) / selectionQuad.Width * (selectionQuad.Width + scale.X); + + if (scale.Y != 0 && selectionQuad.Height > 0) + position.Y = selectionQuad.TopLeft.Y + yOffset + (position.Y - selectionQuad.TopLeft.Y) / selectionQuad.Height * (selectionQuad.Height + scale.Y); + + return position; + } + + /// + /// Returns a quad surrounding the provided points. + /// + /// The points to calculate a quad for. + public static Quad GetSurroundingQuad(IEnumerable points) + { + if (!points.Any()) + return new Quad(); + + Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); + Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); + + // Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted + foreach (var p in points) + { + minPosition = Vector2.ComponentMin(minPosition, p); + maxPosition = Vector2.ComponentMax(maxPosition, p); + } + + Vector2 size = maxPosition - minPosition; + + return new Quad(minPosition.X, minPosition.Y, size.X, size.Y); + } + + /// + /// Returns a gamefield-space quad surrounding the provided hit objects. + /// + /// The hit objects to calculate a quad for. + public static Quad GetSurroundingQuad(IEnumerable hitObjects) => + GetSurroundingQuad(hitObjects.SelectMany(h => + { + if (h is IHasPath path) + { + return new[] + { + h.Position, + // can't use EndPosition for reverse slider cases. + h.Position + path.Path.PositionAt(1) + }; + } + + return new[] { h.Position }; + })); + } +} From ba8ebefb50dca3a31462ecf4b282036ada782991 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jul 2023 18:09:07 +0200 Subject: [PATCH 005/207] Add basic structure for new rotation handler --- .../Compose/Components/SelectionHandler.cs | 9 ++++++ .../Components/SelectionRotationHandler.cs | 31 +++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 9b44b15fe4bb..80df796fd7fa 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -55,6 +55,8 @@ public abstract partial class SelectionHandler : CompositeDrawable, IKeyBindi [Resolved(CanBeNull = true)] protected IEditorChangeHandler ChangeHandler { get; private set; } + protected SelectionRotationHandler RotationHandler { get; private set; } + protected SelectionHandler() { selectedBlueprints = new List>(); @@ -66,6 +68,8 @@ protected SelectionHandler() [BackgroundDependencyLoader] private void load() { + RotationHandler = CreateRotationHandler(); + InternalChild = SelectionBox = CreateSelectionBox(); SelectedItems.CollectionChanged += (_, _) => @@ -132,6 +136,11 @@ protected virtual void OnOperationEnded() /// Whether any items could be rotated. public virtual bool HandleRotation(float angle) => false; + /// + /// Creates the handler to use for rotation operations. + /// + public virtual SelectionRotationHandler CreateRotationHandler() => new SelectionRotationHandler(); + /// /// Handles the selected items being scaled. /// diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs new file mode 100644 index 000000000000..595edbb4fc81 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Bindables; +using osuTK; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + /// + /// Base handler for editor rotation operations. + /// + public class SelectionRotationHandler + { + /// + /// Whether the rotation can currently be performed. + /// + public Bindable CanRotate { get; private set; } = new BindableBool(); + + public virtual void Begin() + { + } + + public virtual void Update(float rotation, Vector2 origin) + { + } + + public virtual void Commit() + { + } + } +} From ba904fd77bbb530b505817b32dbb31ac0e5baa1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jul 2023 19:18:38 +0200 Subject: [PATCH 006/207] Migrate osu! rotation handling to `SelectionRotationHandler` --- .../Edit/OsuSelectionHandler.cs | 30 +----- .../Edit/OsuSelectionRotationHandler.cs | 98 +++++++++++++++++++ 2 files changed, 101 insertions(+), 27 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 468d8ae9f572..1d46b8ff8a6b 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -28,11 +28,6 @@ public partial class OsuSelectionHandler : EditorSelectionHandler [Resolved(CanBeNull = true)] private IDistanceSnapProvider? snapProvider { get; set; } - /// - /// During a transform, the initial origin is stored so it can be used throughout the operation. - /// - private Vector2? referenceOrigin; - /// /// During a transform, the initial path types of a single selected slider are stored so they /// can be maintained throughout the operation. @@ -54,7 +49,6 @@ protected override void OnSelectionChanged() protected override void OnOperationEnded() { base.OnOperationEnded(); - referenceOrigin = null; referencePathTypes = null; } @@ -170,28 +164,10 @@ private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; } - public override bool HandleRotation(float delta) + public override SelectionRotationHandler CreateRotationHandler() => new OsuSelectionRotationHandler(ChangeHandler) { - var hitObjects = selectedMovableObjects; - - Quad quad = GeometryUtils.GetSurroundingQuad(hitObjects); - - referenceOrigin ??= quad.Centre; - - foreach (var h in hitObjects) - { - h.Position = GeometryUtils.RotatePointAroundOrigin(h.Position, referenceOrigin.Value, delta); - - if (h is IHasPath path) - { - foreach (PathControlPoint cp in path.Path.ControlPoints) - cp.Position = GeometryUtils.RotatePointAroundOrigin(cp.Position, Vector2.Zero, delta); - } - } - - // this isn't always the case but let's be lenient for now. - return true; - } + SelectedItems = { BindTarget = SelectedItems } + }; private void scaleSlider(Slider slider, Vector2 scale) { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs new file mode 100644 index 000000000000..0eb7637786da --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs @@ -0,0 +1,98 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Bindables; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Utils; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Edit +{ + public class OsuSelectionRotationHandler : SelectionRotationHandler + { + private readonly IEditorChangeHandler? changeHandler; + + public BindableList SelectedItems { get; } = new BindableList(); + + public OsuSelectionRotationHandler(IEditorChangeHandler? changeHandler) + { + this.changeHandler = changeHandler; + + SelectedItems.CollectionChanged += (_, __) => updateState(); + } + + private void updateState() + { + var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects); + CanRotate.Value = quad.Width > 0 || quad.Height > 0; + } + + private OsuHitObject[]? objectsInRotation; + + private Vector2? defaultOrigin; + private Dictionary? originalPositions; + private Dictionary? originalPathControlPointPositions; + + public override void Begin() + { + if (objectsInRotation != null) + throw new InvalidOperationException($"Cannot {nameof(Begin)} a rotate operation while another is in progress!"); + + changeHandler?.BeginChange(); + + objectsInRotation = selectedMovableObjects.ToArray(); + defaultOrigin = GeometryUtils.GetSurroundingQuad(objectsInRotation).Centre; + originalPositions = objectsInRotation.ToDictionary(obj => obj, obj => obj.Position); + originalPathControlPointPositions = objectsInRotation.OfType().ToDictionary( + obj => obj, + obj => obj.Path.ControlPoints.Select(point => point.Position).ToArray()); + } + + public override void Update(float rotation, Vector2? origin = null) + { + if (objectsInRotation == null) + throw new InvalidOperationException($"Cannot {nameof(Update)} a rotate operation without calling {nameof(Begin)} first!"); + + Debug.Assert(originalPositions != null && originalPathControlPointPositions != null && defaultOrigin != null); + + Vector2 actualOrigin = origin ?? defaultOrigin.Value; + + foreach (var ho in objectsInRotation) + { + ho.Position = GeometryUtils.RotatePointAroundOrigin(originalPositions[ho], actualOrigin, rotation); + + if (ho is IHasPath withPath) + { + var originalPath = originalPathControlPointPositions[withPath]; + + for (int i = 0; i < withPath.Path.ControlPoints.Count; ++i) + withPath.Path.ControlPoints[i].Position = GeometryUtils.RotatePointAroundOrigin(originalPath[i], Vector2.Zero, rotation); + } + } + } + + public override void Commit() + { + if (objectsInRotation == null) + throw new InvalidOperationException($"Cannot {nameof(Commit)} a rotate operation without calling {nameof(Begin)} first!"); + + changeHandler?.EndChange(); + + objectsInRotation = null; + originalPositions = null; + originalPathControlPointPositions = null; + defaultOrigin = null; + } + + private IEnumerable selectedMovableObjects => SelectedItems.Cast() + .Where(h => h is not Spinner); + } +} From f8047d6ab6d96bf9c7b87fcf50b93e2b084da2b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jul 2023 19:48:39 +0200 Subject: [PATCH 007/207] Migrate skin element rotation handling to `SelectionRotationHandler` --- .../SkinEditor/SkinSelectionHandler.cs | 29 +----- .../SkinSelectionRotationHandler.cs | 94 +++++++++++++++++++ 2 files changed, 98 insertions(+), 25 deletions(-) create mode 100644 osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index 4a1ddd9d698d..bee973bea024 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -26,31 +26,11 @@ public partial class SkinSelectionHandler : SelectionHandler new SkinSelectionRotationHandler(ChangeHandler) { - if (SelectedBlueprints.Count == 1) - { - // for single items, rotate around the origin rather than the selection centre. - ((Drawable)SelectedBlueprints.First().Item).Rotation += angle; - } - else - { - var selectionQuad = getSelectionQuad(); - - foreach (var b in SelectedBlueprints) - { - var drawableItem = (Drawable)b.Item; - - var rotatedPosition = GeometryUtils.RotatePointAroundOrigin(b.ScreenSpaceSelectionPoint, selectionQuad.Centre, angle); - updateDrawablePosition(drawableItem, rotatedPosition); - - drawableItem.Rotation += angle; - } - } - - // this isn't always the case but let's be lenient for now. - return true; - } + SelectedItems = { BindTarget = SelectedItems }, + UpdatePosition = updateDrawablePosition + }; public override bool HandleScale(Vector2 scale, Anchor anchor) { @@ -172,7 +152,6 @@ protected override void OnSelectionChanged() { base.OnSelectionChanged(); - SelectionBox.CanRotate = true; SelectionBox.CanScaleX = true; SelectionBox.CanScaleY = true; SelectionBox.CanFlipX = true; diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs new file mode 100644 index 000000000000..e60e2b1e1221 --- /dev/null +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs @@ -0,0 +1,94 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Compose.Components; +using osu.Game.Skinning; +using osu.Game.Utils; +using osuTK; + +namespace osu.Game.Overlays.SkinEditor +{ + public class SkinSelectionRotationHandler : SelectionRotationHandler + { + private readonly IEditorChangeHandler? changeHandler; + + public BindableList SelectedItems { get; } = new BindableList(); + public Action UpdatePosition { get; init; } = null!; + + public SkinSelectionRotationHandler(IEditorChangeHandler? changeHandler) + { + this.changeHandler = changeHandler; + + SelectedItems.CollectionChanged += (_, __) => updateState(); + } + + private void updateState() + { + CanRotate.Value = SelectedItems.Count > 0; + } + + private Drawable[]? objectsInRotation; + + private Vector2? defaultOrigin; + private Dictionary? originalRotations; + private Dictionary? originalPositions; + + public override void Begin() + { + if (objectsInRotation != null) + throw new InvalidOperationException($"Cannot {nameof(Begin)} a rotate operation while another is in progress!"); + + changeHandler?.BeginChange(); + + objectsInRotation = SelectedItems.Cast().ToArray(); + originalRotations = objectsInRotation.ToDictionary(d => d, d => d.Rotation); + originalPositions = objectsInRotation.ToDictionary(d => d, d => d.ToScreenSpace(d.OriginPosition)); + defaultOrigin = GeometryUtils.GetSurroundingQuad(objectsInRotation.SelectMany(d => d.ScreenSpaceDrawQuad.GetVertices().ToArray())).Centre; + } + + public override void Update(float rotation, Vector2? origin = null) + { + if (objectsInRotation == null) + throw new InvalidOperationException($"Cannot {nameof(Update)} a rotate operation without calling {nameof(Begin)} first!"); + + Debug.Assert(originalRotations != null && originalPositions != null && defaultOrigin != null); + + if (objectsInRotation.Length == 1 && origin == null) + { + // for single items, rotate around the origin rather than the selection centre by default. + objectsInRotation[0].Rotation = originalRotations.Single().Value + rotation; + return; + } + + var actualOrigin = origin ?? defaultOrigin.Value; + + foreach (var drawableItem in objectsInRotation) + { + var rotatedPosition = GeometryUtils.RotatePointAroundOrigin(originalPositions[drawableItem], actualOrigin, rotation); + UpdatePosition(drawableItem, rotatedPosition); + + drawableItem.Rotation = originalRotations[drawableItem] + rotation; + } + } + + public override void Commit() + { + if (objectsInRotation == null) + throw new InvalidOperationException($"Cannot {nameof(Commit)} a rotate operation without calling {nameof(Begin)} first!"); + + changeHandler?.EndChange(); + + objectsInRotation = null; + originalPositions = null; + originalRotations = null; + defaultOrigin = null; + } + } +} From 21df0e2d60824ea1e34cd88f60741484d251d049 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jul 2023 19:57:31 +0200 Subject: [PATCH 008/207] Migrate test to `SelectionRotationHandler` --- .../Editing/TestSceneComposeSelectBox.cs | 50 ++++++++++++++++--- 1 file changed, 44 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 7a0b3d0c1a99..147488812e44 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -3,7 +3,9 @@ #nullable disable +using System; using System.Linq; +using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -34,13 +36,12 @@ public void SetUp() => Schedule(() => { RelativeSizeAxes = Axes.Both, - CanRotate = true, CanScaleX = true, CanScaleY = true, CanFlipX = true, CanFlipY = true, - OnRotation = handleRotation, + RotationHandler = new TestSelectionRotationHandler(() => selectionArea), OnScale = handleScale } } @@ -71,11 +72,48 @@ private bool handleScale(Vector2 amount, Anchor reference) return true; } - private bool handleRotation(float angle) + private class TestSelectionRotationHandler : SelectionRotationHandler { - // kinda silly and wrong, but just showing that the drag handles work. - selectionArea.Rotation += angle; - return true; + private readonly Func getTargetContainer; + + public TestSelectionRotationHandler(Func getTargetContainer) + { + this.getTargetContainer = getTargetContainer; + + CanRotate.Value = true; + } + + [CanBeNull] + private Container targetContainer; + + private float? initialRotation; + + public override void Begin() + { + if (targetContainer != null) + throw new InvalidOperationException($"Cannot {nameof(Begin)} a rotate operation while another is in progress!"); + + targetContainer = getTargetContainer(); + initialRotation = targetContainer!.Rotation; + } + + public override void Update(float rotation, Vector2? origin = null) + { + if (targetContainer == null) + throw new InvalidOperationException($"Cannot {nameof(Update)} a rotate operation without calling {nameof(Begin)} first!"); + + // kinda silly and wrong, but just showing that the drag handles work. + targetContainer.Rotation = initialRotation!.Value + rotation; + } + + public override void Commit() + { + if (targetContainer == null) + throw new InvalidOperationException($"Cannot {nameof(Commit)} a rotate operation without calling {nameof(Begin)} first!"); + + targetContainer = null; + initialRotation = null; + } } [Test] From aec3ca250cc3301415d0ba38bc0058b2a2463205 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jul 2023 20:01:30 +0200 Subject: [PATCH 009/207] Migrate `SelectionHandler` to use `SelectionRotationHandler` --- .../Edit/OsuSelectionHandler.cs | 1 - .../Edit/Compose/Components/SelectionBox.cs | 41 ++++++++----------- .../Components/SelectionBoxRotationHandle.cs | 20 +++++---- .../Compose/Components/SelectionHandler.cs | 2 +- .../Components/SelectionRotationHandler.cs | 9 +++- 5 files changed, 37 insertions(+), 36 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 1d46b8ff8a6b..1dfbf4179b29 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -40,7 +40,6 @@ protected override void OnSelectionChanged() Quad quad = selectedMovableObjects.Length > 0 ? GeometryUtils.GetSurroundingQuad(selectedMovableObjects) : new Quad(); - SelectionBox.CanRotate = quad.Width > 0 || quad.Height > 0; SelectionBox.CanFlipX = SelectionBox.CanScaleX = quad.Width > 0; SelectionBox.CanFlipY = SelectionBox.CanScaleY = quad.Height > 0; SelectionBox.CanReverse = EditorBeatmap.SelectedHitObjects.Count > 1 || EditorBeatmap.SelectedHitObjects.Any(s => s is Slider); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 5d9fac739c9c..53442071b53b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -22,7 +23,7 @@ public partial class SelectionBox : CompositeDrawable private const float button_padding = 5; - public Func? OnRotation; + public SelectionRotationHandler? RotationHandler { get; init; } public Func? OnScale; public Func? OnFlip; public Func? OnReverse; @@ -51,22 +52,7 @@ public bool CanReverse } } - private bool canRotate; - - /// - /// Whether rotation support should be enabled. - /// - public bool CanRotate - { - get => canRotate; - set - { - if (canRotate == value) return; - - canRotate = value; - recreate(); - } - } + private IBindable canRotate = new BindableBool(); private bool canScaleX; @@ -161,7 +147,14 @@ public string Text private OsuColour colours { get; set; } = null!; [BackgroundDependencyLoader] - private void load() => recreate(); + private void load() + { + if (RotationHandler != null) + canRotate.BindTo(RotationHandler.CanRotate); + + canRotate.BindValueChanged(_ => recreate()); + recreate(); + } protected override bool OnKeyDown(KeyDownEvent e) { @@ -174,10 +167,10 @@ protected override bool OnKeyDown(KeyDownEvent e) return CanReverse && reverseButton?.TriggerClick() == true; case Key.Comma: - return CanRotate && rotateCounterClockwiseButton?.TriggerClick() == true; + return canRotate.Value && rotateCounterClockwiseButton?.TriggerClick() == true; case Key.Period: - return CanRotate && rotateClockwiseButton?.TriggerClick() == true; + return canRotate.Value && rotateClockwiseButton?.TriggerClick() == true; } return base.OnKeyDown(e); @@ -254,14 +247,14 @@ private void recreate() if (CanScaleY) addYScaleComponents(); if (CanFlipX) addXFlipComponents(); if (CanFlipY) addYFlipComponents(); - if (CanRotate) addRotationComponents(); + if (canRotate.Value) addRotationComponents(); if (CanReverse) reverseButton = addButton(FontAwesome.Solid.Backward, "Reverse pattern (Ctrl-G)", () => OnReverse?.Invoke()); } private void addRotationComponents() { - rotateCounterClockwiseButton = addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise (Ctrl-<)", () => OnRotation?.Invoke(-90)); - rotateClockwiseButton = addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise (Ctrl->)", () => OnRotation?.Invoke(90)); + rotateCounterClockwiseButton = addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise (Ctrl-<)", () => RotationHandler?.Rotate(-90)); + rotateClockwiseButton = addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise (Ctrl->)", () => RotationHandler?.Rotate(90)); addRotateHandle(Anchor.TopLeft); addRotateHandle(Anchor.TopRight); @@ -331,7 +324,7 @@ private void addRotateHandle(Anchor anchor) var handle = new SelectionBoxRotationHandle { Anchor = anchor, - HandleRotate = angle => OnRotation?.Invoke(angle) + RotationHandler = RotationHandler }; handle.OperationStarted += operationStarted; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs index c2a3f12efda6..4107a096927e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.EnumExtensions; @@ -21,7 +22,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { public partial class SelectionBoxRotationHandle : SelectionBoxDragHandle, IHasTooltip { - public Action HandleRotate { get; set; } + [CanBeNull] + public SelectionRotationHandler RotationHandler { get; init; } public LocalisableString TooltipText { get; private set; } @@ -63,10 +65,10 @@ protected override void UpdateHoverState() protected override bool OnDragStart(DragStartEvent e) { - bool handle = base.OnDragStart(e); - if (handle) - cumulativeRotation.Value = 0; - return handle; + if (RotationHandler == null) return false; + + RotationHandler.Begin(); + return true; } protected override void OnDrag(DragEvent e) @@ -99,7 +101,9 @@ protected override void OnKeyUp(KeyUpEvent e) protected override void OnDragEnd(DragEndEvent e) { - base.OnDragEnd(e); + RotationHandler?.Commit(); + UpdateHoverState(); + cumulativeRotation.Value = null; rawCumulativeRotation = 0; TooltipText = default; @@ -116,14 +120,12 @@ private float convertDragEventToAngleOfRotation(DragEvent e) private void applyRotation(bool shouldSnap) { - float oldRotation = cumulativeRotation.Value ?? 0; - float newRotation = shouldSnap ? snap(rawCumulativeRotation, snap_step) : MathF.Round(rawCumulativeRotation); newRotation = (newRotation - 180) % 360 + 180; cumulativeRotation.Value = newRotation; - HandleRotate?.Invoke(newRotation - oldRotation); + RotationHandler?.Update(newRotation); TooltipText = shouldSnap ? EditorStrings.RotationSnapped(newRotation) : EditorStrings.RotationUnsnapped(newRotation); } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 80df796fd7fa..31ad8fa3d780 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -84,7 +84,7 @@ public SelectionBox CreateSelectionBox() OperationStarted = OnOperationBegan, OperationEnded = OnOperationEnded, - OnRotation = HandleRotation, + RotationHandler = RotationHandler, OnScale = HandleScale, OnFlip = HandleFlip, OnReverse = HandleReverse, diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs index 595edbb4fc81..d5dd1d38d4f2 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs @@ -16,11 +16,18 @@ public class SelectionRotationHandler /// public Bindable CanRotate { get; private set; } = new BindableBool(); + public void Rotate(float rotation, Vector2? origin = null) + { + Begin(); + Update(rotation, origin); + Commit(); + } + public virtual void Begin() { } - public virtual void Update(float rotation, Vector2 origin) + public virtual void Update(float rotation, Vector2? origin = null) { } From a201152b042e0dfb13a4abaca85def6b5fc23577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 23 Jul 2023 20:09:31 +0200 Subject: [PATCH 010/207] Add xmldoc to `SelectionRotationHandler` --- .../Components/SelectionRotationHandler.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs index d5dd1d38d4f2..6524f7fa35c0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs @@ -16,6 +16,18 @@ public class SelectionRotationHandler /// public Bindable CanRotate { get; private set; } = new BindableBool(); + /// + /// Performs a single, instant, atomic rotation operation. + /// + /// + /// This method is intended to be used in atomic contexts (such as when pressing a single button). + /// For continuous operations, see the -- flow. + /// + /// Rotation to apply in degrees. + /// + /// The origin point to rotate around. + /// If the default value is supplied, a sane implementation-defined default will be used. + /// public void Rotate(float rotation, Vector2? origin = null) { Begin(); @@ -23,14 +35,48 @@ public void Rotate(float rotation, Vector2? origin = null) Commit(); } + /// + /// Begins a continuous rotation operation. + /// + /// + /// This flow is intended to be used when a rotation operation is made incrementally (such as when dragging a rotation handle or slider). + /// For instantaneous, atomic operations, use the convenience method. + /// public virtual void Begin() { } + /// + /// Updates a continuous rotation operation. + /// Must be preceded by a call. + /// + /// + /// + /// This flow is intended to be used when a rotation operation is made incrementally (such as when dragging a rotation handle or slider). + /// As such, the values of and supplied should be relative to the state of the objects being rotated + /// when was called, rather than instantaneous deltas. + /// + /// + /// For instantaneous, atomic operations, use the convenience method. + /// + /// + /// Rotation to apply in degrees. + /// + /// The origin point to rotate around. + /// If the default value is supplied, a sane implementation-defined default will be used. + /// public virtual void Update(float rotation, Vector2? origin = null) { } + /// + /// Ends a continuous rotation operation. + /// Must be preceded by a call. + /// + /// + /// This flow is intended to be used when a rotation operation is made incrementally (such as when dragging a rotation handle or slider). + /// For instantaneous, atomic operations, use the convenience method. + /// public virtual void Commit() { } From 2e9379474d70999019d0c2e38affc421db186b5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jul 2023 19:21:20 +0900 Subject: [PATCH 011/207] Change spinner rotation animation to match input 1:1 --- .../Skinning/Default/SpinnerRotationTracker.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs index bf06f513b719..719cf57d986a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/SpinnerRotationTracker.cs @@ -72,9 +72,9 @@ protected override void Update() lastAngle = thisAngle; - IsSpinning.Value = isSpinnableTime && Math.Abs(currentRotation / 2 - Rotation) > 5f; + IsSpinning.Value = isSpinnableTime && Math.Abs(currentRotation - Rotation) > 10f; - Rotation = (float)Interpolation.Damp(Rotation, currentRotation / 2, 0.99, Math.Abs(Time.Elapsed)); + Rotation = (float)Interpolation.Damp(Rotation, currentRotation, 0.99, Math.Abs(Time.Elapsed)); } /// From 060ad36d601d613c7f81e06e3e4ca1acca4b1d43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jul 2023 20:19:03 +0900 Subject: [PATCH 012/207] Add test coverage of music control in editor from external --- .../TestSceneBeatmapEditorNavigation.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs index 5483be56761b..54ee1659e169 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneBeatmapEditorNavigation.cs @@ -170,6 +170,39 @@ public void TestLastTimestampRememberedOnExit() AddUntilStep("time is correct", () => getEditor().ChildrenOfType().First().CurrentTime, () => Is.EqualTo(1234)); } + [Test] + public void TestAttemptGlobalMusicOperationFromEditor() + { + BeatmapSetInfo beatmapSet = null!; + + AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely()); + AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach()); + + AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet)); + AddUntilStep("wait for song select", + () => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet) + && Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect + && songSelect.IsLoaded); + + AddUntilStep("wait for music playing", () => Game.MusicController.IsPlaying); + AddStep("user request stop", () => Game.MusicController.Stop(requestedByUser: true)); + AddUntilStep("wait for music stopped", () => !Game.MusicController.IsPlaying); + + AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0))); + AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse); + + AddUntilStep("music still stopped", () => !Game.MusicController.IsPlaying); + AddStep("user request play", () => Game.MusicController.Play(requestedByUser: true)); + AddUntilStep("music still stopped", () => !Game.MusicController.IsPlaying); + + AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield())); + AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect); + + AddUntilStep("wait for music playing", () => Game.MusicController.IsPlaying); + AddStep("user request stop", () => Game.MusicController.Stop(requestedByUser: true)); + AddUntilStep("wait for music stopped", () => !Game.MusicController.IsPlaying); + } + private EditorBeatmap getEditorBeatmap() => getEditor().ChildrenOfType().Single(); private Editor getEditor() => (Editor)Game.ScreenStack.CurrentScreen; From 157b1f301b11ad6887b79edbe31e386d21f2c674 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jul 2023 19:58:23 +0900 Subject: [PATCH 013/207] Rename `AllowTrackAdjustments` to more understandable `ApplyModTrackAdjustments` --- .../Visual/TestSceneOsuScreenStack.cs | 26 +++++++++---------- .../Overlays/FirstRunSetup/ScreenUIScale.cs | 2 +- osu.Game/Overlays/MusicController.cs | 14 +++++----- osu.Game/Screens/Edit/Editor.cs | 2 +- osu.Game/Screens/IOsuScreen.cs | 2 +- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- .../Spectate/MultiSpectatorScreen.cs | 2 +- osu.Game/Screens/OsuScreen.cs | 14 +++++----- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 2 +- 10 files changed, 34 insertions(+), 34 deletions(-) diff --git a/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs b/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs index 7f01a67903d3..6b3971735418 100644 --- a/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs +++ b/osu.Game.Tests/Visual/TestSceneOsuScreenStack.cs @@ -56,38 +56,38 @@ public void ParallaxAssignmentTest() public void AllowTrackAdjustmentsTest() { AddStep("push allowing screen", () => stack.Push(loadNewScreen())); - AddAssert("allows adjustments 1", () => musicController.AllowTrackAdjustments); + AddAssert("allows adjustments 1", () => musicController.ApplyModTrackAdjustments); AddStep("push inheriting screen", () => stack.Push(loadNewScreen())); - AddAssert("allows adjustments 2", () => musicController.AllowTrackAdjustments); + AddAssert("allows adjustments 2", () => musicController.ApplyModTrackAdjustments); AddStep("push disallowing screen", () => stack.Push(loadNewScreen())); - AddAssert("disallows adjustments 3", () => !musicController.AllowTrackAdjustments); + AddAssert("disallows adjustments 3", () => !musicController.ApplyModTrackAdjustments); AddStep("push inheriting screen", () => stack.Push(loadNewScreen())); - AddAssert("disallows adjustments 4", () => !musicController.AllowTrackAdjustments); + AddAssert("disallows adjustments 4", () => !musicController.ApplyModTrackAdjustments); AddStep("push inheriting screen", () => stack.Push(loadNewScreen())); - AddAssert("disallows adjustments 5", () => !musicController.AllowTrackAdjustments); + AddAssert("disallows adjustments 5", () => !musicController.ApplyModTrackAdjustments); AddStep("push allowing screen", () => stack.Push(loadNewScreen())); - AddAssert("allows adjustments 6", () => musicController.AllowTrackAdjustments); + AddAssert("allows adjustments 6", () => musicController.ApplyModTrackAdjustments); // Now start exiting from screens AddStep("exit screen", () => stack.Exit()); - AddAssert("disallows adjustments 7", () => !musicController.AllowTrackAdjustments); + AddAssert("disallows adjustments 7", () => !musicController.ApplyModTrackAdjustments); AddStep("exit screen", () => stack.Exit()); - AddAssert("disallows adjustments 8", () => !musicController.AllowTrackAdjustments); + AddAssert("disallows adjustments 8", () => !musicController.ApplyModTrackAdjustments); AddStep("exit screen", () => stack.Exit()); - AddAssert("disallows adjustments 9", () => !musicController.AllowTrackAdjustments); + AddAssert("disallows adjustments 9", () => !musicController.ApplyModTrackAdjustments); AddStep("exit screen", () => stack.Exit()); - AddAssert("allows adjustments 10", () => musicController.AllowTrackAdjustments); + AddAssert("allows adjustments 10", () => musicController.ApplyModTrackAdjustments); AddStep("exit screen", () => stack.Exit()); - AddAssert("allows adjustments 11", () => musicController.AllowTrackAdjustments); + AddAssert("allows adjustments 11", () => musicController.ApplyModTrackAdjustments); } public partial class TestScreen : ScreenWithBeatmapBackground @@ -129,12 +129,12 @@ private partial class TestOsuScreenStack : OsuScreenStack private partial class AllowScreen : OsuScreen { - public override bool? AllowTrackAdjustments => true; + public override bool? ApplyModTrackAdjustments => true; } public partial class DisallowScreen : OsuScreen { - public override bool? AllowTrackAdjustments => false; + public override bool? ApplyModTrackAdjustments => false; } private partial class InheritScreen : OsuScreen diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs index e3cd2ae36c89..02f0ad95063f 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs @@ -104,7 +104,7 @@ private partial class NestedSongSelect : PlaySongSelect { protected override bool ControlGlobalMusic => false; - public override bool? AllowTrackAdjustments => false; + public override bool? ApplyModTrackAdjustments => false; } private partial class UIScaleSlider : RoundedSliderBar diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 0d175a624c57..562140f5cbb6 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -356,20 +356,20 @@ private void onTrackCompleted() NextTrack(); } - private bool allowTrackAdjustments; + private bool applyModTrackAdjustments; /// /// Whether mod track adjustments are allowed to be applied. /// - public bool AllowTrackAdjustments + public bool ApplyModTrackAdjustments { - get => allowTrackAdjustments; + get => applyModTrackAdjustments; set { - if (allowTrackAdjustments == value) + if (applyModTrackAdjustments == value) return; - allowTrackAdjustments = value; + applyModTrackAdjustments = value; ResetTrackAdjustments(); } } @@ -377,7 +377,7 @@ public bool AllowTrackAdjustments private AudioAdjustments modTrackAdjustments; /// - /// Resets the adjustments currently applied on and applies the mod adjustments if is true. + /// Resets the adjustments currently applied on and applies the mod adjustments if is true. /// /// /// Does not reset any adjustments applied directly to the beatmap track. @@ -390,7 +390,7 @@ public void ResetTrackAdjustments() CurrentTrack.RemoveAllAdjustments(AdjustableProperty.Tempo); CurrentTrack.RemoveAllAdjustments(AdjustableProperty.Volume); - if (allowTrackAdjustments) + if (applyModTrackAdjustments) { CurrentTrack.BindAdjustments(modTrackAdjustments = new AudioAdjustments()); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 17e5eb8ef667..b885eee46f7e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -66,7 +66,7 @@ public partial class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler true; - public override bool? AllowTrackAdjustments => false; + public override bool? ApplyModTrackAdjustments => false; protected override bool PlayExitSound => !ExitConfirmed && !switchingDifficulty; diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index cceede5424d9..756fbb80a74f 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -67,7 +67,7 @@ public interface IOsuScreen : IScreen /// Whether mod track adjustments should be applied on entering this screen. /// A value means that the parent screen's value of this setting will be used. /// - bool? AllowTrackAdjustments { get; } + bool? ApplyModTrackAdjustments { get; } /// /// Invoked when the back button has been pressed to close any overlays before exiting this . diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 75b673cf1b58..8d08de4168f1 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -41,7 +41,7 @@ public abstract partial class RoomSubScreen : OnlinePlaySubScreen, IPreviewTrack [Cached(typeof(IBindable))] public readonly Bindable SelectedItem = new Bindable(); - public override bool? AllowTrackAdjustments => true; + public override bool? ApplyModTrackAdjustments => true; protected override BackgroundScreen CreateBackground() => new RoomBackgroundScreen(Room.Playlist.FirstOrDefault()) { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 2d2aa0f1d555..92ce86bef2ab 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -29,7 +29,7 @@ public partial class MultiSpectatorScreen : SpectatorScreen public override bool DisallowExternalBeatmapRulesetChanges => true; // We are managing our own adjustments. For now, this happens inside the Player instances themselves. - public override bool? AllowTrackAdjustments => false; + public override bool? ApplyModTrackAdjustments => false; /// /// Whether all spectating players have finished loading. diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 9c098794a6c9..869d14c0307e 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -85,7 +85,7 @@ public abstract partial class OsuScreen : Screen, IOsuScreen, IHasDescription [Resolved] private MusicController musicController { get; set; } - public virtual bool? AllowTrackAdjustments => null; + public virtual bool? ApplyModTrackAdjustments => null; public Bindable Beatmap { get; private set; } @@ -95,7 +95,7 @@ public abstract partial class OsuScreen : Screen, IOsuScreen, IHasDescription private OsuScreenDependencies screenDependencies; - private bool? trackAdjustmentStateAtSuspend; + private bool? modTrackAdjustmentStateAtSuspend; internal void CreateLeasedDependencies(IReadOnlyDependencyContainer dependencies) => createDependencies(dependencies); @@ -178,8 +178,8 @@ public override void OnResuming(ScreenTransitionEvent e) // it's feasible to resume to a screen if the target screen never loaded successfully. // in such a case there's no need to restore this value. - if (trackAdjustmentStateAtSuspend != null) - musicController.AllowTrackAdjustments = trackAdjustmentStateAtSuspend.Value; + if (modTrackAdjustmentStateAtSuspend != null) + musicController.ApplyModTrackAdjustments = modTrackAdjustmentStateAtSuspend.Value; base.OnResuming(e); } @@ -188,7 +188,7 @@ public override void OnSuspending(ScreenTransitionEvent e) { base.OnSuspending(e); - trackAdjustmentStateAtSuspend = musicController.AllowTrackAdjustments; + modTrackAdjustmentStateAtSuspend = musicController.ApplyModTrackAdjustments; onSuspendingLogo(); } @@ -197,8 +197,8 @@ public override void OnEntering(ScreenTransitionEvent e) { applyArrivingDefaults(false); - if (AllowTrackAdjustments != null) - musicController.AllowTrackAdjustments = AllowTrackAdjustments.Value; + if (ApplyModTrackAdjustments != null) + musicController.ApplyModTrackAdjustments = ApplyModTrackAdjustments.Value; if (backgroundStack?.Push(ownedBackground = CreateBackground()) != true) { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 379c10a4a48f..e2e8b71c108b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -69,7 +69,7 @@ public abstract partial class Player : ScreenWithBeatmapBackground, ISamplePlayb protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered; // We are managing our own adjustments (see OnEntering/OnExiting). - public override bool? AllowTrackAdjustments => false; + public override bool? ApplyModTrackAdjustments => false; private readonly IBindable gameActive = new Bindable(true); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 47e5325bafad..58755878d0d5 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -60,7 +60,7 @@ public abstract partial class SongSelect : ScreenWithBeatmapBackground, IKeyBind protected virtual bool ShowFooter => true; - public override bool? AllowTrackAdjustments => true; + public override bool? ApplyModTrackAdjustments => true; /// /// Can be null if is false. From 6146f30541086bc1d9b2bb85a03194d663c7d1b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jul 2023 20:00:18 +0900 Subject: [PATCH 014/207] Allow screens to change the ability to interact with the global track --- .../Overlays/Music/MusicKeyBindingHandler.cs | 8 +----- osu.Game/Overlays/MusicController.cs | 28 +++++++++++++++---- osu.Game/Overlays/NowPlayingOverlay.cs | 22 +++++++++------ osu.Game/Screens/IOsuScreen.cs | 6 ++++ osu.Game/Screens/OsuScreen.cs | 10 +++++++ 5 files changed, 53 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs index b7d265c44808..78de76b981b4 100644 --- a/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs +++ b/osu.Game/Overlays/Music/MusicKeyBindingHandler.cs @@ -30,20 +30,14 @@ public partial class MusicKeyBindingHandler : Component, IKeyBindingHandler e) { - if (e.Repeat) + if (e.Repeat || !musicController.AllowTrackControl.Value) return false; switch (e.Action) { case GlobalAction.MusicPlay: - if (game.LocalUserPlaying.Value) - return false; - // use previous state as TogglePause may not update the track's state immediately (state update is run on the audio thread see https://github.com/ppy/osu/issues/9880#issuecomment-674668842) bool wasPlaying = musicController.IsPlaying; diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 562140f5cbb6..665c61edf052 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -40,6 +40,11 @@ public partial class MusicController : CompositeDrawable /// public bool UserPauseRequested { get; private set; } + /// + /// Whether control of the global track should be allowed. + /// + public readonly BindableBool AllowTrackControl = new BindableBool(true); + /// /// Fired when the global has changed. /// Includes direction information for display purposes. @@ -92,8 +97,10 @@ public void SeekTo(double position) seekDelegate?.Cancel(); seekDelegate = Schedule(() => { - if (!beatmap.Disabled) - CurrentTrack.Seek(position); + if (beatmap.Disabled || !AllowTrackControl.Value) + return; + + CurrentTrack.Seek(position); }); } @@ -107,7 +114,7 @@ public void EnsurePlayingSomething() if (CurrentTrack.IsDummyDevice || beatmap.Value.BeatmapSetInfo.DeletePending) { - if (beatmap.Disabled) + if (beatmap.Disabled || !AllowTrackControl.Value) return; Logger.Log($"{nameof(MusicController)} skipping next track to {nameof(EnsurePlayingSomething)}"); @@ -132,6 +139,9 @@ public void EnsurePlayingSomething() /// Whether the operation was successful. public bool Play(bool restart = false, bool requestedByUser = false) { + if (requestedByUser && !AllowTrackControl.Value) + return false; + if (requestedByUser) UserPauseRequested = false; @@ -153,6 +163,9 @@ public bool Play(bool restart = false, bool requestedByUser = false) /// public void Stop(bool requestedByUser = false) { + if (requestedByUser && !AllowTrackControl.Value) + return; + UserPauseRequested |= requestedByUser; if (CurrentTrack.IsRunning) CurrentTrack.StopAsync(); @@ -164,6 +177,9 @@ public void Stop(bool requestedByUser = false) /// Whether the operation was successful. public bool TogglePause() { + if (!AllowTrackControl.Value) + return false; + if (CurrentTrack.IsRunning) Stop(true); else @@ -189,7 +205,7 @@ public void PreviousTrack(Action onSuccess = null) => Sched /// The that indicate the decided action. private PreviousTrackResult prev() { - if (beatmap.Disabled) + if (beatmap.Disabled || !AllowTrackControl.Value) return PreviousTrackResult.None; double currentTrackPosition = CurrentTrack.CurrentTime; @@ -229,7 +245,7 @@ public void NextTrack(Action onSuccess = null) => Schedule(() => private bool next() { - if (beatmap.Disabled) + if (beatmap.Disabled || !AllowTrackControl.Value) return false; queuedDirection = TrackChangeDirection.Next; @@ -352,7 +368,7 @@ private DrawableTrack getQueuedTrack() private void onTrackCompleted() { - if (!CurrentTrack.Looping && !beatmap.Disabled) + if (!CurrentTrack.Looping && !beatmap.Disabled && AllowTrackControl.Value) NextTrack(); } diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 15eefb2d9f41..78539fde7a93 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -68,6 +68,8 @@ public partial class NowPlayingOverlay : OsuFocusedOverlayContainer, INamedOverl [Resolved] private OsuColour colours { get; set; } + private Bindable allowTrackControl; + public NowPlayingOverlay() { Width = 400; @@ -220,8 +222,10 @@ protected override void LoadComplete() { base.LoadComplete(); - beatmap.BindDisabledChanged(_ => Scheduler.AddOnce(beatmapDisabledChanged)); - beatmapDisabledChanged(); + beatmap.BindDisabledChanged(_ => Scheduler.AddOnce(updateEnabledStates)); + + allowTrackControl = musicController.AllowTrackControl.GetBoundCopy(); + allowTrackControl.BindValueChanged(_ => Scheduler.AddOnce(updateEnabledStates), true); musicController.TrackChanged += trackChanged; trackChanged(beatmap.Value); @@ -334,16 +338,18 @@ private void trackChanged(WorkingBeatmap beatmap, TrackChangeDirection direction }; } - private void beatmapDisabledChanged() + private void updateEnabledStates() { - bool disabled = beatmap.Disabled; + bool beatmapDisabled = beatmap.Disabled; + bool trackControlDisabled = !musicController.AllowTrackControl.Value; - if (disabled) + if (beatmapDisabled || trackControlDisabled) playlist?.Hide(); - prevButton.Enabled.Value = !disabled; - nextButton.Enabled.Value = !disabled; - playlistButton.Enabled.Value = !disabled; + prevButton.Enabled.Value = !beatmapDisabled && !trackControlDisabled; + nextButton.Enabled.Value = !beatmapDisabled && !trackControlDisabled; + playlistButton.Enabled.Value = !beatmapDisabled && !trackControlDisabled; + playButton.Enabled.Value = !trackControlDisabled; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index 756fbb80a74f..5b4e2d75f4e8 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -69,6 +69,12 @@ public interface IOsuScreen : IScreen /// bool? ApplyModTrackAdjustments { get; } + /// + /// Whether control of the global track should be allowed via the music controller / now playing overlay. + /// A value means that the parent screen's value of this setting will be used. + /// + bool? AllowGlobalTrackControl { get; } + /// /// Invoked when the back button has been pressed to close any overlays before exiting this . /// diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 869d14c0307e..2dc9d5d49d7d 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -87,6 +87,8 @@ public abstract partial class OsuScreen : Screen, IOsuScreen, IHasDescription public virtual bool? ApplyModTrackAdjustments => null; + public virtual bool? AllowGlobalTrackControl => null; + public Bindable Beatmap { get; private set; } public Bindable Ruleset { get; private set; } @@ -95,6 +97,8 @@ public abstract partial class OsuScreen : Screen, IOsuScreen, IHasDescription private OsuScreenDependencies screenDependencies; + private bool? globalMusicControlStateAtSuspend; + private bool? modTrackAdjustmentStateAtSuspend; internal void CreateLeasedDependencies(IReadOnlyDependencyContainer dependencies) => createDependencies(dependencies); @@ -180,6 +184,8 @@ public override void OnResuming(ScreenTransitionEvent e) // in such a case there's no need to restore this value. if (modTrackAdjustmentStateAtSuspend != null) musicController.ApplyModTrackAdjustments = modTrackAdjustmentStateAtSuspend.Value; + if (globalMusicControlStateAtSuspend != null) + musicController.AllowTrackControl.Value = globalMusicControlStateAtSuspend.Value; base.OnResuming(e); } @@ -189,6 +195,7 @@ public override void OnSuspending(ScreenTransitionEvent e) base.OnSuspending(e); modTrackAdjustmentStateAtSuspend = musicController.ApplyModTrackAdjustments; + globalMusicControlStateAtSuspend = musicController.AllowTrackControl.Value; onSuspendingLogo(); } @@ -200,6 +207,9 @@ public override void OnEntering(ScreenTransitionEvent e) if (ApplyModTrackAdjustments != null) musicController.ApplyModTrackAdjustments = ApplyModTrackAdjustments.Value; + if (AllowGlobalTrackControl != null) + musicController.AllowTrackControl.Value = AllowGlobalTrackControl.Value; + if (backgroundStack?.Push(ownedBackground = CreateBackground()) != true) { // If the constructed instance was not actually pushed to the background stack, we don't want to track it unnecessarily. From 3485b72eaa3885d337d5b71d9fa7c3a87809270f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 25 Jul 2023 20:00:31 +0900 Subject: [PATCH 015/207] Disallow interacting with the global track state in `Player` and `Editor` --- osu.Game/Screens/Edit/Editor.cs | 2 ++ osu.Game/Screens/Play/Player.cs | 2 ++ osu.Game/Screens/Ranking/ResultsScreen.cs | 2 ++ 3 files changed, 6 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b885eee46f7e..d11297b3b28d 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -68,6 +68,8 @@ public partial class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler false; + public override bool? AllowGlobalTrackControl => false; + protected override bool PlayExitSound => !ExitConfirmed && !switchingDifficulty; protected bool HasUnsavedChanges diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e2e8b71c108b..956c4d4856c5 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -71,6 +71,8 @@ public abstract partial class Player : ScreenWithBeatmapBackground, ISamplePlayb // We are managing our own adjustments (see OnEntering/OnExiting). public override bool? ApplyModTrackAdjustments => false; + public override bool? AllowGlobalTrackControl => false; + private readonly IBindable gameActive = new Bindable(true); private readonly Bindable samplePlaybackDisabled = new Bindable(); diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 96fed3e6bae1..43ddcffb629f 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -36,6 +36,8 @@ public abstract partial class ResultsScreen : ScreenWithBeatmapBackground, IKeyB public override bool DisallowExternalBeatmapRulesetChanges => true; + public override bool? AllowGlobalTrackControl => true; + // Temporary for now to stop dual transitions. Should respect the current toolbar mode, but there's no way to do so currently. public override bool HideOverlaysOnEnter => true; From 0bd34a4f13eb79a0dba578f5a17c20e872976a09 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 25 Jul 2023 21:14:21 -0700 Subject: [PATCH 016/207] Always show supporter-only playlist durations --- osu.Game/Localisation/OnlinePlayStrings.cs | 19 +++++++ .../Match/Components/RoomSettingsOverlay.cs | 5 +- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 55 +++++++++++++------ 3 files changed, 59 insertions(+), 20 deletions(-) create mode 100644 osu.Game/Localisation/OnlinePlayStrings.cs diff --git a/osu.Game/Localisation/OnlinePlayStrings.cs b/osu.Game/Localisation/OnlinePlayStrings.cs new file mode 100644 index 000000000000..e9f17cddc08f --- /dev/null +++ b/osu.Game/Localisation/OnlinePlayStrings.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class OnlinePlayStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.OnlinePlay"; + + /// + /// "This duration is only available for osu!supporters." + /// + public static LocalisableString SupporterOnlyDurationNotice => new TranslatableString(getKey(@"supporter_only_duration_notice"), @"This duration is only available for osu!supporters."); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs index 05232fe0e2f0..916b799d5006 100644 --- a/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Match/Components/RoomSettingsOverlay.cs @@ -113,7 +113,7 @@ public SectionContainer() protected partial class Section : Container { - private readonly Container content; + private readonly ReverseChildIDFillFlowContainer content; protected override Container Content => content; @@ -135,10 +135,11 @@ public Section(string title) Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 12), Text = title.ToUpperInvariant(), }, - content = new Container + content = new ReverseChildIDFillFlowContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical }, }, }; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index e93f56c2e2c1..c69ccb7b1ca1 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -23,6 +23,7 @@ using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.Match.Components; using osuTK; +using osu.Game.Localisation; namespace osu.Game.Screens.OnlinePlay.Playlists { @@ -80,6 +81,7 @@ protected partial class MatchSettings : OnlinePlayComposite private IBindable localUser = null!; private readonly Room room; + private OsuSpriteText durationNoticeText = null!; public MatchSettings(Room room) { @@ -141,14 +143,22 @@ private void load(OverlayColourProvider colourProvider, OsuColour colours) }, new Section("Duration") { - Child = new Container + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - Height = 40, - Child = DurationField = new DurationDropdown + new Container { - RelativeSizeAxes = Axes.X - } + RelativeSizeAxes = Axes.X, + Height = 40, + Child = DurationField = new DurationDropdown + { + RelativeSizeAxes = Axes.X + }, + }, + durationNoticeText = new OsuSpriteText + { + Alpha = 0, + Colour = colours.Yellow, + }, } }, new Section("Allowed attempts (across all playlist items)") @@ -305,6 +315,17 @@ private void load(OverlayColourProvider colourProvider, OsuColour colours) MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true); Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue ?? TimeSpan.FromMinutes(30), true); + DurationField.Current.BindValueChanged(duration => + { + if (selectedAvailableDuration) + durationNoticeText.Hide(); + else + { + durationNoticeText.Show(); + durationNoticeText.Text = OnlinePlayStrings.SupporterOnlyDurationNotice; + } + }); + localUser = api.LocalUser.GetBoundCopy(); localUser.BindValueChanged(populateDurations, true); @@ -314,6 +335,10 @@ private void load(OverlayColourProvider colourProvider, OsuColour colours) private void populateDurations(ValueChangedEvent user) { + // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427) + // if we want this to be more accurate we might consider sending an actual end time, not a time span. probably not required though. + const int days_in_month = 31; + DurationField.Items = new[] { TimeSpan.FromMinutes(30), @@ -326,18 +351,9 @@ private void populateDurations(ValueChangedEvent user) TimeSpan.FromDays(3), TimeSpan.FromDays(7), TimeSpan.FromDays(14), + TimeSpan.FromDays(days_in_month), + TimeSpan.FromDays(days_in_month * 3), }; - - // TODO: show these in the interface at all times. - if (user.NewValue.IsSupporter) - { - // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427) - // if we want this to be more accurate we might consider sending an actual end time, not a time span. probably not required though. - const int days_in_month = 31; - - DurationField.AddDropdownItem(TimeSpan.FromDays(days_in_month)); - DurationField.AddDropdownItem(TimeSpan.FromDays(days_in_month * 3)); - } } protected override void Update() @@ -352,7 +368,10 @@ protected override void Update() private void onPlaylistChanged(object? sender, NotifyCollectionChangedEventArgs e) => playlistLength.Text = $"Length: {Playlist.GetTotalDuration()}"; - private bool hasValidSettings => RoomID.Value == null && NameField.Text.Length > 0 && Playlist.Count > 0; + private bool hasValidSettings => RoomID.Value == null && NameField.Text.Length > 0 && Playlist.Count > 0 + && selectedAvailableDuration; + + private bool selectedAvailableDuration => DurationField.Current.Value < TimeSpan.FromDays(31) || localUser.Value.IsSupporter; private void apply() { From 39c2bb240bbd53c1d241313d6160be000fd9a205 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jul 2023 14:03:12 +0900 Subject: [PATCH 017/207] Apply NRT to `SelectionBoxRotationHandle`. --- .../Compose/Components/SelectionBoxRotationHandle.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs index 4107a096927e..8665ec9b08c1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.EnumExtensions; @@ -16,25 +13,24 @@ using osu.Game.Localisation; using osuTK; using osuTK.Graphics; -using Key = osuTK.Input.Key; +using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { public partial class SelectionBoxRotationHandle : SelectionBoxDragHandle, IHasTooltip { - [CanBeNull] - public SelectionRotationHandler RotationHandler { get; init; } + public SelectionRotationHandler? RotationHandler { get; init; } public LocalisableString TooltipText { get; private set; } - private SpriteIcon icon; + private SpriteIcon icon = null!; private const float snap_step = 15; private readonly Bindable cumulativeRotation = new Bindable(); [Resolved] - private SelectionBox selectionBox { get; set; } + private SelectionBox selectionBox { get; set; } = null!; [BackgroundDependencyLoader] private void load() From 7fd6bb9d5f98c6ba74b06f827292a5cab875d322 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jul 2023 14:04:16 +0900 Subject: [PATCH 018/207] Fix a couple of code style issues in `SelectionBox` --- osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 53442071b53b..ed6bbf766841 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -52,7 +52,7 @@ public bool CanReverse } } - private IBindable canRotate = new BindableBool(); + private readonly IBindable canRotate = new BindableBool(); private bool canScaleX; @@ -152,8 +152,7 @@ private void load() if (RotationHandler != null) canRotate.BindTo(RotationHandler.CanRotate); - canRotate.BindValueChanged(_ => recreate()); - recreate(); + canRotate.BindValueChanged(_ => recreate(), true); } protected override bool OnKeyDown(KeyDownEvent e) From 30e0e00c520d8072eb9cfe8fca2c0e2a14fd06e9 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 25 Jul 2023 22:57:01 -0700 Subject: [PATCH 019/207] Rename `selectedAvailableDuration` to `hasValidDuration` --- .../OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index c69ccb7b1ca1..f7bd7fc5308b 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -317,7 +317,7 @@ private void load(OverlayColourProvider colourProvider, OsuColour colours) DurationField.Current.BindValueChanged(duration => { - if (selectedAvailableDuration) + if (hasValidDuration) durationNoticeText.Hide(); else { @@ -369,9 +369,9 @@ private void onPlaylistChanged(object? sender, NotifyCollectionChangedEventArgs playlistLength.Text = $"Length: {Playlist.GetTotalDuration()}"; private bool hasValidSettings => RoomID.Value == null && NameField.Text.Length > 0 && Playlist.Count > 0 - && selectedAvailableDuration; + && hasValidDuration; - private bool selectedAvailableDuration => DurationField.Current.Value < TimeSpan.FromDays(31) || localUser.Value.IsSupporter; + private bool hasValidDuration => DurationField.Current.Value < TimeSpan.FromDays(31) || localUser.Value.IsSupporter; private void apply() { From c1ba8fe175c9bff9e018f2409fbc3cfa041e4902 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jul 2023 16:07:45 +0900 Subject: [PATCH 020/207] Rename `BackgroundBeatmapProcessor` to `BackgroundDataStoreProcessor` --- ...orTests.cs => BackgroundDataStoreProcessorTests.cs} | 8 ++++---- ...mapProcessor.cs => BackgroundDataStoreProcessor.cs} | 10 +++++++--- osu.Game/OsuGame.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 2 +- 4 files changed, 13 insertions(+), 9 deletions(-) rename osu.Game.Tests/Database/{BackgroundBeatmapProcessorTests.cs => BackgroundDataStoreProcessorTests.cs} (92%) rename osu.Game/{BackgroundBeatmapProcessor.cs => BackgroundDataStoreProcessor.cs} (97%) diff --git a/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs similarity index 92% rename from osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs rename to osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index c876316be4f9..15bb0fc09c00 100644 --- a/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Database { [HeadlessTest] - public partial class BackgroundBeatmapProcessorTests : OsuTestScene, ILocalUserPlayInfo + public partial class BackgroundDataStoreProcessorTests : OsuTestScene, ILocalUserPlayInfo { public IBindable IsPlaying => isPlaying; @@ -59,7 +59,7 @@ public void TestDifficultyProcessing() AddStep("Run background processor", () => { - Add(new TestBackgroundBeatmapProcessor()); + Add(new TestBackgroundDataStoreProcessor()); }); AddUntilStep("wait for difficulties repopulated", () => @@ -98,7 +98,7 @@ public void TestDifficultyProcessingWhilePlaying() AddStep("Run background processor", () => { - Add(new TestBackgroundBeatmapProcessor()); + Add(new TestBackgroundDataStoreProcessor()); }); AddWaitStep("wait some", 500); @@ -124,7 +124,7 @@ public void TestDifficultyProcessingWhilePlaying() }); } - public partial class TestBackgroundBeatmapProcessor : BackgroundBeatmapProcessor + public partial class TestBackgroundDataStoreProcessor : BackgroundDataStoreProcessor { protected override int TimeToSleepDuringGameplay => 10; } diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs similarity index 97% rename from osu.Game/BackgroundBeatmapProcessor.cs rename to osu.Game/BackgroundDataStoreProcessor.cs index b553fee50352..c475a07c795b 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -24,7 +24,10 @@ namespace osu.Game { - public partial class BackgroundBeatmapProcessor : Component + /// + /// Performs background updating of data stores at startup. + /// + public partial class BackgroundDataStoreProcessor : Component { [Resolved] private RulesetStore rulesetStore { get; set; } = null!; @@ -61,7 +64,8 @@ protected override void LoadComplete() Task.Factory.StartNew(() => { - Logger.Log("Beginning background beatmap processing.."); + Logger.Log("Beginning background data store processing.."); + checkForOutdatedStarRatings(); processBeatmapSetsWithMissingMetrics(); processScoresWithMissingStatistics(); @@ -74,7 +78,7 @@ protected override void LoadComplete() return; } - Logger.Log("Finished background beatmap processing!"); + Logger.Log("Finished background data store processing!"); }); } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1a40bb8e3d41..c60bff9e4cab 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1025,7 +1025,7 @@ protected override void LoadComplete() loadComponentSingleFile(CreateHighPerformanceSession(), Add); - loadComponentSingleFile(new BackgroundBeatmapProcessor(), Add); + loadComponentSingleFile(new BackgroundDataStoreProcessor(), Add); Add(difficultyRecommender); Add(externalLinkOpener = new ExternalLinkOpener()); diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index c6f443382450..a3bbb2c09e9b 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -66,7 +66,7 @@ public class ScoreInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftD /// If this does not match , /// the total score has not yet been updated to reflect the current scoring values. /// - /// See 's conversion logic. + /// See 's conversion logic. /// /// /// This may not match the version stored in the replay files. From 30baac0f3db82d4df246c75bb458113be90c210e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jul 2023 16:08:02 +0900 Subject: [PATCH 021/207] Avoid reprocessing scores which already failed an upgrade previously Closes https://github.com/ppy/osu/issues/24301. --- osu.Game/BackgroundDataStoreProcessor.cs | 6 ++++-- osu.Game/Database/RealmAccess.cs | 3 ++- osu.Game/Scoring/ScoreInfo.cs | 9 +++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index c475a07c795b..ae9e9527deee 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -186,7 +186,7 @@ private void processScoresWithMissingStatistics() realmAccess.Run(r => { - foreach (var score in r.All()) + foreach (var score in r.All().Where(s => !s.TotalScoreUpgradeFailed)) { if (score.BeatmapInfo != null && score.Statistics.Sum(kvp => kvp.Value) > 0 @@ -225,6 +225,7 @@ private void processScoresWithMissingStatistics() catch (Exception e) { Logger.Log(@$"Failed to populate maximum statistics for {id}: {e}"); + realmAccess.Write(r => r.Find(id)!.TotalScoreUpgradeFailed = true); } } } @@ -234,7 +235,7 @@ private void convertLegacyTotalScoreToStandardised() Logger.Log("Querying for scores that need total score conversion..."); HashSet scoreIds = realmAccess.Run(r => new HashSet(r.All() - .Where(s => s.BeatmapInfo != null && s.TotalScoreVersion == 30000002) + .Where(s => !s.TotalScoreUpgradeFailed && s.BeatmapInfo != null && s.TotalScoreVersion == 30000002) .AsEnumerable().Select(s => s.ID))); Logger.Log($"Found {scoreIds.Count} scores which require total score conversion."); @@ -283,6 +284,7 @@ private void convertLegacyTotalScoreToStandardised() catch (Exception e) { Logger.Log($"Failed to convert total score for {id}: {e}"); + realmAccess.Write(r => r.Find(id)!.TotalScoreUpgradeFailed = true); ++failedCount; } } diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index f32b161bb617..04a4b28fa48c 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -82,8 +82,9 @@ public class RealmAccess : IDisposable /// 30 2023-06-16 Run migration of old lazer scores again. This time with more correct rounding considerations. /// 31 2023-06-26 Add Version and LegacyTotalScore to ScoreInfo, set Version to 30000002 and copy TotalScore into LegacyTotalScore for legacy scores. /// 32 2023-07-09 Populate legacy scores with the ScoreV2 mod (and restore TotalScore to the legacy total for such scores) using replay files. + /// 33 2023-07-26 Add TotalScoreUpgradeFailed flag to ScoreInfo to track upgrade failures. /// - private const int schema_version = 32; + private const int schema_version = 33; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index a3bbb2c09e9b..c3a45332e464 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -81,6 +81,15 @@ public class ScoreInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftD /// public long? LegacyTotalScore { get; set; } + /// + /// If an reprocess of total score failed to update this score to the latest version, this flag will become true. + /// Should be used to ensure we don't repeatedly attempt to update the same scores each startup even though we already know they will fail. + /// + /// + /// See https://github.com/ppy/osu/issues/24301 for one example of how this can occur(missing beatmap file on disk). + /// + public bool TotalScoreUpgradeFailed { get; set; } + public int MaxCombo { get; set; } public double Accuracy { get; set; } From a2f1ced5a224e6ff1c239c430be62793b3b24fe0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jul 2023 16:22:21 +0900 Subject: [PATCH 022/207] Add test coverage of failed-to-upgrade flag --- .../BackgroundDataStoreProcessorTests.cs | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index 15bb0fc09c00..d7ec57202657 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -8,6 +8,9 @@ using osu.Framework.Extensions; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Scoring; +using osu.Game.Scoring.Legacy; using osu.Game.Screens.Play; using osu.Game.Tests.Beatmaps.IO; using osu.Game.Tests.Visual; @@ -124,6 +127,32 @@ public void TestDifficultyProcessingWhilePlaying() }); } + [Test] + public void TestScoreUpgradeFailed() + { + ScoreInfo scoreInfo = null!; + + AddStep("Add score which requires upgrade (but has no beatmap)", () => + { + Realm.Write(r => + { + r.Add(scoreInfo = new ScoreInfo(ruleset: r.All().First(), beatmap: new BeatmapInfo + { + BeatmapSet = new BeatmapSetInfo(), + Ruleset = r.All().First(), + }) + { + TotalScoreVersion = 30000002, + IsLegacyScore = true, + }); + }); + }); + + AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor())); + + AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreUpgradeFailed)); + } + public partial class TestBackgroundDataStoreProcessor : BackgroundDataStoreProcessor { protected override int TimeToSleepDuringGameplay => 10; From 235cb2d73fcc64ffb2c85d1dafb2a03486557276 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jul 2023 16:28:38 +0900 Subject: [PATCH 023/207] Add test coverage of upgrade success for good measure --- .../BackgroundDataStoreProcessorTests.cs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index d7ec57202657..23b88b7395d6 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -127,6 +127,30 @@ public void TestDifficultyProcessingWhilePlaying() }); } + [Test] + public void TestScoreUpgradeSuccess() + { + ScoreInfo scoreInfo = null!; + + AddStep("Add score which requires upgrade (but has no beatmap)", () => + { + Realm.Write(r => + { + r.Add(scoreInfo = new ScoreInfo(ruleset: r.All().First(), beatmap: r.All().First()) + { + TotalScoreVersion = 30000002, + LegacyTotalScore = 123456, + IsLegacyScore = true, + }); + }); + }); + + AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor())); + + AddUntilStep("Score version upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(LegacyScoreEncoder.LATEST_VERSION)); + AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreUpgradeFailed), () => Is.False); + } + [Test] public void TestScoreUpgradeFailed() { @@ -150,7 +174,8 @@ public void TestScoreUpgradeFailed() AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor())); - AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreUpgradeFailed)); + AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreUpgradeFailed), () => Is.True); + AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000002)); } public partial class TestBackgroundDataStoreProcessor : BackgroundDataStoreProcessor From 9d87ff2986f3430b4c44b03d895720f5c8b57bd3 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 26 Jul 2023 01:37:55 -0700 Subject: [PATCH 024/207] Improve supporter-only duration notice and fix max duration of non-supporter See https://github.com/ppy/osu-web/blob/3c542292569b336c4dff38e2b52a823149ec1a81/.env.example#L230. Might have more duration options in the future. --- osu.Game/Localisation/OnlinePlayStrings.cs | 4 ++-- .../OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Localisation/OnlinePlayStrings.cs b/osu.Game/Localisation/OnlinePlayStrings.cs index e9f17cddc08f..1853cb753a32 100644 --- a/osu.Game/Localisation/OnlinePlayStrings.cs +++ b/osu.Game/Localisation/OnlinePlayStrings.cs @@ -10,9 +10,9 @@ public static class OnlinePlayStrings private const string prefix = @"osu.Game.Resources.Localisation.OnlinePlay"; /// - /// "This duration is only available for osu!supporters." + /// "Playlist durations longer than 2 weeks require an active osu!supporter tag." /// - public static LocalisableString SupporterOnlyDurationNotice => new TranslatableString(getKey(@"supporter_only_duration_notice"), @"This duration is only available for osu!supporters."); + public static LocalisableString SupporterOnlyDurationNotice => new TranslatableString(getKey(@"supporter_only_duration_notice"), @"Playlist durations longer than 2 weeks require an active osu!supporter tag."); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index f7bd7fc5308b..84e419d67a71 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -371,7 +371,7 @@ private void onPlaylistChanged(object? sender, NotifyCollectionChangedEventArgs private bool hasValidSettings => RoomID.Value == null && NameField.Text.Length > 0 && Playlist.Count > 0 && hasValidDuration; - private bool hasValidDuration => DurationField.Current.Value < TimeSpan.FromDays(31) || localUser.Value.IsSupporter; + private bool hasValidDuration => DurationField.Current.Value <= TimeSpan.FromDays(14) || localUser.Value.IsSupporter; private void apply() { From 269d4d1cd6eda979cb0aa1bf42fc802e247e6886 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jul 2023 14:49:05 +0900 Subject: [PATCH 025/207] Add test coverage of autoplay restore not working --- .../Navigation/TestScenePerformFromScreen.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index 1c8fa775b9cd..c8f1f081d9b5 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -13,6 +13,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Overlays; +using osu.Game.Rulesets.Mods; using osu.Game.Screens; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; @@ -86,6 +87,29 @@ public void TestPerformAtMenuFromPlayerLoader() AddAssert("did perform", () => actionPerformed); } + [Test] + public void TestPerformAtMenuFromPlayerLoaderWithAutoplayShortcut() + { + importAndWaitForSongSelect(); + + AddStep("press ctrl+enter", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Key(Key.Enter); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen is PlayerLoader); + + AddAssert("Mods include autoplay", () => Game.SelectedMods.Value.Any(m => m is ModAutoplay)); + + AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true)); + AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is MainMenu); + AddAssert("did perform", () => actionPerformed); + + AddAssert("Mods don't include autoplay", () => !Game.SelectedMods.Value.Any(m => m is ModAutoplay)); + } + [Test] public void TestPerformEnsuresScreenIsLoaded() { From f15394fb6d5975cb599102f7718b496997b11048 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jul 2023 14:42:43 +0900 Subject: [PATCH 026/207] Fix temporary auto mod (ctrl+enter at song select) not reverting in all scenarios --- osu.Game/Screens/Select/PlaySongSelect.cs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index b99d949b4342..fe13d6d5a883 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -146,12 +146,24 @@ Player createPlayer() public override void OnResuming(ScreenTransitionEvent e) { base.OnResuming(e); + revertMods(); + } - if (playerLoader != null) - { - Mods.Value = modsAtGameplayStart; - playerLoader = null; - } + public override bool OnExiting(ScreenExitEvent e) + { + if (base.OnExiting(e)) + return true; + + revertMods(); + return false; + } + + private void revertMods() + { + if (playerLoader == null) return; + + Mods.Value = modsAtGameplayStart; + playerLoader = null; } } } From 06fe5583cb9ecd4915aad3835b09ae732dfc1aff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jul 2023 15:47:57 +0900 Subject: [PATCH 027/207] Expose a new SSDQ from playfield for skinnable area bounds --- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 17 +++++++++++++++++ osu.Game/Rulesets/UI/Playfield.cs | 11 +++++++++++ 2 files changed, 28 insertions(+) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index e3ebadc836ad..7b0044723841 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Objects; @@ -25,6 +26,22 @@ public partial class ManiaPlayfield : ScrollingPlayfield private readonly List stages = new List(); + public override Quad SkinnableComponentScreenSpaceDrawQuad + { + get + { + if (Stages.Count == 1) + return Stages.First().ScreenSpaceDrawQuad; + + RectangleF area = RectangleF.Empty; + + foreach (var stage in Stages) + area = RectangleF.Union(area, stage.ScreenSpaceDrawQuad.AABBFloat); + + return area; + } + } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos)); public ManiaPlayfield(List stageDefinitions) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 3f263aba6366..e9c35555c865 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -23,6 +23,7 @@ using osuTK; using osu.Game.Rulesets.Objects.Pooling; using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics.Primitives; namespace osu.Game.Rulesets.UI { @@ -94,6 +95,16 @@ public IEnumerable AllHitObjects /// public readonly BindableBool DisplayJudgements = new BindableBool(true); + /// + /// A screen space draw quad which resembles the edges of the playfield for skinning purposes. + /// This will allow users / components to snap objects to the "edge" of the playfield. + /// + /// + /// Rulesets which reduce the visible area further than the full relative playfield space itself + /// should retarget this to the ScreenSpaceDrawQuad of the appropriate container. + /// + public virtual Quad SkinnableComponentScreenSpaceDrawQuad => ScreenSpaceDrawQuad; + [Resolved(CanBeNull = true)] [CanBeNull] protected IReadOnlyList Mods { get; private set; } From 5bd06832d0a4e35bbbd52eea2ffb3970764e6d0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jul 2023 15:48:40 +0900 Subject: [PATCH 028/207] Fix skin component toolbox not working correctly for ruleset matching Until now, the only usage of ruleset layers was where there is both a ruleset specific and non-ruleset-specific layer present. The matching code was making assumptions about this. As I tried to add a new playfield layer which breaks this assumption, non-ruleset-specifc components were not being displayed in the toolbox. This turned out to be due to a `target` of `null` being provided due to the weird `getTarget` matching (that happened to *just* do what we wanted previously due to the equals implementation, but only because there was a container without the ruleset present in the available targets). I've changed this to be a more appropriate lookup method, where the target for dependency sourcing is provided separately from the ruleset filter. --- .../Overlays/SkinEditor/SkinComponentToolbox.cs | 17 +++++++++++++---- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 4 ++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs index 1ce253d67c65..a476fc1a6d00 100644 --- a/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs +++ b/osu.Game/Overlays/SkinEditor/SkinComponentToolbox.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; +using osu.Game.Rulesets; using osu.Game.Screens.Edit.Components; using osu.Game.Skinning; using osuTK; @@ -23,14 +24,22 @@ public partial class SkinComponentToolbox : EditorSidebarSection { public Action? RequestPlacement; - private readonly SkinComponentsContainer? target; + private readonly SkinComponentsContainer target; + + private readonly RulesetInfo? ruleset; private FillFlowContainer fill = null!; - public SkinComponentToolbox(SkinComponentsContainer? target = null) - : base(target?.Lookup.Ruleset == null ? SkinEditorStrings.Components : LocalisableString.Interpolate($"{SkinEditorStrings.Components} ({target.Lookup.Ruleset.Name})")) + /// + /// Create a new component toolbox for the specified taget. + /// + /// The target. This is mainly used as a dependency source to find candidate components. + /// A ruleset to filter components by. If null, only components which are not ruleset-specific will be included. + public SkinComponentToolbox(SkinComponentsContainer target, RulesetInfo? ruleset) + : base(ruleset == null ? SkinEditorStrings.Components : LocalisableString.Interpolate($"{SkinEditorStrings.Components} ({ruleset.Name})")) { this.target = target; + this.ruleset = ruleset; } [BackgroundDependencyLoader] @@ -51,7 +60,7 @@ private void reloadComponents() { fill.Clear(); - var skinnableTypes = SerialisedDrawableInfo.GetAllAvailableDrawables(target?.Lookup.Ruleset); + var skinnableTypes = SerialisedDrawableInfo.GetAllAvailableDrawables(ruleset); foreach (var type in skinnableTypes) attemptAddComponent(type); } diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 2b23ce290fc9..67cf0eab1852 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -366,14 +366,14 @@ private void targetChanged(ValueChangedEvent tar // If the new target has a ruleset, let's show ruleset-specific items at the top, and the rest below. if (target.NewValue.Ruleset != null) { - componentsSidebar.Add(new SkinComponentToolbox(skinComponentsContainer) + componentsSidebar.Add(new SkinComponentToolbox(skinComponentsContainer, target.NewValue.Ruleset) { RequestPlacement = requestPlacement }); } // Remove the ruleset from the lookup to get base components. - componentsSidebar.Add(new SkinComponentToolbox(getTarget(new SkinComponentsContainerLookup(target.NewValue.Target))) + componentsSidebar.Add(new SkinComponentToolbox(skinComponentsContainer, null) { RequestPlacement = requestPlacement }); From 6cf065f6d1c92ddcdf36efa97fbe4634ee3cd9f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 28 Jul 2023 15:48:21 +0900 Subject: [PATCH 029/207] Add playfield layer to skin editor --- osu.Game/Screens/Play/HUDOverlay.cs | 14 ++++++++++++-- osu.Game/Skinning/SkinComponentsContainerLookup.cs | 5 ++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index d11171e3feda..6696332307ca 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -12,6 +12,7 @@ using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Configuration; @@ -103,10 +104,11 @@ protected override bool ShouldBeConsideredForInput(Drawable child) private readonly List hideTargets; + private readonly Drawable playfieldComponents; + public HUDOverlay(DrawableRuleset drawableRuleset, IReadOnlyList mods, bool alwaysShowLeaderboard = true) { Drawable rulesetComponents; - this.drawableRuleset = drawableRuleset; this.mods = mods; @@ -123,6 +125,9 @@ public HUDOverlay(DrawableRuleset drawableRuleset, IReadOnlyList mods, bool rulesetComponents = drawableRuleset != null ? new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, } : Empty(), + playfieldComponents = drawableRuleset != null + ? new SkinComponentsContainer(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.Playfield, drawableRuleset.Ruleset.RulesetInfo)) { AlwaysPresent = true, } + : Empty(), topRightElements = new FillFlowContainer { Anchor = Anchor.TopRight, @@ -162,7 +167,7 @@ public HUDOverlay(DrawableRuleset drawableRuleset, IReadOnlyList mods, bool }, }; - hideTargets = new List { mainComponents, rulesetComponents, topRightElements }; + hideTargets = new List { mainComponents, rulesetComponents, playfieldComponents, topRightElements }; if (!alwaysShowLeaderboard) hideTargets.Add(LeaderboardFlow); @@ -230,6 +235,11 @@ protected override void Update() { base.Update(); + Quad playfieldScreenSpaceDrawQuad = drawableRuleset.Playfield.SkinnableComponentScreenSpaceDrawQuad; + + playfieldComponents.Position = ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft); + playfieldComponents.Size = ToLocalSpace(playfieldScreenSpaceDrawQuad.BottomRight) - ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft); + float? lowestTopScreenSpaceLeft = null; float? lowestTopScreenSpaceRight = null; diff --git a/osu.Game/Skinning/SkinComponentsContainerLookup.cs b/osu.Game/Skinning/SkinComponentsContainerLookup.cs index fbc0ab58ad10..34358c3f0693 100644 --- a/osu.Game/Skinning/SkinComponentsContainerLookup.cs +++ b/osu.Game/Skinning/SkinComponentsContainerLookup.cs @@ -68,7 +68,10 @@ public enum TargetArea MainHUDComponents, [Description("Song select")] - SongSelect + SongSelect, + + [Description("Playfield")] + Playfield } } } From 26c128a09323cb0ee00cdb3c93d0463367b2f451 Mon Sep 17 00:00:00 2001 From: QuantumSno Date: Fri, 28 Jul 2023 14:39:30 -0400 Subject: [PATCH 030/207] added keybind and localization string --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 6 +++++- osu.Game/Localisation/GlobalActionKeyBindingStrings.cs | 5 +++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 01c454e3f966..3d24afbb16a9 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -116,9 +116,10 @@ protected override void LoadComplete() new KeyBinding(new[] { InputKey.F3 }, GlobalAction.DecreaseScrollSpeed), new KeyBinding(new[] { InputKey.F4 }, GlobalAction.IncreaseScrollSpeed), new KeyBinding(new[] { InputKey.Shift, InputKey.Tab }, GlobalAction.ToggleInGameInterface), + new KeyBinding(InputKey.Tab, GlobalAction.ToggleInGameLeaderboard), new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay), new KeyBinding(InputKey.Control, GlobalAction.HoldForHUD), - new KeyBinding(InputKey.Tab, GlobalAction.ToggleChatFocus), + new KeyBinding(InputKey.Enter, GlobalAction.ToggleChatFocus), new KeyBinding(InputKey.F1, GlobalAction.SaveReplay), new KeyBinding(InputKey.F2, GlobalAction.ExportReplay), }; @@ -285,6 +286,9 @@ public enum GlobalAction [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleInGameInterface))] ToggleInGameInterface, + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleInGameLeaderboard))] + ToggleInGameLeaderboard, + // Song select keybindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleModSelection))] ToggleModSelection, diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index f93d86225c82..ceefc279682f 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -219,6 +219,11 @@ public static class GlobalActionKeyBindingStrings /// public static LocalisableString ToggleInGameInterface => new TranslatableString(getKey(@"toggle_in_game_interface"), @"Toggle in-game interface"); + /// + /// "Toggle in-game leaderboard" + /// + public static LocalisableString ToggleInGameLeaderboard => new TranslatableString(getKey(@"toggle_in_game_leaderboard"), @"Toggle in-game leaderboard"); + /// /// "Toggle mod select" /// From a4065486c198fd49fd538ba779f7e6cfb08fe79e Mon Sep 17 00:00:00 2001 From: QuantumSno Date: Fri, 28 Jul 2023 14:39:41 -0400 Subject: [PATCH 031/207] bound bind during gameplay --- osu.Game/Screens/Play/HUDOverlay.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index d11171e3feda..9c001b3db370 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -78,6 +78,7 @@ protected override bool ShouldBeConsideredForInput(Drawable child) public Bindable ShowHud { get; } = new BindableBool(); private Bindable configVisibilityMode; + private Bindable configLeaderboardVisibility; private Bindable configSettingsOverlay; private readonly BindableBool replayLoaded = new BindableBool(); @@ -179,6 +180,7 @@ private void load(OsuConfigManager config, INotificationOverlay notificationOver ModDisplay.Current.Value = mods; configVisibilityMode = config.GetBindable(OsuSetting.HUDVisibilityMode); + configLeaderboardVisibility = config.GetBindable(OsuSetting.GameplayLeaderboard); configSettingsOverlay = config.GetBindable(OsuSetting.ReplaySettingsOverlay); if (configVisibilityMode.Value == HUDVisibilityMode.Never && !hasShownNotificationOnce) @@ -381,6 +383,10 @@ public bool OnPressed(KeyBindingPressEvent e) } return true; + + case GlobalAction.ToggleInGameLeaderboard: + configLeaderboardVisibility.Value = !configLeaderboardVisibility.Value; + return true; } return false; From aca8310cd1df87a131e1e1dfc7d0bc047101c578 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 28 Jul 2023 23:36:57 +0200 Subject: [PATCH 032/207] Fix non-compiling test To be fair, currently the test is a bit pointless (as it has no reason to be a `SkinnableTestScene`, it gains precisely nothing from it - all that is shown there is some generic components on song select). But that is no worse then `master`, so look away for now. --- .../Visual/Gameplay/TestSceneSkinEditorComponentsList.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs index 515bc09bbb9e..b7b2a6c1750d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditorComponentsList.cs @@ -8,6 +8,7 @@ using osu.Game.Overlays.SkinEditor; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Skinning; namespace osu.Game.Tests.Visual.Gameplay { @@ -19,7 +20,9 @@ public partial class TestSceneSkinEditorComponentsList : SkinnableTestScene [Test] public void TestToggleEditor() { - AddStep("show available components", () => SetContents(_ => new SkinComponentToolbox + var skinComponentsContainer = new SkinComponentsContainer(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.SongSelect)); + + AddStep("show available components", () => SetContents(_ => new SkinComponentToolbox(skinComponentsContainer, null) { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, From cd416e09f9cb0e7110c79bf84c4a99c17c5dee0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 29 Jul 2023 00:36:27 +0200 Subject: [PATCH 033/207] Add test scene for checking spinner judgements --- .../TestSceneSpinnerJudgement.cs | 147 ++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerJudgement.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerJudgement.cs new file mode 100644 index 000000000000..c969cb11b4c9 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerJudgement.cs @@ -0,0 +1,147 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Extensions.TypeExtensions; +using osu.Framework.Screens; +using osu.Game.Beatmaps; +using osu.Game.Replays; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Replays; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public partial class TestSceneSpinnerJudgement : RateAdjustedBeatmapTestScene + { + private const double time_spinner_start = 2000; + private const double time_spinner_end = 4000; + + private List judgementResults = new List(); + private ScoreAccessibleReplayPlayer currentPlayer = null!; + + [Test] + public void TestHitNothing() + { + performTest(new List()); + + AddAssert("all min judgements", () => judgementResults.All(result => result.Type == result.Judgement.MinResult)); + } + + [TestCase(1)] + [TestCase(2)] + [TestCase(5)] + public void TestNumberOfSpins(int spins) + { + performTest(generateReplay(spins)); + + for (int i = 0; i < spins; ++i) + assertResult(i, HitResult.SmallBonus); + + assertResult(spins, HitResult.IgnoreMiss); + } + + [Test] + public void TestHitEverything() + { + performTest(generateReplay(20)); + + AddAssert("all max judgements", () => judgementResults.All(result => result.Type == result.Judgement.MaxResult)); + } + + private static List generateReplay(int spins) + { + var replayFrames = new List(); + + const int frames_per_spin = 30; + + for (int i = 0; i < spins * frames_per_spin; ++i) + { + float totalProgress = i / (float)(spins * frames_per_spin); + float spinProgress = (i % frames_per_spin) / (float)frames_per_spin; + double time = time_spinner_start + (time_spinner_end - time_spinner_start) * totalProgress; + float posX = MathF.Cos(2 * MathF.PI * spinProgress); + float posY = MathF.Sin(2 * MathF.PI * spinProgress); + Vector2 finalPos = OsuPlayfield.BASE_SIZE / 2 + new Vector2(posX, posY) * 50; + + replayFrames.Add(new OsuReplayFrame(time, finalPos, OsuAction.LeftButton)); + } + + return replayFrames; + } + + private void performTest(List frames) + { + AddStep("load player", () => + { + Beatmap.Value = CreateWorkingBeatmap(new Beatmap + { + HitObjects = + { + new Spinner + { + StartTime = time_spinner_start, + EndTime = time_spinner_end, + Position = OsuPlayfield.BASE_SIZE / 2 + } + }, + BeatmapInfo = + { + Difficulty = new BeatmapDifficulty(), + Ruleset = new OsuRuleset().RulesetInfo + }, + }); + + var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); + + p.OnLoadComplete += _ => + { + p.ScoreProcessor.NewJudgement += result => + { + if (currentPlayer == p) judgementResults.Add(result); + }; + }; + + LoadScreen(currentPlayer = p); + judgementResults = new List(); + }); + + AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0); + AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen()); + AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value); + } + + private void assertResult(int index, HitResult expectedResult) + { + AddAssert($"{typeof(T).ReadableName()} ({index}) judged as {expectedResult}", + () => judgementResults.Where(j => j.HitObject is T).OrderBy(j => j.HitObject.StartTime).ElementAt(index).Type, + () => Is.EqualTo(expectedResult)); + } + + private partial class ScoreAccessibleReplayPlayer : ReplayPlayer + { + public new ScoreProcessor ScoreProcessor => base.ScoreProcessor; + + protected override bool PauseOnFocusLost => false; + + public ScoreAccessibleReplayPlayer(Score score) + : base(score, new PlayerConfiguration + { + AllowPause = false, + ShowResults = false, + }) + { + } + } + } +} From 1fd4a6dc96fe5b87ee9c616a2518775a731a765e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 29 Jul 2023 01:07:08 +0200 Subject: [PATCH 034/207] Fix tests crashing due to `HUDOverlay` not finding `DrawableRuleset` in `Update()` --- osu.Game/Screens/Play/HUDOverlay.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 6696332307ca..536d3ac14650 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -70,7 +70,9 @@ protected override bool ShouldBeConsideredForInput(Drawable child) public Bindable ShowHealthBar = new Bindable(true); + [CanBeNull] private readonly DrawableRuleset drawableRuleset; + private readonly IReadOnlyList mods; /// @@ -106,7 +108,7 @@ protected override bool ShouldBeConsideredForInput(Drawable child) private readonly Drawable playfieldComponents; - public HUDOverlay(DrawableRuleset drawableRuleset, IReadOnlyList mods, bool alwaysShowLeaderboard = true) + public HUDOverlay([CanBeNull] DrawableRuleset drawableRuleset, IReadOnlyList mods, bool alwaysShowLeaderboard = true) { Drawable rulesetComponents; this.drawableRuleset = drawableRuleset; @@ -235,10 +237,13 @@ protected override void Update() { base.Update(); - Quad playfieldScreenSpaceDrawQuad = drawableRuleset.Playfield.SkinnableComponentScreenSpaceDrawQuad; + if (drawableRuleset != null) + { + Quad playfieldScreenSpaceDrawQuad = drawableRuleset.Playfield.SkinnableComponentScreenSpaceDrawQuad; - playfieldComponents.Position = ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft); - playfieldComponents.Size = ToLocalSpace(playfieldScreenSpaceDrawQuad.BottomRight) - ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft); + playfieldComponents.Position = ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft); + playfieldComponents.Size = ToLocalSpace(playfieldScreenSpaceDrawQuad.BottomRight) - ToLocalSpace(playfieldScreenSpaceDrawQuad.TopLeft); + } float? lowestTopScreenSpaceLeft = null; float? lowestTopScreenSpaceRight = null; From a3301dc7ffb0a6e4958065f8b8210a3b98c1fdc4 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 28 Jul 2023 23:22:28 -0700 Subject: [PATCH 035/207] Fix accuracy break info decimal separator being incorrect in certain languages --- osu.Game/Screens/Play/Break/BreakInfoLine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Break/BreakInfoLine.cs b/osu.Game/Screens/Play/Break/BreakInfoLine.cs index 7261155c9445..b8696352e883 100644 --- a/osu.Game/Screens/Play/Break/BreakInfoLine.cs +++ b/osu.Game/Screens/Play/Break/BreakInfoLine.cs @@ -58,7 +58,7 @@ public BreakInfoLine(LocalisableString name, string prefix = @"") private void currentValueChanged(ValueChangedEvent e) { - string newText = prefix + Format(e.NewValue); + LocalisableString newText = LocalisableString.Interpolate($"{prefix}{Format(e.NewValue)}"); if (valueText.Text == newText) return; From 8a06914438fd3d1d8bdcc462c5fe196891bee2c7 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Tue, 25 Jul 2023 20:50:55 +0900 Subject: [PATCH 036/207] remove #nullable disable in tournament --- .../NonVisual/LadderInfoSerialisationTest.cs | 5 +- .../Screens/TestSceneLadderEditorScreen.cs | 2 +- .../Screens/TestSceneSeedingEditorScreen.cs | 3 +- .../Screens/TestSceneTeamIntroScreen.cs | 2 +- .../Screens/TestSceneTeamWinScreen.cs | 2 +- .../TournamentTestScene.cs | 9 +-- .../Components/DrawableTeamFlag.cs | 10 ++- .../Components/DrawableTeamTitle.cs | 8 +-- .../Components/DrawableTournamentTeam.cs | 11 ++-- osu.Game.Tournament/Components/SongBar.cs | 8 +-- .../Components/TournamentBeatmapPanel.cs | 18 +++-- .../Components/TournamentMatchChatDisplay.cs | 4 +- .../Components/TourneyVideo.cs | 6 +- osu.Game.Tournament/IPC/FileBasedIPC.cs | 65 +++++++++---------- osu.Game.Tournament/JsonPointConverter.cs | 9 +-- osu.Game.Tournament/Models/LadderInfo.cs | 6 +- osu.Game.Tournament/Models/RoundBeatmap.cs | 1 - osu.Game.Tournament/Models/StableInfo.cs | 6 +- osu.Game.Tournament/Models/TournamentMatch.cs | 22 +++---- osu.Game.Tournament/Models/TournamentTeam.cs | 9 ++- .../Screens/Drawings/Components/Group.cs | 4 +- .../Components/ScrollingTeamContainer.cs | 27 ++++---- .../Components/StorageBackedTeamList.cs | 6 +- .../Components/VisualiserContainer.cs | 4 +- .../Screens/Drawings/DrawingsScreen.cs | 28 ++++---- .../Screens/Editors/LadderEditorScreen.cs | 58 +++++++---------- .../Gameplay/Components/MatchHeader.cs | 8 +-- .../Gameplay/Components/TeamDisplay.cs | 2 +- .../Gameplay/Components/TeamScoreDisplay.cs | 24 ++++--- .../Components/TournamentMatchScoreDisplay.cs | 4 +- .../Screens/Gameplay/GameplayScreen.cs | 36 +++++----- .../Ladder/Components/DrawableMatchTeam.cs | 29 ++++----- .../Components/DrawableTournamentMatch.cs | 28 ++++---- .../Components/DrawableTournamentRound.cs | 4 +- .../Ladder/Components/LadderEditorSettings.cs | 22 +++---- .../Ladder/Components/SettingsTeamDropdown.cs | 2 +- .../Screens/Ladder/LadderScreen.cs | 12 ++-- .../Screens/MapPool/MapPoolScreen.cs | 38 ++++++----- .../Screens/Schedule/ScheduleScreen.cs | 10 ++- .../Screens/Setup/ResolutionSelector.cs | 6 +- .../Screens/Setup/SetupScreen.cs | 22 +++---- .../Screens/Setup/StablePathSelectScreen.cs | 12 ++-- .../Screens/Setup/TournamentSwitcher.cs | 10 ++- .../Screens/Showcase/ShowcaseScreen.cs | 2 +- .../Screens/TeamIntro/SeedingScreen.cs | 22 +++---- .../Screens/TeamIntro/TeamIntroScreen.cs | 6 +- .../Screens/TeamWin/TeamWinScreen.cs | 12 ++-- .../Screens/TournamentMatchScreen.cs | 8 +-- osu.Game.Tournament/TournamentGame.cs | 8 +-- osu.Game.Tournament/TournamentGameBase.cs | 29 +++++---- osu.Game.Tournament/TournamentSceneManager.cs | 20 +++--- 51 files changed, 330 insertions(+), 379 deletions(-) diff --git a/osu.Game.Tournament.Tests/NonVisual/LadderInfoSerialisationTest.cs b/osu.Game.Tournament.Tests/NonVisual/LadderInfoSerialisationTest.cs index 962a6fbd6c4c..d18651b8bb26 100644 --- a/osu.Game.Tournament.Tests/NonVisual/LadderInfoSerialisationTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/LadderInfoSerialisationTest.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json; using NUnit.Framework; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Tournament.Models; namespace osu.Game.Tournament.Tests.NonVisual @@ -35,8 +36,8 @@ private static LadderInfo createSampleLadder() PlayersPerTeam = { Value = 4 }, Teams = { - match.Team1.Value, - match.Team2.Value, + match.Team1.Value.AsNonNull(), + match.Team2.Value.AsNonNull(), }, Rounds = { diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs index 29ecdaf873ce..b9b150d26419 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs @@ -94,7 +94,7 @@ public void TestResetBracketTeams() AddStep("release mouse button", () => InputManager.ReleaseButton(MouseButton.Left)); - AddAssert("assert ladder teams reset", () => Ladder.CurrentMatch.Value.Team1.Value == null && Ladder.CurrentMatch.Value.Team2.Value == null); + AddAssert("assert ladder teams reset", () => Ladder.CurrentMatch.Value?.Team1.Value == null && Ladder.CurrentMatch.Value?.Team2.Value == null); } } } diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs index 15d4fb1f3f21..a7ef4d7a2904 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.Editors; @@ -17,7 +18,7 @@ private void load() { var match = CreateSampleMatch(); - Add(new SeedingEditorScreen(match.Team1.Value, new TeamEditorScreen()) + Add(new SeedingEditorScreen(match.Team1.Value.AsNonNull(), new TeamEditorScreen()) { Width = 0.85f // create room for control panel }); diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs index b76e0d752158..d6941848b714 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs @@ -21,7 +21,7 @@ private void load() { Team1 = { Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "USA") }, Team2 = { Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "JPN") }, - Round = { Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals") } + Round = { Value = Ladder.Rounds.First(g => g.Name.Value == "Quarterfinals") } }; Add(new TeamIntroScreen diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs index 05c21fa0b11e..1e3ff72d43cd 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs @@ -17,7 +17,7 @@ public void TestBasic() { var match = Ladder.CurrentMatch.Value!; - match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals"); + match.Round.Value = Ladder.Rounds.First(g => g.Name.Value == "Quarterfinals"); match.Completed.Value = true; }); diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs index 84e5da62f449..0cf1e2221167 100644 --- a/osu.Game.Tournament.Tests/TournamentTestScene.cs +++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Framework.Utils; @@ -40,10 +41,10 @@ private void load(TournamentStorage storage) match = CreateSampleMatch(); - Ladder.Rounds.Add(match.Round.Value); + Ladder.Rounds.Add(match.Round.Value.AsNonNull()); Ladder.Matches.Add(match); - Ladder.Teams.Add(match.Team1.Value); - Ladder.Teams.Add(match.Team2.Value); + Ladder.Teams.Add(match.Team1.Value.AsNonNull()); + Ladder.Teams.Add(match.Team2.Value.AsNonNull()); Ruleset.BindTo(Ladder.Ruleset); Dependencies.CacheAs(new StableInfo(storage)); @@ -152,7 +153,7 @@ public virtual void SetUpSteps() }, Round = { - Value = new TournamentRound { Name = { Value = "Quarterfinals" } } + Value = new TournamentRound { Name = { Value = "Quarterfinals" } }, } }; diff --git a/osu.Game.Tournament/Components/DrawableTeamFlag.cs b/osu.Game.Tournament/Components/DrawableTeamFlag.cs index 317d685ee732..aef854bb8dfb 100644 --- a/osu.Game.Tournament/Components/DrawableTeamFlag.cs +++ b/osu.Game.Tournament/Components/DrawableTeamFlag.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -17,14 +15,14 @@ namespace osu.Game.Tournament.Components { public partial class DrawableTeamFlag : Container { - private readonly TournamentTeam team; + private readonly TournamentTeam? team; [UsedImplicitly] - private Bindable flag; + private Bindable? flag; - private Sprite flagSprite; + private Sprite? flagSprite; - public DrawableTeamFlag(TournamentTeam team) + public DrawableTeamFlag(TournamentTeam? team) { this.team = team; } diff --git a/osu.Game.Tournament/Components/DrawableTeamTitle.cs b/osu.Game.Tournament/Components/DrawableTeamTitle.cs index 68cc46be19e2..85b3e5419cde 100644 --- a/osu.Game.Tournament/Components/DrawableTeamTitle.cs +++ b/osu.Game.Tournament/Components/DrawableTeamTitle.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -12,12 +10,12 @@ namespace osu.Game.Tournament.Components { public partial class DrawableTeamTitle : TournamentSpriteTextWithBackground { - private readonly TournamentTeam team; + private readonly TournamentTeam? team; [UsedImplicitly] - private Bindable acronym; + private Bindable? acronym; - public DrawableTeamTitle(TournamentTeam team) + public DrawableTeamTitle(TournamentTeam? team) { this.team = team; } diff --git a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs index 0036f5f115a4..9583682e8be0 100644 --- a/osu.Game.Tournament/Components/DrawableTournamentTeam.cs +++ b/osu.Game.Tournament/Components/DrawableTournamentTeam.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -14,15 +12,15 @@ namespace osu.Game.Tournament.Components { public abstract partial class DrawableTournamentTeam : CompositeDrawable { - public readonly TournamentTeam Team; + public readonly TournamentTeam? Team; protected readonly Container Flag; protected readonly TournamentSpriteText AcronymText; [UsedImplicitly] - private Bindable acronym; + private Bindable? acronym; - protected DrawableTournamentTeam(TournamentTeam team) + protected DrawableTournamentTeam(TournamentTeam? team) { Team = team; @@ -36,7 +34,8 @@ protected DrawableTournamentTeam(TournamentTeam team) [BackgroundDependencyLoader] private void load() { - if (Team == null) return; + if (Team == null) + return; (acronym = Team.Acronym.GetBoundCopy()).BindValueChanged(_ => AcronymText.Text = Team?.Acronym.Value?.ToUpperInvariant() ?? string.Empty, true); } diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 4f4a02ccf1e2..19d63286d8d3 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -24,12 +22,12 @@ namespace osu.Game.Tournament.Components { public partial class SongBar : CompositeDrawable { - private TournamentBeatmap beatmap; + private TournamentBeatmap? beatmap; public const float HEIGHT = 145 / 2f; [Resolved] - private IBindable ruleset { get; set; } + private IBindable ruleset { get; set; } = null!; public TournamentBeatmap Beatmap { @@ -55,7 +53,7 @@ public LegacyMods Mods } } - private FillFlowContainer flow; + private FillFlowContainer flow = null!; private bool expanded; diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 49478a217404..2d5844d02bec 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -22,13 +22,13 @@ public partial class TournamentBeatmapPanel : CompositeDrawable { public readonly TournamentBeatmap? Beatmap; - private readonly string mod; + private readonly string? mod; public const float HEIGHT = 50; private readonly Bindable currentMatch = new Bindable(); - private Box flash = null!; + private Box? flash; public TournamentBeatmapPanel(TournamentBeatmap? beatmap, string mod = "") { @@ -135,25 +135,29 @@ private void matchChanged(ValueChangedEvent match) match.OldValue.PicksBans.CollectionChanged -= picksBansOnCollectionChanged; if (match.NewValue != null) match.NewValue.PicksBans.CollectionChanged += picksBansOnCollectionChanged; - - Scheduler.AddOnce(updateState); + updateState(); } private void picksBansOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) - => Scheduler.AddOnce(updateState); + => updateState(); private BeatmapChoice? choice; private void updateState() { - var newChoice = currentMatch.Value?.PicksBans.FirstOrDefault(p => p.BeatmapID == Beatmap?.OnlineID); + if (currentMatch.Value == null) + { + return; + } + + var newChoice = currentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == Beatmap?.OnlineID); bool shouldFlash = newChoice != choice; if (newChoice != null) { if (shouldFlash) - flash.FadeOutFromOne(500).Loop(0, 10); + flash?.FadeOutFromOne(500).Loop(0, 10); BorderThickness = 6; diff --git a/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs index e943cb8b8c00..0998e606e913 100644 --- a/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs +++ b/osu.Game.Tournament/Components/TournamentMatchChatDisplay.cs @@ -92,9 +92,9 @@ public MatchMessage(Message message, LadderInfo info) { if (info.CurrentMatch.Value is TournamentMatch match) { - if (match.Team1.Value.Players.Any(u => u.OnlineID == Message.Sender.OnlineID)) + if (match.Team1.Value?.Players.Any(u => u.OnlineID == Message.Sender.OnlineID) == true) UsernameColour = TournamentGame.COLOUR_RED; - else if (match.Team2.Value.Players.Any(u => u.OnlineID == Message.Sender.OnlineID)) + else if (match.Team2.Value?.Players.Any(u => u.OnlineID == Message.Sender.OnlineID) == true) UsernameColour = TournamentGame.COLOUR_BLUE; } } diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index b9ce84b73541..6e45c7556bea 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -19,8 +17,8 @@ public partial class TourneyVideo : CompositeDrawable { private readonly string filename; private readonly bool drawFallbackGradient; - private Video video; - private ManualClock manualClock; + private Video? video; + private ManualClock? manualClock; public bool VideoAvailable => video != null; diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 7babb3ea5a59..333dd0fd73f2 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -1,12 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.IO; using System.Linq; -using JetBrains.Annotations; using Microsoft.Win32; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; @@ -24,36 +21,35 @@ namespace osu.Game.Tournament.IPC { public partial class FileBasedIPC : MatchIPCInfo { - public Storage IPCStorage { get; private set; } + public Storage? IPCStorage { get; private set; } [Resolved] - protected IAPIProvider API { get; private set; } + protected IAPIProvider API { get; private set; } = null!; [Resolved] - protected IRulesetStore Rulesets { get; private set; } + protected IRulesetStore Rulesets { get; private set; } = null!; [Resolved] - private GameHost host { get; set; } + private GameHost host { get; set; } = null!; [Resolved] - private LadderInfo ladder { get; set; } + private LadderInfo ladder { get; set; } = null!; [Resolved] - private StableInfo stableInfo { get; set; } + private StableInfo stableInfo { get; set; } = null!; private int lastBeatmapId; - private ScheduledDelegate scheduled; - private GetBeatmapRequest beatmapLookupRequest; + private ScheduledDelegate? scheduled; + private GetBeatmapRequest? beatmapLookupRequest; [BackgroundDependencyLoader] private void load() { - string stablePath = stableInfo.StablePath ?? findStablePath(); + string? stablePath = stableInfo.StablePath ?? findStablePath(); initialiseIPCStorage(stablePath); } - [CanBeNull] - private Storage initialiseIPCStorage(string path) + private Storage? initialiseIPCStorage(string? path) { scheduled?.Cancel(); @@ -89,9 +85,9 @@ private Storage initialiseIPCStorage(string path) lastBeatmapId = beatmapId; - var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId && b.Beatmap != null); + var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId); - if (existing != null) + if (existing?.Beatmap != null) Beatmap.Value = existing.Beatmap; else { @@ -114,7 +110,7 @@ private Storage initialiseIPCStorage(string path) using (var stream = IPCStorage.GetStream(file_ipc_channel_filename)) using (var sr = new StreamReader(stream)) { - ChatChannel.Value = sr.ReadLine(); + ChatChannel.Value = sr.ReadLine().AsNonNull(); } } catch (Exception) @@ -140,8 +136,8 @@ private Storage initialiseIPCStorage(string path) using (var stream = IPCStorage.GetStream(file_ipc_scores_filename)) using (var sr = new StreamReader(stream)) { - Score1.Value = int.Parse(sr.ReadLine()); - Score2.Value = int.Parse(sr.ReadLine()); + Score1.Value = int.Parse(sr.ReadLine().AsNonNull()); + Score2.Value = int.Parse(sr.ReadLine().AsNonNull()); } } catch (Exception) @@ -164,7 +160,7 @@ private Storage initialiseIPCStorage(string path) /// /// Path to the IPC directory /// Whether the supplied path was a valid IPC directory. - public bool SetIPCLocation(string path) + public bool SetIPCLocation(string? path) { if (path == null || !ipcFileExistsInDirectory(path)) return false; @@ -184,29 +180,28 @@ public bool SetIPCLocation(string path) /// Whether an IPC directory was successfully auto-detected. public bool AutoDetectIPCLocation() => SetIPCLocation(findStablePath()); - private static bool ipcFileExistsInDirectory(string p) => p != null && File.Exists(Path.Combine(p, "ipc.txt")); + private static bool ipcFileExistsInDirectory(string? p) => p != null && File.Exists(Path.Combine(p, "ipc.txt")); - [CanBeNull] - private string findStablePath() + private string? findStablePath() { - string stableInstallPath = findFromEnvVar() ?? - findFromRegistry() ?? - findFromLocalAppData() ?? - findFromDotFolder(); + string? stableInstallPath = findFromEnvVar() ?? + findFromRegistry() ?? + findFromLocalAppData() ?? + findFromDotFolder(); Logger.Log($"Stable path for tourney usage: {stableInstallPath}"); return stableInstallPath; } - private string findFromEnvVar() + private string? findFromEnvVar() { try { Logger.Log("Trying to find stable with environment variables"); - string stableInstallPath = Environment.GetEnvironmentVariable("OSU_STABLE_PATH"); + string? stableInstallPath = Environment.GetEnvironmentVariable("OSU_STABLE_PATH"); if (ipcFileExistsInDirectory(stableInstallPath)) - return stableInstallPath; + return stableInstallPath!; } catch { @@ -215,7 +210,7 @@ private string findFromEnvVar() return null; } - private string findFromLocalAppData() + private string? findFromLocalAppData() { Logger.Log("Trying to find stable in %LOCALAPPDATA%"); string stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); @@ -226,7 +221,7 @@ private string findFromLocalAppData() return null; } - private string findFromDotFolder() + private string? findFromDotFolder() { Logger.Log("Trying to find stable in dotfolders"); string stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu"); @@ -237,16 +232,16 @@ private string findFromDotFolder() return null; } - private string findFromRegistry() + private string? findFromRegistry() { Logger.Log("Trying to find stable in registry"); try { - string stableInstallPath; + string? stableInstallPath; #pragma warning disable CA1416 - using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) + using (RegistryKey? key = Registry.ClassesRoot.OpenSubKey("osu")) stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty)?.ToString()?.Split('"')[1].Replace("osu!.exe", ""); #pragma warning restore CA1416 diff --git a/osu.Game.Tournament/JsonPointConverter.cs b/osu.Game.Tournament/JsonPointConverter.cs index d3b40a352687..a58ec47612cc 100644 --- a/osu.Game.Tournament/JsonPointConverter.cs +++ b/osu.Game.Tournament/JsonPointConverter.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Diagnostics; using System.Drawing; @@ -28,7 +26,7 @@ public override Point ReadJson(JsonReader reader, Type objectType, Point existin if (reader.TokenType != JsonToken.StartObject) { // if there's no object present then this is using string representation (System.Drawing.Point serializes to "x,y") - string str = (string)reader.Value; + string? str = (string?)reader.Value; Debug.Assert(str != null); @@ -45,9 +43,12 @@ public override Point ReadJson(JsonReader reader, Type objectType, Point existin if (reader.TokenType == JsonToken.PropertyName) { - string name = reader.Value?.ToString(); + string? name = reader.Value?.ToString(); int? val = reader.ReadAsInt32(); + if (name == null) + continue; + if (val == null) continue; diff --git a/osu.Game.Tournament/Models/LadderInfo.cs b/osu.Game.Tournament/Models/LadderInfo.cs index 229837c94e61..219a2a7bfb16 100644 --- a/osu.Game.Tournament/Models/LadderInfo.cs +++ b/osu.Game.Tournament/Models/LadderInfo.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using Newtonsoft.Json; @@ -17,7 +15,7 @@ namespace osu.Game.Tournament.Models [Serializable] public class LadderInfo { - public Bindable Ruleset = new Bindable(); + public Bindable Ruleset = new Bindable(); public BindableList Matches = new BindableList(); public BindableList Rounds = new BindableList(); @@ -27,7 +25,7 @@ public class LadderInfo public List Progressions = new List(); [JsonIgnore] // updated manually in TournamentGameBase - public Bindable CurrentMatch = new Bindable(); + public Bindable CurrentMatch = new Bindable(); public Bindable ChromaKeyWidth = new BindableInt(1024) { diff --git a/osu.Game.Tournament/Models/RoundBeatmap.cs b/osu.Game.Tournament/Models/RoundBeatmap.cs index f2ec26124607..b03b28b3b827 100644 --- a/osu.Game.Tournament/Models/RoundBeatmap.cs +++ b/osu.Game.Tournament/Models/RoundBeatmap.cs @@ -8,7 +8,6 @@ namespace osu.Game.Tournament.Models public class RoundBeatmap { public int ID; - public string Mods = string.Empty; [JsonProperty("BeatmapInfo")] diff --git a/osu.Game.Tournament/Models/StableInfo.cs b/osu.Game.Tournament/Models/StableInfo.cs index 1ae80d459684..7ee0b4a361af 100644 --- a/osu.Game.Tournament/Models/StableInfo.cs +++ b/osu.Game.Tournament/Models/StableInfo.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.IO; using Newtonsoft.Json; @@ -20,12 +18,12 @@ public class StableInfo /// /// Path to the IPC directory used by the stable (cutting-edge) install. /// - public string StablePath { get; set; } + public string? StablePath { get; set; } /// /// Fired whenever stable info is successfully saved to file. /// - public event Action OnStableInfoSaved; + public event Action? OnStableInfoSaved; private const string config_path = "stable.json"; diff --git a/osu.Game.Tournament/Models/TournamentMatch.cs b/osu.Game.Tournament/Models/TournamentMatch.cs index 97c2060f2c86..0a700eb4d64a 100644 --- a/osu.Game.Tournament/Models/TournamentMatch.cs +++ b/osu.Game.Tournament/Models/TournamentMatch.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -33,16 +31,16 @@ public List Acronyms } [JsonIgnore] - public readonly Bindable Team1 = new Bindable(); + public readonly Bindable Team1 = new Bindable(); - public string Team1Acronym; + public string? Team1Acronym; public readonly Bindable Team1Score = new Bindable(); [JsonIgnore] - public readonly Bindable Team2 = new Bindable(); + public readonly Bindable Team2 = new Bindable(); - public string Team2Acronym; + public string? Team2Acronym; public readonly Bindable Team2Score = new Bindable(); @@ -53,13 +51,13 @@ public List Acronyms public readonly ObservableCollection PicksBans = new ObservableCollection(); [JsonIgnore] - public readonly Bindable Round = new Bindable(); + public readonly Bindable Round = new Bindable(); [JsonIgnore] - public readonly Bindable Progression = new Bindable(); + public readonly Bindable Progression = new Bindable(); [JsonIgnore] - public readonly Bindable LosersProgression = new Bindable(); + public readonly Bindable LosersProgression = new Bindable(); /// /// Should not be set directly. Use LadderInfo.CurrentMatch.Value = this instead. @@ -79,7 +77,7 @@ public TournamentMatch() Team2.BindValueChanged(t => Team2Acronym = t.NewValue?.Acronym.Value, true); } - public TournamentMatch(TournamentTeam team1 = null, TournamentTeam team2 = null) + public TournamentMatch(TournamentTeam? team1 = null, TournamentTeam? team2 = null) : this() { Team1.Value = team1; @@ -87,10 +85,10 @@ public TournamentMatch(TournamentTeam team1 = null, TournamentTeam team2 = null) } [JsonIgnore] - public TournamentTeam Winner => !Completed.Value ? null : Team1Score.Value > Team2Score.Value ? Team1.Value : Team2.Value; + public TournamentTeam? Winner => !Completed.Value ? null : Team1Score.Value > Team2Score.Value ? Team1.Value : Team2.Value; [JsonIgnore] - public TournamentTeam Loser => !Completed.Value ? null : Team1Score.Value > Team2Score.Value ? Team2.Value : Team1.Value; + public TournamentTeam? Loser => !Completed.Value ? null : Team1Score.Value > Team2Score.Value ? Team2.Value : Team1.Value; public TeamColour WinnerColour => Winner == Team1.Value ? TeamColour.Red : TeamColour.Blue; diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs index 1beea517d570..3587aa937ea7 100644 --- a/osu.Game.Tournament/Models/TournamentTeam.cs +++ b/osu.Game.Tournament/Models/TournamentTeam.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; namespace osu.Game.Tournament.Models { @@ -39,7 +38,7 @@ public double AverageRank { int[] ranks = Players.Select(p => p.Rank) .Where(i => i.HasValue) - .Select(i => i.Value) + .Select(i => i.AsNonNull().Value) .ToArray(); if (ranks.Length == 0) @@ -66,14 +65,14 @@ public TournamentTeam() { // use a sane default flag name based on acronym. if (val.OldValue.StartsWith(FlagName.Value, StringComparison.InvariantCultureIgnoreCase)) - FlagName.Value = val.NewValue.Length >= 2 ? val.NewValue?.Substring(0, 2).ToUpperInvariant() : string.Empty; + FlagName.Value = val.NewValue?.Length >= 2 ? val.NewValue.Substring(0, 2).ToUpperInvariant() : string.Empty; }; FullName.ValueChanged += val => { // use a sane acronym based on full name. if (val.OldValue.StartsWith(Acronym.Value, StringComparison.InvariantCultureIgnoreCase)) - Acronym.Value = val.NewValue.Length >= 3 ? val.NewValue?.Substring(0, 3).ToUpperInvariant() : string.Empty; + Acronym.Value = val.NewValue?.Length >= 3 ? val.NewValue.Substring(0, 3).ToUpperInvariant() : string.Empty; }; } diff --git a/osu.Game.Tournament/Screens/Drawings/Components/Group.cs b/osu.Game.Tournament/Screens/Drawings/Components/Group.cs index a79e2253a4e7..9d4474a58cd1 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/Group.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/Group.cs @@ -84,7 +84,7 @@ public void AddTeam(TournamentTeam team) public bool ContainsTeam(string fullName) { - return allTeams.Any(t => t.Team.FullName.Value == fullName); + return allTeams.Any(t => t.Team?.FullName.Value == fullName); } public bool RemoveTeam(TournamentTeam team) @@ -112,7 +112,7 @@ public string GetStringRepresentation() { StringBuilder sb = new StringBuilder(); foreach (GroupTeam gt in allTeams) - sb.AppendLine(gt.Team.FullName.Value); + sb.AppendLine(gt.Team?.FullName.Value); return sb.ToString(); } } diff --git a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs index c2b15dd3e926..4a47de87148b 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -22,8 +21,8 @@ namespace osu.Game.Tournament.Screens.Drawings.Components { public partial class ScrollingTeamContainer : Container { - public event Action OnScrollStarted; - public event Action OnSelected; + public event Action? OnScrollStarted; + public event Action? OnSelected; private readonly List availableTeams = new List(); @@ -42,7 +41,7 @@ public partial class ScrollingTeamContainer : Container private double lastTime; - private ScheduledDelegate delayedStateChangeDelegate; + private ScheduledDelegate? delayedStateChangeDelegate; public ScrollingTeamContainer() { @@ -117,7 +116,7 @@ private void setScrollState(ScrollState newState) if (!Children.Any()) break; - ScrollingTeam closest = null; + ScrollingTeam? closest = null; foreach (var c in Children) { @@ -139,15 +138,14 @@ private void setScrollState(ScrollState newState) Trace.Assert(closest != null, "closest != null"); - // ReSharper disable once PossibleNullReferenceException - offset += DrawWidth / 2f - (closest.Position.X + closest.DrawWidth / 2f); + offset += DrawWidth / 2f - (closest.AsNonNull().Position.X + closest.AsNonNull().DrawWidth / 2f); - ScrollingTeam st = closest; + ScrollingTeam st = closest.AsNonNull(); availableTeams.RemoveAll(at => at == st.Team); st.Selected = true; - OnSelected?.Invoke(st.Team); + OnSelected?.Invoke(st.Team.AsNonNull()); delayedStateChangeDelegate = Scheduler.AddDelayed(() => setScrollState(ScrollState.Idle), 10000); break; @@ -174,7 +172,7 @@ public void AddTeam(TournamentTeam team) setScrollState(ScrollState.Idle); } - public void AddTeams(IEnumerable teams) + public void AddTeams(IEnumerable? teams) { if (teams == null) return; @@ -190,8 +188,11 @@ public void ClearTeams() setScrollState(ScrollState.Idle); } - public void RemoveTeam(TournamentTeam team) + public void RemoveTeam(TournamentTeam? team) { + if (team == null) + return; + availableTeams.Remove(team); foreach (var c in Children) @@ -311,6 +312,8 @@ protected enum ScrollState public partial class ScrollingTeam : DrawableTournamentTeam { + public new TournamentTeam Team => base.Team.AsNonNull(); + public const float WIDTH = 58; public const float HEIGHT = 44; diff --git a/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs index 74afb42c1ab4..e13462b9bdc5 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.IO; @@ -39,7 +37,7 @@ public IEnumerable Teams { while (sr.Peek() != -1) { - string line = sr.ReadLine()?.Trim(); + string? line = sr.ReadLine()?.Trim(); if (string.IsNullOrEmpty(line)) continue; @@ -56,7 +54,7 @@ public IEnumerable Teams teams.Add(new TournamentTeam { FullName = { Value = split[1], }, - Acronym = { Value = split.Length >= 3 ? split[2] : null, }, + Acronym = { Value = split.Length >= 3 ? split[2] : string.Empty, }, FlagName = { Value = split[0] } }); } diff --git a/osu.Game.Tournament/Screens/Drawings/Components/VisualiserContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/VisualiserContainer.cs index 676eec14cdf1..d5e39e3f4453 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/VisualiserContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/VisualiserContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -72,7 +70,7 @@ private partial class VisualiserLine : Container private float leftPos => -(float)((Time.Current + Offset) / CycleTime) + expiredCount; - private Texture texture; + private Texture texture = null!; private int expiredCount; diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 23d0edf26e41..b2dd4e8c3675 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -1,14 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -28,27 +27,27 @@ public partial class DrawingsScreen : TournamentScreen { private const string results_filename = "drawings_results.txt"; - private ScrollingTeamContainer teamsContainer; - private GroupContainer groupsContainer; - private TournamentSpriteText fullTeamNameText; + private ScrollingTeamContainer teamsContainer = null!; + private GroupContainer groupsContainer = null!; + private TournamentSpriteText fullTeamNameText = null!; private readonly List allTeams = new List(); - private DrawingsConfigManager drawingsConfig; + private DrawingsConfigManager drawingsConfig = null!; - private Task writeOp; + private Task? writeOp; - private Storage storage; + private Storage storage = null!; - public ITeamList TeamList; + public ITeamList? TeamList; [BackgroundDependencyLoader] private void load(Storage storage) { - RelativeSizeAxes = Axes.Both; - this.storage = storage; + RelativeSizeAxes = Axes.Both; + TeamList ??= new StorageBackedTeamList(storage); if (!TeamList.Teams.Any()) @@ -224,7 +223,7 @@ private void reloadTeams() teamsContainer.ClearTeams(); allTeams.Clear(); - foreach (TournamentTeam t in TeamList.Teams) + foreach (TournamentTeam t in TeamList.AsNonNull().Teams) { if (groupsContainer.ContainsTeam(t.FullName.Value)) continue; @@ -251,7 +250,7 @@ private void reset(bool loadLastResults = false) using (Stream stream = storage.GetStream(results_filename, FileAccess.Read, FileMode.Open)) using (StreamReader sr = new StreamReader(stream)) { - string line; + string? line; while ((line = sr.ReadLine()?.Trim()) != null) { @@ -261,8 +260,7 @@ private void reset(bool loadLastResults = false) if (line.ToUpperInvariant().StartsWith("GROUP", StringComparison.Ordinal)) continue; - // ReSharper disable once AccessToModifiedClosure - TournamentTeam teamToAdd = allTeams.FirstOrDefault(t => t.FullName.Value == line); + TournamentTeam? teamToAdd = allTeams.FirstOrDefault(t => t.FullName.Value == line); if (teamToAdd == null) continue; diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs index 9411892dc51a..a82ac57ee917 100644 --- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs @@ -1,12 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Drawing; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -35,13 +32,12 @@ public partial class LadderEditorScreen : LadderScreen, IHasContextMenu [Cached] private LadderEditorInfo editorInfo = new LadderEditorInfo(); - private WarningBox rightClickMessage; + private WarningBox rightClickMessage = null!; - private RectangularPositionSnapGrid grid; + private RectangularPositionSnapGrid grid = null!; [Resolved(canBeNull: true)] - [CanBeNull] - private IDialogOverlay dialogOverlay { get; set; } + private IDialogOverlay? dialogOverlay { get; set; } protected override bool DrawLoserPaths => true; @@ -94,36 +90,28 @@ public void BeginJoin(TournamentMatch match, bool losers) ScrollContent.Add(new JoinVisualiser(MatchesContainer, match, losers, UpdateLayout)); } - public MenuItem[] ContextMenuItems - { - get + public MenuItem[] ContextMenuItems => + new MenuItem[] { - if (editorInfo == null) - return Array.Empty(); - - return new MenuItem[] + new OsuMenuItem("Create new match", MenuItemType.Highlighted, () => { - new OsuMenuItem("Create new match", MenuItemType.Highlighted, () => - { - Vector2 pos = MatchesContainer.Count == 0 ? Vector2.Zero : lastMatchesContainerMouseDownPosition; + Vector2 pos = MatchesContainer.Count == 0 ? Vector2.Zero : lastMatchesContainerMouseDownPosition; - TournamentMatch newMatch = new TournamentMatch { Position = { Value = new Point((int)pos.X, (int)pos.Y) } }; + TournamentMatch newMatch = new TournamentMatch { Position = { Value = new Point((int)pos.X, (int)pos.Y) } }; - LadderInfo.Matches.Add(newMatch); + LadderInfo.Matches.Add(newMatch); - editorInfo.Selected.Value = newMatch; - }), - new OsuMenuItem("Reset teams", MenuItemType.Destructive, () => + editorInfo.Selected.Value = newMatch; + }), + new OsuMenuItem("Reset teams", MenuItemType.Destructive, () => + { + dialogOverlay?.Push(new LadderResetTeamsDialog(() => { - dialogOverlay?.Push(new LadderResetTeamsDialog(() => - { - foreach (var p in MatchesContainer) - p.Match.Reset(); - })); - }) - }; - } - } + foreach (var p in MatchesContainer) + p.Match.Reset(); + })); + }) + }; public void Remove(TournamentMatch match) { @@ -135,11 +123,11 @@ private partial class JoinVisualiser : CompositeDrawable private readonly Container matchesContainer; public readonly TournamentMatch Source; private readonly bool losers; - private readonly Action complete; + private readonly Action? complete; - private ProgressionPath path; + private ProgressionPath? path; - public JoinVisualiser(Container matchesContainer, TournamentMatch source, bool losers, Action complete) + public JoinVisualiser(Container matchesContainer, TournamentMatch source, bool losers, Action? complete) { this.matchesContainer = matchesContainer; RelativeSizeAxes = Axes.Both; @@ -153,7 +141,7 @@ public JoinVisualiser(Container matchesContainer, Tourn Source.Progression.Value = null; } - private DrawableTournamentMatch findTarget(InputState state) + private DrawableTournamentMatch? findTarget(InputState state) { return matchesContainer.FirstOrDefault(d => d.ReceivePositionalInputAt(state.Mouse.Position)); } diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs index 8f7484980d5e..69f150c8ac9a 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/MatchHeader.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -14,9 +12,9 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components { public partial class MatchHeader : Container { - private TeamScoreDisplay teamDisplay1; - private TeamScoreDisplay teamDisplay2; - private DrawableTournamentHeaderLogo logo; + private TeamScoreDisplay teamDisplay1 = null!; + private TeamScoreDisplay teamDisplay2 = null!; + private DrawableTournamentHeaderLogo logo = null!; private bool showScores = true; diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs index c23327a43f6a..3fdbbb597323 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs @@ -35,7 +35,7 @@ public bool ShowScore } } - public TeamDisplay(TournamentTeam team, TeamColour colour, Bindable currentTeamScore, int pointsToWin) + public TeamDisplay(TournamentTeam? team, TeamColour colour, Bindable currentTeamScore, int pointsToWin) : base(team) { AutoSizeAxes = Axes.Both; diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs index 57fe1c731250..c7fcfae602bc 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -17,16 +15,22 @@ public partial class TeamScoreDisplay : CompositeDrawable { private readonly TeamColour teamColour; - private readonly Bindable currentMatch = new Bindable(); - private readonly Bindable currentTeam = new Bindable(); + private readonly Bindable currentMatch = new Bindable(); + private readonly Bindable currentTeam = new Bindable(); private readonly Bindable currentTeamScore = new Bindable(); - private TeamDisplay teamDisplay; + private TeamDisplay? teamDisplay; public bool ShowScore { - get => teamDisplay.ShowScore; - set => teamDisplay.ShowScore = value; + get => teamDisplay?.ShowScore ?? false; + set + { + if (teamDisplay != null) + { + teamDisplay.ShowScore = value; + } + } } public TeamScoreDisplay(TeamColour teamColour) @@ -48,7 +52,7 @@ private void load(LadderInfo ladder) updateMatch(); } - private void matchChanged(ValueChangedEvent match) + private void matchChanged(ValueChangedEvent match) { currentTeamScore.UnbindBindings(); currentTeam.UnbindBindings(); @@ -78,7 +82,7 @@ protected override bool OnMouseDown(MouseDownEvent e) switch (e.Button) { case MouseButton.Left: - if (currentTeamScore.Value < currentMatch.Value.PointsToWin) + if (currentTeamScore.Value < currentMatch.Value?.PointsToWin) currentTeamScore.Value++; return true; @@ -91,7 +95,7 @@ protected override bool OnMouseDown(MouseDownEvent e) return base.OnMouseDown(e); } - private void teamChanged(ValueChangedEvent team) + private void teamChanged(ValueChangedEvent team) { bool wasShowingScores = teamDisplay?.ShowScore ?? false; diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs index 838e5fa07108..7ae20acc770a 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TournamentMatchScoreDisplay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -146,7 +144,7 @@ protected override void UpdateAfterChildren() private partial class MatchScoreCounter : CommaSeparatedScoreCounter { - private OsuSpriteText displayedSpriteText; + private OsuSpriteText displayedSpriteText = null!; public MatchScoreCounter() { diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index 4258522cffdc..79c50e60ba09 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -26,16 +24,16 @@ public partial class GameplayScreen : BeatmapInfoScreen private readonly BindableBool warmup = new BindableBool(); public readonly Bindable State = new Bindable(); - private OsuButton warmupButton; - private MatchIPCInfo ipc; + private OsuButton warmupButton = null!; + private MatchIPCInfo ipc = null!; [Resolved(canBeNull: true)] - private TournamentSceneManager sceneManager { get; set; } + private TournamentSceneManager? sceneManager { get; set; } [Resolved] - private TournamentMatchChatDisplay chat { get; set; } + private TournamentMatchChatDisplay chat { get; set; } = null!; - private Drawable chroma; + private Drawable chroma = null!; [BackgroundDependencyLoader] private void load(LadderInfo ladder, MatchIPCInfo ipc) @@ -142,7 +140,7 @@ protected override void LoadComplete() State.BindValueChanged(_ => updateState(), true); } - protected override void CurrentMatchChanged(ValueChangedEvent match) + protected override void CurrentMatchChanged(ValueChangedEvent match) { base.CurrentMatchChanged(match); @@ -153,29 +151,35 @@ protected override void CurrentMatchChanged(ValueChangedEvent m scheduledScreenChange?.Cancel(); } - private ScheduledDelegate scheduledScreenChange; - private ScheduledDelegate scheduledContract; + private ScheduledDelegate? scheduledScreenChange; + private ScheduledDelegate? scheduledContract; - private TournamentMatchScoreDisplay scoreDisplay; + private TournamentMatchScoreDisplay scoreDisplay = null!; private TourneyState lastState; - private MatchHeader header; + private MatchHeader header = null!; private void contract() { + if (!IsLoaded) + return; + scheduledContract?.Cancel(); SongBar.Expanded = false; scoreDisplay.FadeOut(100); - using (chat?.BeginDelayedSequence(500)) - chat?.Expand(); + using (chat.BeginDelayedSequence(500)) + chat.Expand(); } private void expand() { + if (!IsLoaded) + return; + scheduledContract?.Cancel(); - chat?.Contract(); + chat.Contract(); using (BeginDelayedSequence(300)) { @@ -252,7 +256,7 @@ public override void Show() private partial class ChromaArea : CompositeDrawable { [Resolved] - private LadderInfo ladder { get; set; } + private LadderInfo ladder { get; set; } = null!; [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs index 2b66df1a31d4..a380ad9b4927 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -28,20 +26,20 @@ public partial class DrawableMatchTeam : DrawableTournamentTeam, IHasContextMenu { private readonly TournamentMatch match; private readonly bool losers; - private TournamentSpriteText scoreText; - private Box background; - private Box backgroundRight; + private TournamentSpriteText scoreText = null!; + private Box background = null!; + private Box backgroundRight = null!; private readonly Bindable score = new Bindable(); private readonly BindableBool completed = new BindableBool(); private Color4 colourWinner; - private readonly Func isWinner; - private LadderEditorScreen ladderEditor; + private readonly Func? isWinner; + private LadderEditorScreen ladderEditor = null!; [Resolved(canBeNull: true)] - private LadderInfo ladderInfo { get; set; } + private LadderInfo? ladderInfo { get; set; } private void setCurrent() { @@ -56,9 +54,9 @@ private void setCurrent() } [Resolved(CanBeNull = true)] - private LadderEditorInfo editorInfo { get; set; } + private LadderEditorInfo? editorInfo { get; set; } - public DrawableMatchTeam(TournamentTeam team, TournamentMatch match, bool losers) + public DrawableMatchTeam(TournamentTeam? team, TournamentMatch match, bool losers) : base(team) { this.match = match; @@ -72,14 +70,11 @@ public DrawableMatchTeam(TournamentTeam team, TournamentMatch match, bool losers AcronymText.Padding = new MarginPadding { Left = 50 }; AcronymText.Font = OsuFont.Torus.With(size: 22, weight: FontWeight.Bold); - if (match != null) - { - isWinner = () => match.Winner == Team; + isWinner = () => match.Winner == Team; - completed.BindTo(match.Completed); - if (team != null) - score.BindTo(team == match.Team1.Value ? match.Team1Score : match.Team2Score); - } + completed.BindTo(match.Completed); + if (team != null) + score.BindTo(team == match.Team1.Value ? match.Team1Score : match.Team2Score); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs index c394877ae9f0..7b8f2e373b76 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs @@ -1,10 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -27,13 +26,13 @@ public partial class DrawableTournamentMatch : CompositeDrawable protected readonly FillFlowContainer Flow; private readonly Drawable selectionBox; private readonly Drawable currentMatchSelectionBox; - private Bindable globalSelection; + private Bindable? globalSelection; [Resolved(CanBeNull = true)] - private LadderEditorInfo editorInfo { get; set; } + private LadderEditorInfo? editorInfo { get; set; } [Resolved(CanBeNull = true)] - private LadderInfo ladderInfo { get; set; } + private LadderInfo? ladderInfo { get; set; } public DrawableTournamentMatch(TournamentMatch match, bool editor = false) { @@ -129,7 +128,7 @@ public DrawableTournamentMatch(TournamentMatch match, bool editor = false) /// /// Fired when something changed that requires a ladder redraw. /// - public Action Changed; + public Action? Changed; private readonly List refBindables = new List(); @@ -201,20 +200,22 @@ private void updateProgression() } else { - transferProgression(Match.Progression?.Value, Match.Winner); - transferProgression(Match.LosersProgression?.Value, Match.Loser); + Debug.Assert(Match.Winner != null); + transferProgression(Match.Progression.Value, Match.Winner); + Debug.Assert(Match.Loser != null); + transferProgression(Match.LosersProgression.Value, Match.Loser); } Changed?.Invoke(); } - private void transferProgression(TournamentMatch destination, TournamentTeam team) + private void transferProgression(TournamentMatch? destination, TournamentTeam team) { if (destination == null) return; bool progressionAbove = destination.ID < Match.ID; - Bindable destinationTeam; + Bindable destinationTeam; // check for the case where we have already transferred out value if (destination.Team1.Value == team) @@ -268,8 +269,8 @@ private void updateTeams() { foreach (var conditional in Match.ConditionalMatches) { - bool team1Match = conditional.Acronyms.Contains(Match.Team1Acronym); - bool team2Match = conditional.Acronyms.Contains(Match.Team2Acronym); + bool team1Match = Match.Team1Acronym != null && conditional.Acronyms.Contains(Match.Team1Acronym); + bool team2Match = Match.Team2Acronym != null && conditional.Acronyms.Contains(Match.Team2Acronym); if (team1Match && team2Match) Match.Date.Value = conditional.Date.Value; @@ -344,6 +345,9 @@ public void Remove() Match.Progression.Value = null; Match.LosersProgression.Value = null; + if (ladderInfo == null) + return; + ladderInfo.Matches.Remove(Match); foreach (var m in ladderInfo.Matches) diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs index 4b2a29247bc6..216e0a5a3ea0 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentRound.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -52,7 +50,7 @@ public DrawableTournamentRound(TournamentRound round, bool losers = false) name.BindValueChanged(_ => textName.Text = ((losers ? "Losers " : "") + round.Name).ToUpperInvariant(), true); description = round.Description.GetBoundCopy(); - description.BindValueChanged(_ => textDescription.Text = round.Description.Value?.ToUpperInvariant(), true); + description.BindValueChanged(_ => textDescription.Text = round.Description.Value?.ToUpperInvariant() ?? string.Empty, true); } } } diff --git a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs index 5c9c14cc306c..9f0fa1991595 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/LadderEditorSettings.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Collections.Specialized; using System.Diagnostics; @@ -23,17 +21,17 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { public partial class LadderEditorSettings : CompositeDrawable { - private SettingsDropdown roundDropdown; - private PlayerCheckbox losersCheckbox; - private DateTextBox dateTimeBox; - private SettingsTeamDropdown team1Dropdown; - private SettingsTeamDropdown team2Dropdown; + private SettingsDropdown roundDropdown = null!; + private PlayerCheckbox losersCheckbox = null!; + private DateTextBox dateTimeBox = null!; + private SettingsTeamDropdown team1Dropdown = null!; + private SettingsTeamDropdown team2Dropdown = null!; [Resolved] - private LadderEditorInfo editorInfo { get; set; } + private LadderEditorInfo editorInfo { get; set; } = null!; [Resolved] - private LadderInfo ladderInfo { get; set; } + private LadderInfo ladderInfo { get; set; } = null!; [BackgroundDependencyLoader] private void load() @@ -77,7 +75,7 @@ private void load() }; } - private void roundDropdownChanged(ValueChangedEvent round) + private void roundDropdownChanged(ValueChangedEvent round) { if (editorInfo.Selected.Value?.Date.Value < round.NewValue?.StartDate.Value) { @@ -101,11 +99,11 @@ protected override void OnHoverLost(HoverLostEvent e) { } - private partial class SettingsRoundDropdown : SettingsDropdown + private partial class SettingsRoundDropdown : SettingsDropdown { public SettingsRoundDropdown(BindableList rounds) { - Current = new Bindable(); + Current = new Bindable(); foreach (var r in rounds.Prepend(new TournamentRound())) add(r); diff --git a/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs b/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs index 00e5353edd9e..7e35190e2e12 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/SettingsTeamDropdown.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tournament.Screens.Ladder.Components { - public partial class SettingsTeamDropdown : SettingsDropdown + public partial class SettingsTeamDropdown : SettingsDropdown { public SettingsTeamDropdown(BindableList teams) { diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs index 2d5281b89369..4f56a2fcc910 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Specialized; using System.Diagnostics; using System.Linq; @@ -22,13 +20,13 @@ namespace osu.Game.Tournament.Screens.Ladder { public partial class LadderScreen : TournamentScreen { - protected Container MatchesContainer; - private Container paths; - private Container headings; + protected Container MatchesContainer = null!; + private Container paths = null!; + private Container headings = null!; - protected LadderDragContainer ScrollContent; + protected LadderDragContainer ScrollContent = null!; - protected Container Content; + protected Container Content = null!; [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index f45da861cb92..3091f4293c43 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -24,20 +22,20 @@ namespace osu.Game.Tournament.Screens.MapPool { public partial class MapPoolScreen : TournamentMatchScreen { - private FillFlowContainer> mapFlows; + private FillFlowContainer> mapFlows = null!; [Resolved(canBeNull: true)] - private TournamentSceneManager sceneManager { get; set; } + private TournamentSceneManager? sceneManager { get; set; } private TeamColour pickColour; private ChoiceType pickType; - private OsuButton buttonRedBan; - private OsuButton buttonBlueBan; - private OsuButton buttonRedPick; - private OsuButton buttonBluePick; + private OsuButton buttonRedBan = null!; + private OsuButton buttonBlueBan = null!; + private OsuButton buttonRedPick = null!; + private OsuButton buttonBluePick = null!; - private ScheduledDelegate scheduledScreenChange; + private ScheduledDelegate? scheduledScreenChange; [BackgroundDependencyLoader] private void load(MatchIPCInfo ipc) @@ -113,7 +111,7 @@ private void load(MatchIPCInfo ipc) ipc.Beatmap.BindValueChanged(beatmapChanged); } - private Bindable splitMapPoolByMods; + private Bindable? splitMapPoolByMods; protected override void LoadComplete() { @@ -148,6 +146,9 @@ private void setMode(TeamColour colour, ChoiceType choiceType) private void setNextMode() { + if (CurrentMatch.Value == null) + return; + const TeamColour roll_winner = TeamColour.Red; //todo: draw from match var nextColour = (CurrentMatch.Value.PicksBans.LastOrDefault()?.Team ?? roll_winner) == TeamColour.Red ? TeamColour.Blue : TeamColour.Red; @@ -169,11 +170,11 @@ protected override bool OnMouseDown(MouseDownEvent e) addForBeatmap(map.Beatmap.OnlineID); else { - var existing = CurrentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == map.Beatmap?.OnlineID); + var existing = CurrentMatch.Value?.PicksBans.FirstOrDefault(p => p.BeatmapID == map.Beatmap?.OnlineID); if (existing != null) { - CurrentMatch.Value.PicksBans.Remove(existing); + CurrentMatch.Value?.PicksBans.Remove(existing); setNextMode(); } } @@ -186,13 +187,13 @@ protected override bool OnMouseDown(MouseDownEvent e) private void reset() { - CurrentMatch.Value.PicksBans.Clear(); + CurrentMatch.Value?.PicksBans.Clear(); setNextMode(); } private void addForBeatmap(int beatmapId) { - if (CurrentMatch.Value == null) + if (CurrentMatch.Value?.Round.Value == null) return; if (CurrentMatch.Value.Round.Value.Beatmaps.All(b => b.Beatmap?.OnlineID != beatmapId)) @@ -228,7 +229,7 @@ public override void Hide() base.Hide(); } - protected override void CurrentMatchChanged(ValueChangedEvent match) + protected override void CurrentMatchChanged(ValueChangedEvent match) { base.CurrentMatchChanged(match); updateDisplay(); @@ -245,12 +246,15 @@ private void updateDisplay() if (CurrentMatch.Value.Round.Value != null) { - FillFlowContainer currentFlow = null; - string currentMods = null; + FillFlowContainer? currentFlow = null; + string? currentMods = null; int flowCount = 0; foreach (var b in CurrentMatch.Value.Round.Value.Beatmaps) { + if (b.Beatmap == null) + continue; + if (currentFlow == null || (LadderInfo.SplitMapPoolByMods.Value && currentMods != b.Mods)) { mapFlows.Add(currentFlow = new FillFlowContainer diff --git a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs index 9232b4c6899d..063c231add02 100644 --- a/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs +++ b/osu.Game.Tournament/Screens/Schedule/ScheduleScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Allocation; @@ -21,9 +19,9 @@ namespace osu.Game.Tournament.Screens.Schedule { public partial class ScheduleScreen : TournamentScreen { - private readonly Bindable currentMatch = new Bindable(); - private Container mainContainer; - private LadderInfo ladder; + private readonly Bindable currentMatch = new Bindable(); + private Container mainContainer = null!; + private LadderInfo ladder = null!; [BackgroundDependencyLoader] private void load(LadderInfo ladder) @@ -107,7 +105,7 @@ protected override void LoadComplete() currentMatch.BindValueChanged(matchChanged, true); } - private void matchChanged(ValueChangedEvent match) + private void matchChanged(ValueChangedEvent match) { var upcoming = ladder.Matches.Where(p => !p.Completed.Value && p.Team1.Value != null && p.Team2.Value != null && Math.Abs(p.Date.Value.DayOfYear - DateTimeOffset.UtcNow.DayOfYear) < 4); var conditionals = ladder diff --git a/osu.Game.Tournament/Screens/Setup/ResolutionSelector.cs b/osu.Game.Tournament/Screens/Setup/ResolutionSelector.cs index e6ab6f143a17..c700e3bfdd59 100644 --- a/osu.Game.Tournament/Screens/Setup/ResolutionSelector.cs +++ b/osu.Game.Tournament/Screens/Setup/ResolutionSelector.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; @@ -14,9 +12,9 @@ internal partial class ResolutionSelector : ActionableInfo private const int minimum_window_height = 480; private const int maximum_window_height = 2160; - public new Action Action; + public new Action? Action; - private OsuNumberBox numberBox; + private OsuNumberBox? numberBox; protected override Drawable CreateComponent() { diff --git a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs index 5c7bbed69ce9..1152759c2a63 100644 --- a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Drawing; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -24,27 +22,27 @@ namespace osu.Game.Tournament.Screens.Setup { public partial class SetupScreen : TournamentScreen { - private FillFlowContainer fillFlow; + private FillFlowContainer fillFlow = null!; - private LoginOverlay loginOverlay; - private ResolutionSelector resolution; + private LoginOverlay? loginOverlay; + private ResolutionSelector resolution = null!; [Resolved] - private MatchIPCInfo ipc { get; set; } + private MatchIPCInfo ipc { get; set; } = null!; [Resolved] - private StableInfo stableInfo { get; set; } + private StableInfo stableInfo { get; set; } = null!; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; [Resolved] - private RulesetStore rulesets { get; set; } + private RulesetStore rulesets { get; set; } = null!; [Resolved(canBeNull: true)] - private TournamentSceneManager sceneManager { get; set; } + private TournamentSceneManager? sceneManager { get; set; } - private Bindable windowSize; + private Bindable windowSize = null!; [BackgroundDependencyLoader] private void load(FrameworkConfigManager frameworkConfig) @@ -115,7 +113,7 @@ private void reload() Failing = api.IsLoggedIn != true, Description = "In order to access the API and display metadata, signing in is required." }, - new LabelledDropdown + new LabelledDropdown { Label = "Ruleset", Description = "Decides what stats are displayed and which ranks are retrieved for players. This requires a restart to reload data for an existing bracket.", diff --git a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs index 463b012b773d..c3d072b6a08a 100644 --- a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.IO; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -24,19 +22,19 @@ namespace osu.Game.Tournament.Screens.Setup public partial class StablePathSelectScreen : TournamentScreen { [Resolved(canBeNull: true)] - private TournamentSceneManager sceneManager { get; set; } + private TournamentSceneManager? sceneManager { get; set; } [Resolved] - private MatchIPCInfo ipc { get; set; } + private MatchIPCInfo ipc { get; set; } = null!; - private OsuDirectorySelector directorySelector; - private DialogOverlay overlay; + private OsuDirectorySelector directorySelector = null!; + private DialogOverlay? overlay; [BackgroundDependencyLoader(true)] private void load(Storage storage, OsuColour colours) { var initialStorage = (ipc as FileBasedIPC)?.IPCStorage ?? storage; - string initialPath = new DirectoryInfo(initialStorage.GetFullPath(string.Empty)).Parent?.FullName; + string? initialPath = new DirectoryInfo(initialStorage.GetFullPath(string.Empty)).Parent?.FullName; AddRangeInternal(new Drawable[] { diff --git a/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs b/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs index ae49ccb63b14..e55cbc2dbb4d 100644 --- a/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs +++ b/osu.Game.Tournament/Screens/Setup/TournamentSwitcher.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; @@ -13,12 +11,12 @@ namespace osu.Game.Tournament.Screens.Setup { internal partial class TournamentSwitcher : ActionableInfo { - private OsuDropdown dropdown; - private OsuButton folderButton; - private OsuButton reloadTournamentsButton; + private OsuDropdown dropdown = null!; + private OsuButton folderButton = null!; + private OsuButton reloadTournamentsButton = null!; [Resolved] - private TournamentGameBase game { get; set; } + private TournamentGameBase game { get; set; } = null!; [BackgroundDependencyLoader] private void load(TournamentStorage storage) diff --git a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs index db4d6198e6c7..ae2ec0c29106 100644 --- a/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs +++ b/osu.Game.Tournament/Screens/Showcase/ShowcaseScreen.cs @@ -42,7 +42,7 @@ private void load() }); } - protected override void CurrentMatchChanged(ValueChangedEvent match) + protected override void CurrentMatchChanged(ValueChangedEvent match) { // showcase screen doesn't care about a match being selected. // base call intentionally omitted to not show match warning. diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index b07a0a65dd91..120a76c12762 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -22,12 +20,12 @@ namespace osu.Game.Tournament.Screens.TeamIntro { public partial class SeedingScreen : TournamentMatchScreen { - private Container mainContainer; + private Container mainContainer = null!; - private readonly Bindable currentTeam = new Bindable(); + private readonly Bindable currentTeam = new Bindable(); - private TourneyButton showFirstTeamButton; - private TourneyButton showSecondTeamButton; + private TourneyButton showFirstTeamButton = null!; + private TourneyButton showSecondTeamButton = null!; [BackgroundDependencyLoader] private void load() @@ -53,13 +51,13 @@ private void load() { RelativeSizeAxes = Axes.X, Text = "Show first team", - Action = () => currentTeam.Value = CurrentMatch.Value.Team1.Value, + Action = () => currentTeam.Value = CurrentMatch.Value?.Team1.Value, }, showSecondTeamButton = new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Show second team", - Action = () => currentTeam.Value = CurrentMatch.Value.Team2.Value, + Action = () => currentTeam.Value = CurrentMatch.Value?.Team2.Value, }, new SettingsTeamDropdown(LadderInfo.Teams) { @@ -73,7 +71,7 @@ private void load() currentTeam.BindValueChanged(teamChanged, true); } - private void teamChanged(ValueChangedEvent team) => updateTeamDisplay(); + private void teamChanged(ValueChangedEvent team) => updateTeamDisplay(); public override void Show() { @@ -84,7 +82,7 @@ public override void Show() updateTeamDisplay(); } - protected override void CurrentMatchChanged(ValueChangedEvent match) + protected override void CurrentMatchChanged(ValueChangedEvent match) { base.CurrentMatchChanged(match); @@ -256,7 +254,7 @@ private void load(TextureStore textures) private partial class LeftInfo : CompositeDrawable { - public LeftInfo(TournamentTeam team) + public LeftInfo(TournamentTeam? team) { FillFlowContainer fill; @@ -315,7 +313,7 @@ public RowDisplay(string left, string right) private partial class TeamDisplay : DrawableTournamentTeam { - public TeamDisplay(TournamentTeam team) + public TeamDisplay(TournamentTeam? team) : base(team) { AutoSizeAxes = Axes.Both; diff --git a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs index 950a63808c3b..2280f21d4717 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/TeamIntroScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -15,7 +13,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro { public partial class TeamIntroScreen : TournamentMatchScreen { - private Container mainContainer; + private Container mainContainer = null!; [BackgroundDependencyLoader] private void load() @@ -36,7 +34,7 @@ private void load() }; } - protected override void CurrentMatchChanged(ValueChangedEvent match) + protected override void CurrentMatchChanged(ValueChangedEvent match) { base.CurrentMatchChanged(match); diff --git a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs index 9206de1dc2f2..af216135418a 100644 --- a/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs +++ b/osu.Game.Tournament/Screens/TeamWin/TeamWinScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -16,12 +14,12 @@ namespace osu.Game.Tournament.Screens.TeamWin { public partial class TeamWinScreen : TournamentMatchScreen { - private Container mainContainer; + private Container mainContainer = null!; private readonly Bindable currentCompleted = new Bindable(); - private TourneyVideo blueWinVideo; - private TourneyVideo redWinVideo; + private TourneyVideo blueWinVideo = null!; + private TourneyVideo redWinVideo = null!; [BackgroundDependencyLoader] private void load() @@ -51,7 +49,7 @@ private void load() currentCompleted.BindValueChanged(_ => update()); } - protected override void CurrentMatchChanged(ValueChangedEvent match) + protected override void CurrentMatchChanged(ValueChangedEvent match) { base.CurrentMatchChanged(match); @@ -70,7 +68,7 @@ private void update() => Scheduler.AddOnce(() => { var match = CurrentMatch.Value; - if (match.Winner == null) + if (match?.Winner == null) { mainContainer.Clear(); return; diff --git a/osu.Game.Tournament/Screens/TournamentMatchScreen.cs b/osu.Game.Tournament/Screens/TournamentMatchScreen.cs index 58444d0c1b21..5a9b9d05ed9e 100644 --- a/osu.Game.Tournament/Screens/TournamentMatchScreen.cs +++ b/osu.Game.Tournament/Screens/TournamentMatchScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Game.Tournament.Models; @@ -10,8 +8,8 @@ namespace osu.Game.Tournament.Screens { public abstract partial class TournamentMatchScreen : TournamentScreen { - protected readonly Bindable CurrentMatch = new Bindable(); - private WarningBox noMatchWarning; + protected readonly Bindable CurrentMatch = new Bindable(); + private WarningBox? noMatchWarning; protected override void LoadComplete() { @@ -21,7 +19,7 @@ protected override void LoadComplete() CurrentMatch.BindValueChanged(CurrentMatchChanged, true); } - protected virtual void CurrentMatchChanged(ValueChangedEvent match) + protected virtual void CurrentMatchChanged(ValueChangedEvent match) { if (match.NewValue == null) { diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index c79cc9cd575d..ba3b17b513e6 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Drawing; using System.Linq; using osu.Framework.Allocation; @@ -35,12 +33,12 @@ public partial class TournamentGame : TournamentGameBase public static readonly Color4 ELEMENT_FOREGROUND_COLOUR = Color4Extensions.FromHex("#000"); public static readonly Color4 TEXT_COLOUR = Color4Extensions.FromHex("#fff"); - private Drawable heightWarning; + private Drawable heightWarning = null!; - private Bindable windowMode; + private Bindable windowMode = null!; private readonly BindableSize windowSize = new BindableSize(); - private LoadingSpinner loadingSpinner; + private LoadingSpinner loadingSpinner = null!; [Cached(typeof(IDialogOverlay))] private readonly DialogOverlay dialogOverlay = new DialogOverlay(); diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 509c8bb9405c..ee03b4c35d78 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -1,14 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.IO; using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Framework.Input; @@ -31,11 +30,11 @@ namespace osu.Game.Tournament public partial class TournamentGameBase : OsuGameBase { public const string BRACKET_FILENAME = @"bracket.json"; - private LadderInfo ladder; - private TournamentStorage storage; - private DependencyContainer dependencies; - private FileBasedIPC ipc; - private BeatmapLookupCache beatmapCache; + private LadderInfo ladder = null!; + private TournamentStorage storage = null!; + private DependencyContainer dependencies = null!; + private FileBasedIPC ipc = null!; + private BeatmapLookupCache beatmapCache = null!; protected Task BracketLoadTask => bracketLoadTaskCompletionSource.Task; @@ -54,7 +53,7 @@ public override EndpointConfiguration CreateEndpoints() return new ProductionEndpointConfiguration(); } - private TournamentSpriteText initialisationText; + private TournamentSpriteText initialisationText = null!; [BackgroundDependencyLoader] private void load(Storage baseStorage) @@ -78,6 +77,8 @@ private void load(Storage baseStorage) dependencies.CacheAs(new StableInfo(storage)); beatmapCache = dependencies.Get(); + + ladder = new LadderInfo(); } protected override void LoadComplete() @@ -100,11 +101,11 @@ private async Task readBracket() { using (Stream stream = storage.GetStream(BRACKET_FILENAME, FileAccess.Read, FileMode.Open)) using (var sr = new StreamReader(stream)) - ladder = JsonConvert.DeserializeObject(await sr.ReadToEndAsync().ConfigureAwait(false), new JsonPointConverter()); + { + ladder = JsonConvert.DeserializeObject(await sr.ReadToEndAsync().ConfigureAwait(false), new JsonPointConverter()) ?? ladder; + } } - ladder ??= new LadderInfo(); - var resolvedRuleset = ladder.Ruleset.Value != null ? RulesetStore.GetRuleset(ladder.Ruleset.Value.ShortName) : RulesetStore.AvailableRulesets.First(); @@ -283,7 +284,7 @@ private async Task addSeedingBeatmaps() private void updateLoadProgressMessage(string s) => Schedule(() => initialisationText.Text = s); - public void PopulatePlayer(TournamentUser user, Action success = null, Action failure = null, bool immediate = false) + public void PopulatePlayer(TournamentUser user, Action? success = null, Action? failure = null, bool immediate = false) { var req = new GetUserRequest(user.OnlineID, ladder.Ruleset.Value); @@ -348,8 +349,8 @@ public string GetSerialisedLadder() foreach (var r in ladder.Rounds) r.Matches = ladder.Matches.Where(p => p.Round.Value == r).Select(p => p.ID).ToList(); - ladder.Progressions = ladder.Matches.Where(p => p.Progression.Value != null).Select(p => new TournamentProgression(p.ID, p.Progression.Value.ID)).Concat( - ladder.Matches.Where(p => p.LosersProgression.Value != null).Select(p => new TournamentProgression(p.ID, p.LosersProgression.Value.ID, true))) + ladder.Progressions = ladder.Matches.Where(p => p.Progression.Value != null).Select(p => new TournamentProgression(p.ID, p.Progression.Value.AsNonNull().ID)).Concat( + ladder.Matches.Where(p => p.LosersProgression.Value != null).Select(p => new TournamentProgression(p.ID, p.LosersProgression.Value.AsNonNull().ID, true))) .ToList(); return JsonConvert.SerializeObject(ladder, diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 9ac5a9de12b1..c69b76ae29c9 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Allocation; @@ -35,8 +33,8 @@ namespace osu.Game.Tournament [Cached] public partial class TournamentSceneManager : CompositeDrawable { - private Container screens; - private TourneyVideo video; + private Container screens = null!; + private TourneyVideo video = null!; public const int CONTROL_AREA_WIDTH = 200; @@ -50,8 +48,8 @@ public partial class TournamentSceneManager : CompositeDrawable [Cached] private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay(); - private Container chatContainer; - private FillFlowContainer buttons; + private Container chatContainer = null!; + private FillFlowContainer buttons = null!; public TournamentSceneManager() { @@ -166,10 +164,10 @@ private void load() private float depth; - private Drawable currentScreen; - private ScheduledDelegate scheduledHide; + private Drawable? currentScreen; + private ScheduledDelegate? scheduledHide; - private Drawable temporaryScreen; + private Drawable? temporaryScreen; public void SetScreen(Drawable screen) { @@ -284,7 +282,7 @@ public ScreenButton(Type type, Key? shortcutKey = null) Y = -2, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = shortcutKey.ToString(), + Text = shortcutKey.Value.ToString(), } } }); @@ -304,7 +302,7 @@ protected override bool OnKeyDown(KeyDownEvent e) private bool isSelected; - public Action RequestSelection; + public Action? RequestSelection; public bool IsSelected { From 4c33013674a9f2ddd761d966e1c6e08e88de66a6 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sat, 29 Jul 2023 22:41:26 +0900 Subject: [PATCH 037/207] null check in test --- .../Screens/TestSceneMapPoolScreen.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs index 2c17cd0d52f7..254d5c699652 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs @@ -1,11 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Tournament.Components; @@ -16,7 +15,7 @@ namespace osu.Game.Tournament.Tests.Screens { public partial class TestSceneMapPoolScreen : TournamentScreenTestScene { - private MapPoolScreen screen; + private MapPoolScreen screen = null!; [BackgroundDependencyLoader] private void load() @@ -32,7 +31,7 @@ public void TestFewMaps() { AddStep("load few maps", () => { - Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear(); + Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Clear(); for (int i = 0; i < 8; i++) addBeatmap(); @@ -52,7 +51,7 @@ public void TestJustEnoughMaps() { AddStep("load just enough maps", () => { - Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear(); + Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Clear(); for (int i = 0; i < 18; i++) addBeatmap(); @@ -72,7 +71,7 @@ public void TestManyMaps() { AddStep("load many maps", () => { - Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear(); + Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Clear(); for (int i = 0; i < 19; i++) addBeatmap(); @@ -92,7 +91,7 @@ public void TestJustEnoughMods() { AddStep("load many maps", () => { - Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear(); + Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Clear(); for (int i = 0; i < 11; i++) addBeatmap(i > 4 ? Ruleset.Value.CreateInstance().AllMods.ElementAt(i).Acronym : "NM"); @@ -118,7 +117,7 @@ public void TestManyMods() { AddStep("load many maps", () => { - Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear(); + Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Clear(); for (int i = 0; i < 12; i++) addBeatmap(i > 4 ? Ruleset.Value.CreateInstance().AllMods.ElementAt(i).Acronym : "NM"); @@ -138,7 +137,7 @@ public void TestSplitMapPoolByMods() { AddStep("load many maps", () => { - Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Clear(); + Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Clear(); for (int i = 0; i < 12; i++) addBeatmap(i > 4 ? Ruleset.Value.CreateInstance().AllMods.ElementAt(i).Acronym : "NM"); @@ -155,7 +154,7 @@ public void TestSplitMapPoolByMods() private void addBeatmap(string mods = "NM") { - Ladder.CurrentMatch.Value.Round.Value.Beatmaps.Add(new RoundBeatmap + Ladder.CurrentMatch.Value.AsNonNull().Round.Value.AsNonNull().Beatmaps.Add(new RoundBeatmap { Beatmap = CreateSampleBeatmap(), Mods = mods From 43a51671ac83a9f548577037cbeb68827af192a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 29 Jul 2023 16:12:02 +0200 Subject: [PATCH 038/207] Fix wrong test step name --- osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index c8f1f081d9b5..5fe4bb934006 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -104,7 +104,7 @@ public void TestPerformAtMenuFromPlayerLoaderWithAutoplayShortcut() AddAssert("Mods include autoplay", () => Game.SelectedMods.Value.Any(m => m is ModAutoplay)); AddStep("try to perform", () => Game.PerformFromScreen(_ => actionPerformed = true)); - AddUntilStep("returned to song select", () => Game.ScreenStack.CurrentScreen is MainMenu); + AddUntilStep("returned to main menu", () => Game.ScreenStack.CurrentScreen is MainMenu); AddAssert("did perform", () => actionPerformed); AddAssert("Mods don't include autoplay", () => !Game.SelectedMods.Value.Any(m => m is ModAutoplay)); From 033c9091c04377869a13185230fb4e95bf62c665 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 01:39:31 +0900 Subject: [PATCH 039/207] use cast instead AsNonNull --- osu.Game.Tournament/Models/TournamentTeam.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs index 3587aa937ea7..b3b2f213ce80 100644 --- a/osu.Game.Tournament/Models/TournamentTeam.cs +++ b/osu.Game.Tournament/Models/TournamentTeam.cs @@ -38,7 +38,7 @@ public double AverageRank { int[] ranks = Players.Select(p => p.Rank) .Where(i => i.HasValue) - .Select(i => i.AsNonNull().Value) + .Cast() .ToArray(); if (ranks.Length == 0) From 625ed729eed3a76703fe0c1713fa9617baf5e33b Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 01:39:56 +0900 Subject: [PATCH 040/207] debug assert `closest != null` --- .../Screens/Drawings/Components/ScrollingTeamContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs index 4a47de87148b..7f19e8a497ed 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs @@ -136,11 +136,11 @@ private void setScrollState(ScrollState newState) closest = stc; } - Trace.Assert(closest != null, "closest != null"); + Debug.Assert(closest != null, "closest != null"); - offset += DrawWidth / 2f - (closest.AsNonNull().Position.X + closest.AsNonNull().DrawWidth / 2f); + offset += DrawWidth / 2f - (closest.Position.X + closest.DrawWidth / 2f); - ScrollingTeam st = closest.AsNonNull(); + ScrollingTeam st = closest; availableTeams.RemoveAll(at => at == st.Team); From cb4adf115cae1dbef79f1b8af64d0d71c768b537 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 01:40:13 +0900 Subject: [PATCH 041/207] RemoveTeam shouldn't have nullable arg --- .../Screens/Drawings/Components/ScrollingTeamContainer.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs index 7f19e8a497ed..d4e0f2985266 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/ScrollingTeamContainer.cs @@ -188,11 +188,8 @@ public void ClearTeams() setScrollState(ScrollState.Idle); } - public void RemoveTeam(TournamentTeam? team) + public void RemoveTeam(TournamentTeam team) { - if (team == null) - return; - availableTeams.Remove(team); foreach (var c in Children) From 9482f7445614907f001aee2097939d25ad6c0b92 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 01:49:57 +0900 Subject: [PATCH 042/207] fix nullable for TeamList --- osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index b2dd4e8c3675..fc59b486fe79 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -39,7 +39,7 @@ public partial class DrawingsScreen : TournamentScreen private Storage storage = null!; - public ITeamList? TeamList; + public ITeamList TeamList = null!; [BackgroundDependencyLoader] private void load(Storage storage) @@ -48,7 +48,8 @@ private void load(Storage storage) RelativeSizeAxes = Axes.Both; - TeamList ??= new StorageBackedTeamList(storage); + if (TeamList.IsNull()) + TeamList = new StorageBackedTeamList(storage); if (!TeamList.Teams.Any()) { @@ -223,7 +224,7 @@ private void reloadTeams() teamsContainer.ClearTeams(); allTeams.Clear(); - foreach (TournamentTeam t in TeamList.AsNonNull().Teams) + foreach (TournamentTeam t in TeamList.Teams) { if (groupsContainer.ContainsTeam(t.FullName.Value)) continue; From 88a1cf40052a92f70f8055fd1da33b44d575f022 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 01:57:44 +0900 Subject: [PATCH 043/207] remove all `canBeNull` from attribute --- osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs | 2 +- osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs | 2 +- .../Screens/Ladder/Components/DrawableMatchTeam.cs | 4 ++-- .../Screens/Ladder/Components/DrawableTournamentMatch.cs | 4 ++-- osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs | 2 +- osu.Game.Tournament/Screens/Setup/SetupScreen.cs | 2 +- osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs index a82ac57ee917..4074e681f9bf 100644 --- a/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/LadderEditorScreen.cs @@ -36,7 +36,7 @@ public partial class LadderEditorScreen : LadderScreen, IHasContextMenu private RectangularPositionSnapGrid grid = null!; - [Resolved(canBeNull: true)] + [Resolved] private IDialogOverlay? dialogOverlay { get; set; } protected override bool DrawLoserPaths => true; diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index 79c50e60ba09..20188cc5dc44 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -27,7 +27,7 @@ public partial class GameplayScreen : BeatmapInfoScreen private OsuButton warmupButton = null!; private MatchIPCInfo ipc = null!; - [Resolved(canBeNull: true)] + [Resolved] private TournamentSceneManager? sceneManager { get; set; } [Resolved] diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs index a380ad9b4927..637591c6f6f7 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableMatchTeam.cs @@ -38,7 +38,7 @@ public partial class DrawableMatchTeam : DrawableTournamentTeam, IHasContextMenu private readonly Func? isWinner; private LadderEditorScreen ladderEditor = null!; - [Resolved(canBeNull: true)] + [Resolved] private LadderInfo? ladderInfo { get; set; } private void setCurrent() @@ -53,7 +53,7 @@ private void setCurrent() ladderInfo.CurrentMatch.Value.Current.Value = true; } - [Resolved(CanBeNull = true)] + [Resolved] private LadderEditorInfo? editorInfo { get; set; } public DrawableMatchTeam(TournamentTeam? team, TournamentMatch match, bool losers) diff --git a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs index 7b8f2e373b76..4de47d7c7f66 100644 --- a/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs +++ b/osu.Game.Tournament/Screens/Ladder/Components/DrawableTournamentMatch.cs @@ -28,10 +28,10 @@ public partial class DrawableTournamentMatch : CompositeDrawable private readonly Drawable currentMatchSelectionBox; private Bindable? globalSelection; - [Resolved(CanBeNull = true)] + [Resolved] private LadderEditorInfo? editorInfo { get; set; } - [Resolved(CanBeNull = true)] + [Resolved] private LadderInfo? ladderInfo { get; set; } public DrawableTournamentMatch(TournamentMatch match, bool editor = false) diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index 3091f4293c43..cfce2694e900 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -24,7 +24,7 @@ public partial class MapPoolScreen : TournamentMatchScreen { private FillFlowContainer> mapFlows = null!; - [Resolved(canBeNull: true)] + [Resolved] private TournamentSceneManager? sceneManager { get; set; } private TeamColour pickColour; diff --git a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs index 1152759c2a63..df1ce69c33f8 100644 --- a/osu.Game.Tournament/Screens/Setup/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/SetupScreen.cs @@ -39,7 +39,7 @@ public partial class SetupScreen : TournamentScreen [Resolved] private RulesetStore rulesets { get; set; } = null!; - [Resolved(canBeNull: true)] + [Resolved] private TournamentSceneManager? sceneManager { get; set; } private Bindable windowSize = null!; diff --git a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs index c3d072b6a08a..74404e06f8dd 100644 --- a/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/Setup/StablePathSelectScreen.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tournament.Screens.Setup { public partial class StablePathSelectScreen : TournamentScreen { - [Resolved(canBeNull: true)] + [Resolved] private TournamentSceneManager? sceneManager { get; set; } [Resolved] From 65b4ae506ee563b9a2b940c4a72b9c9d34ac5dc2 Mon Sep 17 00:00:00 2001 From: QuantumSno Date: Sat, 29 Jul 2023 13:18:47 -0400 Subject: [PATCH 044/207] Moved enum to bottom of enumeration table --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 3d24afbb16a9..79f098f90ebb 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -286,9 +286,6 @@ public enum GlobalAction [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleInGameInterface))] ToggleInGameInterface, - [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleInGameLeaderboard))] - ToggleInGameLeaderboard, - // Song select keybindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleModSelection))] ToggleModSelection, @@ -382,5 +379,9 @@ public enum GlobalAction [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleReplaySettings))] ToggleReplaySettings, + + // Editor (cont) + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleInGameLeaderboard))] + ToggleInGameLeaderboard, } } From 5fa0a21b564890637beda25061c454573317ae9b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jul 2023 18:14:20 +0900 Subject: [PATCH 045/207] Add corner radius around player areas --- osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index dc4a2df9d803..54f03569de2c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -68,6 +68,7 @@ public PlayerArea(int userId, SpectatorPlayerClock clock) RelativeSizeAxes = Axes.Both; Masking = true; + CornerRadius = 5; AudioContainer audioContainer; InternalChildren = new Drawable[] From 1826819663776a737ab77ad2c1265266067cea2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jul 2023 18:56:29 +0900 Subject: [PATCH 046/207] Move `Facade` to nested class --- .../Multiplayer/Spectate/PlayerGrid.cs | 12 ++++++++++ .../Multiplayer/Spectate/PlayerGrid_Facade.cs | 22 ------------------- 2 files changed, 12 insertions(+), 22 deletions(-) delete mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Facade.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs index 771a8c0de4e0..87072e8d9ef0 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs @@ -169,5 +169,17 @@ protected override void Update() foreach (var cell in facadeContainer) cell.Size = cellSize; } + + /// + /// A facade of the grid which is used as a dummy object to store the required position/size of cells. + /// + public partial class Facade : Drawable + { + public Facade() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Facade.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Facade.cs deleted file mode 100644 index 934c22c9180a..000000000000 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Facade.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics; - -namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate -{ - public partial class PlayerGrid - { - /// - /// A facade of the grid which is used as a dummy object to store the required position/size of cells. - /// - private partial class Facade : Drawable - { - public Facade() - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - } - } - } -} From 84bc14c1ddc1e878a0245fce39aa1252f2c398cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jul 2023 18:56:41 +0900 Subject: [PATCH 047/207] Improve animation and sizing of maximised screen display --- .../Multiplayer/Spectate/PlayerGrid.cs | 38 +++++++++----- .../Multiplayer/Spectate/PlayerGrid_Cell.cs | 52 +++++++++---------- 2 files changed, 51 insertions(+), 39 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs index 87072e8d9ef0..c88feb12bd67 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { @@ -15,20 +16,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate /// public partial class PlayerGrid : CompositeDrawable { + public const float ANIMATION_DELAY = 400; + /// /// A temporary limitation on the number of players, because only layouts up to 16 players are supported for a single screen. /// Todo: Can be removed in the future with scrolling support + performance improvements. /// public const int MAX_PLAYERS = 16; - private const float player_spacing = 5; + private const float player_spacing = 6; /// /// The currently-maximised facade. /// - public Drawable MaximisedFacade => maximisedFacade; + public Facade MaximisedFacade { get; } - private readonly Facade maximisedFacade; private readonly Container paddingContainer; private readonly FillFlowContainer facadeContainer; private readonly Container cellContainer; @@ -48,12 +50,18 @@ public PlayerGrid() RelativeSizeAxes = Axes.Both, Child = facadeContainer = new FillFlowContainer { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Spacing = new Vector2(player_spacing), } }, - maximisedFacade = new Facade { RelativeSizeAxes = Axes.Both } + MaximisedFacade = new Facade + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.8f), + } } }, cellContainer = new Container { RelativeSizeAxes = Axes.Both } @@ -91,26 +99,30 @@ public void Add(Drawable content) private void toggleMaximisationState(Cell target) { + bool anyMaximised = target.IsMaximised = !target.IsMaximised; + // Iterate through all cells to ensure only one is maximised at any time. - foreach (var i in cellContainer.ToList()) + foreach (var cell in cellContainer.ToList()) { - if (i == target) - i.IsMaximised = !i.IsMaximised; - else - i.IsMaximised = false; + if (cell != target) + cell.IsMaximised = false; - if (i.IsMaximised) + if (cell.IsMaximised) { // Transfer cell to the maximised facade. - i.SetFacade(maximisedFacade); - cellContainer.ChangeChildDepth(i, maximisedInstanceDepth -= 0.001f); + cell.SetFacade(MaximisedFacade); + cellContainer.ChangeChildDepth(cell, maximisedInstanceDepth -= 0.001f); } else { // Transfer cell back to its original facade. - i.SetFacade(facadeContainer[i.FacadeIndex]); + cell.SetFacade(facadeContainer[cell.FacadeIndex]); } + + cell.FadeColour(anyMaximised && cell != target ? Color4.Gray : Color4.White, ANIMATION_DELAY, Easing.Out); } + + facadeContainer.ScaleTo(anyMaximised ? 0.95f : 1, ANIMATION_DELAY, Easing.OutQuint); } protected override void Update() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs index 4a8b8f49e1ed..d11e79d8202c 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osuTK; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate @@ -40,7 +41,8 @@ private partial class Cell : CompositeDrawable public bool IsMaximised; private Facade facade; - private bool isTracking = true; + + private bool isAnimating; public Cell(int facadeIndex, Drawable content) { @@ -54,11 +56,23 @@ protected override void Update() { base.Update(); - if (isTracking) - { - Position = getFinalPosition(); - Size = getFinalSize(); - } + var targetPos = getFinalPosition(); + var targetSize = getFinalSize(); + + double duration = isAnimating ? 60 : 0; + + Position = new Vector2( + (float)Interpolation.DampContinuously(Position.X, targetPos.X, duration, Time.Elapsed), + (float)Interpolation.DampContinuously(Position.Y, targetPos.Y, duration, Time.Elapsed) + ); + + Size = new Vector2( + (float)Interpolation.DampContinuously(Size.X, targetSize.X, duration, Time.Elapsed), + (float)Interpolation.DampContinuously(Size.Y, targetSize.Y, duration, Time.Elapsed) + ); + + // If we don't track the animating state, the animation will also occur when resizing the window. + isAnimating &= !Precision.AlmostEquals(Position, targetPos, 0.01f); } /// @@ -66,30 +80,16 @@ protected override void Update() /// public void SetFacade([NotNull] Facade newFacade) { - Facade lastFacade = facade; facade = newFacade; - - if (lastFacade == null || lastFacade == newFacade) - return; - - isTracking = false; - - this.MoveTo(getFinalPosition(), 400, Easing.OutQuint).ResizeTo(getFinalSize(), 400, Easing.OutQuint) - .Then() - .OnComplete(_ => - { - if (facade == newFacade) - isTracking = true; - }); + isAnimating = true; } - private Vector2 getFinalPosition() - { - var topLeft = Parent.ToLocalSpace(facade.ToScreenSpace(Vector2.Zero)); - return topLeft + facade.DrawSize / 2; - } + private Vector2 getFinalPosition() => + Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.Centre); - private Vector2 getFinalSize() => facade.DrawSize; + private Vector2 getFinalSize() => + Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.BottomRight) + - Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.TopLeft); protected override bool OnClick(ClickEvent e) { From 38244c081ff942940a95df3b6a74f27bb3c24368 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jul 2023 19:08:46 +0900 Subject: [PATCH 048/207] Further refactorings along with shadow implementation --- .../Multiplayer/Spectate/PlayerArea.cs | 2 -- .../Multiplayer/Spectate/PlayerGrid.cs | 19 ++++++-------- .../Multiplayer/Spectate/PlayerGrid_Cell.cs | 26 +++++++++++++------ 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 54f03569de2c..1b03452df738 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -67,8 +67,6 @@ public PlayerArea(int userId, SpectatorPlayerClock clock) SpectatorPlayerClock = clock; RelativeSizeAxes = Axes.Both; - Masking = true; - CornerRadius = 5; AudioContainer audioContainer; InternalChildren = new Drawable[] diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs index c88feb12bd67..c162b87727ee 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs @@ -83,8 +83,7 @@ public void Add(Drawable content) var facade = new Facade(); facadeContainer.Add(facade); - var cell = new Cell(index, content) { ToggleMaximisationState = toggleMaximisationState }; - cell.SetFacade(facade); + var cell = new Cell(index, content, facade) { ToggleMaximisationState = toggleMaximisationState }; cellContainer.Add(cell); } @@ -99,30 +98,28 @@ public void Add(Drawable content) private void toggleMaximisationState(Cell target) { - bool anyMaximised = target.IsMaximised = !target.IsMaximised; + // in the case the target is the already maximised cell, no cell should be maximised. + bool hasMaximised = !target.IsMaximised; // Iterate through all cells to ensure only one is maximised at any time. foreach (var cell in cellContainer.ToList()) { - if (cell != target) - cell.IsMaximised = false; - - if (cell.IsMaximised) + if (hasMaximised && cell == target) { // Transfer cell to the maximised facade. - cell.SetFacade(MaximisedFacade); + cell.SetFacade(MaximisedFacade, true); cellContainer.ChangeChildDepth(cell, maximisedInstanceDepth -= 0.001f); } else { // Transfer cell back to its original facade. - cell.SetFacade(facadeContainer[cell.FacadeIndex]); + cell.SetFacade(facadeContainer[cell.FacadeIndex], false); } - cell.FadeColour(anyMaximised && cell != target ? Color4.Gray : Color4.White, ANIMATION_DELAY, Easing.Out); + cell.FadeColour(hasMaximised && cell != target ? Color4.Gray : Color4.White, ANIMATION_DELAY, Easing.OutQuint); } - facadeContainer.ScaleTo(anyMaximised ? 0.95f : 1, ANIMATION_DELAY, Easing.OutQuint); + facadeContainer.ScaleTo(hasMaximised ? 0.95f : 1, ANIMATION_DELAY, Easing.OutQuint); } protected override void Update() diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs index d11e79d8202c..4e624da17f80 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid_Cell.cs @@ -1,12 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; -using JetBrains.Annotations; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Input.Events; using osu.Framework.Utils; using osuTK; @@ -33,23 +31,27 @@ private partial class Cell : CompositeDrawable /// /// An action that toggles the maximisation state of this cell. /// - public Action ToggleMaximisationState; + public Action? ToggleMaximisationState; /// /// Whether this cell is currently maximised. /// - public bool IsMaximised; + public bool IsMaximised { get; private set; } private Facade facade; private bool isAnimating; - public Cell(int facadeIndex, Drawable content) + public Cell(int facadeIndex, Drawable content, Facade facade) { FacadeIndex = facadeIndex; + this.facade = facade; Origin = Anchor.Centre; InternalChild = Content = content; + + Masking = true; + CornerRadius = 5; } protected override void Update() @@ -78,10 +80,18 @@ protected override void Update() /// /// Makes this cell track a new facade. /// - public void SetFacade([NotNull] Facade newFacade) + public void SetFacade(Facade newFacade, bool isMaximised) { facade = newFacade; + IsMaximised = isMaximised; isAnimating = true; + + TweenEdgeEffectTo(new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = isMaximised ? 30 : 10, + Colour = Colour4.Black.Opacity(isMaximised ? 0.5f : 0.2f), + }, ANIMATION_DELAY, Easing.OutQuint); } private Vector2 getFinalPosition() => @@ -93,7 +103,7 @@ private Vector2 getFinalSize() => protected override bool OnClick(ClickEvent e) { - ToggleMaximisationState(this); + ToggleMaximisationState?.Invoke(this); return true; } } From 75625f089e1f8f9957bf6a7fb52bc48f552f38d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 26 Jul 2023 19:22:21 +0900 Subject: [PATCH 049/207] Hide toolbar when entering multiplayer spectator --- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 2d2aa0f1d555..58eed7e85c18 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -31,6 +31,8 @@ public partial class MultiSpectatorScreen : SpectatorScreen // We are managing our own adjustments. For now, this happens inside the Player instances themselves. public override bool? AllowTrackAdjustments => false; + public override bool HideOverlaysOnEnter => true; + /// /// Whether all spectating players have finished loading. /// From 9d928c0225d3b86257f1cf8cd71a606c46038892 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 29 Jul 2023 10:39:50 -0700 Subject: [PATCH 050/207] Apply NRT to `BreakInfoLine` --- osu.Game/Screens/Play/Break/BreakInfoLine.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/Break/BreakInfoLine.cs b/osu.Game/Screens/Play/Break/BreakInfoLine.cs index b8696352e883..c6a0ca0ef68b 100644 --- a/osu.Game/Screens/Play/Break/BreakInfoLine.cs +++ b/osu.Game/Screens/Play/Break/BreakInfoLine.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -71,7 +69,7 @@ protected virtual LocalisableString Format(T count) if (count is Enum countEnum) return countEnum.GetDescription(); - return count.ToString(); + return count.ToString() ?? string.Empty; } [BackgroundDependencyLoader] From 740898dffb910d5d6e8306c746e9578b383b47d0 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 29 Jul 2023 10:40:18 -0700 Subject: [PATCH 051/207] Remove unnecessary prefix parameter --- osu.Game/Screens/Play/Break/BreakInfoLine.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/Break/BreakInfoLine.cs b/osu.Game/Screens/Play/Break/BreakInfoLine.cs index c6a0ca0ef68b..cfdf6cc65137 100644 --- a/osu.Game/Screens/Play/Break/BreakInfoLine.cs +++ b/osu.Game/Screens/Play/Break/BreakInfoLine.cs @@ -24,12 +24,8 @@ public partial class BreakInfoLine : Container private readonly OsuSpriteText text; private readonly OsuSpriteText valueText; - private readonly string prefix; - - public BreakInfoLine(LocalisableString name, string prefix = @"") + public BreakInfoLine(LocalisableString name) { - this.prefix = prefix; - AutoSizeAxes = Axes.Y; Children = new Drawable[] { @@ -45,7 +41,7 @@ public BreakInfoLine(LocalisableString name, string prefix = @"") { Anchor = Anchor.Centre, Origin = Anchor.CentreLeft, - Text = prefix + @"-", + Text = @"-", Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17), Margin = new MarginPadding { Left = margin } } @@ -56,7 +52,7 @@ public BreakInfoLine(LocalisableString name, string prefix = @"") private void currentValueChanged(ValueChangedEvent e) { - LocalisableString newText = LocalisableString.Interpolate($"{prefix}{Format(e.NewValue)}"); + LocalisableString newText = Format(e.NewValue); if (valueText.Text == newText) return; @@ -82,8 +78,8 @@ private void load(OsuColour colours) public partial class PercentageBreakInfoLine : BreakInfoLine { - public PercentageBreakInfoLine(LocalisableString name, string prefix = "") - : base(name, prefix) + public PercentageBreakInfoLine(LocalisableString name) + : base(name) { } From 6ad8339c6674300806378693d79cc9f12285e71a Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 02:30:11 +0900 Subject: [PATCH 052/207] use no null when true --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 333dd0fd73f2..daf1417f6f85 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics.CodeAnalysis; using System.IO; using System.Linq; using Microsoft.Win32; @@ -180,7 +181,7 @@ public bool SetIPCLocation(string? path) /// Whether an IPC directory was successfully auto-detected. public bool AutoDetectIPCLocation() => SetIPCLocation(findStablePath()); - private static bool ipcFileExistsInDirectory(string? p) => p != null && File.Exists(Path.Combine(p, "ipc.txt")); + private static bool ipcFileExistsInDirectory([NotNullWhen(true)] string? p) => p != null && File.Exists(Path.Combine(p, "ipc.txt")); private string? findStablePath() { @@ -201,7 +202,7 @@ public bool SetIPCLocation(string? path) string? stableInstallPath = Environment.GetEnvironmentVariable("OSU_STABLE_PATH"); if (ipcFileExistsInDirectory(stableInstallPath)) - return stableInstallPath!; + return stableInstallPath; } catch { From a1f0e5978448040c646e0252ffc7fdf4077beb17 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 02:30:51 +0900 Subject: [PATCH 053/207] remove null check for b.Beatmap --- osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index cfce2694e900..9caa87d75e71 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -252,9 +252,6 @@ private void updateDisplay() foreach (var b in CurrentMatch.Value.Round.Value.Beatmaps) { - if (b.Beatmap == null) - continue; - if (currentFlow == null || (LadderInfo.SplitMapPoolByMods.Value && currentMods != b.Mods)) { mapFlows.Add(currentFlow = new FillFlowContainer From bc2ca11bb0497b81c0a891a34aac645089828e02 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 02:31:05 +0900 Subject: [PATCH 054/207] move to initialiser --- osu.Game.Tournament/TournamentGameBase.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index ee03b4c35d78..eecd097a9733 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tournament public partial class TournamentGameBase : OsuGameBase { public const string BRACKET_FILENAME = @"bracket.json"; - private LadderInfo ladder = null!; + private LadderInfo ladder = new LadderInfo(); private TournamentStorage storage = null!; private DependencyContainer dependencies = null!; private FileBasedIPC ipc = null!; @@ -77,8 +77,6 @@ private void load(Storage baseStorage) dependencies.CacheAs(new StableInfo(storage)); beatmapCache = dependencies.Get(); - - ladder = new LadderInfo(); } protected override void LoadComplete() From 059012130924902e36d3e8311d23e4381b7d07eb Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 02:38:19 +0900 Subject: [PATCH 055/207] string mod should not null Already assigned in the constructor --- osu.Game.Tournament/Components/TournamentBeatmapPanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 2d5844d02bec..0f916d931dd4 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -22,7 +22,7 @@ public partial class TournamentBeatmapPanel : CompositeDrawable { public readonly TournamentBeatmap? Beatmap; - private readonly string? mod; + private readonly string mod; public const float HEIGHT = 50; From ba80d1e2d5e766a5791b047fcad736adb4f54340 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 02:49:13 +0900 Subject: [PATCH 056/207] remove nullable for Box --- osu.Game.Tournament/Components/TournamentBeatmapPanel.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 0f916d931dd4..ba922c7c7b71 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -28,7 +28,7 @@ public partial class TournamentBeatmapPanel : CompositeDrawable private readonly Bindable currentMatch = new Bindable(); - private Box? flash; + private Box flash = null!; public TournamentBeatmapPanel(TournamentBeatmap? beatmap, string mod = "") { @@ -135,11 +135,12 @@ private void matchChanged(ValueChangedEvent match) match.OldValue.PicksBans.CollectionChanged -= picksBansOnCollectionChanged; if (match.NewValue != null) match.NewValue.PicksBans.CollectionChanged += picksBansOnCollectionChanged; - updateState(); + + Scheduler.AddOnce(updateState); } private void picksBansOnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) - => updateState(); + => Scheduler.AddOnce(updateState); private BeatmapChoice? choice; @@ -157,7 +158,7 @@ private void updateState() if (newChoice != null) { if (shouldFlash) - flash?.FadeOutFromOne(500).Loop(0, 10); + flash.FadeOutFromOne(500).Loop(0, 10); BorderThickness = 6; From 5d09eca1041e10119738707c608340a3d8ddf861 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 02:49:30 +0900 Subject: [PATCH 057/207] revert test change --- osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs index d6941848b714..b76e0d752158 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs @@ -21,7 +21,7 @@ private void load() { Team1 = { Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "USA") }, Team2 = { Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "JPN") }, - Round = { Value = Ladder.Rounds.First(g => g.Name.Value == "Quarterfinals") } + Round = { Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals") } }; Add(new TeamIntroScreen From 99dd156d5322ab17a542b855796323fadd981e36 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 02:50:16 +0900 Subject: [PATCH 058/207] remove useless using --- osu.Game.Tournament/Models/TournamentTeam.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tournament/Models/TournamentTeam.cs b/osu.Game.Tournament/Models/TournamentTeam.cs index b3b2f213ce80..3cd527aaaa7c 100644 --- a/osu.Game.Tournament/Models/TournamentTeam.cs +++ b/osu.Game.Tournament/Models/TournamentTeam.cs @@ -5,7 +5,6 @@ using System.Linq; using Newtonsoft.Json; using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; namespace osu.Game.Tournament.Models { From 404a927caf55f05be24d1eadf0f3bda06227d92e Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Sun, 30 Jul 2023 02:56:52 +0900 Subject: [PATCH 059/207] fix Possible NullReferenceException in test --- .../Components/TestSceneTournamentModDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs index cea4306ff870..0cb95c2e43b2 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs @@ -45,7 +45,7 @@ private void load() private void success(APIBeatmap beatmap) { - var ruleset = rulesets.GetRuleset(Ladder.Ruleset.Value.OnlineID); + var ruleset = rulesets.GetRuleset(Ladder.Ruleset.Value?.OnlineID ?? -1); if (ruleset == null) return; From 6ebfafa9c3b1f24cc89ec72a577e70a799b052d1 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 29 Jul 2023 11:02:04 -0700 Subject: [PATCH 060/207] Remove unnecessary text comparison --- osu.Game/Screens/Play/Break/BreakInfoLine.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/Break/BreakInfoLine.cs b/osu.Game/Screens/Play/Break/BreakInfoLine.cs index cfdf6cc65137..3357963fac00 100644 --- a/osu.Game/Screens/Play/Break/BreakInfoLine.cs +++ b/osu.Game/Screens/Play/Break/BreakInfoLine.cs @@ -47,17 +47,7 @@ public BreakInfoLine(LocalisableString name) } }; - Current.ValueChanged += currentValueChanged; - } - - private void currentValueChanged(ValueChangedEvent e) - { - LocalisableString newText = Format(e.NewValue); - - if (valueText.Text == newText) - return; - - valueText.Text = newText; + Current.ValueChanged += text => valueText.Text = Format(text.NewValue); } protected virtual LocalisableString Format(T count) From 4ddf05602f1f117f8ab0248c937a2487e76f59a3 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 29 Jul 2023 11:05:15 -0700 Subject: [PATCH 061/207] Update `BreakInfoLine` to formatting standards --- osu.Game/Screens/Play/Break/BreakInfoLine.cs | 34 ++++++++++++-------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Play/Break/BreakInfoLine.cs b/osu.Game/Screens/Play/Break/BreakInfoLine.cs index 3357963fac00..147650ca07a2 100644 --- a/osu.Game/Screens/Play/Break/BreakInfoLine.cs +++ b/osu.Game/Screens/Play/Break/BreakInfoLine.cs @@ -21,21 +21,30 @@ public partial class BreakInfoLine : Container public Bindable Current = new Bindable(); - private readonly OsuSpriteText text; - private readonly OsuSpriteText valueText; + private readonly LocalisableString name; + + private OsuSpriteText valueText = null!; public BreakInfoLine(LocalisableString name) { + this.name = name; + AutoSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { Children = new Drawable[] { - text = new OsuSpriteText + new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.CentreRight, Text = name, Font = OsuFont.GetFont(size: 17), - Margin = new MarginPadding { Right = margin } + Margin = new MarginPadding { Right = margin }, + Colour = colours.Yellow, }, valueText = new OsuSpriteText { @@ -43,11 +52,17 @@ public BreakInfoLine(LocalisableString name) Origin = Anchor.CentreLeft, Text = @"-", Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 17), - Margin = new MarginPadding { Left = margin } + Margin = new MarginPadding { Left = margin }, + Colour = colours.YellowLight, } }; + } - Current.ValueChanged += text => valueText.Text = Format(text.NewValue); + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(text => valueText.Text = Format(text.NewValue), true); } protected virtual LocalisableString Format(T count) @@ -57,13 +72,6 @@ protected virtual LocalisableString Format(T count) return count.ToString() ?? string.Empty; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - text.Colour = colours.Yellow; - valueText.Colour = colours.YellowLight; - } } public partial class PercentageBreakInfoLine : BreakInfoLine From 945d89e955cb350b55b383a5cd5f90dda72ad10c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jul 2023 13:45:42 +0900 Subject: [PATCH 062/207] Move disables to loading screens for better coverage of edge cases --- osu.Game/Screens/Edit/Editor.cs | 2 -- osu.Game/Screens/Edit/EditorLoader.cs | 2 ++ osu.Game/Screens/Play/Player.cs | 2 -- osu.Game/Screens/Play/PlayerLoader.cs | 2 ++ 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index d11297b3b28d..b885eee46f7e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -68,8 +68,6 @@ public partial class Editor : ScreenWithBeatmapBackground, IKeyBindingHandler false; - public override bool? AllowGlobalTrackControl => false; - protected override bool PlayExitSound => !ExitConfirmed && !switchingDifficulty; protected bool HasUnsavedChanges diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index f665b7c51180..8bcfa7b9f0f5 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -42,6 +42,8 @@ public partial class EditorLoader : ScreenWithBeatmapBackground public override bool DisallowExternalBeatmapRulesetChanges => true; + public override bool? AllowGlobalTrackControl => false; + [Resolved] private BeatmapManager beatmapManager { get; set; } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 956c4d4856c5..e2e8b71c108b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -71,8 +71,6 @@ public abstract partial class Player : ScreenWithBeatmapBackground, ISamplePlayb // We are managing our own adjustments (see OnEntering/OnExiting). public override bool? ApplyModTrackAdjustments => false; - public override bool? AllowGlobalTrackControl => false; - private readonly IBindable gameActive = new Bindable(true); private readonly Bindable samplePlaybackDisabled = new Bindable(); diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 4b15bac0f343..872425e3fde7 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -46,6 +46,8 @@ public partial class PlayerLoader : ScreenWithBeatmapBackground public override bool DisallowExternalBeatmapRulesetChanges => true; + public override bool? AllowGlobalTrackControl => false; + // Here because IsHovered will not update unless we do so. public override bool HandlePositionalInput => true; From 713829163689bea7bd053a62d5edcbc30e12097e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jul 2023 13:52:58 +0900 Subject: [PATCH 063/207] Adjust xmldoc to explicitly metnion it only affects end user control --- osu.Game/Overlays/MusicController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 665c61edf052..0986c0513ca8 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -41,7 +41,7 @@ public partial class MusicController : CompositeDrawable public bool UserPauseRequested { get; private set; } /// - /// Whether control of the global track should be allowed. + /// Whether user control of the global track should be allowed. /// public readonly BindableBool AllowTrackControl = new BindableBool(true); From feea412bec6d4d015a2a4fdcc9fd373ddee817ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jul 2023 14:40:00 +0900 Subject: [PATCH 064/207] Add test with only one player --- .../TestSceneMultiSpectatorScreen.cs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index e09496b6e908..e81dc87d4fc2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -65,6 +65,19 @@ public override void SetUpSteps() AddStep("clear playing users", () => playingUsers.Clear()); } + [TestCase(1)] + [TestCase(4)] + public void TestGeneral(int count) + { + int[] userIds = getPlayerIds(count); + + start(userIds); + loadSpectateScreen(); + + sendFrames(userIds, 1000); + AddWaitStep("wait a bit", 20); + } + [Test] public void TestDelayedStart() { @@ -88,18 +101,6 @@ public void TestDelayedStart() AddUntilStep("two players added", () => spectatorScreen.ChildrenOfType().Count() == 2); } - [Test] - public void TestGeneral() - { - int[] userIds = getPlayerIds(4); - - start(userIds); - loadSpectateScreen(); - - sendFrames(userIds, 1000); - AddWaitStep("wait a bit", 20); - } - [Test] public void TestSpectatorPlayerInteractiveElementsHidden() { From 45ceaba00d83027f679a3130bd0c269ebb24f790 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jul 2023 14:40:58 +0900 Subject: [PATCH 065/207] Disable multiplayer spectator zoom when there's only one player's screen visible --- .../Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs index c162b87727ee..6e71c010e5bc 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerGrid.cs @@ -98,8 +98,8 @@ public void Add(Drawable content) private void toggleMaximisationState(Cell target) { - // in the case the target is the already maximised cell, no cell should be maximised. - bool hasMaximised = !target.IsMaximised; + // in the case the target is the already maximised cell (or there is only one cell), no cell should be maximised. + bool hasMaximised = !target.IsMaximised && cellContainer.Count > 1; // Iterate through all cells to ensure only one is maximised at any time. foreach (var cell in cellContainer.ToList()) From 4f83c8661a67685f4897e99679d987f416104437 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jul 2023 16:12:55 +0900 Subject: [PATCH 066/207] Remove unnecessary async fetch of beatmap in `NowPlayingOverlay` No idea if this was historically required for some reason, but it's definitely not required now. --- osu.Game/Overlays/NowPlayingOverlay.cs | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 15eefb2d9f41..f6fad0ef6c79 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -293,21 +292,10 @@ private void trackChanged(WorkingBeatmap beatmap, TrackChangeDirection direction // avoid using scheduler as our scheduler may not be run for a long time, holding references to beatmaps. pendingBeatmapSwitch = delegate { - // todo: this can likely be replaced with WorkingBeatmap.GetBeatmapAsync() - Task.Run(() => - { - if (beatmap?.Beatmap == null) // this is not needed if a placeholder exists - { - title.Text = @"Nothing to play"; - artist.Text = @"Nothing to play"; - } - else - { - BeatmapMetadata metadata = beatmap.Metadata; - title.Text = new RomanisableString(metadata.TitleUnicode, metadata.Title); - artist.Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); - } - }); + BeatmapMetadata metadata = beatmap.Metadata; + + title.Text = new RomanisableString(metadata.TitleUnicode, metadata.Title); + artist.Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); LoadComponentAsync(new Background(beatmap) { Depth = float.MaxValue }, newBackground => { From 07d224ecb6bfab5f72bb8f79210a8f43bd9a39e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jul 2023 16:17:04 +0900 Subject: [PATCH 067/207] Apply NRT to `NowPlayingOverlay` --- osu.Game/Overlays/NowPlayingOverlay.cs | 39 +++++++++++++------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index f6fad0ef6c79..37cb85983cf2 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; @@ -39,33 +38,33 @@ public partial class NowPlayingOverlay : OsuFocusedOverlayContainer, INamedOverl private const float bottom_black_area_height = 55; private const float margin = 10; - private Drawable background; - private ProgressBar progressBar; + private Drawable background = null!; + private ProgressBar progressBar = null!; - private IconButton prevButton; - private IconButton playButton; - private IconButton nextButton; - private IconButton playlistButton; + private IconButton prevButton = null!; + private IconButton playButton = null!; + private IconButton nextButton = null!; + private IconButton playlistButton = null!; - private SpriteText title, artist; + private SpriteText title = null!, artist = null!; - private PlaylistOverlay playlist; + private PlaylistOverlay? playlist; - private Container dragContainer; - private Container playerContainer; - private Container playlistContainer; + private Container dragContainer = null!; + private Container playerContainer = null!; + private Container playlistContainer = null!; protected override string PopInSampleName => "UI/now-playing-pop-in"; protected override string PopOutSampleName => "UI/now-playing-pop-out"; [Resolved] - private MusicController musicController { get; set; } + private MusicController musicController { get; set; } = null!; [Resolved] - private Bindable beatmap { get; set; } + private Bindable beatmap { get; set; } = null!; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; public NowPlayingOverlay() { @@ -285,7 +284,7 @@ protected override void Update() } } - private Action pendingBeatmapSwitch; + private Action? pendingBeatmapSwitch; private void trackChanged(WorkingBeatmap beatmap, TrackChangeDirection direction = TrackChangeDirection.None) { @@ -338,7 +337,7 @@ protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - if (musicController != null) + if (musicController.IsNotNull()) musicController.TrackChanged -= trackChanged; } @@ -371,7 +370,7 @@ private partial class Background : BufferedContainer private readonly Sprite sprite; private readonly WorkingBeatmap beatmap; - public Background(WorkingBeatmap beatmap = null) + public Background(WorkingBeatmap beatmap) : base(cachedFrameBuffer: true) { this.beatmap = beatmap; @@ -401,7 +400,7 @@ public Background(WorkingBeatmap beatmap = null) [BackgroundDependencyLoader] private void load(LargeTextureStore textures) { - sprite.Texture = beatmap?.GetBackground() ?? textures.Get(@"Backgrounds/bg4"); + sprite.Texture = beatmap.GetBackground() ?? textures.Get(@"Backgrounds/bg4"); } } From de61b74e910d2342a6eeafac5a804796fe8418aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jul 2023 16:21:35 +0900 Subject: [PATCH 068/207] Add proper cancellation and out-of-order blocking logic to `NowPlayingOverlay`'s background carousel --- osu.Game/Overlays/NowPlayingOverlay.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 37cb85983cf2..89442e29bcf9 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -286,8 +287,14 @@ protected override void Update() private Action? pendingBeatmapSwitch; + private CancellationTokenSource? backgroundLoadCancellation; + + private WorkingBeatmap? currentBeatmap; + private void trackChanged(WorkingBeatmap beatmap, TrackChangeDirection direction = TrackChangeDirection.None) { + currentBeatmap = beatmap; + // avoid using scheduler as our scheduler may not be run for a long time, holding references to beatmaps. pendingBeatmapSwitch = delegate { @@ -296,8 +303,16 @@ private void trackChanged(WorkingBeatmap beatmap, TrackChangeDirection direction title.Text = new RomanisableString(metadata.TitleUnicode, metadata.Title); artist.Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist); + backgroundLoadCancellation?.Cancel(); + LoadComponentAsync(new Background(beatmap) { Depth = float.MaxValue }, newBackground => { + if (beatmap != currentBeatmap) + { + newBackground.Dispose(); + return; + } + switch (direction) { case TrackChangeDirection.Next: @@ -317,7 +332,7 @@ private void trackChanged(WorkingBeatmap beatmap, TrackChangeDirection direction background = newBackground; playerContainer.Add(newBackground); - }); + }, (backgroundLoadCancellation = new CancellationTokenSource()).Token); }; } From 35ec55c1f6df3d9ded611c6cd8afb3f696c101ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jul 2023 16:41:33 +0900 Subject: [PATCH 069/207] Don't queue export replay operations if button is disabled --- osu.Game/Screens/Ranking/ReplayDownloadButton.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index a5b33e584db2..aa734d20772a 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -117,11 +117,17 @@ public bool OnPressed(KeyBindingPressEvent e) return true; case GlobalAction.ExportReplay: - State.BindValueChanged(exportWhenReady, true); - - // start the import via button - if (State.Value != DownloadState.LocallyAvailable) + if (State.Value == DownloadState.LocallyAvailable) + { + State.BindValueChanged(exportWhenReady, true); + } + else + { + // A download needs to be performed before we can export this replay. button.TriggerClick(); + if (button.Enabled.Value) + State.BindValueChanged(exportWhenReady, true); + } return true; } From 6d5b3617b3cf8af702573d0443c7f0da276834c6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 30 Jul 2023 16:41:45 +0900 Subject: [PATCH 070/207] Remove pending export operation if active score is changed --- osu.Game/Screens/Ranking/ReplayDownloadButton.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index aa734d20772a..b6166e97f637 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -83,6 +83,10 @@ private void load(OsuGame? game, ScoreModelDownloader scoreDownloader) Score.BindValueChanged(score => { + // An export may be pending from the last score. + // Reset this to meet user expectations (a new score which has just been switched to shouldn't export) + State.ValueChanged -= exportWhenReady; + downloadTracker?.RemoveAndDisposeImmediately(); if (score.NewValue != null) From 1981e49a40723f16476fae208a28c693fca03c01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 30 Jul 2023 14:28:16 +0200 Subject: [PATCH 071/207] Fix nullability inspection --- osu.Game/Overlays/NowPlayingOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 4132db42bc6f..6abde713b653 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -67,7 +67,7 @@ public partial class NowPlayingOverlay : OsuFocusedOverlayContainer, INamedOverl [Resolved] private OsuColour colours { get; set; } = null!; - private Bindable allowTrackControl; + private Bindable allowTrackControl = null!; public NowPlayingOverlay() { From d3435483eba7d8cd2dc113e1deeeb5ce2a13779b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 30 Jul 2023 15:50:10 +0200 Subject: [PATCH 072/207] Fix multiplayer match screen being exited from when not current This was supposed to be fixed by #24255, but has popped up as a regression on Sentry since: https://sentry.ppy.sh/organizations/ppy/issues/22749/?project=2&referrer=regression_activity-email On a fifteen-minute check I cannot figure out how to reproduce, so rather than spending further brain cycles on this, just apply the same explicit guard that like fifteen other places do. --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index f5746ca96ca3..f13b47c0f583 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -265,7 +265,8 @@ public override bool OnExiting(ScreenExitEvent e) dialogOverlay.Push(new ConfirmDialog("Are you sure you want to leave this multiplayer match?", () => { exitConfirmed = true; - this.Exit(); + if (this.IsCurrentScreen()) + this.Exit(); })); } From 7763f3dd404afa4a1dea1039804e36ca4c469213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 30 Jul 2023 19:05:08 +0200 Subject: [PATCH 073/207] Fix osu! logo suddenly disappearing during rapid exit --- osu.Game/Screens/Menu/MainMenu.cs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 510c9a5373cb..22040b4f0baa 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -202,6 +203,9 @@ public override void OnEntering(ScreenTransitionEvent e) dialogOverlay?.Push(new StorageErrorDialog(osuStorage, osuStorage.Error)); } + [CanBeNull] + private Drawable proxiedLogo; + protected override void LogoArriving(OsuLogo logo, bool resuming) { base.LogoArriving(logo, resuming); @@ -211,7 +215,7 @@ protected override void LogoArriving(OsuLogo logo, bool resuming) logo.FadeColour(Color4.White, 100, Easing.OutQuint); logo.FadeIn(100, Easing.OutQuint); - logo.ProxyToContainer(logoTarget); + proxiedLogo = logo.ProxyToContainer(logoTarget); if (resuming) { @@ -250,12 +254,27 @@ protected override void LogoSuspending(OsuLogo logo) var seq = logo.FadeOut(300, Easing.InSine) .ScaleTo(0.2f, 300, Easing.InSine); - logo.ReturnProxy(); + if (proxiedLogo != null) + { + logo.ReturnProxy(); + proxiedLogo = null; + } seq.OnComplete(_ => Buttons.SetOsuLogo(null)); seq.OnAbort(_ => Buttons.SetOsuLogo(null)); } + protected override void LogoExiting(OsuLogo logo) + { + base.LogoExiting(logo); + + if (proxiedLogo != null) + { + logo.ReturnProxy(); + proxiedLogo = null; + } + } + public override void OnSuspending(ScreenTransitionEvent e) { base.OnSuspending(e); From 262f25dce826574cbfd466fbecf52e151dabe642 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 30 Jul 2023 19:39:30 +0200 Subject: [PATCH 074/207] Make `SelectionRotationHandler` a `Component` --- .../Edit/OsuSelectionHandler.cs | 5 +--- .../Edit/OsuSelectionRotationHandler.cs | 23 ++++++++++----- .../Editing/TestSceneComposeSelectBox.cs | 2 +- .../SkinEditor/SkinSelectionHandler.cs | 3 +- .../SkinSelectionRotationHandler.cs | 28 +++++++++++++------ .../Compose/Components/SelectionHandler.cs | 8 ++++-- .../Components/SelectionRotationHandler.cs | 3 +- 7 files changed, 45 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 1dfbf4179b29..e81941d25479 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -163,10 +163,7 @@ private static void adjustScaleFromAnchor(ref Vector2 scale, Anchor reference) if ((reference & Anchor.y0) > 0) scale.Y = -scale.Y; } - public override SelectionRotationHandler CreateRotationHandler() => new OsuSelectionRotationHandler(ChangeHandler) - { - SelectedItems = { BindTarget = SelectedItems } - }; + public override SelectionRotationHandler CreateRotationHandler() => new OsuSelectionRotationHandler(); private void scaleSlider(Slider slider, Vector2 scale) { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs index 0eb7637786da..21fb8a67de55 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionRotationHandler.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -16,17 +17,25 @@ namespace osu.Game.Rulesets.Osu.Edit { - public class OsuSelectionRotationHandler : SelectionRotationHandler + public partial class OsuSelectionRotationHandler : SelectionRotationHandler { - private readonly IEditorChangeHandler? changeHandler; + [Resolved] + private IEditorChangeHandler? changeHandler { get; set; } - public BindableList SelectedItems { get; } = new BindableList(); + private BindableList selectedItems { get; } = new BindableList(); - public OsuSelectionRotationHandler(IEditorChangeHandler? changeHandler) + [BackgroundDependencyLoader] + private void load(EditorBeatmap editorBeatmap) { - this.changeHandler = changeHandler; + selectedItems.BindTo(editorBeatmap.SelectedHitObjects); + } + + protected override void LoadComplete() + { + base.LoadComplete(); - SelectedItems.CollectionChanged += (_, __) => updateState(); + selectedItems.CollectionChanged += (_, __) => updateState(); + updateState(); } private void updateState() @@ -92,7 +101,7 @@ public override void Commit() defaultOrigin = null; } - private IEnumerable selectedMovableObjects => SelectedItems.Cast() + private IEnumerable selectedMovableObjects => selectedItems.Cast() .Where(h => h is not Spinner); } } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 147488812e44..9901118ce877 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -72,7 +72,7 @@ private bool handleScale(Vector2 amount, Anchor reference) return true; } - private class TestSelectionRotationHandler : SelectionRotationHandler + private partial class TestSelectionRotationHandler : SelectionRotationHandler { private readonly Func getTargetContainer; diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs index bee973bea024..72216f040e2a 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionHandler.cs @@ -26,9 +26,8 @@ public partial class SkinSelectionHandler : SelectionHandler new SkinSelectionRotationHandler(ChangeHandler) + public override SelectionRotationHandler CreateRotationHandler() => new SkinSelectionRotationHandler { - SelectedItems = { BindTarget = SelectedItems }, UpdatePosition = updateDrawablePosition }; diff --git a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs index e60e2b1e1221..60f69000a28c 100644 --- a/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs +++ b/osu.Game/Overlays/SkinEditor/SkinSelectionRotationHandler.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Screens.Edit; @@ -15,23 +16,32 @@ namespace osu.Game.Overlays.SkinEditor { - public class SkinSelectionRotationHandler : SelectionRotationHandler + public partial class SkinSelectionRotationHandler : SelectionRotationHandler { - private readonly IEditorChangeHandler? changeHandler; - - public BindableList SelectedItems { get; } = new BindableList(); public Action UpdatePosition { get; init; } = null!; - public SkinSelectionRotationHandler(IEditorChangeHandler? changeHandler) + [Resolved] + private IEditorChangeHandler? changeHandler { get; set; } + + private BindableList selectedItems { get; } = new BindableList(); + + [BackgroundDependencyLoader] + private void load(SkinEditor skinEditor) + { + selectedItems.BindTo(skinEditor.SelectedComponents); + } + + protected override void LoadComplete() { - this.changeHandler = changeHandler; + base.LoadComplete(); - SelectedItems.CollectionChanged += (_, __) => updateState(); + selectedItems.CollectionChanged += (_, __) => updateState(); + updateState(); } private void updateState() { - CanRotate.Value = SelectedItems.Count > 0; + CanRotate.Value = selectedItems.Count > 0; } private Drawable[]? objectsInRotation; @@ -47,7 +57,7 @@ public override void Begin() changeHandler?.BeginChange(); - objectsInRotation = SelectedItems.Cast().ToArray(); + objectsInRotation = selectedItems.Cast().ToArray(); originalRotations = objectsInRotation.ToDictionary(d => d, d => d.Rotation); originalPositions = objectsInRotation.ToDictionary(d => d, d => d.ToScreenSpace(d.OriginPosition)); defaultOrigin = GeometryUtils.GetSurroundingQuad(objectsInRotation.SelectMany(d => d.ScreenSpaceDrawQuad.GetVertices().ToArray())).Centre; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 31ad8fa3d780..57f9513cc10b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -68,9 +68,11 @@ protected SelectionHandler() [BackgroundDependencyLoader] private void load() { - RotationHandler = CreateRotationHandler(); - - InternalChild = SelectionBox = CreateSelectionBox(); + AddRangeInternal(new Drawable[] + { + RotationHandler = CreateRotationHandler(), + SelectionBox = CreateSelectionBox(), + }); SelectedItems.CollectionChanged += (_, _) => { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs index 6524f7fa35c0..5faa4a108dc5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionRotationHandler.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Framework.Graphics; using osuTK; namespace osu.Game.Screens.Edit.Compose.Components @@ -9,7 +10,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Base handler for editor rotation operations. /// - public class SelectionRotationHandler + public partial class SelectionRotationHandler : Component { /// /// Whether the rotation can currently be performed. From ebe5dd2ac97683761d1712b896235c5d53a2611e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 30 Jul 2023 20:21:41 +0200 Subject: [PATCH 075/207] Interface with `SelectionRotationHandler` via DI rather than explicit passing --- .../Visual/Editing/TestSceneComposeSelectBox.cs | 10 +++++++++- .../Screens/Edit/Compose/Components/SelectionBox.cs | 13 +++++++------ .../Components/SelectionBoxRotationHandle.cs | 13 +++++++------ .../Edit/Compose/Components/SelectionHandler.cs | 10 ++++++++-- 4 files changed, 31 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index 9901118ce877..80c69aacf695 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -7,6 +7,7 @@ using System.Linq; using JetBrains.Annotations; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Testing; @@ -22,6 +23,14 @@ public partial class TestSceneComposeSelectBox : OsuManualInputManagerTestScene private Container selectionArea; private SelectionBox selectionBox; + [Cached(typeof(SelectionRotationHandler))] + private TestSelectionRotationHandler rotationHandler; + + public TestSceneComposeSelectBox() + { + rotationHandler = new TestSelectionRotationHandler(() => selectionArea); + } + [SetUp] public void SetUp() => Schedule(() => { @@ -41,7 +50,6 @@ public void SetUp() => Schedule(() => CanFlipX = true, CanFlipY = true, - RotationHandler = new TestSelectionRotationHandler(() => selectionArea), OnScale = handleScale } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index ed6bbf766841..876e8ccbe997 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -23,7 +23,9 @@ public partial class SelectionBox : CompositeDrawable private const float button_padding = 5; - public SelectionRotationHandler? RotationHandler { get; init; } + [Resolved] + private SelectionRotationHandler? rotationHandler { get; set; } + public Func? OnScale; public Func? OnFlip; public Func? OnReverse; @@ -149,8 +151,8 @@ public string Text [BackgroundDependencyLoader] private void load() { - if (RotationHandler != null) - canRotate.BindTo(RotationHandler.CanRotate); + if (rotationHandler != null) + canRotate.BindTo(rotationHandler.CanRotate); canRotate.BindValueChanged(_ => recreate(), true); } @@ -252,8 +254,8 @@ private void recreate() private void addRotationComponents() { - rotateCounterClockwiseButton = addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise (Ctrl-<)", () => RotationHandler?.Rotate(-90)); - rotateClockwiseButton = addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise (Ctrl->)", () => RotationHandler?.Rotate(90)); + rotateCounterClockwiseButton = addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise (Ctrl-<)", () => rotationHandler?.Rotate(-90)); + rotateClockwiseButton = addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise (Ctrl->)", () => rotationHandler?.Rotate(90)); addRotateHandle(Anchor.TopLeft); addRotateHandle(Anchor.TopRight); @@ -323,7 +325,6 @@ private void addRotateHandle(Anchor anchor) var handle = new SelectionBoxRotationHandle { Anchor = anchor, - RotationHandler = RotationHandler }; handle.OperationStarted += operationStarted; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs index 8665ec9b08c1..024749a7012d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBoxRotationHandle.cs @@ -19,8 +19,6 @@ namespace osu.Game.Screens.Edit.Compose.Components { public partial class SelectionBoxRotationHandle : SelectionBoxDragHandle, IHasTooltip { - public SelectionRotationHandler? RotationHandler { get; init; } - public LocalisableString TooltipText { get; private set; } private SpriteIcon icon = null!; @@ -32,6 +30,9 @@ public partial class SelectionBoxRotationHandle : SelectionBoxDragHandle, IHasTo [Resolved] private SelectionBox selectionBox { get; set; } = null!; + [Resolved] + private SelectionRotationHandler? rotationHandler { get; set; } + [BackgroundDependencyLoader] private void load() { @@ -61,9 +62,9 @@ protected override void UpdateHoverState() protected override bool OnDragStart(DragStartEvent e) { - if (RotationHandler == null) return false; + if (rotationHandler == null) return false; - RotationHandler.Begin(); + rotationHandler.Begin(); return true; } @@ -97,7 +98,7 @@ protected override void OnKeyUp(KeyUpEvent e) protected override void OnDragEnd(DragEndEvent e) { - RotationHandler?.Commit(); + rotationHandler?.Commit(); UpdateHoverState(); cumulativeRotation.Value = null; @@ -121,7 +122,7 @@ private void applyRotation(bool shouldSnap) cumulativeRotation.Value = newRotation; - RotationHandler?.Update(newRotation); + rotationHandler?.Update(newRotation); TooltipText = shouldSnap ? EditorStrings.RotationSnapped(newRotation) : EditorStrings.RotationUnsnapped(newRotation); } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 57f9513cc10b..158b4066bc81 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -65,12 +65,19 @@ protected SelectionHandler() AlwaysPresent = true; } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs(RotationHandler = CreateRotationHandler()); + return dependencies; + } + [BackgroundDependencyLoader] private void load() { AddRangeInternal(new Drawable[] { - RotationHandler = CreateRotationHandler(), + RotationHandler, SelectionBox = CreateSelectionBox(), }); @@ -86,7 +93,6 @@ public SelectionBox CreateSelectionBox() OperationStarted = OnOperationBegan, OperationEnded = OnOperationEnded, - RotationHandler = RotationHandler, OnScale = HandleScale, OnFlip = HandleFlip, OnReverse = HandleReverse, From 72005bef7c037977b084f8c5b8eb60a516610573 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 31 Jul 2023 15:10:58 +0900 Subject: [PATCH 076/207] Fix skin editor crashing if the same component is provided twice --- osu.Game/Overlays/SkinEditor/SkinEditor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SkinEditor/SkinEditor.cs b/osu.Game/Overlays/SkinEditor/SkinEditor.cs index 2b23ce290fc9..8179d58ddffa 100644 --- a/osu.Game/Overlays/SkinEditor/SkinEditor.cs +++ b/osu.Game/Overlays/SkinEditor/SkinEditor.cs @@ -356,7 +356,7 @@ private void targetChanged(ValueChangedEvent tar { new SettingsDropdown { - Items = availableTargets.Select(t => t.Lookup), + Items = availableTargets.Select(t => t.Lookup).Distinct(), Current = selectedTarget, } } From d78cc6085194de5f321f8a22354f7d9ddc73696b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 1 Aug 2023 07:12:40 +0900 Subject: [PATCH 077/207] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 7f15d9fafde1..651e5b1fe63e 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml b/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml index 4a1545a4233b..f5a49210ea11 100644 --- a/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml +++ b/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml b/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml index 45d27dda7014..ed4725dd9410 100644 --- a/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml +++ b/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml b/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml index 452b9683eccc..cc88d3080ad6 100644 --- a/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml +++ b/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Tests.Android/AndroidManifest.xml b/osu.Game.Tests.Android/AndroidManifest.xml index f25b2e5328bc..6f91fb928cae 100644 --- a/osu.Game.Tests.Android/AndroidManifest.xml +++ b/osu.Game.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file From 912f31dabc7160fbd32ccb0a47fd733e427274e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Aug 2023 23:37:11 +0200 Subject: [PATCH 193/207] Declare media permissions in game project for editor usage --- osu.Android/AndroidManifest.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Android/AndroidManifest.xml b/osu.Android/AndroidManifest.xml index fb54c8e15157..af102a1e4ee6 100644 --- a/osu.Android/AndroidManifest.xml +++ b/osu.Android/AndroidManifest.xml @@ -2,4 +2,7 @@ + + + \ No newline at end of file From a942b6ff745193ff9b3b0f8c624d5905a4495225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Aug 2023 07:27:02 +0200 Subject: [PATCH 194/207] Replace inline comment with actual explanation of what's happening --- osu.Game/Database/LegacyBeatmapExporter.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/LegacyBeatmapExporter.cs b/osu.Game/Database/LegacyBeatmapExporter.cs index c00977d07247..ece705f68557 100644 --- a/osu.Game/Database/LegacyBeatmapExporter.cs +++ b/osu.Game/Database/LegacyBeatmapExporter.cs @@ -72,7 +72,16 @@ public LegacyBeatmapExporter(Storage storage) if (hitObject is not IHasPath hasPath) continue; - // Make sure the last control point is inherit type + // stable's hit object parsing expects the entire slider to use only one type of curve, + // and happens to use the last non-empty curve type read for the entire slider. + // this clear of the last control point type handles an edge case + // wherein the last control point of an otherwise-single-segment slider path has a different type than previous, + // which would lead to sliders being mangled when exported back to stable. + // normally, that would be handled by the `BezierConverter.ConvertToModernBezier()` call below, + // which outputs a slider path containing only Bezier control points, + // but a non-inherited last control point is (rightly) not considered to be starting a new segment, + // therefore it would fail to clear the `CountSegments() <= 1` check. + // by clearing explicitly we both fix the issue and avoid unnecessary conversions to Bezier. if (hasPath.Path.ControlPoints.Count > 1) hasPath.Path.ControlPoints[^1].Type = null; From dd1ac461db2c779c5f9a24bfedbdb3437f503c6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Aug 2023 08:23:58 +0200 Subject: [PATCH 195/207] Reformat xmldoc --- osu.Game/Rulesets/Objects/SliderPath.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Objects/SliderPath.cs b/osu.Game/Rulesets/Objects/SliderPath.cs index 1c02b18a0ffe..34113285a420 100644 --- a/osu.Game/Rulesets/Objects/SliderPath.cs +++ b/osu.Game/Rulesets/Objects/SliderPath.cs @@ -201,13 +201,20 @@ public List PointsInSegment(PathControlPoint controlPoint) /// Returns the progress values at which (control point) segments of the path end. /// Ranges from 0 (beginning of the path) to 1 (end of the path) to infinity (beyond the end of the path). /// - /// In case is less than , + /// + /// truncates the progression values to [0,1], + /// so you can't use this method in conjunction with that one to retrieve the positions of segment ends beyond the end of the path. + /// + /// + /// + /// In case is less than , /// the last segment ends after the end of the path, hence it returns a value greater than 1. - /// + /// + /// /// In case is greater than , - /// the last segment ends before the end of the path, hence it returns a value less than 1. - /// truncates the progression values to [0,1], - /// so you can't use this method to retrieve the positions of segment ends beyond the end of the path. + /// the last segment ends before the end of the path, hence it returns a value less than 1. + /// + /// public IEnumerable GetSegmentEnds() { ensureValid(); From 479c463751e0159049bb63fe27937072a9b836c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Aug 2023 08:27:08 +0200 Subject: [PATCH 196/207] Explain why segment end positions are not recovered in test --- osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs index 16e4ae13d9f1..635d9f9604d6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSliderPath.cs @@ -207,6 +207,7 @@ public void TestGetSegmentEnds() AddStep("shorten last segment", () => path.ExpectedDistance.Value = 150); AddAssert("segment ends are correct", () => path.GetSegmentEnds(), () => Is.EqualTo(distances.Select(d => d / 150))); + // see remarks in `GetSegmentEnds()` xmldoc (`SliderPath.PositionAt()` clamps progress to [0,1]). AddAssert("segment end positions not recovered", () => path.GetSegmentEnds().Select(p => path.PositionAt(p)), () => Is.EqualTo(new[] { positions[1], From 5009fd379421f928bca35028b00e6b88374bdb5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 20 Aug 2023 17:57:45 +0900 Subject: [PATCH 197/207] Add test coverage of song bar crash --- .../Components/TestSceneSongBar.cs | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs b/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs index 0f31192a9c1b..d52b45318528 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs @@ -2,27 +2,34 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps.Legacy; -using osu.Game.Tests.Visual; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; namespace osu.Game.Tournament.Tests.Components { [TestFixture] - public partial class TestSceneSongBar : OsuTestScene + public partial class TestSceneSongBar : TournamentTestScene { - [Cached] - private readonly LadderInfo ladder = new LadderInfo(); - private SongBar songBar = null!; [SetUpSteps] - public void SetUpSteps() + public override void SetUpSteps() { + base.SetUpSteps(); + + AddStep("setup picks bans", () => + { + Ladder.CurrentMatch.Value!.PicksBans.Add(new BeatmapChoice + { + BeatmapID = CreateSampleBeatmap().OnlineID, + Team = TeamColour.Red, + Type = ChoiceType.Pick, + }); + }); + AddStep("create bar", () => Child = songBar = new SongBar { RelativeSizeAxes = Axes.X, @@ -38,12 +45,14 @@ public void TestSongBar() AddStep("set beatmap", () => { var beatmap = CreateAPIBeatmap(Ruleset.Value); + beatmap.CircleSize = 3.4f; beatmap.ApproachRate = 6.8f; beatmap.OverallDifficulty = 5.5f; beatmap.StarRating = 4.56f; beatmap.Length = 123456; beatmap.BPM = 133; + beatmap.OnlineID = CreateSampleBeatmap().OnlineID; songBar.Beatmap = new TournamentBeatmap(beatmap); }); From 1067769b24e924bb34d4cc8975e94e162c7198c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Aug 2023 15:33:58 +0900 Subject: [PATCH 198/207] Remove masking on song bar Turns out this breaks when a border style is applied for picks/bans, and it wasn't doing much for visuals anyway. --- osu.Game.Tournament/Components/SongBar.cs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 3d060600f730..cde826628e91 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -234,7 +234,7 @@ private void refreshContent() } } }, - new UnmaskedTournamentBeatmapPanel(beatmap) + new TournamentBeatmapPanel(beatmap) { RelativeSizeAxes = Axes.X, Width = 0.5f, @@ -277,18 +277,4 @@ static void cp(SpriteText s, bool bold) } } } - - internal partial class UnmaskedTournamentBeatmapPanel : TournamentBeatmapPanel - { - public UnmaskedTournamentBeatmapPanel(IBeatmapInfo? beatmap, string mod = "") - : base(beatmap, mod) - { - } - - [BackgroundDependencyLoader] - private void load() - { - Masking = false; - } - } } From f03c64462e5bd6a21626e7030cfaaf13a50c2e78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Aug 2023 08:58:49 +0200 Subject: [PATCH 199/207] Better convey meaning of zero last year placement via tooltip --- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 241692d5155c..250d5acaae79 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -10,7 +10,9 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Settings; using osu.Game.Tournament.Models; @@ -128,7 +130,7 @@ public TeamRow(TournamentTeam team, TournamentScreen parent) Width = 0.2f, Current = Model.Seed }, - new SettingsSlider + new SettingsSlider { LabelText = "Last Year Placement", Width = 0.33f, @@ -175,6 +177,11 @@ public TeamRow(TournamentTeam team, TournamentScreen parent) }; } + private partial class LastYearPlacementSlider : RoundedSliderBar + { + public override LocalisableString TooltipText => Current.Value == 0 ? "N/A" : base.TooltipText; + } + public partial class PlayerEditor : CompositeDrawable { private readonly TournamentTeam team; From 827d48adcc5fb3d334c5175879898b604871b4d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Aug 2023 16:10:48 +0900 Subject: [PATCH 200/207] Fix test coverage not actually covering crash --- osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs b/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs index d52b45318528..e0444b6126ed 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneSongBar.cs @@ -14,6 +14,7 @@ namespace osu.Game.Tournament.Tests.Components public partial class TestSceneSongBar : TournamentTestScene { private SongBar songBar = null!; + private TournamentBeatmap ladderBeatmap = null!; [SetUpSteps] public override void SetUpSteps() @@ -22,9 +23,10 @@ public override void SetUpSteps() AddStep("setup picks bans", () => { + ladderBeatmap = CreateSampleBeatmap(); Ladder.CurrentMatch.Value!.PicksBans.Add(new BeatmapChoice { - BeatmapID = CreateSampleBeatmap().OnlineID, + BeatmapID = ladderBeatmap.OnlineID, Team = TeamColour.Red, Type = ChoiceType.Pick, }); @@ -52,7 +54,7 @@ public void TestSongBar() beatmap.StarRating = 4.56f; beatmap.Length = 123456; beatmap.BPM = 133; - beatmap.OnlineID = CreateSampleBeatmap().OnlineID; + beatmap.OnlineID = ladderBeatmap.OnlineID; songBar.Beatmap = new TournamentBeatmap(beatmap); }); From e7d61e00022a4a09e8164eb809e2a1dc6b34dfa6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Aug 2023 17:59:24 +0900 Subject: [PATCH 201/207] Fix star fountain directions not matching stable --- .../Visual/Menus/TestSceneStarFountain.cs | 2 +- osu.Game/Screens/Menu/KiaiMenuFountains.cs | 30 +++++++++++++++---- osu.Game/Screens/Menu/StarFountain.cs | 6 ++-- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs index b12f3e7946c8..bb327e596223 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneStarFountain.cs @@ -44,7 +44,7 @@ public void TestPew() foreach (var fountain in Children.OfType()) { if (RNG.NextSingle() > 0.8f) - fountain.Shoot(); + fountain.Shoot(RNG.Next(-1, 2)); } }, 150); } diff --git a/osu.Game/Screens/Menu/KiaiMenuFountains.cs b/osu.Game/Screens/Menu/KiaiMenuFountains.cs index a4d58e398a6e..07c06dcdb9c1 100644 --- a/osu.Game/Screens/Menu/KiaiMenuFountains.cs +++ b/osu.Game/Screens/Menu/KiaiMenuFountains.cs @@ -2,10 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Graphics; +using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; @@ -13,6 +13,9 @@ namespace osu.Game.Screens.Menu { public partial class KiaiMenuFountains : BeatSyncedContainer { + private StarFountain leftFountain = null!; + private StarFountain rightFountain = null!; + [BackgroundDependencyLoader] private void load() { @@ -20,13 +23,13 @@ private void load() Children = new[] { - new StarFountain + leftFountain = new StarFountain { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, X = 250, }, - new StarFountain + rightFountain = new StarFountain { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, @@ -58,8 +61,25 @@ public void Shoot() if (lastTrigger != null && Clock.CurrentTime - lastTrigger < 500) return; - foreach (var fountain in Children.OfType()) - fountain.Shoot(); + int direction = RNG.Next(-1, 2); + + switch (direction) + { + case -1: + leftFountain.Shoot(1); + rightFountain.Shoot(-1); + break; + + case 0: + leftFountain.Shoot(0); + rightFountain.Shoot(0); + break; + + case 1: + leftFountain.Shoot(-1); + rightFountain.Shoot(1); + break; + } lastTrigger = Clock.CurrentTime; } diff --git a/osu.Game/Screens/Menu/StarFountain.cs b/osu.Game/Screens/Menu/StarFountain.cs index 0d35f6e0e0fb..fd59ec3573c7 100644 --- a/osu.Game/Screens/Menu/StarFountain.cs +++ b/osu.Game/Screens/Menu/StarFountain.cs @@ -23,7 +23,7 @@ private void load() InternalChild = spewer = new StarFountainSpewer(); } - public void Shoot() => spewer.Shoot(); + public void Shoot(int direction) => spewer.Shoot(direction); protected override void SkinChanged(ISkinSource skin) { @@ -81,10 +81,10 @@ private float getCurrentAngle() return lastShootDirection * x_velocity_from_direction * (float)(1 - 2 * (Clock.CurrentTime - lastShootTime!.Value) / shoot_duration) + getRandomVariance(x_velocity_random_variance); } - public void Shoot() + public void Shoot(int direction) { lastShootTime = Clock.CurrentTime; - lastShootDirection = RNG.Next(-1, 2); + lastShootDirection = direction; } private static float getRandomVariance(float variance) => RNG.NextSingle(-variance, variance); From 5f040a991b3f1492c97d247799a3245b2b825ee1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Aug 2023 19:05:20 +0900 Subject: [PATCH 202/207] Fix potential crash when loading menu items due to cross-thread ops --- osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs index eb046932e6d2..2f2cb7e5f8ce 100644 --- a/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/DrawableOsuMenuItem.cs @@ -40,8 +40,14 @@ private void load() AddInternal(hoverClickSounds = new HoverClickSounds()); updateTextColour(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); Item.Action.BindDisabledChanged(_ => updateState(), true); + FinishTransforms(); } private void updateTextColour() From 662073c47220d513ed3ef510d70ab0ccb850e334 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Aug 2023 19:35:04 +0900 Subject: [PATCH 203/207] Fix some incorrect comments / test step descriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index 23b88b7395d6..f766faec9a36 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -132,7 +132,7 @@ public void TestScoreUpgradeSuccess() { ScoreInfo scoreInfo = null!; - AddStep("Add score which requires upgrade (but has no beatmap)", () => + AddStep("Add score which requires upgrade (and has beatmap)", () => { Realm.Write(r => { diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index c3a45332e464..526c4217ef6c 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -86,7 +86,7 @@ public class ScoreInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftD /// Should be used to ensure we don't repeatedly attempt to update the same scores each startup even though we already know they will fail. /// /// - /// See https://github.com/ppy/osu/issues/24301 for one example of how this can occur(missing beatmap file on disk). + /// See https://github.com/ppy/osu/issues/24301 for one example of how this can occur (missing beatmap file on disk). /// public bool TotalScoreUpgradeFailed { get; set; } From b3e7416972f097242bf8c9fe407dec3567d489d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Aug 2023 19:36:22 +0900 Subject: [PATCH 204/207] Rename new flag and update xmldoc to match --- .../Database/BackgroundDataStoreProcessorTests.cs | 4 ++-- osu.Game/BackgroundDataStoreProcessor.cs | 8 ++++---- osu.Game/Scoring/ScoreInfo.cs | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs index f766faec9a36..da46392e4b0e 100644 --- a/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs +++ b/osu.Game.Tests/Database/BackgroundDataStoreProcessorTests.cs @@ -148,7 +148,7 @@ public void TestScoreUpgradeSuccess() AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor())); AddUntilStep("Score version upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(LegacyScoreEncoder.LATEST_VERSION)); - AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreUpgradeFailed), () => Is.False); + AddAssert("Score not marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.False); } [Test] @@ -174,7 +174,7 @@ public void TestScoreUpgradeFailed() AddStep("Run background processor", () => Add(new TestBackgroundDataStoreProcessor())); - AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreUpgradeFailed), () => Is.True); + AddUntilStep("Score marked as failed", () => Realm.Run(r => r.Find(scoreInfo.ID)!.BackgroundReprocessingFailed), () => Is.True); AddAssert("Score version not upgraded", () => Realm.Run(r => r.Find(scoreInfo.ID)!.TotalScoreVersion), () => Is.EqualTo(30000002)); } diff --git a/osu.Game/BackgroundDataStoreProcessor.cs b/osu.Game/BackgroundDataStoreProcessor.cs index ae9e9527deee..f29b100ee8df 100644 --- a/osu.Game/BackgroundDataStoreProcessor.cs +++ b/osu.Game/BackgroundDataStoreProcessor.cs @@ -186,7 +186,7 @@ private void processScoresWithMissingStatistics() realmAccess.Run(r => { - foreach (var score in r.All().Where(s => !s.TotalScoreUpgradeFailed)) + foreach (var score in r.All().Where(s => !s.BackgroundReprocessingFailed)) { if (score.BeatmapInfo != null && score.Statistics.Sum(kvp => kvp.Value) > 0 @@ -225,7 +225,7 @@ private void processScoresWithMissingStatistics() catch (Exception e) { Logger.Log(@$"Failed to populate maximum statistics for {id}: {e}"); - realmAccess.Write(r => r.Find(id)!.TotalScoreUpgradeFailed = true); + realmAccess.Write(r => r.Find(id)!.BackgroundReprocessingFailed = true); } } } @@ -235,7 +235,7 @@ private void convertLegacyTotalScoreToStandardised() Logger.Log("Querying for scores that need total score conversion..."); HashSet scoreIds = realmAccess.Run(r => new HashSet(r.All() - .Where(s => !s.TotalScoreUpgradeFailed && s.BeatmapInfo != null && s.TotalScoreVersion == 30000002) + .Where(s => !s.BackgroundReprocessingFailed && s.BeatmapInfo != null && s.TotalScoreVersion == 30000002) .AsEnumerable().Select(s => s.ID))); Logger.Log($"Found {scoreIds.Count} scores which require total score conversion."); @@ -284,7 +284,7 @@ private void convertLegacyTotalScoreToStandardised() catch (Exception e) { Logger.Log($"Failed to convert total score for {id}: {e}"); - realmAccess.Write(r => r.Find(id)!.TotalScoreUpgradeFailed = true); + realmAccess.Write(r => r.Find(id)!.BackgroundReprocessingFailed = true); ++failedCount; } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 526c4217ef6c..2efea2105c2f 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -82,13 +82,13 @@ public class ScoreInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftD public long? LegacyTotalScore { get; set; } /// - /// If an reprocess of total score failed to update this score to the latest version, this flag will become true. - /// Should be used to ensure we don't repeatedly attempt to update the same scores each startup even though we already know they will fail. + /// If background processing of this beatmap failed in some way, this flag will become true. + /// Should be used to ensure we don't repeatedly attempt to reprocess the same scores each startup even though we already know they will fail. /// /// /// See https://github.com/ppy/osu/issues/24301 for one example of how this can occur (missing beatmap file on disk). /// - public bool TotalScoreUpgradeFailed { get; set; } + public bool BackgroundReprocessingFailed { get; set; } public int MaxCombo { get; set; } From 8533cba0bf6a02b05b8e9bfe99aaee2822dfe5a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Aug 2023 17:27:05 +0200 Subject: [PATCH 205/207] Fix mismatching schema version in comment --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 36781b0454b7..db4f0d986453 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -83,7 +83,7 @@ public class RealmAccess : IDisposable /// 31 2023-06-26 Add Version and LegacyTotalScore to ScoreInfo, set Version to 30000002 and copy TotalScore into LegacyTotalScore for legacy scores. /// 32 2023-07-09 Populate legacy scores with the ScoreV2 mod (and restore TotalScore to the legacy total for such scores) using replay files. /// 33 2023-08-16 Reset default chat toggle key binding to avoid conflict with newly added leaderboard toggle key binding. - /// 35 2023-08-21 Add TotalScoreUpgradeFailed flag to ScoreInfo to track upgrade failures. + /// 34 2023-08-21 Add TotalScoreUpgradeFailed flag to ScoreInfo to track upgrade failures. /// private const int schema_version = 34; From 273dcf9150f441c475c524455b12376a96d3930f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Aug 2023 17:44:35 +0200 Subject: [PATCH 206/207] Also update the reference to added flag in schema change breakdown --- osu.Game/Database/RealmAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index db4f0d986453..cd97bb6430d3 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -83,7 +83,7 @@ public class RealmAccess : IDisposable /// 31 2023-06-26 Add Version and LegacyTotalScore to ScoreInfo, set Version to 30000002 and copy TotalScore into LegacyTotalScore for legacy scores. /// 32 2023-07-09 Populate legacy scores with the ScoreV2 mod (and restore TotalScore to the legacy total for such scores) using replay files. /// 33 2023-08-16 Reset default chat toggle key binding to avoid conflict with newly added leaderboard toggle key binding. - /// 34 2023-08-21 Add TotalScoreUpgradeFailed flag to ScoreInfo to track upgrade failures. + /// 34 2023-08-21 Add BackgroundReprocessingFailed flag to ScoreInfo to track upgrade failures. /// private const int schema_version = 34; From 5454d1caa1927428fe339e3d5b47ddfe45f9dfb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 6 Aug 2023 17:40:55 +0200 Subject: [PATCH 207/207] Remove global action container input queue workaround As described in #24248, the workaround employed by `GlobalActionContainer`, wherein it tried to handle actions with priority before its children by being placed in front of the children and not _actually containing_ said children, is blocking the resolution of some rather major input handling issues that allow key releases to be received by deparented drawables. To resolve, migrate `GlobalActionContainer` to use `Prioritised`, which can be done without regressing certain mouse button flows after ppy/osu-framework#5966. --- .../Input/Bindings/GlobalActionContainer.cs | 34 +++++-------------- osu.Game/OsuGameBase.cs | 19 ++++++----- .../Visual/OsuManualInputManagerTestScene.cs | 8 ++++- 3 files changed, 25 insertions(+), 36 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 9a0a2d5c154a..296232d9ea77 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -3,33 +3,26 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Localisation; namespace osu.Game.Input.Bindings { - public partial class GlobalActionContainer : DatabasedKeyBindingContainer, IHandleGlobalKeyboardInput + public partial class GlobalActionContainer : DatabasedKeyBindingContainer, IHandleGlobalKeyboardInput, IKeyBindingHandler { - private readonly Drawable? handler; - - private InputManager? parentInputManager; + private readonly IKeyBindingHandler? handler; public GlobalActionContainer(OsuGameBase? game) : base(matchingMode: KeyCombinationMatchingMode.Modifiers) { - if (game is IKeyBindingHandler) - handler = game; + if (game is IKeyBindingHandler h) + handler = h; } - protected override void LoadComplete() - { - base.LoadComplete(); - - parentInputManager = GetContainingInputManager(); - } + protected override bool Prioritised => true; // IMPORTANT: Take care when changing order of the items in the enumerable. // It is used to decide the order of precedence, with the earlier items having higher precedence. @@ -161,20 +154,9 @@ protected override void LoadComplete() new KeyBinding(InputKey.F3, GlobalAction.MusicPlay) }; - protected override IEnumerable KeyBindingInputQueue - { - get - { - // To ensure the global actions are handled with priority, this GlobalActionContainer is actually placed after game content. - // It does not contain children as expected, so we need to forward the NonPositionalInputQueue from the parent input manager to correctly - // allow the whole game to handle these actions. - - // An eventual solution to this hack is to create localised action containers for individual components like SongSelect, but this will take some rearranging. - var inputQueue = parentInputManager?.NonPositionalInputQueue ?? base.KeyBindingInputQueue; + public bool OnPressed(KeyBindingPressEvent e) => handler?.OnPressed(e) == true; - return handler != null ? inputQueue.Prepend(handler) : inputQueue; - } - } + public void OnReleased(KeyBindingReleaseEvent e) => handler?.OnReleased(e); } public enum GlobalAction diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 6737caa5f931..75b46a0a4ddc 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -392,17 +392,18 @@ private void load(ReadableKeyCombinationProvider keyCombinationProvider, Framewo { SafeAreaOverrideEdges = SafeAreaOverrideEdges, RelativeSizeAxes = Axes.Both, - Child = CreateScalingContainer().WithChildren(new Drawable[] + Child = CreateScalingContainer().WithChild(globalBindings = new GlobalActionContainer(this) { - (GlobalCursorDisplay = new GlobalCursorDisplay + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both - }).WithChild(content = new OsuTooltipContainer(GlobalCursorDisplay.MenuCursor) - { - RelativeSizeAxes = Axes.Both - }), - // to avoid positional input being blocked by children, ensure the GlobalActionContainer is above everything. - globalBindings = new GlobalActionContainer(this) + (GlobalCursorDisplay = new GlobalCursorDisplay + { + RelativeSizeAxes = Axes.Both + }).WithChild(content = new OsuTooltipContainer(GlobalCursorDisplay.MenuCursor) + { + RelativeSizeAxes = Axes.Both + }), + } }) }); diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index 37260b3b1315..ffe40243abe9 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -57,7 +57,13 @@ protected OsuManualInputManagerTestScene() } if (CreateNestedActionContainer) - mainContent.Add(new GlobalActionContainer(null)); + { + var globalActionContainer = new GlobalActionContainer(null) + { + Child = mainContent + }; + mainContent = globalActionContainer; + } base.Content.AddRange(new Drawable[] {