From e3ba7d9ba56c334b2fe815ca1d15e59539bd9f65 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 24 Jan 2022 10:24:40 +0100 Subject: [PATCH 001/481] Wiggle mod expansion Free dlc! --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 31 ++++++++++++++++------ 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index ff6ba6e121..e0b09213b7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -2,8 +2,10 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; @@ -22,8 +24,21 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) }; - private const int wiggle_duration = 90; // (ms) Higher = fewer wiggles - private const int wiggle_strength = 10; // Higher = stronger wiggles + [SettingSource("Wiggle strength", "something")] + public BindableDouble WiggleStrength { get; } = new BindableDouble(10) + { + MinValue = 1f, + MaxValue = 15f, + Precision = .5f + }; + + [SettingSource("Wiggle duration", "milliseconds per wiggle")] + public BindableInt WiggleDuration { get; } = new BindableInt(90) + { + MinValue = 40, + MaxValue = 300, + Precision = 5 + }; protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => drawableOnApplyCustomUpdateState(hitObject, state); @@ -42,18 +57,18 @@ namespace osu.Game.Rulesets.Osu.Mods Random objRand = new Random((int)osuObject.StartTime); // Wiggle all objects during TimePreempt - int amountWiggles = (int)osuObject.TimePreempt / wiggle_duration; + int amountWiggles = (int)osuObject.TimePreempt / WiggleDuration.Value; void wiggle() { float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI); - float nextDist = (float)(objRand.NextDouble() * wiggle_strength); - drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), wiggle_duration); + float nextDist = (float)(objRand.NextDouble() * WiggleStrength.Value); + drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), WiggleDuration.Value); } for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * WiggleDuration.Value)) wiggle(); } @@ -61,11 +76,11 @@ namespace osu.Game.Rulesets.Osu.Mods if (!(osuObject is IHasDuration endTime)) return; - amountWiggles = (int)(endTime.Duration / wiggle_duration); + amountWiggles = (int)(endTime.Duration / WiggleDuration.Value); for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * WiggleDuration.Value)) wiggle(); } } From a427e20090fc6ea3a3161f1f90bfa3df35bcf9ef Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 24 Jan 2022 10:38:11 +0100 Subject: [PATCH 002/481] Fixes --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index e0b09213b7..7116ee819c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) }; - [SettingSource("Wiggle strength", "something")] + [SettingSource("Strength")] public BindableDouble WiggleStrength { get; } = new BindableDouble(10) { MinValue = 1f, @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Mods Precision = .5f }; - [SettingSource("Wiggle duration", "milliseconds per wiggle")] + [SettingSource("Duration", "Milliseconds per wiggle")] public BindableInt WiggleDuration { get; } = new BindableInt(90) { MinValue = 40, From 836cb1ee323416b71d2f649bfd725d098fa8dad9 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Mon, 24 Jan 2022 12:16:29 +0100 Subject: [PATCH 003/481] Suggested boundary change --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 7116ee819c..10806318b0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -25,11 +25,11 @@ namespace osu.Game.Rulesets.Osu.Mods public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform) }; [SettingSource("Strength")] - public BindableDouble WiggleStrength { get; } = new BindableDouble(10) + public BindableDouble WiggleStrength { get; } = new BindableDouble(1) { - MinValue = 1f, - MaxValue = 15f, - Precision = .5f + MinValue = .1f, + MaxValue = 2f, + Precision = .1f }; [SettingSource("Duration", "Milliseconds per wiggle")] @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.Mods void wiggle() { float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI); - float nextDist = (float)(objRand.NextDouble() * WiggleStrength.Value); + float nextDist = (float)(objRand.NextDouble() * WiggleStrength.Value * 7); drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), WiggleDuration.Value); } From 35be0f24d02a5474b05b997526f662021311bb4d Mon Sep 17 00:00:00 2001 From: mk-56 Date: Thu, 27 Jan 2022 00:10:15 +0100 Subject: [PATCH 004/481] fixed leading "0"s not being present infront of decimal floats --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 10806318b0..853c974a04 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -27,9 +27,9 @@ namespace osu.Game.Rulesets.Osu.Mods [SettingSource("Strength")] public BindableDouble WiggleStrength { get; } = new BindableDouble(1) { - MinValue = .1f, + MinValue = 0.1f, MaxValue = 2f, - Precision = .1f + Precision = 0.1f }; [SettingSource("Duration", "Milliseconds per wiggle")] From dd8fc710fafc1319bb16613f056b3cf9285d4308 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Fri, 4 Feb 2022 15:48:46 +0100 Subject: [PATCH 005/481] removed wiggle duration --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 853c974a04..fae9d785fb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -31,15 +31,6 @@ namespace osu.Game.Rulesets.Osu.Mods MaxValue = 2f, Precision = 0.1f }; - - [SettingSource("Duration", "Milliseconds per wiggle")] - public BindableInt WiggleDuration { get; } = new BindableInt(90) - { - MinValue = 40, - MaxValue = 300, - Precision = 5 - }; - protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => drawableOnApplyCustomUpdateState(hitObject, state); protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) => drawableOnApplyCustomUpdateState(hitObject, state); @@ -57,18 +48,18 @@ namespace osu.Game.Rulesets.Osu.Mods Random objRand = new Random((int)osuObject.StartTime); // Wiggle all objects during TimePreempt - int amountWiggles = (int)osuObject.TimePreempt / WiggleDuration.Value; + int amountWiggles = (int)osuObject.TimePreempt / 70; void wiggle() { float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI); float nextDist = (float)(objRand.NextDouble() * WiggleStrength.Value * 7); - drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), WiggleDuration.Value); + drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), 70); } for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * WiggleDuration.Value)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * 70)) wiggle(); } @@ -76,11 +67,11 @@ namespace osu.Game.Rulesets.Osu.Mods if (!(osuObject is IHasDuration endTime)) return; - amountWiggles = (int)(endTime.Duration / WiggleDuration.Value); + amountWiggles = (int)(endTime.Duration / 70); for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * WiggleDuration.Value)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * 70)) wiggle(); } } From a25b6e6a099233f02ec88121de7f9b508d32d378 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Thu, 10 Mar 2022 00:42:58 -0800 Subject: [PATCH 006/481] Proof of Concept draft for Taiko touch input --- osu.Game.Rulesets.Taiko/TaikoInputManager.cs | 2 + osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 - .../UI/DrawableTaikoRuleset.cs | 6 + .../UI/DrumTouchInputArea.cs | 141 ++++++++++++++++++ osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 5 +- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 1 + 6 files changed, 153 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs diff --git a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs index 058bec5111..20766029bb 100644 --- a/osu.Game.Rulesets.Taiko/TaikoInputManager.cs +++ b/osu.Game.Rulesets.Taiko/TaikoInputManager.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; +using osu.Framework.Allocation; using osu.Framework.Input.Bindings; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko { + [Cached] // Used for touch input, see DrumTouchInputArea. public class TaikoInputManager : RulesetInputManager { public TaikoInputManager(RulesetInfo ruleset) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index e56aabaf9d..4a22619f06 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.Taiko public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { - new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre), - new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim), new KeyBinding(InputKey.D, TaikoAction.LeftRim), new KeyBinding(InputKey.F, TaikoAction.LeftCentre), new KeyBinding(InputKey.J, TaikoAction.RightCentre), diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 824b95639b..cad134067f 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -52,6 +52,12 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.X, Depth = float.MaxValue }); + + DrumTouchInputArea touchInput = new DrumTouchInputArea(Playfield) { + RelativePositionAxes = Axes.Both, + RelativeSizeAxes = Axes.Both, + }; + Overlays.Add(touchInput); } protected override void UpdateAfterChildren() diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs new file mode 100644 index 0000000000..647187226a --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -0,0 +1,141 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Framework.Logging; +using osu.Game.Graphics; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.UI +{ + /// + /// An overlay that captures and displays Taiko mouse and touch input. + /// The boundaries of this overlay defines the interactable area for touch input. + /// A secondary InputDrum is attached by this overlay, which defines the circulary boundary which distinguishes "centre" from "rim" hits, and also displays input. + /// + public class DrumTouchInputArea : Container + { + // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) + private const float overhangPercent = 0.33f; + private readonly InputDrum touchInputDrum; + + [Resolved(canBeNull: true)] + private TaikoInputManager taikoInputManager { get; set; } + + private KeyBindingContainer keyBindingContainer; + + // Which Taiko action was pressed by the last OnMouseDown event, so that the corresponding action can be released OnMouseUp even if the cursor position moved + private TaikoAction mouseAction; + + // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved + private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); + + private Playfield playfield; + + public DrumTouchInputArea(Playfield playfield) { + this.playfield = playfield; + + RelativeSizeAxes = Axes.Both; + RelativePositionAxes = Axes.Both; + Children = new Drawable[] + { + new Box() { + Alpha = 0.0f, + Colour = new OsuColour().Blue, + + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + }, + new Container() { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + + Children = new Drawable[] + { + touchInputDrum = new InputDrum(playfield.HitObjectContainer) { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } + }, + }; + + } + protected override void LoadComplete() + { + keyBindingContainer = taikoInputManager?.KeyBindingContainer; + + Padding = new MarginPadding { + Top = playfield.ScreenSpaceDrawQuad.BottomLeft.Y, + Bottom = -DrawHeight * overhangPercent, + }; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); + keyBindingContainer?.TriggerPressed(mouseAction); + return base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseUpEvent e) + { + keyBindingContainer?.TriggerReleased(mouseAction); + base.OnMouseUp(e); + } + + protected override bool OnTouchDown(TouchDownEvent e) + { + TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); + if (touchActions.ContainsKey(e.Touch.Source)) { + touchActions[e.Touch.Source] = taikoAction; + } + else { + touchActions.Add(e.Touch.Source, taikoAction); + } + keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); + + return base.OnTouchDown(e); + } + + protected override void OnTouchUp(TouchUpEvent e) + { + keyBindingContainer?.TriggerReleased(touchActions[e.Touch.Source]); + base.OnTouchUp(e); + } + + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { + bool leftSide = inputIsOnLeftSide(inputPosition); + bool centreHit = inputIsCenterHit(inputPosition); + + return leftSide ? + (centreHit ? TaikoAction.LeftCentre : TaikoAction.LeftRim) : + (centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim); + } + + private bool inputIsOnLeftSide(Vector2 inputPosition) { + Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; + return inputPositionToDrumCentreDelta.X < 0f; + } + + private bool inputIsCenterHit(Vector2 inputPosition) { + Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; + + float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; + float centreRadius = (inputDrumRadius * InputDrum.centre_size); + return inputPositionToDrumCentreDelta.Length <= centreRadius; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 16be20f7f3..1949d09de1 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -22,6 +22,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { + public const float centre_size = 0.7f; private const float middle_split = 0.025f; [Cached] @@ -122,14 +123,14 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.7f) + Size = new Vector2(centre_size) }, centreHit = new Sprite { Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.7f), + Size = new Vector2(centre_size), Alpha = 0, Blending = BlendingParameters.Additive } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index d650cab729..2f7e0dc08f 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -144,6 +144,7 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, }, drumRollHitContainer.CreateProxy(), + // new DrumTouchInputArea(this), }; RegisterPool(50); From 317869078f470889330702b7b99b6146569b4f75 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Thu, 10 Mar 2022 05:09:07 -0800 Subject: [PATCH 007/481] Basic functionality for Taiko touch input now complete --- .../UI/DrawableTaikoRuleset.cs | 12 ++-- .../UI/DrumSamplePlayer.cs | 55 ++++++++++++++++ .../UI/DrumTouchInputArea.cs | 62 ++++++++----------- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 19 +----- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 4 +- 5 files changed, 93 insertions(+), 59 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index cad134067f..9cd5b0ca7f 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects; @@ -35,6 +36,8 @@ namespace osu.Game.Rulesets.Taiko.UI private SkinnableDrawable scroller; + private DrumTouchInputArea drumTouchInputArea; + public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { @@ -53,11 +56,8 @@ namespace osu.Game.Rulesets.Taiko.UI Depth = float.MaxValue }); - DrumTouchInputArea touchInput = new DrumTouchInputArea(Playfield) { - RelativePositionAxes = Axes.Both, - RelativeSizeAxes = Axes.Both, - }; - Overlays.Add(touchInput); + if ((drumTouchInputArea = CreateDrumTouchInputArea()) != null) + KeyBindingInputManager.Add(drumTouchInputArea); } protected override void UpdateAfterChildren() @@ -76,6 +76,8 @@ namespace osu.Game.Rulesets.Taiko.UI return ControlPoints[result]; } + public DrumTouchInputArea CreateDrumTouchInputArea() => new DrumTouchInputArea(); + public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer(); protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs new file mode 100644 index 0000000000..458dbc6ca1 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -0,0 +1,55 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Taiko.UI +{ + internal class DrumSamplePlayer : Container, IKeyBindingHandler { + private DrumSampleTriggerSource leftRimSampleTriggerSource; + private DrumSampleTriggerSource leftCentreSampleTriggerSource; + private DrumSampleTriggerSource rightCentreSampleTriggerSource; + private DrumSampleTriggerSource rightRimSampleTriggerSource; + + public DrumSamplePlayer(HitObjectContainer hitObjectContainer) { + Children = new Drawable[] + { + leftRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + leftCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + rightCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + rightRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), + }; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == TaikoAction.LeftRim) + { + leftRimSampleTriggerSource.Play(HitType.Rim); + } + else if (e.Action == TaikoAction.LeftCentre) + { + leftCentreSampleTriggerSource.Play(HitType.Centre); + } + else if (e.Action == TaikoAction.RightCentre) + { + rightCentreSampleTriggerSource.Play(HitType.Centre); + } + else if (e.Action == TaikoAction.RightRim) + { + rightRimSampleTriggerSource.Play(HitType.Rim); + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } +} diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 647187226a..d610e84c8d 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -10,9 +10,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Logging; using osu.Game.Graphics; -using osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Taiko.UI @@ -25,11 +23,9 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float overhangPercent = 0.33f; - private readonly InputDrum touchInputDrum; - - [Resolved(canBeNull: true)] - private TaikoInputManager taikoInputManager { get; set; } + private const float overhangPercent = 0.35f; + private InputDrum touchInputDrum; + private Circle drumBackground; private KeyBindingContainer keyBindingContainer; @@ -39,55 +35,55 @@ namespace osu.Game.Rulesets.Taiko.UI // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); - private Playfield playfield; - - public DrumTouchInputArea(Playfield playfield) { - this.playfield = playfield; - + public DrumTouchInputArea() { RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; Children = new Drawable[] { - new Box() { - Alpha = 0.0f, - Colour = new OsuColour().Blue, - - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - }, new Container() { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, - Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, - Children = new Drawable[] { - touchInputDrum = new InputDrum(playfield.HitObjectContainer) { + drumBackground = new Circle() { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fit, + Alpha = 0.9f, + }, + touchInputDrum = new InputDrum() { Anchor = Anchor.Centre, Origin = Anchor.Centre, }, } }, }; - } + protected override void LoadComplete() { - keyBindingContainer = taikoInputManager?.KeyBindingContainer; - Padding = new MarginPadding { - Top = playfield.ScreenSpaceDrawQuad.BottomLeft.Y, - Bottom = -DrawHeight * overhangPercent, + Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield + Bottom = -touchInputDrum.DrawHeight * overhangPercent, // The drum should go past the bottom of the screen so that it can be wider }; } + [BackgroundDependencyLoader] + private void load(TaikoInputManager taikoInputManager, OsuColour colours) + { + keyBindingContainer = taikoInputManager?.KeyBindingContainer; + drumBackground.Colour = colours.Gray0; + } + protected override bool OnMouseDown(MouseDownEvent e) { mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); keyBindingContainer?.TriggerPressed(mouseAction); - return base.OnMouseDown(e); + return true; } protected override void OnMouseUp(MouseUpEvent e) @@ -99,20 +95,16 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnTouchDown(TouchDownEvent e) { TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); - if (touchActions.ContainsKey(e.Touch.Source)) { - touchActions[e.Touch.Source] = taikoAction; - } - else { - touchActions.Add(e.Touch.Source, taikoAction); - } + touchActions.Add(e.Touch.Source, taikoAction); keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); - return base.OnTouchDown(e); + return true; } protected override void OnTouchUp(TouchUpEvent e) { keyBindingContainer?.TriggerReleased(touchActions[e.Touch.Source]); + touchActions.Remove(e.Touch.Source); base.OnTouchUp(e); } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 1949d09de1..76594b86c1 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -10,8 +10,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK; @@ -25,13 +23,8 @@ namespace osu.Game.Rulesets.Taiko.UI public const float centre_size = 0.7f; private const float middle_split = 0.025f; - [Cached] - private DrumSampleTriggerSource sampleTriggerSource; - - public InputDrum(HitObjectContainer hitObjectContainer) + public InputDrum() { - sampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer); - RelativeSizeAxes = Axes.Both; } @@ -70,8 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI CentreAction = TaikoAction.RightCentre } } - }), - sampleTriggerSource + }) }; } @@ -95,9 +87,6 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - [Resolved] - private DrumSampleTriggerSource sampleTriggerSource { get; set; } - public TaikoHalfDrum(bool flipped) { Masking = true; @@ -158,15 +147,11 @@ namespace osu.Game.Rulesets.Taiko.UI { target = centreHit; back = centre; - - sampleTriggerSource.Play(HitType.Centre); } else if (e.Action == RimAction) { target = rimHit; back = rim; - - sampleTriggerSource.Play(HitType.Rim); } if (target != null) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 2f7e0dc08f..71aee107d2 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.Taiko.UI Children = new Drawable[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), - new InputDrum(HitObjectContainer) + new InputDrum() { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, }, drumRollHitContainer.CreateProxy(), - // new DrumTouchInputArea(this), + new DrumSamplePlayer(HitObjectContainer), }; RegisterPool(50); From 5ce57fa34a70a23971e56495a96ee86081395166 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Thu, 10 Mar 2022 06:17:06 -0800 Subject: [PATCH 008/481] Improve readability for getTaikoActionFromInput --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index d610e84c8d..a2c33a85e8 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -109,12 +109,12 @@ namespace osu.Game.Rulesets.Taiko.UI } private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { - bool leftSide = inputIsOnLeftSide(inputPosition); bool centreHit = inputIsCenterHit(inputPosition); + bool leftSide = inputIsOnLeftSide(inputPosition); - return leftSide ? - (centreHit ? TaikoAction.LeftCentre : TaikoAction.LeftRim) : - (centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim); + return centreHit ? + (leftSide ? TaikoAction.LeftCentre : TaikoAction.RightCentre) : + (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); } private bool inputIsOnLeftSide(Vector2 inputPosition) { From 38c61b2c1d088e788953e0246b94923dc9b896eb Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 00:14:33 -0800 Subject: [PATCH 009/481] Fix crash when loading legacy osu!taiko skins with touch input --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 43c5c07f80..321389676a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -8,8 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.Taiko.UI; using osu.Game.Skinning; using osuTK; @@ -112,9 +110,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public readonly Sprite Rim; public readonly Sprite Centre; - [Resolved] - private DrumSampleTriggerSource sampleTriggerSource { get; set; } - public LegacyHalfDrum(bool flipped) { Masking = true; @@ -149,12 +144,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy if (e.Action == CentreAction) { target = Centre; - sampleTriggerSource.Play(HitType.Centre); } else if (e.Action == RimAction) { target = Rim; - sampleTriggerSource.Play(HitType.Rim); } if (target != null) From 7336c2c0bd06010ea7fabc12a91833ab1bd4e3ab Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 02:48:08 -0800 Subject: [PATCH 010/481] Fix osu!taiko alignment issue with legacy skins on Touch Controls --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 321389676a..d3eeb8e16a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { Child = content = new Container { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Size = new Vector2(180, 200), Children = new Drawable[] { From 0f83308f7b1c18ebca3c89333954e26e34be2b56 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 02:49:10 -0800 Subject: [PATCH 011/481] Update osu!taiko TestSceneInputDrum with InputDrum changes for touch controls --- osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs index 24db046748..10fca30865 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneInputDrum.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(200), - Child = new InputDrum(playfield.HitObjectContainer) + Child = new InputDrum() } }); } From 1ed06f30e7feff438354617b55a32f2022728ef7 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 03:33:03 -0800 Subject: [PATCH 012/481] osu!Taiko touch implementation code cleanup --- .../UI/DrumSamplePlayer.cs | 27 +++++++++---------- .../UI/DrumTouchInputArea.cs | 4 +-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs index 458dbc6ca1..609da0b389 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -28,21 +28,20 @@ namespace osu.Game.Rulesets.Taiko.UI public bool OnPressed(KeyBindingPressEvent e) { - if (e.Action == TaikoAction.LeftRim) + switch (e.Action) { - leftRimSampleTriggerSource.Play(HitType.Rim); - } - else if (e.Action == TaikoAction.LeftCentre) - { - leftCentreSampleTriggerSource.Play(HitType.Centre); - } - else if (e.Action == TaikoAction.RightCentre) - { - rightCentreSampleTriggerSource.Play(HitType.Centre); - } - else if (e.Action == TaikoAction.RightRim) - { - rightRimSampleTriggerSource.Play(HitType.Rim); + case TaikoAction.LeftRim: + leftRimSampleTriggerSource.Play(HitType.Rim); + break; + case TaikoAction.LeftCentre: + leftCentreSampleTriggerSource.Play(HitType.Centre); + break; + case TaikoAction.RightCentre: + rightCentreSampleTriggerSource.Play(HitType.Centre); + break; + case TaikoAction.RightRim: + rightRimSampleTriggerSource.Play(HitType.Rim); + break; } return false; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index a2c33a85e8..ccf59507d3 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float overhangPercent = 0.35f; + private const float offscreenPercent = 0.35f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.UI { Padding = new MarginPadding { Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield - Bottom = -touchInputDrum.DrawHeight * overhangPercent, // The drum should go past the bottom of the screen so that it can be wider + Bottom = -touchInputDrum.DrawHeight * offscreenPercent, // The drum should go past the bottom of the screen so that it can be wider }; } From c33a661a4990d681ec595a7b35afc9016d6b144c Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 03:36:03 -0800 Subject: [PATCH 013/481] osu!taiko touch implementation syntax formatting cleanup --- osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs index 609da0b389..d5215c05e9 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -10,13 +10,15 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko.UI { - internal class DrumSamplePlayer : Container, IKeyBindingHandler { + internal class DrumSamplePlayer : Container, IKeyBindingHandler + { private DrumSampleTriggerSource leftRimSampleTriggerSource; private DrumSampleTriggerSource leftCentreSampleTriggerSource; private DrumSampleTriggerSource rightCentreSampleTriggerSource; private DrumSampleTriggerSource rightRimSampleTriggerSource; - public DrumSamplePlayer(HitObjectContainer hitObjectContainer) { + public DrumSamplePlayer(HitObjectContainer hitObjectContainer) + { Children = new Drawable[] { leftRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), From 35053eaeba38a8e15b609b7ee18421c532a9321a Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 04:43:57 -0800 Subject: [PATCH 014/481] Show and hide osu!taiko touch controls overlay based on most recent input type detected --- .../UI/DrumTouchInputArea.cs | 22 ++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index ccf59507d3..4b2c9c9936 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -35,12 +35,15 @@ namespace osu.Game.Rulesets.Taiko.UI // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); + private Container visibleComponents; + public DrumTouchInputArea() { RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; Children = new Drawable[] { - new Container() { + visibleComponents = new Container() { + Alpha = 0.0f, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, Anchor = Anchor.BottomCentre, @@ -81,6 +84,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnMouseDown(MouseDownEvent e) { + ShowTouchControls(); mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); keyBindingContainer?.TriggerPressed(mouseAction); return true; @@ -94,6 +98,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnTouchDown(TouchDownEvent e) { + ShowTouchControls(); TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); touchActions.Add(e.Touch.Source, taikoAction); keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); @@ -108,6 +113,21 @@ namespace osu.Game.Rulesets.Taiko.UI base.OnTouchUp(e); } + protected override bool OnKeyDown(KeyDownEvent e) + { + HideTouchControls(); + return false; + } + + + public void ShowTouchControls() { + visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); + } + + public void HideTouchControls() { + visibleComponents.Animate(components => components.FadeOut(2000, Easing.OutQuint)); + } + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { bool centreHit = inputIsCenterHit(inputPosition); bool leftSide = inputIsOnLeftSide(inputPosition); From 848b005097de38e213d7f33cf474e33840b7af6f Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Fri, 11 Mar 2022 04:48:57 -0800 Subject: [PATCH 015/481] Remove unneccessary whitespace --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 4b2c9c9936..a944d3770b 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -119,7 +119,6 @@ namespace osu.Game.Rulesets.Taiko.UI return false; } - public void ShowTouchControls() { visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); } From ac17c047f6264eff3c289810f83f1f5110e495d0 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 05:01:40 -0800 Subject: [PATCH 016/481] Code formatting --- .../UI/DrumTouchInputArea.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index a944d3770b..046816c013 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -37,7 +37,8 @@ namespace osu.Game.Rulesets.Taiko.UI private Container visibleComponents; - public DrumTouchInputArea() { + public DrumTouchInputArea() + { RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; Children = new Drawable[] @@ -102,7 +103,6 @@ namespace osu.Game.Rulesets.Taiko.UI TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); touchActions.Add(e.Touch.Source, taikoAction); keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); - return true; } @@ -119,15 +119,18 @@ namespace osu.Game.Rulesets.Taiko.UI return false; } - public void ShowTouchControls() { + public void ShowTouchControls() + { visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); } - public void HideTouchControls() { + public void HideTouchControls() + { visibleComponents.Animate(components => components.FadeOut(2000, Easing.OutQuint)); } - private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) + { bool centreHit = inputIsCenterHit(inputPosition); bool leftSide = inputIsOnLeftSide(inputPosition); @@ -136,12 +139,14 @@ namespace osu.Game.Rulesets.Taiko.UI (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); } - private bool inputIsOnLeftSide(Vector2 inputPosition) { + private bool inputIsOnLeftSide(Vector2 inputPosition) + { Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; return inputPositionToDrumCentreDelta.X < 0f; } - private bool inputIsCenterHit(Vector2 inputPosition) { + private bool inputIsCenterHit(Vector2 inputPosition) + { Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; From 6f99455d9490469af3938fe19fcef361bdab1098 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 05:17:45 -0800 Subject: [PATCH 017/481] Improve centre input size fitting for legacy skins --- .../Skinning/Legacy/LegacyInputDrum.cs | 1 + osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 2 +- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index d3eeb8e16a..8ab9e56aec 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy /// internal class LegacyInputDrum : Container { + public float centre_size = 0.5f; private LegacyHalfDrum left; private LegacyHalfDrum right; private Container content; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 046816c013..5f4eecd3bb 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.UI Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; - float centreRadius = (inputDrumRadius * InputDrum.centre_size); + float centreRadius = (inputDrumRadius * touchInputDrum.centre_size); return inputPositionToDrumCentreDelta.Length <= centreRadius; } } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 76594b86c1..a7ae763ab5 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { - public const float centre_size = 0.7f; + public float centre_size = 0.7f; private const float middle_split = 0.025f; public InputDrum() @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.UI Scale = new Vector2(0.9f), Children = new Drawable[] { - new TaikoHalfDrum(false) + new TaikoHalfDrum(false, centre_size) { Name = "Left Half", Anchor = Anchor.Centre, @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI RimAction = TaikoAction.LeftRim, CentreAction = TaikoAction.LeftCentre }, - new TaikoHalfDrum(true) + new TaikoHalfDrum(true, centre_size) { Name = "Right Half", Anchor = Anchor.Centre, @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - public TaikoHalfDrum(bool flipped) + public TaikoHalfDrum(bool flipped, float centre_size) { Masking = true; From 9db80c33350d194fef952c0fb98e0c1252b09097 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 05:32:02 -0800 Subject: [PATCH 018/481] Code cleanup --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 5f4eecd3bb..82b289ef14 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Taiko.UI // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) private const float offscreenPercent = 0.35f; private InputDrum touchInputDrum; - private Circle drumBackground; + private Circle drumBackground; private KeyBindingContainer keyBindingContainer; @@ -70,7 +70,8 @@ namespace osu.Game.Rulesets.Taiko.UI protected override void LoadComplete() { - Padding = new MarginPadding { + Padding = new MarginPadding + { Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield Bottom = -touchInputDrum.DrawHeight * offscreenPercent, // The drum should go past the bottom of the screen so that it can be wider }; From 2de6bb033bffdaeeb8a1095264b9ad745b0065c3 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 07:51:40 -0800 Subject: [PATCH 019/481] Adjust default touch drum overlay size to be more comfortable on phones --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 82b289ef14..e501a40d20 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float offscreenPercent = 0.35f; + private const float offscreenPercent = 0.4f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -62,6 +62,7 @@ namespace osu.Game.Rulesets.Taiko.UI touchInputDrum = new InputDrum() { Anchor = Anchor.Centre, Origin = Anchor.Centre, + centre_size = 0.8f, }, } }, From b628a65cfa5e790961ec3db6edc27fe41c89334b Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Sat, 12 Mar 2022 08:03:24 -0800 Subject: [PATCH 020/481] Revert "Adjust default touch drum overlay size to be more comfortable on phones" This reverts commit 2de6bb033bffdaeeb8a1095264b9ad745b0065c3. --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index e501a40d20..82b289ef14 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float offscreenPercent = 0.4f; + private const float offscreenPercent = 0.35f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -62,7 +62,6 @@ namespace osu.Game.Rulesets.Taiko.UI touchInputDrum = new InputDrum() { Anchor = Anchor.Centre, Origin = Anchor.Centre, - centre_size = 0.8f, }, } }, From ac329664276eeaec1a41b2284e69dfefc065ac9c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 3 Apr 2022 01:31:51 +0300 Subject: [PATCH 021/481] Replicate osu!(stable)'s hit target position with "Classic" mod --- osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs | 3 +++ osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 8 +++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index 5a6f57bc36..d18b88761d 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -16,6 +16,9 @@ namespace osu.Game.Rulesets.Taiko.Mods { drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset; drawableTaikoRuleset.LockPlayfieldAspect.Value = false; + + var playfield = (TaikoPlayfield)drawableRuleset.Playfield; + playfield.ClassicHitTargetPosition.Value = true; } public void Update(Playfield playfield) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 504b10e9bc..f2fa63c99e 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; @@ -31,6 +32,11 @@ namespace osu.Game.Rulesets.Taiko.UI /// public const float DEFAULT_HEIGHT = 200; + /// + /// Whether the hit target should be nudged further towards the left area, matching the stable "classic" position. + /// + public Bindable ClassicHitTargetPosition = new BindableBool(); + private Container hitExplosionContainer; private Container kiaiExplosionContainer; private JudgementContainer judgementContainer; @@ -190,7 +196,7 @@ namespace osu.Game.Rulesets.Taiko.UI // Padding is required to be updated for elements which are based on "absolute" X sized elements. // This is basically allowing for correct alignment as relative pieces move around them. - rightArea.Padding = new MarginPadding { Left = leftArea.DrawWidth }; + rightArea.Padding = new MarginPadding { Left = leftArea.DrawWidth - (ClassicHitTargetPosition.Value ? 45 : 0) }; hitTargetOffsetContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; mascot.Scale = new Vector2(DrawHeight / DEFAULT_HEIGHT); From 16f626fb646a637cc908a7d1733457008b7d10ad Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 6 Apr 2022 01:21:19 +0300 Subject: [PATCH 022/481] Add legacy classic taiko player test scene --- .../TestSceneLegacyTaikoPlayer.cs | 28 +++++++++++++++++++ .../TestSceneTaikoPlayer.cs | 9 ++++++ 2 files changed, 37 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneLegacyTaikoPlayer.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneLegacyTaikoPlayer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneLegacyTaikoPlayer.cs new file mode 100644 index 0000000000..d1f80768b6 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneLegacyTaikoPlayer.cs @@ -0,0 +1,28 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Game.Database; +using osu.Game.Rulesets.Taiko.Mods; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class TestSceneLegacyTaikoPlayer : PlayerTestScene + { + protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset(); + + [BackgroundDependencyLoader] + private void load(SkinManager skins) + { + skins.CurrentSkinInfo.Value = DefaultLegacySkin.CreateInfo().ToLiveUnmanaged(); + } + + protected override TestPlayer CreatePlayer(Ruleset ruleset) + { + SelectedMods.Value = new[] { new TaikoModClassic() }; + return base.CreatePlayer(ruleset); + } + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs index cd7511241a..8b7a2d0c13 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs @@ -1,6 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Game.Database; +using osu.Game.Skinning; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests @@ -8,5 +11,11 @@ namespace osu.Game.Rulesets.Taiko.Tests public class TestSceneTaikoPlayer : PlayerTestScene { protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset(); + + [BackgroundDependencyLoader] + private void load(SkinManager skins) + { + skins.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLiveUnmanaged(); + } } } From 55c56c03a59837e6265174c80085ec62c61b0771 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 6 Apr 2022 02:34:07 +0300 Subject: [PATCH 023/481] Propagate legacy input drum size to main piece --- .../Skinning/Legacy/LegacyInputDrum.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 43c5c07f80..0d5ce6201b 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public LegacyInputDrum() { - RelativeSizeAxes = Axes.Both; + Size = new Vector2(180, 200); } [BackgroundDependencyLoader] @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { Child = content = new Container { - Size = new Vector2(180, 200), + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { new Sprite @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy const float ratio = 1.6f; // because the right half is flipped, we need to position using width - position to get the true "topleft" origin position - float negativeScaleAdjust = content.Width / ratio; + float negativeScaleAdjust = Width / ratio; if (skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value >= 2.1m) { @@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy // Relying on RelativeSizeAxes.Both + FillMode.Fit doesn't work due to the precise pixel layout requirements. // This is a bit ugly but makes the non-legacy implementations a lot cleaner to implement. - content.Scale = new Vector2(DrawHeight / content.Size.Y); + Scale = new Vector2(Parent.DrawHeight / Size.Y); } /// From e4f6e842b045b6bf09be49dc1821554aa0ed70cc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 6 Apr 2022 02:34:35 +0300 Subject: [PATCH 024/481] Expose input drum `SkinnableDrawable` in `InputDrum` for width consumption This is probably not a good way to approach this, but I'm unsure about any other way. --- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 16be20f7f3..1c76a214ed 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -24,6 +24,8 @@ namespace osu.Game.Rulesets.Taiko.UI { private const float middle_split = 0.025f; + public SkinnableDrawable Skinnable { get; private set; } + [Cached] private DrumSampleTriggerSource sampleTriggerSource; @@ -39,7 +41,7 @@ namespace osu.Game.Rulesets.Taiko.UI { Children = new Drawable[] { - new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container + Skinnable = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, @@ -69,7 +71,10 @@ namespace osu.Game.Rulesets.Taiko.UI CentreAction = TaikoAction.RightCentre } } - }), + }) + { + CentreComponent = false, + }, sampleTriggerSource }; } From ec5ad995f830bd74b8f4ecae8d8e5f7898250948 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 6 Apr 2022 02:37:51 +0300 Subject: [PATCH 025/481] Reorder taiko playfield elements to fix hit explosion Z-ordering --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 95 +++++++++++--------- 1 file changed, 54 insertions(+), 41 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index f2fa63c99e..63b9d4d9ad 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -48,8 +48,8 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly IDictionary explosionPools = new Dictionary(); private ProxyContainer topLevelHitContainer; + private InputDrum inputDrum; private Container rightArea; - private Container leftArea; /// /// is purposefully not called on this to prevent i.e. being able to interact @@ -57,14 +57,36 @@ namespace osu.Game.Rulesets.Taiko.UI /// private BarLinePlayfield barLinePlayfield; - private Container hitTargetOffsetContent; + private Container playfieldContent; + private Container playfieldOverlay; [BackgroundDependencyLoader] private void load(OsuColour colours) { + Container leftArea = null; + InternalChildren = new[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundRight), _ => new PlayfieldBackgroundRight()), + leftArea = new Container + { + Name = "Left overlay", + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + BorderColour = colours.Gray0, + Children = new Drawable[] + { + new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), + } + }, + mascot = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Mascot), _ => Empty()) + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.TopLeft, + RelativePositionAxes = Axes.Y, + RelativeSizeAxes = Axes.None, + Y = 0.2f + }, rightArea = new Container { Name = "Right area", @@ -74,7 +96,7 @@ namespace osu.Game.Rulesets.Taiko.UI { new Container { - Name = "Masked elements before hit objects", + Name = "Elements before hit objects", RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, Children = new[] @@ -89,22 +111,28 @@ namespace osu.Game.Rulesets.Taiko.UI } } }, - hitTargetOffsetContent = new Container + new Container { + Name = "Masked hit objects content", + RelativeSizeAxes = Axes.Both, + Masking = true, + Child = playfieldContent = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + barLinePlayfield = new BarLinePlayfield(), + HitObjectContainer, + } + } + }, + playfieldOverlay = new Container + { + Name = "Elements after hit objects", RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - barLinePlayfield = new BarLinePlayfield(), - new Container - { - Name = "Hit objects", - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - HitObjectContainer, - drumRollHitContainer = new DrumRollHitContainer() - } - }, + drumRollHitContainer = new DrumRollHitContainer(), kiaiExplosionContainer = new Container { Name = "Kiai hit explosions", @@ -120,38 +148,21 @@ namespace osu.Game.Rulesets.Taiko.UI }, } }, - leftArea = new Container - { - Name = "Left overlay", - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - BorderColour = colours.Gray0, - Children = new Drawable[] - { - new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), - new InputDrum(HitObjectContainer) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - } - }, - mascot = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Mascot), _ => Empty()) - { - Origin = Anchor.BottomLeft, - Anchor = Anchor.TopLeft, - RelativePositionAxes = Axes.Y, - RelativeSizeAxes = Axes.None, - Y = 0.2f - }, topLevelHitContainer = new ProxyContainer { Name = "Top level hit objects", RelativeSizeAxes = Axes.Both, }, drumRollHitContainer.CreateProxy(), + inputDrum = new InputDrum(HitObjectContainer) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, }; + leftArea.Add(inputDrum.CreateProxy()); + RegisterPool(50); RegisterPool(50); @@ -196,8 +207,10 @@ namespace osu.Game.Rulesets.Taiko.UI // Padding is required to be updated for elements which are based on "absolute" X sized elements. // This is basically allowing for correct alignment as relative pieces move around them. - rightArea.Padding = new MarginPadding { Left = leftArea.DrawWidth - (ClassicHitTargetPosition.Value ? 45 : 0) }; - hitTargetOffsetContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; + var inputDrumSize = inputDrum.Skinnable.Drawable.ToSpaceOfOtherDrawable(inputDrum.Skinnable.Drawable.DrawSize, this); + rightArea.Padding = new MarginPadding { Left = inputDrumSize.X }; + playfieldContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; + playfieldOverlay.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; mascot.Scale = new Vector2(DrawHeight / DEFAULT_HEIGHT); } From ce70957fbfa0d9b6d0d78d3c96e886c98b052cd9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 6 Apr 2022 02:38:56 +0300 Subject: [PATCH 026/481] Remove redundant code --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 3 +-- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 0d5ce6201b..5ff952f929 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { private LegacyHalfDrum left; private LegacyHalfDrum right; - private Container content; public LegacyInputDrum() { @@ -32,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy [BackgroundDependencyLoader] private void load(ISkinSource skin) { - Child = content = new Container + Child = new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 63b9d4d9ad..610078600a 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load(OsuColour colours) { - Container leftArea = null; + Container leftArea; InternalChildren = new[] { From c1693e4387c51be66faf53d38bf0e1706d0a54a1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 7 Apr 2022 00:12:40 +0300 Subject: [PATCH 027/481] Use `LegacySkinPlayerTestScene` instead of reimplementing --- osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs | 9 --------- ...ikoPlayer.cs => TestSceneTaikoPlayerLegacySkin.cs} | 11 +---------- 2 files changed, 1 insertion(+), 19 deletions(-) rename osu.Game.Rulesets.Taiko.Tests/{TestSceneLegacyTaikoPlayer.cs => TestSceneTaikoPlayerLegacySkin.cs} (62%) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs index 8b7a2d0c13..cd7511241a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs @@ -1,9 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; -using osu.Game.Database; -using osu.Game.Skinning; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests @@ -11,11 +8,5 @@ namespace osu.Game.Rulesets.Taiko.Tests public class TestSceneTaikoPlayer : PlayerTestScene { protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset(); - - [BackgroundDependencyLoader] - private void load(SkinManager skins) - { - skins.CurrentSkinInfo.Value = DefaultSkin.CreateInfo().ToLiveUnmanaged(); - } } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneLegacyTaikoPlayer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerLegacySkin.cs similarity index 62% rename from osu.Game.Rulesets.Taiko.Tests/TestSceneLegacyTaikoPlayer.cs rename to osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerLegacySkin.cs index d1f80768b6..13df24c988 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneLegacyTaikoPlayer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayerLegacySkin.cs @@ -1,24 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; -using osu.Game.Database; using osu.Game.Rulesets.Taiko.Mods; -using osu.Game.Skinning; using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests { - public class TestSceneLegacyTaikoPlayer : PlayerTestScene + public class TestSceneTaikoPlayerLegacySkin : LegacySkinPlayerTestScene { protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset(); - [BackgroundDependencyLoader] - private void load(SkinManager skins) - { - skins.CurrentSkinInfo.Value = DefaultLegacySkin.CreateInfo().ToLiveUnmanaged(); - } - protected override TestPlayer CreatePlayer(Ruleset ruleset) { SelectedMods.Value = new[] { new TaikoModClassic() }; From b84a3b7834df1604a60e928b4dd211be67904049 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 7 Apr 2022 21:39:53 +0300 Subject: [PATCH 028/481] Rewrite input drum measurements to autosize on X axis --- .../Skinning/Legacy/LegacyInputDrum.cs | 12 +- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 252 ++++++++++-------- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 20 +- 3 files changed, 154 insertions(+), 130 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 5ff952f929..476ad4a75d 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -20,20 +20,22 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy /// internal class LegacyInputDrum : Container { + private Container content; private LegacyHalfDrum left; private LegacyHalfDrum right; public LegacyInputDrum() { - Size = new Vector2(180, 200); + RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; } [BackgroundDependencyLoader] private void load(ISkinSource skin) { - Child = new Container + Child = content = new Container { - RelativeSizeAxes = Axes.Both, + Size = new Vector2(180, 200), Children = new Drawable[] { new Sprite @@ -66,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy const float ratio = 1.6f; // because the right half is flipped, we need to position using width - position to get the true "topleft" origin position - float negativeScaleAdjust = Width / ratio; + float negativeScaleAdjust = content.Width / ratio; if (skin.GetConfig(SkinConfiguration.LegacySetting.Version)?.Value >= 2.1m) { @@ -90,7 +92,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy // Relying on RelativeSizeAxes.Both + FillMode.Fit doesn't work due to the precise pixel layout requirements. // This is a bit ugly but makes the non-legacy implementations a lot cleaner to implement. - Scale = new Vector2(Parent.DrawHeight / Size.Y); + content.Scale = new Vector2(DrawHeight / content.Size.Y); } /// diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 1c76a214ed..ad77eb100b 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -12,6 +12,7 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osuTK; @@ -24,8 +25,6 @@ namespace osu.Game.Rulesets.Taiko.UI { private const float middle_split = 0.025f; - public SkinnableDrawable Skinnable { get; private set; } - [Cached] private DrumSampleTriggerSource sampleTriggerSource; @@ -33,7 +32,8 @@ namespace osu.Game.Rulesets.Taiko.UI { sampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer); - RelativeSizeAxes = Axes.Both; + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; } [BackgroundDependencyLoader] @@ -41,12 +41,32 @@ namespace osu.Game.Rulesets.Taiko.UI { Children = new Drawable[] { - Skinnable = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container + new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new DefaultInputDrum()) { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + }, + sampleTriggerSource + }; + } + + private class DefaultInputDrum : AspectContainer + { + public DefaultInputDrum() + { + RelativeSizeAxes = Axes.Y; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, Scale = new Vector2(0.9f), - Children = new Drawable[] + Children = new[] { new TaikoHalfDrum(false) { @@ -71,134 +91,130 @@ namespace osu.Game.Rulesets.Taiko.UI CentreAction = TaikoAction.RightCentre } } - }) - { - CentreComponent = false, - }, - sampleTriggerSource - }; - } - - /// - /// A half-drum. Contains one centre and one rim hit. - /// - private class TaikoHalfDrum : Container, IKeyBindingHandler - { - /// - /// The key to be used for the rim of the half-drum. - /// - public TaikoAction RimAction; - - /// - /// The key to be used for the centre of the half-drum. - /// - public TaikoAction CentreAction; - - private readonly Sprite rim; - private readonly Sprite rimHit; - private readonly Sprite centre; - private readonly Sprite centreHit; - - [Resolved] - private DrumSampleTriggerSource sampleTriggerSource { get; set; } - - public TaikoHalfDrum(bool flipped) - { - Masking = true; - - Children = new Drawable[] - { - rim = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both - }, - rimHit = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Blending = BlendingParameters.Additive, - }, - centre = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.7f) - }, - centreHit = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.7f), - Alpha = 0, - Blending = BlendingParameters.Additive - } }; } - [BackgroundDependencyLoader] - private void load(TextureStore textures, OsuColour colours) + /// + /// A half-drum. Contains one centre and one rim hit. + /// + private class TaikoHalfDrum : Container, IKeyBindingHandler { - rim.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer"); - rimHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer-hit"); - centre.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner"); - centreHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner-hit"); + /// + /// The key to be used for the rim of the half-drum. + /// + public TaikoAction RimAction; - rimHit.Colour = colours.Blue; - centreHit.Colour = colours.Pink; - } + /// + /// The key to be used for the centre of the half-drum. + /// + public TaikoAction CentreAction; - public bool OnPressed(KeyBindingPressEvent e) - { - Drawable target = null; - Drawable back = null; + private readonly Sprite rim; + private readonly Sprite rimHit; + private readonly Sprite centre; + private readonly Sprite centreHit; - if (e.Action == CentreAction) + [Resolved] + private DrumSampleTriggerSource sampleTriggerSource { get; set; } + + public TaikoHalfDrum(bool flipped) { - target = centreHit; - back = centre; + Masking = true; - sampleTriggerSource.Play(HitType.Centre); - } - else if (e.Action == RimAction) - { - target = rimHit; - back = rim; - - sampleTriggerSource.Play(HitType.Rim); + Children = new Drawable[] + { + rim = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both + }, + rimHit = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Blending = BlendingParameters.Additive, + }, + centre = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.7f) + }, + centreHit = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.7f), + Alpha = 0, + Blending = BlendingParameters.Additive + } + }; } - if (target != null) + [BackgroundDependencyLoader] + private void load(TextureStore textures, OsuColour colours) { - const float scale_amount = 0.05f; - const float alpha_amount = 0.5f; + rim.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer"); + rimHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer-hit"); + centre.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner"); + centreHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner-hit"); - const float down_time = 40; - const float up_time = 1000; - - back.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint) - .Then() - .ScaleTo(1, up_time, Easing.OutQuint); - - target.Animate( - t => t.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint), - t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, Easing.OutQuint) - ).Then( - t => t.ScaleTo(1, up_time, Easing.OutQuint), - t => t.FadeOut(up_time, Easing.OutQuint) - ); + rimHit.Colour = colours.Blue; + centreHit.Colour = colours.Pink; } - return false; - } + public bool OnPressed(KeyBindingPressEvent e) + { + Drawable target = null; + Drawable back = null; - public void OnReleased(KeyBindingReleaseEvent e) - { + if (e.Action == CentreAction) + { + target = centreHit; + back = centre; + + sampleTriggerSource.Play(HitType.Centre); + } + else if (e.Action == RimAction) + { + target = rimHit; + back = rim; + + sampleTriggerSource.Play(HitType.Rim); + } + + if (target != null) + { + const float scale_amount = 0.05f; + const float alpha_amount = 0.5f; + + const float down_time = 40; + const float up_time = 1000; + + back.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint) + .Then() + .ScaleTo(1, up_time, Easing.OutQuint); + + target.Animate( + t => t.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint), + t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, Easing.OutQuint) + ).Then( + t => t.ScaleTo(1, up_time, Easing.OutQuint), + t => t.FadeOut(up_time, Easing.OutQuint) + ); + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } } } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 610078600a..5891672f29 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -77,6 +77,13 @@ namespace osu.Game.Rulesets.Taiko.UI Children = new Drawable[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), + inputDrum = new InputDrum(HitObjectContainer) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + }, } }, mascot = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Mascot), _ => Empty()) @@ -154,13 +161,13 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, }, drumRollHitContainer.CreateProxy(), - inputDrum = new InputDrum(HitObjectContainer) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, }; + // to prioritise receiving key presses on input drum before objects, move input drum to the end of the hierarchy... + leftArea.Remove(inputDrum); + AddInternal(inputDrum); + + // ...and create a proxy to keep the input drum displayed behind the playfield elements. leftArea.Add(inputDrum.CreateProxy()); RegisterPool(50); @@ -207,8 +214,7 @@ namespace osu.Game.Rulesets.Taiko.UI // Padding is required to be updated for elements which are based on "absolute" X sized elements. // This is basically allowing for correct alignment as relative pieces move around them. - var inputDrumSize = inputDrum.Skinnable.Drawable.ToSpaceOfOtherDrawable(inputDrum.Skinnable.Drawable.DrawSize, this); - rightArea.Padding = new MarginPadding { Left = inputDrumSize.X }; + rightArea.Padding = new MarginPadding { Left = inputDrum.Width }; playfieldContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; playfieldOverlay.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 }; From f48533b8a292d95202f1a3833e1569391c5a160a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 13 Apr 2022 04:38:07 +0300 Subject: [PATCH 029/481] Inline input drum proxying logic --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 30 +++++++++----------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 5891672f29..d75e3fb303 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -63,27 +63,27 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load(OsuColour colours) { - Container leftArea; + inputDrum = new InputDrum(HitObjectContainer) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + }; InternalChildren = new[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundRight), _ => new PlayfieldBackgroundRight()), - leftArea = new Container + new Container { Name = "Left overlay", RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, BorderColour = colours.Gray0, - Children = new Drawable[] + Children = new[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), - inputDrum = new InputDrum(HitObjectContainer) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.X, - RelativeSizeAxes = Axes.Y, - }, + inputDrum.CreateProxy(), } }, mascot = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.Mascot), _ => Empty()) @@ -161,15 +161,11 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, }, drumRollHitContainer.CreateProxy(), + // this is added at the end of the hierarchy to receive input before taiko objects. + // but is proxied below everything to not cover visual effects such as hit explosions. + inputDrum, }; - // to prioritise receiving key presses on input drum before objects, move input drum to the end of the hierarchy... - leftArea.Remove(inputDrum); - AddInternal(inputDrum); - - // ...and create a proxy to keep the input drum displayed behind the playfield elements. - leftArea.Add(inputDrum.CreateProxy()); - RegisterPool(50); RegisterPool(50); From f7a658450fb2592a171165b57528786d32190d0d Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Wed, 1 Jun 2022 00:54:49 -0700 Subject: [PATCH 030/481] Move DrawableTaikoRulesetTestScene's hardcoded beatmap to CreateBeatmap(..) method instead of load(..) method, so that the class is more extensible and reusable --- .../DrawableTaikoRulesetTestScene.cs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs index f5e7304c12..2853aa32f4 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs @@ -21,13 +21,29 @@ namespace osu.Game.Rulesets.Taiko.Tests protected DrawableTaikoRuleset DrawableRuleset { get; private set; } protected Container PlayfieldContainer { get; private set; } + protected ControlPointInfo controlPointInfo { get; private set; } + [BackgroundDependencyLoader] private void load() { - var controlPointInfo = new ControlPointInfo(); + controlPointInfo = new ControlPointInfo(); controlPointInfo.Add(0, new TimingControlPoint()); - IWorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap + IWorkingBeatmap beatmap = CreateWorkingBeatmap(CreateBeatmap(new TaikoRuleset().RulesetInfo)); + + Add(PlayfieldContainer = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + Height = DEFAULT_PLAYFIELD_CONTAINER_HEIGHT, + Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset)) } + }); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + return new Beatmap { HitObjects = new List { new Hit { Type = HitType.Centre } }, BeatmapInfo = new BeatmapInfo @@ -39,19 +55,10 @@ namespace osu.Game.Rulesets.Taiko.Tests Title = @"Sample Beatmap", Author = { Username = @"peppy" }, }, - Ruleset = new TaikoRuleset().RulesetInfo + Ruleset = ruleset }, ControlPointInfo = controlPointInfo - }); - - Add(PlayfieldContainer = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - Height = DEFAULT_PLAYFIELD_CONTAINER_HEIGHT, - Children = new[] { DrawableRuleset = new DrawableTaikoRuleset(new TaikoRuleset(), beatmap.GetPlayableBeatmap(new TaikoRuleset().RulesetInfo)) } - }); + }; } } } From ae996d1404611ad99c5e66d4ee650ecdbac6a86a Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Wed, 1 Jun 2022 00:56:03 -0700 Subject: [PATCH 031/481] Add manual test scene for DrumTouchInputArea --- .../TestSceneDrumTouchInputArea.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs new file mode 100644 index 0000000000..e82594cf4f --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs @@ -0,0 +1,53 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.UI; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [TestFixture] + public class TestSceneDrumTouchInputArea : DrawableTaikoRulesetTestScene + { + protected const double NUM_HIT_OBJECTS = 10; + protected const double HIT_OBJECT_TIME_SPACING_MS = 1000; + + [BackgroundDependencyLoader] + private void load() + { + var drumTouchInputArea = new DrumTouchInputArea(); + DrawableRuleset.KeyBindingInputManager.Add(drumTouchInputArea); + drumTouchInputArea.ShowTouchControls(); + } + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + List hitObjects = new List(); + + for (var i = 0; i < NUM_HIT_OBJECTS; i++) { + hitObjects.Add(new Hit + { + StartTime = Time.Current + i * HIT_OBJECT_TIME_SPACING_MS, + IsStrong = isOdd(i), + Type = isOdd(i / 2) ? HitType.Centre : HitType.Rim + }); + } + + var beatmap = new Beatmap + { + BeatmapInfo = { Ruleset = ruleset }, + HitObjects = hitObjects + }; + + return beatmap; + } + + private bool isOdd(int number) { + return number % 2 == 0; + } + } +} From fcc05396bcb2425482741e9cfc55c4bf32a580a7 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Wed, 1 Jun 2022 00:57:24 -0700 Subject: [PATCH 032/481] Remove unused import --- osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index b5bf33bc9f..e52c95e34a 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects; From f6e9dfe7bfda690cee565adbbb06cce0fb7cd039 Mon Sep 17 00:00:00 2001 From: Aaron Hong Date: Wed, 1 Jun 2022 01:03:21 -0700 Subject: [PATCH 033/481] Fix naming rule violations --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 2 +- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 6 +++--- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 8ab9e56aec..7a74567abb 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy /// internal class LegacyInputDrum : Container { - public float centre_size = 0.5f; + public float CentreSize = 0.5f; private LegacyHalfDrum left; private LegacyHalfDrum right; private Container content; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 82b289ef14..959666ffe9 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Taiko.UI public class DrumTouchInputArea : Container { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float offscreenPercent = 0.35f; + private const float offscreen_percent = 0.35f; private InputDrum touchInputDrum; private Circle drumBackground; @@ -73,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.UI Padding = new MarginPadding { Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield - Bottom = -touchInputDrum.DrawHeight * offscreenPercent, // The drum should go past the bottom of the screen so that it can be wider + Bottom = -touchInputDrum.DrawHeight * offscreen_percent, // The drum should go past the bottom of the screen so that it can be wider }; } @@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.Taiko.UI Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; - float centreRadius = (inputDrumRadius * touchInputDrum.centre_size); + float centreRadius = (inputDrumRadius * touchInputDrum.CentreSize); return inputPositionToDrumCentreDelta.Length <= centreRadius; } } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index a7ae763ab5..f1120e44ab 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { - public float centre_size = 0.7f; + public float CentreSize = 0.7f; private const float middle_split = 0.025f; public InputDrum() @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.UI Scale = new Vector2(0.9f), Children = new Drawable[] { - new TaikoHalfDrum(false, centre_size) + new TaikoHalfDrum(false, CentreSize) { Name = "Left Half", Anchor = Anchor.Centre, @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI RimAction = TaikoAction.LeftRim, CentreAction = TaikoAction.LeftCentre }, - new TaikoHalfDrum(true, centre_size) + new TaikoHalfDrum(true, CentreSize) { Name = "Right Half", Anchor = Anchor.Centre, From f3d4cd3f9545040d4b62b22a3e32cafaac5bc6e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 14:36:07 +0900 Subject: [PATCH 034/481] Fix various code inspection issues --- .../DrawableTaikoRulesetTestScene.cs | 2 +- .../TestSceneDrumTouchInputArea.cs | 8 ++++--- .../UI/DrumSamplePlayer.cs | 11 ++++++---- .../UI/DrumTouchInputArea.cs | 22 ++++++++++--------- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 6 ++--- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- 6 files changed, 29 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs index 2853aa32f4..a099795d12 100644 --- a/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs +++ b/osu.Game.Rulesets.Taiko.Tests/DrawableTaikoRulesetTestScene.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Tests protected DrawableTaikoRuleset DrawableRuleset { get; private set; } protected Container PlayfieldContainer { get; private set; } - protected ControlPointInfo controlPointInfo { get; private set; } + private ControlPointInfo controlPointInfo { get; set; } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs index e82594cf4f..45555a55c1 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs @@ -28,7 +28,8 @@ namespace osu.Game.Rulesets.Taiko.Tests { List hitObjects = new List(); - for (var i = 0; i < NUM_HIT_OBJECTS; i++) { + for (int i = 0; i < NUM_HIT_OBJECTS; i++) + { hitObjects.Add(new Hit { StartTime = Time.Current + i * HIT_OBJECT_TIME_SPACING_MS, @@ -36,7 +37,7 @@ namespace osu.Game.Rulesets.Taiko.Tests Type = isOdd(i / 2) ? HitType.Centre : HitType.Rim }); } - + var beatmap = new Beatmap { BeatmapInfo = { Ruleset = ruleset }, @@ -46,7 +47,8 @@ namespace osu.Game.Rulesets.Taiko.Tests return beatmap; } - private bool isOdd(int number) { + private bool isOdd(int number) + { return number % 2 == 0; } } diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs index d5215c05e9..47a9094ed2 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -12,10 +12,10 @@ namespace osu.Game.Rulesets.Taiko.UI { internal class DrumSamplePlayer : Container, IKeyBindingHandler { - private DrumSampleTriggerSource leftRimSampleTriggerSource; - private DrumSampleTriggerSource leftCentreSampleTriggerSource; - private DrumSampleTriggerSource rightCentreSampleTriggerSource; - private DrumSampleTriggerSource rightRimSampleTriggerSource; + private readonly DrumSampleTriggerSource leftRimSampleTriggerSource; + private readonly DrumSampleTriggerSource leftCentreSampleTriggerSource; + private readonly DrumSampleTriggerSource rightCentreSampleTriggerSource; + private readonly DrumSampleTriggerSource rightRimSampleTriggerSource; public DrumSamplePlayer(HitObjectContainer hitObjectContainer) { @@ -35,12 +35,15 @@ namespace osu.Game.Rulesets.Taiko.UI case TaikoAction.LeftRim: leftRimSampleTriggerSource.Play(HitType.Rim); break; + case TaikoAction.LeftCentre: leftCentreSampleTriggerSource.Play(HitType.Centre); break; + case TaikoAction.RightCentre: rightCentreSampleTriggerSource.Play(HitType.Centre); break; + case TaikoAction.RightRim: rightRimSampleTriggerSource.Play(HitType.Rim); break; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 959666ffe9..8d1b8a4478 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -24,8 +24,9 @@ namespace osu.Game.Rulesets.Taiko.UI { // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) private const float offscreen_percent = 0.35f; - private InputDrum touchInputDrum; - private Circle drumBackground; + + private readonly InputDrum touchInputDrum; + private readonly Circle drumBackground; private KeyBindingContainer keyBindingContainer; @@ -33,9 +34,9 @@ namespace osu.Game.Rulesets.Taiko.UI private TaikoAction mouseAction; // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved - private Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); + private readonly Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); - private Container visibleComponents; + private readonly Container visibleComponents; public DrumTouchInputArea() { @@ -43,7 +44,8 @@ namespace osu.Game.Rulesets.Taiko.UI RelativePositionAxes = Axes.Both; Children = new Drawable[] { - visibleComponents = new Container() { + visibleComponents = new Container + { Alpha = 0.0f, RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, @@ -51,7 +53,8 @@ namespace osu.Game.Rulesets.Taiko.UI Origin = Anchor.BottomCentre, Children = new Drawable[] { - drumBackground = new Circle() { + drumBackground = new Circle + { RelativeSizeAxes = Axes.Both, RelativePositionAxes = Axes.Both, Anchor = Anchor.Centre, @@ -59,7 +62,8 @@ namespace osu.Game.Rulesets.Taiko.UI FillMode = FillMode.Fit, Alpha = 0.9f, }, - touchInputDrum = new InputDrum() { + touchInputDrum = new InputDrum + { Anchor = Anchor.Centre, Origin = Anchor.Centre, }, @@ -135,9 +139,7 @@ namespace osu.Game.Rulesets.Taiko.UI bool centreHit = inputIsCenterHit(inputPosition); bool leftSide = inputIsOnLeftSide(inputPosition); - return centreHit ? - (leftSide ? TaikoAction.LeftCentre : TaikoAction.RightCentre) : - (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); + return centreHit ? (leftSide ? TaikoAction.LeftCentre : TaikoAction.RightCentre) : (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); } private bool inputIsOnLeftSide(Vector2 inputPosition) diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index f1120e44ab..e984bef3ce 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - public TaikoHalfDrum(bool flipped, float centre_size) + public TaikoHalfDrum(bool flipped, float centreSize) { Masking = true; @@ -112,14 +112,14 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(centre_size) + Size = new Vector2(centreSize) }, centreHit = new Sprite { Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Size = new Vector2(centre_size), + Size = new Vector2(centreSize), Alpha = 0, Blending = BlendingParameters.Additive } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index e24f861269..b83364ddf2 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -123,7 +123,7 @@ namespace osu.Game.Rulesets.Taiko.UI Children = new Drawable[] { new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()), - new InputDrum() + new InputDrum { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, From 45adca17da95f3ff1179661a8d87e48e13145806 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 14:39:57 +0900 Subject: [PATCH 035/481] Make `DrumSamplePlayer` a `CompositeDrawable` --- osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs index 47a9094ed2..b65e2af3d8 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumSamplePlayer.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Taiko.UI { - internal class DrumSamplePlayer : Container, IKeyBindingHandler + internal class DrumSamplePlayer : CompositeDrawable, IKeyBindingHandler { private readonly DrumSampleTriggerSource leftRimSampleTriggerSource; private readonly DrumSampleTriggerSource leftCentreSampleTriggerSource; @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.UI public DrumSamplePlayer(HitObjectContainer hitObjectContainer) { - Children = new Drawable[] + InternalChildren = new Drawable[] { leftRimSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), leftCentreSampleTriggerSource = new DrumSampleTriggerSource(hitObjectContainer), From 859a83ac900e48466921e3bf869b0d8bc86f9211 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Jun 2022 14:48:26 +0900 Subject: [PATCH 036/481] Remove unused field and fix typo --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs | 1 - osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index 7a74567abb..d3eeb8e16a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -18,7 +18,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy /// internal class LegacyInputDrum : Container { - public float CentreSize = 0.5f; private LegacyHalfDrum left; private LegacyHalfDrum right; private Container content; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 8d1b8a4478..a66c52df1f 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.UI /// /// An overlay that captures and displays Taiko mouse and touch input. /// The boundaries of this overlay defines the interactable area for touch input. - /// A secondary InputDrum is attached by this overlay, which defines the circulary boundary which distinguishes "centre" from "rim" hits, and also displays input. + /// A secondary InputDrum is attached by this overlay, which defines the circular boundary which distinguishes "centre" from "rim" hits, and also displays input. /// public class DrumTouchInputArea : Container { From 5b96f67a8b7e49a7d12da341fc298a125b4f5786 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Mon, 4 Jul 2022 20:49:26 +0100 Subject: [PATCH 037/481] Remove non-overlapping velocity buff --- .../Difficulty/Evaluators/AimEvaluator.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs index 0694746cbf..76d5ccf682 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Evaluators/AimEvaluator.cs @@ -108,13 +108,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Evaluators // Reward for % distance up to 125 / strainTime for overlaps where velocity is still changing. double overlapVelocityBuff = Math.Min(125 / Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime), Math.Abs(prevVelocity - currVelocity)); - // Reward for % distance slowed down compared to previous, paying attention to not award overlap - double nonOverlapVelocityBuff = Math.Abs(prevVelocity - currVelocity) - // do not award overlap - * Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, Math.Min(osuCurrObj.LazyJumpDistance, osuLastObj.LazyJumpDistance) / 100)), 2); - - // Choose the largest bonus, multiplied by ratio. - velocityChangeBonus = Math.Max(overlapVelocityBuff, nonOverlapVelocityBuff) * distRatio; + velocityChangeBonus = overlapVelocityBuff * distRatio; // Penalize for rhythm changes. velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2); From afec7941ffc9915fd99ba3e4fb680b0868edadee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 15 Jul 2022 17:24:40 +0900 Subject: [PATCH 038/481] Adjust default follow circle animations to feel nicer --- .../Skinning/Default/DefaultFollowCircle.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 254e220996..07b99560e5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK.Graphics; @@ -32,11 +33,21 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default protected override void OnTrackingChanged(ValueChangedEvent tracking) { - const float scale_duration = 300f; - const float fade_duration = 300f; + const float duration = 300f; - this.ScaleTo(tracking.NewValue ? DrawableSliderBall.FOLLOW_AREA : 1f, scale_duration, Easing.OutQuint) - .FadeTo(tracking.NewValue ? 1f : 0, fade_duration, Easing.OutQuint); + if (tracking.NewValue) + { + if (Precision.AlmostEquals(0, Alpha)) + this.ScaleTo(1); + + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA, duration, Easing.OutQuint) + .FadeTo(1f, duration, Easing.OutQuint); + } + else + { + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.2f, duration / 2, Easing.OutQuint) + .FadeTo(0, duration / 2, Easing.OutQuint); + } } protected override void OnSliderEnd() From 4ea8fd75cc4277833efca0df2b64f51bd30a8381 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 04:05:07 +0300 Subject: [PATCH 039/481] Replace `Country` class with enumeration --- osu.Game/Users/Country.cs | 770 +++++++++++++++++++++++++++++++++++++- 1 file changed, 755 insertions(+), 15 deletions(-) diff --git a/osu.Game/Users/Country.cs b/osu.Game/Users/Country.cs index b38600fa1b..d1f33627c1 100644 --- a/osu.Game/Users/Country.cs +++ b/osu.Game/Users/Country.cs @@ -1,27 +1,767 @@ // 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.ComponentModel; +using JetBrains.Annotations; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace osu.Game.Users { - public class Country : IEquatable + [JsonConverter(typeof(StringEnumConverter))] + public enum Country { - /// - /// The name of this country. - /// - [JsonProperty(@"name")] - public string FullName; + [Description("Alien")] + XX = 0, - /// - /// Two-letter flag acronym (ISO 3166 standard) - /// - [JsonProperty(@"code")] - public string FlagName; + [Description("Bangladesh")] + BD, - public bool Equals(Country other) => FlagName == other?.FlagName; + [Description("Belgium")] + BE, + + [Description("Burkina Faso")] + BF, + + [Description("Bulgaria")] + BG, + + [Description("Bosnia and Herzegovina")] + BA, + + [Description("Barbados")] + BB, + + [Description("Wallis and Futuna")] + WF, + + [Description("Saint Barthelemy")] + BL, + + [Description("Bermuda")] + BM, + + [Description("Brunei")] + BN, + + [Description("Bolivia")] + BO, + + [Description("Bahrain")] + BH, + + [Description("Burundi")] + BI, + + [Description("Benin")] + BJ, + + [Description("Bhutan")] + BT, + + [Description("Jamaica")] + JM, + + [Description("Bouvet Island")] + BV, + + [Description("Botswana")] + BW, + + [Description("Samoa")] + WS, + + [Description("Bonaire, Saint Eustatius and Saba")] + BQ, + + [Description("Brazil")] + BR, + + [Description("Bahamas")] + BS, + + [Description("Jersey")] + JE, + + [Description("Belarus")] + BY, + + [Description("Belize")] + BZ, + + [Description("Russia")] + RU, + + [Description("Rwanda")] + RW, + + [Description("Serbia")] + RS, + + [Description("East Timor")] + TL, + + [Description("Reunion")] + RE, + + [Description("Turkmenistan")] + TM, + + [Description("Tajikistan")] + TJ, + + [Description("Romania")] + RO, + + [Description("Tokelau")] + TK, + + [Description("Guinea-Bissau")] + GW, + + [Description("Guam")] + GU, + + [Description("Guatemala")] + GT, + + [Description("South Georgia and the South Sandwich Islands")] + GS, + + [Description("Greece")] + GR, + + [Description("Equatorial Guinea")] + GQ, + + [Description("Guadeloupe")] + GP, + + [Description("Japan")] + JP, + + [Description("Guyana")] + GY, + + [Description("Guernsey")] + GG, + + [Description("French Guiana")] + GF, + + [Description("Georgia")] + GE, + + [Description("Grenada")] + GD, + + [Description("United Kingdom")] + GB, + + [Description("Gabon")] + GA, + + [Description("El Salvador")] + SV, + + [Description("Guinea")] + GN, + + [Description("Gambia")] + GM, + + [Description("Greenland")] + GL, + + [Description("Gibraltar")] + GI, + + [Description("Ghana")] + GH, + + [Description("Oman")] + OM, + + [Description("Tunisia")] + TN, + + [Description("Jordan")] + JO, + + [Description("Croatia")] + HR, + + [Description("Haiti")] + HT, + + [Description("Hungary")] + HU, + + [Description("Hong Kong")] + HK, + + [Description("Honduras")] + HN, + + [Description("Heard Island and McDonald Islands")] + HM, + + [Description("Venezuela")] + VE, + + [Description("Puerto Rico")] + PR, + + [Description("Palestinian Territory")] + PS, + + [Description("Palau")] + PW, + + [Description("Portugal")] + PT, + + [Description("Svalbard and Jan Mayen")] + SJ, + + [Description("Paraguay")] + PY, + + [Description("Iraq")] + IQ, + + [Description("Panama")] + PA, + + [Description("French Polynesia")] + PF, + + [Description("Papua New Guinea")] + PG, + + [Description("Peru")] + PE, + + [Description("Pakistan")] + PK, + + [Description("Philippines")] + PH, + + [Description("Pitcairn")] + PN, + + [Description("Poland")] + PL, + + [Description("Saint Pierre and Miquelon")] + PM, + + [Description("Zambia")] + ZM, + + [Description("Western Sahara")] + EH, + + [Description("Estonia")] + EE, + + [Description("Egypt")] + EG, + + [Description("South Africa")] + ZA, + + [Description("Ecuador")] + EC, + + [Description("Italy")] + IT, + + [Description("Vietnam")] + VN, + + [Description("Solomon Islands")] + SB, + + [Description("Ethiopia")] + ET, + + [Description("Somalia")] + SO, + + [Description("Zimbabwe")] + ZW, + + [Description("Saudi Arabia")] + SA, + + [Description("Spain")] + ES, + + [Description("Eritrea")] + ER, + + [Description("Montenegro")] + ME, + + [Description("Moldova")] + MD, + + [Description("Madagascar")] + MG, + + [Description("Saint Martin")] + MF, + + [Description("Morocco")] + MA, + + [Description("Monaco")] + MC, + + [Description("Uzbekistan")] + UZ, + + [Description("Myanmar")] + MM, + + [Description("Mali")] + ML, + + [Description("Macao")] + MO, + + [Description("Mongolia")] + MN, + + [Description("Marshall Islands")] + MH, + + [Description("North Macedonia")] + MK, + + [Description("Mauritius")] + MU, + + [Description("Malta")] + MT, + + [Description("Malawi")] + MW, + + [Description("Maldives")] + MV, + + [Description("Martinique")] + MQ, + + [Description("Northern Mariana Islands")] + MP, + + [Description("Montserrat")] + MS, + + [Description("Mauritania")] + MR, + + [Description("Isle of Man")] + IM, + + [Description("Uganda")] + UG, + + [Description("Tanzania")] + TZ, + + [Description("Malaysia")] + MY, + + [Description("Mexico")] + MX, + + [Description("Israel")] + IL, + + [Description("France")] + FR, + + [Description("British Indian Ocean Territory")] + IO, + + [Description("Saint Helena")] + SH, + + [Description("Finland")] + FI, + + [Description("Fiji")] + FJ, + + [Description("Falkland Islands")] + FK, + + [Description("Micronesia")] + FM, + + [Description("Faroe Islands")] + FO, + + [Description("Nicaragua")] + NI, + + [Description("Netherlands")] + NL, + + [Description("Norway")] + NO, + + [Description("Namibia")] + NA, + + [Description("Vanuatu")] + VU, + + [Description("New Caledonia")] + NC, + + [Description("Niger")] + NE, + + [Description("Norfolk Island")] + NF, + + [Description("Nigeria")] + NG, + + [Description("New Zealand")] + NZ, + + [Description("Nepal")] + NP, + + [Description("Nauru")] + NR, + + [Description("Niue")] + NU, + + [Description("Cook Islands")] + CK, + + [Description("Kosovo")] + XK, + + [Description("Ivory Coast")] + CI, + + [Description("Switzerland")] + CH, + + [Description("Colombia")] + CO, + + [Description("China")] + CN, + + [Description("Cameroon")] + CM, + + [Description("Chile")] + CL, + + [Description("Cocos Islands")] + CC, + + [Description("Canada")] + CA, + + [Description("Republic of the Congo")] + CG, + + [Description("Central African Republic")] + CF, + + [Description("Democratic Republic of the Congo")] + CD, + + [Description("Czech Republic")] + CZ, + + [Description("Cyprus")] + CY, + + [Description("Christmas Island")] + CX, + + [Description("Costa Rica")] + CR, + + [Description("Curacao")] + CW, + + [Description("Cabo Verde")] + CV, + + [Description("Cuba")] + CU, + + [Description("Eswatini")] + SZ, + + [Description("Syria")] + SY, + + [Description("Sint Maarten")] + SX, + + [Description("Kyrgyzstan")] + KG, + + [Description("Kenya")] + KE, + + [Description("South Sudan")] + SS, + + [Description("Suriname")] + SR, + + [Description("Kiribati")] + KI, + + [Description("Cambodia")] + KH, + + [Description("Saint Kitts and Nevis")] + KN, + + [Description("Comoros")] + KM, + + [Description("Sao Tome and Principe")] + ST, + + [Description("Slovakia")] + SK, + + [Description("South Korea")] + KR, + + [Description("Slovenia")] + SI, + + [Description("North Korea")] + KP, + + [Description("Kuwait")] + KW, + + [Description("Senegal")] + SN, + + [Description("San Marino")] + SM, + + [Description("Sierra Leone")] + SL, + + [Description("Seychelles")] + SC, + + [Description("Kazakhstan")] + KZ, + + [Description("Cayman Islands")] + KY, + + [Description("Singapore")] + SG, + + [Description("Sweden")] + SE, + + [Description("Sudan")] + SD, + + [Description("Dominican Republic")] + DO, + + [Description("Dominica")] + DM, + + [Description("Djibouti")] + DJ, + + [Description("Denmark")] + DK, + + [Description("British Virgin Islands")] + VG, + + [Description("Germany")] + DE, + + [Description("Yemen")] + YE, + + [Description("Algeria")] + DZ, + + [Description("United States")] + US, + + [Description("Uruguay")] + UY, + + [Description("Mayotte")] + YT, + + [Description("United States Minor Outlying Islands")] + UM, + + [Description("Lebanon")] + LB, + + [Description("Saint Lucia")] + LC, + + [Description("Laos")] + LA, + + [Description("Tuvalu")] + TV, + + [Description("Taiwan")] + TW, + + [Description("Trinidad and Tobago")] + TT, + + [Description("Turkey")] + TR, + + [Description("Sri Lanka")] + LK, + + [Description("Liechtenstein")] + LI, + + [Description("Latvia")] + LV, + + [Description("Tonga")] + TO, + + [Description("Lithuania")] + LT, + + [Description("Luxembourg")] + LU, + + [Description("Liberia")] + LR, + + [Description("Lesotho")] + LS, + + [Description("Thailand")] + TH, + + [Description("French Southern Territories")] + TF, + + [Description("Togo")] + TG, + + [Description("Chad")] + TD, + + [Description("Turks and Caicos Islands")] + TC, + + [Description("Libya")] + LY, + + [Description("Vatican")] + VA, + + [Description("Saint Vincent and the Grenadines")] + VC, + + [Description("United Arab Emirates")] + AE, + + [Description("Andorra")] + AD, + + [Description("Antigua and Barbuda")] + AG, + + [Description("Afghanistan")] + AF, + + [Description("Anguilla")] + AI, + + [Description("U.S. Virgin Islands")] + VI, + + [Description("Iceland")] + IS, + + [Description("Iran")] + IR, + + [Description("Armenia")] + AM, + + [Description("Albania")] + AL, + + [Description("Angola")] + AO, + + [Description("Antarctica")] + AQ, + + [Description("American Samoa")] + AS, + + [Description("Argentina")] + AR, + + [Description("Australia")] + AU, + + [Description("Austria")] + AT, + + [Description("Aruba")] + AW, + + [Description("India")] + IN, + + [Description("Aland Islands")] + AX, + + [Description("Azerbaijan")] + AZ, + + [Description("Ireland")] + IE, + + [Description("Indonesia")] + ID, + + [Description("Ukraine")] + UA, + + [Description("Qatar")] + QA, + + [Description("Mozambique")] + MZ, } } From 00f4c8052e569ba71a819259a0aff47553add330 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 04:06:25 +0300 Subject: [PATCH 040/481] Update `APIUser` to provide enum from serialised country code --- .../Online/API/Requests/Responses/APIUser.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 63aaa9b90e..1a2a38e789 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -34,8 +34,24 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"previous_usernames")] public string[] PreviousUsernames; + private Country? country; + + public Country Country + { + get => country ??= (Enum.TryParse(userCountry.Code, out Country result) ? result : default); + set => country = value; + } + +#pragma warning disable 649 [JsonProperty(@"country")] - public Country Country; + private UserCountry userCountry; + + private class UserCountry + { + [JsonProperty(@"code")] + public string Code; + } +#pragma warning restore 649 public readonly Bindable Status = new Bindable(); From 1b2b42bb8a7cc2d3b510ef9d45ef075187b832c0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 04:13:04 +0300 Subject: [PATCH 041/481] Update `CountryStatistics` to use `code` for country enum --- osu.Game/Users/CountryStatistics.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Users/CountryStatistics.cs b/osu.Game/Users/CountryStatistics.cs index e784630d56..eaee13a37b 100644 --- a/osu.Game/Users/CountryStatistics.cs +++ b/osu.Game/Users/CountryStatistics.cs @@ -9,11 +9,8 @@ namespace osu.Game.Users { public class CountryStatistics { - [JsonProperty] - public Country Country; - [JsonProperty(@"code")] - public string FlagName; + public Country Country; [JsonProperty(@"active_users")] public long ActiveUsers; From b2b2a4adafab9d6b1dd1d10675b78a629acd0d49 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 04:06:48 +0300 Subject: [PATCH 042/481] Update tournament migration logic to check for null `Country` --- osu.Game.Tournament/Models/TournamentUser.cs | 3 ++- osu.Game.Tournament/TournamentGameBase.cs | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Models/TournamentUser.cs b/osu.Game.Tournament/Models/TournamentUser.cs index 80e58538e5..7faf6d1798 100644 --- a/osu.Game.Tournament/Models/TournamentUser.cs +++ b/osu.Game.Tournament/Models/TournamentUser.cs @@ -22,7 +22,8 @@ namespace osu.Game.Tournament.Models /// /// The player's country. /// - public Country? Country { get; set; } + [JsonProperty("country_code")] + public Country Country { get; set; } /// /// The player's global rank, or null if not available. diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index f2a35ea5b3..853ccec83c 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -186,7 +186,9 @@ namespace osu.Game.Tournament { var playersRequiringPopulation = ladder.Teams .SelectMany(t => t.Players) - .Where(p => string.IsNullOrEmpty(p.Username) || p.Rank == null).ToList(); + .Where(p => string.IsNullOrEmpty(p.Username) + || p.Country == default + || p.Rank == null).ToList(); if (playersRequiringPopulation.Count == 0) return false; From e62049f4a94ec526c81eb0ae61d5b02bbd6b083c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 04:40:37 +0300 Subject: [PATCH 043/481] Update various usages of `Country` inline with new enum --- .../Visual/Online/TestSceneFriendDisplay.cs | 6 +- .../Online/TestSceneRankingsCountryFilter.cs | 16 +--- .../Visual/Online/TestSceneRankingsHeader.cs | 15 +--- .../Visual/Online/TestSceneRankingsOverlay.cs | 12 +-- .../Visual/Online/TestSceneRankingsTables.cs | 12 ++- .../Visual/Online/TestSceneScoresContainer.cs | 36 ++------- .../Visual/Online/TestSceneUserPanel.cs | 6 +- .../Online/TestSceneUserProfileOverlay.cs | 8 +- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 74 ++++--------------- .../TestSceneUserTopScoreContainer.cs | 18 +---- .../API/Requests/GetUserRankingsRequest.cs | 9 ++- .../Profile/Header/TopHeaderContainer.cs | 5 +- osu.Game/Overlays/Rankings/CountryFilter.cs | 2 +- osu.Game/Overlays/Rankings/CountryPill.cs | 7 +- .../Rankings/Tables/CountriesTable.cs | 5 +- osu.Game/Overlays/RankingsOverlay.cs | 8 +- .../Participants/ParticipantPanel.cs | 2 +- osu.Game/Users/Drawables/DrawableFlag.cs | 6 +- osu.Game/Users/Drawables/UpdateableFlag.cs | 4 +- 19 files changed, 74 insertions(+), 177 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs index c5c61cdd72..43d41e9fb9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Online Id = 3103765, IsOnline = true, Statistics = new UserStatistics { GlobalRank = 1111 }, - Country = new Country { FlagName = "JP" }, + Country = Country.JP, CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" }, new APIUser @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online Id = 2, IsOnline = false, Statistics = new UserStatistics { GlobalRank = 2222 }, - Country = new Country { FlagName = "AU" }, + Country = Country.AU, CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", IsSupporter = true, SupportLevel = 3, @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Online { Username = "Evast", Id = 8195163, - Country = new Country { FlagName = "BY" }, + Country = Country.BY, CoverUrl = "https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg", IsOnline = false, LastVisit = DateTimeOffset.Now diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsCountryFilter.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsCountryFilter.cs index e7d799222a..ce0ca0b6c9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsCountryFilter.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsCountryFilter.cs @@ -56,20 +56,12 @@ namespace osu.Game.Tests.Visual.Online } }); - var country = new Country - { - FlagName = "BY", - FullName = "Belarus" - }; - var unknownCountry = new Country - { - FlagName = "CK", - FullName = "Cook Islands" - }; + const Country country = Country.BY; + const Country unknown_country = Country.CK; AddStep("Set country", () => countryBindable.Value = country); - AddStep("Set null country", () => countryBindable.Value = null); - AddStep("Set country with no flag", () => countryBindable.Value = unknownCountry); + AddStep("Set default country", () => countryBindable.Value = default); + AddStep("Set country with no flag", () => countryBindable.Value = unknown_country); } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs index c8f08d70be..9aedfb0a14 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs @@ -30,21 +30,12 @@ namespace osu.Game.Tests.Visual.Online Ruleset = { BindTarget = ruleset } }); - var country = new Country - { - FlagName = "BY", - FullName = "Belarus" - }; - - var unknownCountry = new Country - { - FlagName = "CK", - FullName = "Cook Islands" - }; + const Country country = Country.BY; + const Country unknown_country = Country.CK; AddStep("Set country", () => countryBindable.Value = country); AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); - AddStep("Set country with no flag", () => countryBindable.Value = unknownCountry); + AddStep("Set country with no flag", () => countryBindable.Value = unknown_country); } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs index 62dad7b458..ea7eb14c8e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs @@ -48,15 +48,15 @@ namespace osu.Game.Tests.Visual.Online public void TestFlagScopeDependency() { AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); - AddAssert("Check country is Null", () => countryBindable.Value == null); - AddStep("Set country", () => countryBindable.Value = us_country); + AddAssert("Check country is default", () => countryBindable.Value == default); + AddStep("Set country", () => countryBindable.Value = Country.US); AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance); } [Test] public void TestShowCountry() { - AddStep("Show US", () => rankingsOverlay.ShowCountry(us_country)); + AddStep("Show US", () => rankingsOverlay.ShowCountry(Country.US)); } private void loadRankingsOverlay() @@ -69,12 +69,6 @@ namespace osu.Game.Tests.Visual.Online }; } - private static readonly Country us_country = new Country - { - FlagName = "US", - FullName = "United States" - }; - private class TestRankingsOverlay : RankingsOverlay { public new Bindable Country => base.Country; diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs index e357b0fffc..f889e3f7dd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs @@ -57,8 +57,7 @@ namespace osu.Game.Tests.Visual.Online { new CountryStatistics { - Country = new Country { FlagName = "US", FullName = "United States" }, - FlagName = "US", + Country = Country.US, ActiveUsers = 2_972_623, PlayCount = 3_086_515_743, RankedScore = 449_407_643_332_546, @@ -66,8 +65,7 @@ namespace osu.Game.Tests.Visual.Online }, new CountryStatistics { - Country = new Country { FlagName = "RU", FullName = "Russian Federation" }, - FlagName = "RU", + Country = Country.RU, ActiveUsers = 1_609_989, PlayCount = 1_637_052_841, RankedScore = 221_660_827_473_004, @@ -86,7 +84,7 @@ namespace osu.Game.Tests.Visual.Online User = new APIUser { Username = "first active user", - Country = new Country { FlagName = "JP" }, + Country = Country.JP, Active = true, }, Accuracy = 0.9972, @@ -106,7 +104,7 @@ namespace osu.Game.Tests.Visual.Online User = new APIUser { Username = "inactive user", - Country = new Country { FlagName = "AU" }, + Country = Country.AU, Active = false, }, Accuracy = 0.9831, @@ -126,7 +124,7 @@ namespace osu.Game.Tests.Visual.Online User = new APIUser { Username = "second active user", - Country = new Country { FlagName = "PL" }, + Country = Country.PL, Active = true, }, Accuracy = 0.9584, diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 6ef87f762c..6ac3d5cb86 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -157,11 +157,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 6602580, Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, + Country = Country.ES, }, Mods = new[] { @@ -184,11 +180,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 4608074, Username = @"Skycries", - Country = new Country - { - FullName = @"Brazil", - FlagName = @"BR", - }, + Country = Country.BR, }, Mods = new[] { @@ -210,11 +202,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 1014222, Username = @"eLy", - Country = new Country - { - FullName = @"Japan", - FlagName = @"JP", - }, + Country = Country.JP, }, Mods = new[] { @@ -235,11 +223,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 1541390, Username = @"Toukai", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, + Country = Country.CA, }, Mods = new[] { @@ -259,11 +243,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 7151382, Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, + Country = Country.TH, }, Rank = ScoreRank.D, PP = 160, @@ -302,11 +282,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 7151382, Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, + Country = Country.TH, }, Rank = ScoreRank.D, PP = 160, diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index fff40b3c74..addcf799e3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"flyte", Id = 3103765, - Country = new Country { FlagName = @"JP" }, + Country = Country.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg", Status = { Value = new UserStatusOnline() } }) { Width = 300 }, @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"peppy", Id = 2, - Country = new Country { FlagName = @"AU" }, + Country = Country.AU, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", IsSupporter = true, SupportLevel = 3, @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"Evast", Id = 8195163, - Country = new Country { FlagName = @"BY" }, + Country = Country.BY, CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg", IsOnline = false, LastVisit = DateTimeOffset.Now diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index ad3215b1ef..dd6b022624 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"Somebody", Id = 1, - Country = new Country { FullName = @"Alien" }, + Country = Country.XX, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", JoinDate = DateTimeOffset.Now.AddDays(-1), LastVisit = DateTimeOffset.Now, @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Online Username = @"peppy", Id = 2, IsSupporter = true, - Country = new Country { FullName = @"Australia", FlagName = @"AU" }, + Country = Country.AU, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" })); @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"flyte", Id = 3103765, - Country = new Country { FullName = @"Japan", FlagName = @"JP" }, + Country = Country.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" })); @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Online Username = @"BanchoBot", Id = 3, IsBot = true, - Country = new Country { FullName = @"Saint Helena", FlagName = @"SH" }, + Country = Country.SH, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c4.jpg" })); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index ef0c7d7d4d..48fdf671f1 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -140,11 +140,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6602580, Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, + Country = Country.ES, }, }); } @@ -164,12 +160,8 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6602580, Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, - }, + Country = Country.ES, + } }); } @@ -225,11 +217,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6602580, Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, + Country = Country.ES, }, }, new ScoreInfo @@ -246,11 +234,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 4608074, Username = @"Skycries", - Country = new Country - { - FullName = @"Brazil", - FlagName = @"BR", - }, + Country = Country.BR, }, }, new ScoreInfo @@ -268,11 +252,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 1014222, Username = @"eLy", - Country = new Country - { - FullName = @"Japan", - FlagName = @"JP", - }, + Country = Country.JP, }, }, new ScoreInfo @@ -290,11 +270,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 1541390, Username = @"Toukai", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, + Country = Country.CA, }, }, new ScoreInfo @@ -312,11 +288,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 2243452, Username = @"Satoruu", - Country = new Country - { - FullName = @"Venezuela", - FlagName = @"VE", - }, + Country = Country.VE, }, }, new ScoreInfo @@ -334,11 +306,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 2705430, Username = @"Mooha", - Country = new Country - { - FullName = @"France", - FlagName = @"FR", - }, + Country = Country.FR, }, }, new ScoreInfo @@ -356,11 +324,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 7151382, Username = @"Mayuri Hana", - Country = new Country - { - FullName = @"Thailand", - FlagName = @"TH", - }, + Country = Country.TH, }, }, new ScoreInfo @@ -378,11 +342,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 2051389, Username = @"FunOrange", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, + Country = Country.CA, }, }, new ScoreInfo @@ -400,11 +360,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6169483, Username = @"-Hebel-", - Country = new Country - { - FullName = @"Mexico", - FlagName = @"MX", - }, + Country = Country.MX, }, }, new ScoreInfo @@ -422,11 +378,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6702666, Username = @"prhtnsm", - Country = new Country - { - FullName = @"Germany", - FlagName = @"DE", - }, + Country = Country.DE, }, }, }; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs index 16966e489a..5356e74eae 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs @@ -69,11 +69,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6602580, Username = @"waaiiru", - Country = new Country - { - FullName = @"Spain", - FlagName = @"ES", - }, + Country = Country.ES, }, }, new ScoreInfo @@ -88,11 +84,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 4608074, Username = @"Skycries", - Country = new Country - { - FullName = @"Brazil", - FlagName = @"BR", - }, + Country = Country.BR, }, }, new ScoreInfo @@ -107,11 +99,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 1541390, Username = @"Toukai", - Country = new Country - { - FullName = @"Canada", - FlagName = @"CA", - }, + Country = Country.CA, }, } }; diff --git a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs index ab0cc3a56d..75088675bd 100644 --- a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs @@ -5,6 +5,7 @@ using osu.Framework.IO.Network; using osu.Game.Rulesets; +using osu.Game.Users; namespace osu.Game.Online.API.Requests { @@ -12,9 +13,9 @@ namespace osu.Game.Online.API.Requests { public readonly UserRankingsType Type; - private readonly string country; + private readonly Country country; - public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, string country = null) + public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, Country country = default) : base(ruleset, page) { Type = type; @@ -25,8 +26,8 @@ namespace osu.Game.Online.API.Requests { var req = base.CreateWebRequest(); - if (country != null) - req.AddParameter("country", country); + if (country != default) + req.AddParameter("country", country.ToString()); return req; } diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 9c957e387a..818a84b9aa 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; @@ -174,8 +175,8 @@ namespace osu.Game.Overlays.Profile.Header avatar.User = user; usernameText.Text = user?.Username ?? string.Empty; openUserExternally.Link = $@"{api.WebsiteRootUrl}/users/{user?.Id ?? 0}"; - userFlag.Country = user?.Country; - userCountryText.Text = user?.Country?.FullName ?? "Alien"; + userFlag.Country = user?.Country ?? default; + userCountryText.Text = (user?.Country ?? default).GetDescription(); supporterTag.SupportLevel = user?.SupportLevel ?? 0; titleText.Text = user?.Title ?? string.Empty; titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff"); diff --git a/osu.Game/Overlays/Rankings/CountryFilter.cs b/osu.Game/Overlays/Rankings/CountryFilter.cs index 9ba2018522..469d92a771 100644 --- a/osu.Game/Overlays/Rankings/CountryFilter.cs +++ b/osu.Game/Overlays/Rankings/CountryFilter.cs @@ -91,7 +91,7 @@ namespace osu.Game.Overlays.Rankings private void onCountryChanged(ValueChangedEvent country) { - if (country.NewValue == null) + if (country.NewValue == default) { countryPill.Collapse(); this.ResizeHeightTo(0, duration, Easing.OutQuint); diff --git a/osu.Game/Overlays/Rankings/CountryPill.cs b/osu.Game/Overlays/Rankings/CountryPill.cs index 90f8c85557..96d677611e 100644 --- a/osu.Game/Overlays/Rankings/CountryPill.cs +++ b/osu.Game/Overlays/Rankings/CountryPill.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -93,7 +94,7 @@ namespace osu.Game.Overlays.Rankings { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Action = () => Current.Value = null + Action = Current.SetDefault, } } } @@ -132,11 +133,11 @@ namespace osu.Game.Overlays.Rankings private void onCountryChanged(ValueChangedEvent country) { - if (country.NewValue == null) + if (country.NewValue == default) return; flag.Country = country.NewValue; - countryName.Text = country.NewValue.FullName; + countryName.Text = country.NewValue.GetDescription(); } private class CloseButton : OsuHoverContainer diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index 53d10b3e53..27d4a6b6d3 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -11,6 +11,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Game.Resources.Localisation.Web; @@ -77,8 +78,8 @@ namespace osu.Game.Overlays.Rankings.Tables RelativeSizeAxes = Axes.Y; TextAnchor = Anchor.CentreLeft; - if (!string.IsNullOrEmpty(country.FullName)) - AddLink(country.FullName, () => rankings?.ShowCountry(country)); + if (country != default) + AddLink(country.GetDescription(), () => rankings?.ShowCountry(country)); } } } diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index a66b8cf2ba..9894df1c9d 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -44,7 +44,7 @@ namespace osu.Game.Overlays Country.BindValueChanged(_ => { // if a country is requested, force performance scope. - if (Country.Value != null) + if (!Country.IsDefault) Header.Current.Value = RankingsScope.Performance; Scheduler.AddOnce(triggerTabChanged); @@ -76,7 +76,7 @@ namespace osu.Game.Overlays { // country filtering is only valid for performance scope. if (Header.Current.Value != RankingsScope.Performance) - Country.Value = null; + Country.SetDefault(); Scheduler.AddOnce(triggerTabChanged); } @@ -87,7 +87,7 @@ namespace osu.Game.Overlays public void ShowCountry(Country requested) { - if (requested == null) + if (requested == default) return; Show(); @@ -128,7 +128,7 @@ namespace osu.Game.Overlays switch (Header.Current.Value) { case RankingsScope.Performance: - return new GetUserRankingsRequest(ruleset.Value, country: Country.Value?.FlagName); + return new GetUserRankingsRequest(ruleset.Value, country: Country.Value); case RankingsScope.Country: return new GetCountryRankingsRequest(ruleset.Value); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index f632951f41..652a832689 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -128,7 +128,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Size = new Vector2(28, 20), - Country = user?.Country + Country = user?.Country ?? default }, new OsuSpriteText { diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index e5ac8fa257..84b56a8d16 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; @@ -16,7 +17,7 @@ namespace osu.Game.Users.Drawables { private readonly Country country; - public LocalisableString TooltipText => country?.FullName; + public LocalisableString TooltipText => country == default ? string.Empty : country.GetDescription(); public DrawableFlag(Country country) { @@ -29,7 +30,8 @@ namespace osu.Game.Users.Drawables if (ts == null) throw new ArgumentNullException(nameof(ts)); - Texture = ts.Get($@"Flags/{country?.FlagName ?? @"__"}") ?? ts.Get(@"Flags/__"); + string textureName = country == default ? "__" : country.ToString(); + Texture = ts.Get($@"Flags/{textureName}") ?? ts.Get(@"Flags/__"); } } } diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index fd59bf305d..2f08f1c787 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -32,14 +32,14 @@ namespace osu.Game.Users.Drawables /// public Action Action; - public UpdateableFlag(Country country = null) + public UpdateableFlag(Country country = default) { Country = country; } protected override Drawable CreateDrawable(Country country) { - if (country == null && !ShowPlaceholderOnNull) + if (country == default && !ShowPlaceholderOnNull) return null; return new Container From 08f1280aa8df25140ef4b189fe39b146b94a3e64 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 04:42:33 +0300 Subject: [PATCH 044/481] Add `UsedImplicitly` specification to silence unused member inspection Also applied to `Language` while at it. --- osu.Game/Localisation/Language.cs | 2 ++ osu.Game/Users/Country.cs | 1 + 2 files changed, 3 insertions(+) diff --git a/osu.Game/Localisation/Language.cs b/osu.Game/Localisation/Language.cs index c13a1a10cb..6a4e5110e6 100644 --- a/osu.Game/Localisation/Language.cs +++ b/osu.Game/Localisation/Language.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System.ComponentModel; +using JetBrains.Annotations; namespace osu.Game.Localisation { + [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] public enum Language { [Description(@"English")] diff --git a/osu.Game/Users/Country.cs b/osu.Game/Users/Country.cs index d1f33627c1..e96b96e57c 100644 --- a/osu.Game/Users/Country.cs +++ b/osu.Game/Users/Country.cs @@ -9,6 +9,7 @@ using Newtonsoft.Json.Converters; namespace osu.Game.Users { [JsonConverter(typeof(StringEnumConverter))] + [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] public enum Country { [Description("Alien")] From 7c6f4b798bc66d5936e689ab6d7b4d26005db91a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 05:16:59 +0300 Subject: [PATCH 045/481] Replace `countries.json` with country enum and acronym extension --- osu.Game.Tournament/CountryExtensions.cs | 770 ++++++++++ osu.Game.Tournament/Resources/countries.json | 1252 ----------------- .../Screens/Editors/TeamEditorScreen.cs | 18 +- 3 files changed, 782 insertions(+), 1258 deletions(-) create mode 100644 osu.Game.Tournament/CountryExtensions.cs delete mode 100644 osu.Game.Tournament/Resources/countries.json diff --git a/osu.Game.Tournament/CountryExtensions.cs b/osu.Game.Tournament/CountryExtensions.cs new file mode 100644 index 0000000000..180c7a96af --- /dev/null +++ b/osu.Game.Tournament/CountryExtensions.cs @@ -0,0 +1,770 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Users; + +namespace osu.Game.Tournament +{ + public static class CountryExtensions + { + public static string GetAcronym(this Country country) + { + switch (country) + { + case Country.BD: + return "BGD"; + + case Country.BE: + return "BEL"; + + case Country.BF: + return "BFA"; + + case Country.BG: + return "BGR"; + + case Country.BA: + return "BIH"; + + case Country.BB: + return "BRB"; + + case Country.WF: + return "WLF"; + + case Country.BL: + return "BLM"; + + case Country.BM: + return "BMU"; + + case Country.BN: + return "BRN"; + + case Country.BO: + return "BOL"; + + case Country.BH: + return "BHR"; + + case Country.BI: + return "BDI"; + + case Country.BJ: + return "BEN"; + + case Country.BT: + return "BTN"; + + case Country.JM: + return "JAM"; + + case Country.BV: + return "BVT"; + + case Country.BW: + return "BWA"; + + case Country.WS: + return "WSM"; + + case Country.BQ: + return "BES"; + + case Country.BR: + return "BRA"; + + case Country.BS: + return "BHS"; + + case Country.JE: + return "JEY"; + + case Country.BY: + return "BLR"; + + case Country.BZ: + return "BLZ"; + + case Country.RU: + return "RUS"; + + case Country.RW: + return "RWA"; + + case Country.RS: + return "SRB"; + + case Country.TL: + return "TLS"; + + case Country.RE: + return "REU"; + + case Country.TM: + return "TKM"; + + case Country.TJ: + return "TJK"; + + case Country.RO: + return "ROU"; + + case Country.TK: + return "TKL"; + + case Country.GW: + return "GNB"; + + case Country.GU: + return "GUM"; + + case Country.GT: + return "GTM"; + + case Country.GS: + return "SGS"; + + case Country.GR: + return "GRC"; + + case Country.GQ: + return "GNQ"; + + case Country.GP: + return "GLP"; + + case Country.JP: + return "JPN"; + + case Country.GY: + return "GUY"; + + case Country.GG: + return "GGY"; + + case Country.GF: + return "GUF"; + + case Country.GE: + return "GEO"; + + case Country.GD: + return "GRD"; + + case Country.GB: + return "GBR"; + + case Country.GA: + return "GAB"; + + case Country.SV: + return "SLV"; + + case Country.GN: + return "GIN"; + + case Country.GM: + return "GMB"; + + case Country.GL: + return "GRL"; + + case Country.GI: + return "GIB"; + + case Country.GH: + return "GHA"; + + case Country.OM: + return "OMN"; + + case Country.TN: + return "TUN"; + + case Country.JO: + return "JOR"; + + case Country.HR: + return "HRV"; + + case Country.HT: + return "HTI"; + + case Country.HU: + return "HUN"; + + case Country.HK: + return "HKG"; + + case Country.HN: + return "HND"; + + case Country.HM: + return "HMD"; + + case Country.VE: + return "VEN"; + + case Country.PR: + return "PRI"; + + case Country.PS: + return "PSE"; + + case Country.PW: + return "PLW"; + + case Country.PT: + return "PRT"; + + case Country.SJ: + return "SJM"; + + case Country.PY: + return "PRY"; + + case Country.IQ: + return "IRQ"; + + case Country.PA: + return "PAN"; + + case Country.PF: + return "PYF"; + + case Country.PG: + return "PNG"; + + case Country.PE: + return "PER"; + + case Country.PK: + return "PAK"; + + case Country.PH: + return "PHL"; + + case Country.PN: + return "PCN"; + + case Country.PL: + return "POL"; + + case Country.PM: + return "SPM"; + + case Country.ZM: + return "ZMB"; + + case Country.EH: + return "ESH"; + + case Country.EE: + return "EST"; + + case Country.EG: + return "EGY"; + + case Country.ZA: + return "ZAF"; + + case Country.EC: + return "ECU"; + + case Country.IT: + return "ITA"; + + case Country.VN: + return "VNM"; + + case Country.SB: + return "SLB"; + + case Country.ET: + return "ETH"; + + case Country.SO: + return "SOM"; + + case Country.ZW: + return "ZWE"; + + case Country.SA: + return "SAU"; + + case Country.ES: + return "ESP"; + + case Country.ER: + return "ERI"; + + case Country.ME: + return "MNE"; + + case Country.MD: + return "MDA"; + + case Country.MG: + return "MDG"; + + case Country.MF: + return "MAF"; + + case Country.MA: + return "MAR"; + + case Country.MC: + return "MCO"; + + case Country.UZ: + return "UZB"; + + case Country.MM: + return "MMR"; + + case Country.ML: + return "MLI"; + + case Country.MO: + return "MAC"; + + case Country.MN: + return "MNG"; + + case Country.MH: + return "MHL"; + + case Country.MK: + return "MKD"; + + case Country.MU: + return "MUS"; + + case Country.MT: + return "MLT"; + + case Country.MW: + return "MWI"; + + case Country.MV: + return "MDV"; + + case Country.MQ: + return "MTQ"; + + case Country.MP: + return "MNP"; + + case Country.MS: + return "MSR"; + + case Country.MR: + return "MRT"; + + case Country.IM: + return "IMN"; + + case Country.UG: + return "UGA"; + + case Country.TZ: + return "TZA"; + + case Country.MY: + return "MYS"; + + case Country.MX: + return "MEX"; + + case Country.IL: + return "ISR"; + + case Country.FR: + return "FRA"; + + case Country.IO: + return "IOT"; + + case Country.SH: + return "SHN"; + + case Country.FI: + return "FIN"; + + case Country.FJ: + return "FJI"; + + case Country.FK: + return "FLK"; + + case Country.FM: + return "FSM"; + + case Country.FO: + return "FRO"; + + case Country.NI: + return "NIC"; + + case Country.NL: + return "NLD"; + + case Country.NO: + return "NOR"; + + case Country.NA: + return "NAM"; + + case Country.VU: + return "VUT"; + + case Country.NC: + return "NCL"; + + case Country.NE: + return "NER"; + + case Country.NF: + return "NFK"; + + case Country.NG: + return "NGA"; + + case Country.NZ: + return "NZL"; + + case Country.NP: + return "NPL"; + + case Country.NR: + return "NRU"; + + case Country.NU: + return "NIU"; + + case Country.CK: + return "COK"; + + case Country.XK: + return "XKX"; + + case Country.CI: + return "CIV"; + + case Country.CH: + return "CHE"; + + case Country.CO: + return "COL"; + + case Country.CN: + return "CHN"; + + case Country.CM: + return "CMR"; + + case Country.CL: + return "CHL"; + + case Country.CC: + return "CCK"; + + case Country.CA: + return "CAN"; + + case Country.CG: + return "COG"; + + case Country.CF: + return "CAF"; + + case Country.CD: + return "COD"; + + case Country.CZ: + return "CZE"; + + case Country.CY: + return "CYP"; + + case Country.CX: + return "CXR"; + + case Country.CR: + return "CRI"; + + case Country.CW: + return "CUW"; + + case Country.CV: + return "CPV"; + + case Country.CU: + return "CUB"; + + case Country.SZ: + return "SWZ"; + + case Country.SY: + return "SYR"; + + case Country.SX: + return "SXM"; + + case Country.KG: + return "KGZ"; + + case Country.KE: + return "KEN"; + + case Country.SS: + return "SSD"; + + case Country.SR: + return "SUR"; + + case Country.KI: + return "KIR"; + + case Country.KH: + return "KHM"; + + case Country.KN: + return "KNA"; + + case Country.KM: + return "COM"; + + case Country.ST: + return "STP"; + + case Country.SK: + return "SVK"; + + case Country.KR: + return "KOR"; + + case Country.SI: + return "SVN"; + + case Country.KP: + return "PRK"; + + case Country.KW: + return "KWT"; + + case Country.SN: + return "SEN"; + + case Country.SM: + return "SMR"; + + case Country.SL: + return "SLE"; + + case Country.SC: + return "SYC"; + + case Country.KZ: + return "KAZ"; + + case Country.KY: + return "CYM"; + + case Country.SG: + return "SGP"; + + case Country.SE: + return "SWE"; + + case Country.SD: + return "SDN"; + + case Country.DO: + return "DOM"; + + case Country.DM: + return "DMA"; + + case Country.DJ: + return "DJI"; + + case Country.DK: + return "DNK"; + + case Country.VG: + return "VGB"; + + case Country.DE: + return "DEU"; + + case Country.YE: + return "YEM"; + + case Country.DZ: + return "DZA"; + + case Country.US: + return "USA"; + + case Country.UY: + return "URY"; + + case Country.YT: + return "MYT"; + + case Country.UM: + return "UMI"; + + case Country.LB: + return "LBN"; + + case Country.LC: + return "LCA"; + + case Country.LA: + return "LAO"; + + case Country.TV: + return "TUV"; + + case Country.TW: + return "TWN"; + + case Country.TT: + return "TTO"; + + case Country.TR: + return "TUR"; + + case Country.LK: + return "LKA"; + + case Country.LI: + return "LIE"; + + case Country.LV: + return "LVA"; + + case Country.TO: + return "TON"; + + case Country.LT: + return "LTU"; + + case Country.LU: + return "LUX"; + + case Country.LR: + return "LBR"; + + case Country.LS: + return "LSO"; + + case Country.TH: + return "THA"; + + case Country.TF: + return "ATF"; + + case Country.TG: + return "TGO"; + + case Country.TD: + return "TCD"; + + case Country.TC: + return "TCA"; + + case Country.LY: + return "LBY"; + + case Country.VA: + return "VAT"; + + case Country.VC: + return "VCT"; + + case Country.AE: + return "ARE"; + + case Country.AD: + return "AND"; + + case Country.AG: + return "ATG"; + + case Country.AF: + return "AFG"; + + case Country.AI: + return "AIA"; + + case Country.VI: + return "VIR"; + + case Country.IS: + return "ISL"; + + case Country.IR: + return "IRN"; + + case Country.AM: + return "ARM"; + + case Country.AL: + return "ALB"; + + case Country.AO: + return "AGO"; + + case Country.AQ: + return "ATA"; + + case Country.AS: + return "ASM"; + + case Country.AR: + return "ARG"; + + case Country.AU: + return "AUS"; + + case Country.AT: + return "AUT"; + + case Country.AW: + return "ABW"; + + case Country.IN: + return "IND"; + + case Country.AX: + return "ALA"; + + case Country.AZ: + return "AZE"; + + case Country.IE: + return "IRL"; + + case Country.ID: + return "IDN"; + + case Country.UA: + return "UKR"; + + case Country.QA: + return "QAT"; + + case Country.MZ: + return "MOZ"; + + default: + throw new ArgumentOutOfRangeException(nameof(country)); + } + } + } +} diff --git a/osu.Game.Tournament/Resources/countries.json b/osu.Game.Tournament/Resources/countries.json deleted file mode 100644 index 7306a8bec5..0000000000 --- a/osu.Game.Tournament/Resources/countries.json +++ /dev/null @@ -1,1252 +0,0 @@ -[ - { - "FlagName": "BD", - "FullName": "Bangladesh", - "Acronym": "BGD" - }, - { - "FlagName": "BE", - "FullName": "Belgium", - "Acronym": "BEL" - }, - { - "FlagName": "BF", - "FullName": "Burkina Faso", - "Acronym": "BFA" - }, - { - "FlagName": "BG", - "FullName": "Bulgaria", - "Acronym": "BGR" - }, - { - "FlagName": "BA", - "FullName": "Bosnia and Herzegovina", - "Acronym": "BIH" - }, - { - "FlagName": "BB", - "FullName": "Barbados", - "Acronym": "BRB" - }, - { - "FlagName": "WF", - "FullName": "Wallis and Futuna", - "Acronym": "WLF" - }, - { - "FlagName": "BL", - "FullName": "Saint Barthelemy", - "Acronym": "BLM" - }, - { - "FlagName": "BM", - "FullName": "Bermuda", - "Acronym": "BMU" - }, - { - "FlagName": "BN", - "FullName": "Brunei", - "Acronym": "BRN" - }, - { - "FlagName": "BO", - "FullName": "Bolivia", - "Acronym": "BOL" - }, - { - "FlagName": "BH", - "FullName": "Bahrain", - "Acronym": "BHR" - }, - { - "FlagName": "BI", - "FullName": "Burundi", - "Acronym": "BDI" - }, - { - "FlagName": "BJ", - "FullName": "Benin", - "Acronym": "BEN" - }, - { - "FlagName": "BT", - "FullName": "Bhutan", - "Acronym": "BTN" - }, - { - "FlagName": "JM", - "FullName": "Jamaica", - "Acronym": "JAM" - }, - { - "FlagName": "BV", - "FullName": "Bouvet Island", - "Acronym": "BVT" - }, - { - "FlagName": "BW", - "FullName": "Botswana", - "Acronym": "BWA" - }, - { - "FlagName": "WS", - "FullName": "Samoa", - "Acronym": "WSM" - }, - { - "FlagName": "BQ", - "FullName": "Bonaire, Saint Eustatius and Saba", - "Acronym": "BES" - }, - { - "FlagName": "BR", - "FullName": "Brazil", - "Acronym": "BRA" - }, - { - "FlagName": "BS", - "FullName": "Bahamas", - "Acronym": "BHS" - }, - { - "FlagName": "JE", - "FullName": "Jersey", - "Acronym": "JEY" - }, - { - "FlagName": "BY", - "FullName": "Belarus", - "Acronym": "BLR" - }, - { - "FlagName": "BZ", - "FullName": "Belize", - "Acronym": "BLZ" - }, - { - "FlagName": "RU", - "FullName": "Russia", - "Acronym": "RUS" - }, - { - "FlagName": "RW", - "FullName": "Rwanda", - "Acronym": "RWA" - }, - { - "FlagName": "RS", - "FullName": "Serbia", - "Acronym": "SRB" - }, - { - "FlagName": "TL", - "FullName": "East Timor", - "Acronym": "TLS" - }, - { - "FlagName": "RE", - "FullName": "Reunion", - "Acronym": "REU" - }, - { - "FlagName": "TM", - "FullName": "Turkmenistan", - "Acronym": "TKM" - }, - { - "FlagName": "TJ", - "FullName": "Tajikistan", - "Acronym": "TJK" - }, - { - "FlagName": "RO", - "FullName": "Romania", - "Acronym": "ROU" - }, - { - "FlagName": "TK", - "FullName": "Tokelau", - "Acronym": "TKL" - }, - { - "FlagName": "GW", - "FullName": "Guinea-Bissau", - "Acronym": "GNB" - }, - { - "FlagName": "GU", - "FullName": "Guam", - "Acronym": "GUM" - }, - { - "FlagName": "GT", - "FullName": "Guatemala", - "Acronym": "GTM" - }, - { - "FlagName": "GS", - "FullName": "South Georgia and the South Sandwich Islands", - "Acronym": "SGS" - }, - { - "FlagName": "GR", - "FullName": "Greece", - "Acronym": "GRC" - }, - { - "FlagName": "GQ", - "FullName": "Equatorial Guinea", - "Acronym": "GNQ" - }, - { - "FlagName": "GP", - "FullName": "Guadeloupe", - "Acronym": "GLP" - }, - { - "FlagName": "JP", - "FullName": "Japan", - "Acronym": "JPN" - }, - { - "FlagName": "GY", - "FullName": "Guyana", - "Acronym": "GUY" - }, - { - "FlagName": "GG", - "FullName": "Guernsey", - "Acronym": "GGY" - }, - { - "FlagName": "GF", - "FullName": "French Guiana", - "Acronym": "GUF" - }, - { - "FlagName": "GE", - "FullName": "Georgia", - "Acronym": "GEO" - }, - { - "FlagName": "GD", - "FullName": "Grenada", - "Acronym": "GRD" - }, - { - "FlagName": "GB", - "FullName": "United Kingdom", - "Acronym": "GBR" - }, - { - "FlagName": "GA", - "FullName": "Gabon", - "Acronym": "GAB" - }, - { - "FlagName": "SV", - "FullName": "El Salvador", - "Acronym": "SLV" - }, - { - "FlagName": "GN", - "FullName": "Guinea", - "Acronym": "GIN" - }, - { - "FlagName": "GM", - "FullName": "Gambia", - "Acronym": "GMB" - }, - { - "FlagName": "GL", - "FullName": "Greenland", - "Acronym": "GRL" - }, - { - "FlagName": "GI", - "FullName": "Gibraltar", - "Acronym": "GIB" - }, - { - "FlagName": "GH", - "FullName": "Ghana", - "Acronym": "GHA" - }, - { - "FlagName": "OM", - "FullName": "Oman", - "Acronym": "OMN" - }, - { - "FlagName": "TN", - "FullName": "Tunisia", - "Acronym": "TUN" - }, - { - "FlagName": "JO", - "FullName": "Jordan", - "Acronym": "JOR" - }, - { - "FlagName": "HR", - "FullName": "Croatia", - "Acronym": "HRV" - }, - { - "FlagName": "HT", - "FullName": "Haiti", - "Acronym": "HTI" - }, - { - "FlagName": "HU", - "FullName": "Hungary", - "Acronym": "HUN" - }, - { - "FlagName": "HK", - "FullName": "Hong Kong", - "Acronym": "HKG" - }, - { - "FlagName": "HN", - "FullName": "Honduras", - "Acronym": "HND" - }, - { - "FlagName": "HM", - "FullName": "Heard Island and McDonald Islands", - "Acronym": "HMD" - }, - { - "FlagName": "VE", - "FullName": "Venezuela", - "Acronym": "VEN" - }, - { - "FlagName": "PR", - "FullName": "Puerto Rico", - "Acronym": "PRI" - }, - { - "FlagName": "PS", - "FullName": "Palestinian Territory", - "Acronym": "PSE" - }, - { - "FlagName": "PW", - "FullName": "Palau", - "Acronym": "PLW" - }, - { - "FlagName": "PT", - "FullName": "Portugal", - "Acronym": "PRT" - }, - { - "FlagName": "SJ", - "FullName": "Svalbard and Jan Mayen", - "Acronym": "SJM" - }, - { - "FlagName": "PY", - "FullName": "Paraguay", - "Acronym": "PRY" - }, - { - "FlagName": "IQ", - "FullName": "Iraq", - "Acronym": "IRQ" - }, - { - "FlagName": "PA", - "FullName": "Panama", - "Acronym": "PAN" - }, - { - "FlagName": "PF", - "FullName": "French Polynesia", - "Acronym": "PYF" - }, - { - "FlagName": "PG", - "FullName": "Papua New Guinea", - "Acronym": "PNG" - }, - { - "FlagName": "PE", - "FullName": "Peru", - "Acronym": "PER" - }, - { - "FlagName": "PK", - "FullName": "Pakistan", - "Acronym": "PAK" - }, - { - "FlagName": "PH", - "FullName": "Philippines", - "Acronym": "PHL" - }, - { - "FlagName": "PN", - "FullName": "Pitcairn", - "Acronym": "PCN" - }, - { - "FlagName": "PL", - "FullName": "Poland", - "Acronym": "POL" - }, - { - "FlagName": "PM", - "FullName": "Saint Pierre and Miquelon", - "Acronym": "SPM" - }, - { - "FlagName": "ZM", - "FullName": "Zambia", - "Acronym": "ZMB" - }, - { - "FlagName": "EH", - "FullName": "Western Sahara", - "Acronym": "ESH" - }, - { - "FlagName": "EE", - "FullName": "Estonia", - "Acronym": "EST" - }, - { - "FlagName": "EG", - "FullName": "Egypt", - "Acronym": "EGY" - }, - { - "FlagName": "ZA", - "FullName": "South Africa", - "Acronym": "ZAF" - }, - { - "FlagName": "EC", - "FullName": "Ecuador", - "Acronym": "ECU" - }, - { - "FlagName": "IT", - "FullName": "Italy", - "Acronym": "ITA" - }, - { - "FlagName": "VN", - "FullName": "Vietnam", - "Acronym": "VNM" - }, - { - "FlagName": "SB", - "FullName": "Solomon Islands", - "Acronym": "SLB" - }, - { - "FlagName": "ET", - "FullName": "Ethiopia", - "Acronym": "ETH" - }, - { - "FlagName": "SO", - "FullName": "Somalia", - "Acronym": "SOM" - }, - { - "FlagName": "ZW", - "FullName": "Zimbabwe", - "Acronym": "ZWE" - }, - { - "FlagName": "SA", - "FullName": "Saudi Arabia", - "Acronym": "SAU" - }, - { - "FlagName": "ES", - "FullName": "Spain", - "Acronym": "ESP" - }, - { - "FlagName": "ER", - "FullName": "Eritrea", - "Acronym": "ERI" - }, - { - "FlagName": "ME", - "FullName": "Montenegro", - "Acronym": "MNE" - }, - { - "FlagName": "MD", - "FullName": "Moldova", - "Acronym": "MDA" - }, - { - "FlagName": "MG", - "FullName": "Madagascar", - "Acronym": "MDG" - }, - { - "FlagName": "MF", - "FullName": "Saint Martin", - "Acronym": "MAF" - }, - { - "FlagName": "MA", - "FullName": "Morocco", - "Acronym": "MAR" - }, - { - "FlagName": "MC", - "FullName": "Monaco", - "Acronym": "MCO" - }, - { - "FlagName": "UZ", - "FullName": "Uzbekistan", - "Acronym": "UZB" - }, - { - "FlagName": "MM", - "FullName": "Myanmar", - "Acronym": "MMR" - }, - { - "FlagName": "ML", - "FullName": "Mali", - "Acronym": "MLI" - }, - { - "FlagName": "MO", - "FullName": "Macao", - "Acronym": "MAC" - }, - { - "FlagName": "MN", - "FullName": "Mongolia", - "Acronym": "MNG" - }, - { - "FlagName": "MH", - "FullName": "Marshall Islands", - "Acronym": "MHL" - }, - { - "FlagName": "MK", - "FullName": "North Macedonia", - "Acronym": "MKD" - }, - { - "FlagName": "MU", - "FullName": "Mauritius", - "Acronym": "MUS" - }, - { - "FlagName": "MT", - "FullName": "Malta", - "Acronym": "MLT" - }, - { - "FlagName": "MW", - "FullName": "Malawi", - "Acronym": "MWI" - }, - { - "FlagName": "MV", - "FullName": "Maldives", - "Acronym": "MDV" - }, - { - "FlagName": "MQ", - "FullName": "Martinique", - "Acronym": "MTQ" - }, - { - "FlagName": "MP", - "FullName": "Northern Mariana Islands", - "Acronym": "MNP" - }, - { - "FlagName": "MS", - "FullName": "Montserrat", - "Acronym": "MSR" - }, - { - "FlagName": "MR", - "FullName": "Mauritania", - "Acronym": "MRT" - }, - { - "FlagName": "IM", - "FullName": "Isle of Man", - "Acronym": "IMN" - }, - { - "FlagName": "UG", - "FullName": "Uganda", - "Acronym": "UGA" - }, - { - "FlagName": "TZ", - "FullName": "Tanzania", - "Acronym": "TZA" - }, - { - "FlagName": "MY", - "FullName": "Malaysia", - "Acronym": "MYS" - }, - { - "FlagName": "MX", - "FullName": "Mexico", - "Acronym": "MEX" - }, - { - "FlagName": "IL", - "FullName": "Israel", - "Acronym": "ISR" - }, - { - "FlagName": "FR", - "FullName": "France", - "Acronym": "FRA" - }, - { - "FlagName": "IO", - "FullName": "British Indian Ocean Territory", - "Acronym": "IOT" - }, - { - "FlagName": "SH", - "FullName": "Saint Helena", - "Acronym": "SHN" - }, - { - "FlagName": "FI", - "FullName": "Finland", - "Acronym": "FIN" - }, - { - "FlagName": "FJ", - "FullName": "Fiji", - "Acronym": "FJI" - }, - { - "FlagName": "FK", - "FullName": "Falkland Islands", - "Acronym": "FLK" - }, - { - "FlagName": "FM", - "FullName": "Micronesia", - "Acronym": "FSM" - }, - { - "FlagName": "FO", - "FullName": "Faroe Islands", - "Acronym": "FRO" - }, - { - "FlagName": "NI", - "FullName": "Nicaragua", - "Acronym": "NIC" - }, - { - "FlagName": "NL", - "FullName": "Netherlands", - "Acronym": "NLD" - }, - { - "FlagName": "NO", - "FullName": "Norway", - "Acronym": "NOR" - }, - { - "FlagName": "NA", - "FullName": "Namibia", - "Acronym": "NAM" - }, - { - "FlagName": "VU", - "FullName": "Vanuatu", - "Acronym": "VUT" - }, - { - "FlagName": "NC", - "FullName": "New Caledonia", - "Acronym": "NCL" - }, - { - "FlagName": "NE", - "FullName": "Niger", - "Acronym": "NER" - }, - { - "FlagName": "NF", - "FullName": "Norfolk Island", - "Acronym": "NFK" - }, - { - "FlagName": "NG", - "FullName": "Nigeria", - "Acronym": "NGA" - }, - { - "FlagName": "NZ", - "FullName": "New Zealand", - "Acronym": "NZL" - }, - { - "FlagName": "NP", - "FullName": "Nepal", - "Acronym": "NPL" - }, - { - "FlagName": "NR", - "FullName": "Nauru", - "Acronym": "NRU" - }, - { - "FlagName": "NU", - "FullName": "Niue", - "Acronym": "NIU" - }, - { - "FlagName": "CK", - "FullName": "Cook Islands", - "Acronym": "COK" - }, - { - "FlagName": "XK", - "FullName": "Kosovo", - "Acronym": "XKX" - }, - { - "FlagName": "CI", - "FullName": "Ivory Coast", - "Acronym": "CIV" - }, - { - "FlagName": "CH", - "FullName": "Switzerland", - "Acronym": "CHE" - }, - { - "FlagName": "CO", - "FullName": "Colombia", - "Acronym": "COL" - }, - { - "FlagName": "CN", - "FullName": "China", - "Acronym": "CHN" - }, - { - "FlagName": "CM", - "FullName": "Cameroon", - "Acronym": "CMR" - }, - { - "FlagName": "CL", - "FullName": "Chile", - "Acronym": "CHL" - }, - { - "FlagName": "CC", - "FullName": "Cocos Islands", - "Acronym": "CCK" - }, - { - "FlagName": "CA", - "FullName": "Canada", - "Acronym": "CAN" - }, - { - "FlagName": "CG", - "FullName": "Republic of the Congo", - "Acronym": "COG" - }, - { - "FlagName": "CF", - "FullName": "Central African Republic", - "Acronym": "CAF" - }, - { - "FlagName": "CD", - "FullName": "Democratic Republic of the Congo", - "Acronym": "COD" - }, - { - "FlagName": "CZ", - "FullName": "Czech Republic", - "Acronym": "CZE" - }, - { - "FlagName": "CY", - "FullName": "Cyprus", - "Acronym": "CYP" - }, - { - "FlagName": "CX", - "FullName": "Christmas Island", - "Acronym": "CXR" - }, - { - "FlagName": "CR", - "FullName": "Costa Rica", - "Acronym": "CRI" - }, - { - "FlagName": "CW", - "FullName": "Curacao", - "Acronym": "CUW" - }, - { - "FlagName": "CV", - "FullName": "Cabo Verde", - "Acronym": "CPV" - }, - { - "FlagName": "CU", - "FullName": "Cuba", - "Acronym": "CUB" - }, - { - "FlagName": "SZ", - "FullName": "Eswatini", - "Acronym": "SWZ" - }, - { - "FlagName": "SY", - "FullName": "Syria", - "Acronym": "SYR" - }, - { - "FlagName": "SX", - "FullName": "Sint Maarten", - "Acronym": "SXM" - }, - { - "FlagName": "KG", - "FullName": "Kyrgyzstan", - "Acronym": "KGZ" - }, - { - "FlagName": "KE", - "FullName": "Kenya", - "Acronym": "KEN" - }, - { - "FlagName": "SS", - "FullName": "South Sudan", - "Acronym": "SSD" - }, - { - "FlagName": "SR", - "FullName": "Suriname", - "Acronym": "SUR" - }, - { - "FlagName": "KI", - "FullName": "Kiribati", - "Acronym": "KIR" - }, - { - "FlagName": "KH", - "FullName": "Cambodia", - "Acronym": "KHM" - }, - { - "FlagName": "KN", - "FullName": "Saint Kitts and Nevis", - "Acronym": "KNA" - }, - { - "FlagName": "KM", - "FullName": "Comoros", - "Acronym": "COM" - }, - { - "FlagName": "ST", - "FullName": "Sao Tome and Principe", - "Acronym": "STP" - }, - { - "FlagName": "SK", - "FullName": "Slovakia", - "Acronym": "SVK" - }, - { - "FlagName": "KR", - "FullName": "South Korea", - "Acronym": "KOR" - }, - { - "FlagName": "SI", - "FullName": "Slovenia", - "Acronym": "SVN" - }, - { - "FlagName": "KP", - "FullName": "North Korea", - "Acronym": "PRK" - }, - { - "FlagName": "KW", - "FullName": "Kuwait", - "Acronym": "KWT" - }, - { - "FlagName": "SN", - "FullName": "Senegal", - "Acronym": "SEN" - }, - { - "FlagName": "SM", - "FullName": "San Marino", - "Acronym": "SMR" - }, - { - "FlagName": "SL", - "FullName": "Sierra Leone", - "Acronym": "SLE" - }, - { - "FlagName": "SC", - "FullName": "Seychelles", - "Acronym": "SYC" - }, - { - "FlagName": "KZ", - "FullName": "Kazakhstan", - "Acronym": "KAZ" - }, - { - "FlagName": "KY", - "FullName": "Cayman Islands", - "Acronym": "CYM" - }, - { - "FlagName": "SG", - "FullName": "Singapore", - "Acronym": "SGP" - }, - { - "FlagName": "SE", - "FullName": "Sweden", - "Acronym": "SWE" - }, - { - "FlagName": "SD", - "FullName": "Sudan", - "Acronym": "SDN" - }, - { - "FlagName": "DO", - "FullName": "Dominican Republic", - "Acronym": "DOM" - }, - { - "FlagName": "DM", - "FullName": "Dominica", - "Acronym": "DMA" - }, - { - "FlagName": "DJ", - "FullName": "Djibouti", - "Acronym": "DJI" - }, - { - "FlagName": "DK", - "FullName": "Denmark", - "Acronym": "DNK" - }, - { - "FlagName": "VG", - "FullName": "British Virgin Islands", - "Acronym": "VGB" - }, - { - "FlagName": "DE", - "FullName": "Germany", - "Acronym": "DEU" - }, - { - "FlagName": "YE", - "FullName": "Yemen", - "Acronym": "YEM" - }, - { - "FlagName": "DZ", - "FullName": "Algeria", - "Acronym": "DZA" - }, - { - "FlagName": "US", - "FullName": "United States", - "Acronym": "USA" - }, - { - "FlagName": "UY", - "FullName": "Uruguay", - "Acronym": "URY" - }, - { - "FlagName": "YT", - "FullName": "Mayotte", - "Acronym": "MYT" - }, - { - "FlagName": "UM", - "FullName": "United States Minor Outlying Islands", - "Acronym": "UMI" - }, - { - "FlagName": "LB", - "FullName": "Lebanon", - "Acronym": "LBN" - }, - { - "FlagName": "LC", - "FullName": "Saint Lucia", - "Acronym": "LCA" - }, - { - "FlagName": "LA", - "FullName": "Laos", - "Acronym": "LAO" - }, - { - "FlagName": "TV", - "FullName": "Tuvalu", - "Acronym": "TUV" - }, - { - "FlagName": "TW", - "FullName": "Taiwan", - "Acronym": "TWN" - }, - { - "FlagName": "TT", - "FullName": "Trinidad and Tobago", - "Acronym": "TTO" - }, - { - "FlagName": "TR", - "FullName": "Turkey", - "Acronym": "TUR" - }, - { - "FlagName": "LK", - "FullName": "Sri Lanka", - "Acronym": "LKA" - }, - { - "FlagName": "LI", - "FullName": "Liechtenstein", - "Acronym": "LIE" - }, - { - "FlagName": "LV", - "FullName": "Latvia", - "Acronym": "LVA" - }, - { - "FlagName": "TO", - "FullName": "Tonga", - "Acronym": "TON" - }, - { - "FlagName": "LT", - "FullName": "Lithuania", - "Acronym": "LTU" - }, - { - "FlagName": "LU", - "FullName": "Luxembourg", - "Acronym": "LUX" - }, - { - "FlagName": "LR", - "FullName": "Liberia", - "Acronym": "LBR" - }, - { - "FlagName": "LS", - "FullName": "Lesotho", - "Acronym": "LSO" - }, - { - "FlagName": "TH", - "FullName": "Thailand", - "Acronym": "THA" - }, - { - "FlagName": "TF", - "FullName": "French Southern Territories", - "Acronym": "ATF" - }, - { - "FlagName": "TG", - "FullName": "Togo", - "Acronym": "TGO" - }, - { - "FlagName": "TD", - "FullName": "Chad", - "Acronym": "TCD" - }, - { - "FlagName": "TC", - "FullName": "Turks and Caicos Islands", - "Acronym": "TCA" - }, - { - "FlagName": "LY", - "FullName": "Libya", - "Acronym": "LBY" - }, - { - "FlagName": "VA", - "FullName": "Vatican", - "Acronym": "VAT" - }, - { - "FlagName": "VC", - "FullName": "Saint Vincent and the Grenadines", - "Acronym": "VCT" - }, - { - "FlagName": "AE", - "FullName": "United Arab Emirates", - "Acronym": "ARE" - }, - { - "FlagName": "AD", - "FullName": "Andorra", - "Acronym": "AND" - }, - { - "FlagName": "AG", - "FullName": "Antigua and Barbuda", - "Acronym": "ATG" - }, - { - "FlagName": "AF", - "FullName": "Afghanistan", - "Acronym": "AFG" - }, - { - "FlagName": "AI", - "FullName": "Anguilla", - "Acronym": "AIA" - }, - { - "FlagName": "VI", - "FullName": "U.S. Virgin Islands", - "Acronym": "VIR" - }, - { - "FlagName": "IS", - "FullName": "Iceland", - "Acronym": "ISL" - }, - { - "FlagName": "IR", - "FullName": "Iran", - "Acronym": "IRN" - }, - { - "FlagName": "AM", - "FullName": "Armenia", - "Acronym": "ARM" - }, - { - "FlagName": "AL", - "FullName": "Albania", - "Acronym": "ALB" - }, - { - "FlagName": "AO", - "FullName": "Angola", - "Acronym": "AGO" - }, - { - "FlagName": "AQ", - "FullName": "Antarctica", - "Acronym": "ATA" - }, - { - "FlagName": "AS", - "FullName": "American Samoa", - "Acronym": "ASM" - }, - { - "FlagName": "AR", - "FullName": "Argentina", - "Acronym": "ARG" - }, - { - "FlagName": "AU", - "FullName": "Australia", - "Acronym": "AUS" - }, - { - "FlagName": "AT", - "FullName": "Austria", - "Acronym": "AUT" - }, - { - "FlagName": "AW", - "FullName": "Aruba", - "Acronym": "ABW" - }, - { - "FlagName": "IN", - "FullName": "India", - "Acronym": "IND" - }, - { - "FlagName": "AX", - "FullName": "Aland Islands", - "Acronym": "ALA" - }, - { - "FlagName": "AZ", - "FullName": "Azerbaijan", - "Acronym": "AZE" - }, - { - "FlagName": "IE", - "FullName": "Ireland", - "Acronym": "IRL" - }, - { - "FlagName": "ID", - "FullName": "Indonesia", - "Acronym": "IDN" - }, - { - "FlagName": "UA", - "FullName": "Ukraine", - "Acronym": "UKR" - }, - { - "FlagName": "QA", - "FullName": "Qatar", - "Acronym": "QAT" - }, - { - "FlagName": "MZ", - "FullName": "Mozambique", - "Acronym": "MOZ" - } -] \ No newline at end of file diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 111893d18c..02c0a0d7e1 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -3,13 +3,13 @@ #nullable disable +using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; -using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -45,11 +45,17 @@ namespace osu.Game.Tournament.Screens.Editors private void addAllCountries() { - List countries; + var countries = new List(); - using (Stream stream = game.Resources.GetStream("Resources/countries.json")) - using (var sr = new StreamReader(stream)) - countries = JsonConvert.DeserializeObject>(sr.ReadToEnd()); + foreach (var country in Enum.GetValues(typeof(Country)).Cast().Skip(1)) + { + countries.Add(new TournamentTeam + { + FlagName = { Value = country.ToString() }, + FullName = { Value = country.GetDescription() }, + Acronym = { Value = country.GetAcronym() }, + }); + } Debug.Assert(countries != null); From 9c81241f4ce0abf7e77621f8cf048076446d9cc6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 06:02:04 +0300 Subject: [PATCH 046/481] Fix potential nullref on `APIUser.Country` We need NRT sooner than later... --- osu.Game/Online/API/Requests/Responses/APIUser.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 1a2a38e789..e2e1288b8c 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -38,11 +38,12 @@ namespace osu.Game.Online.API.Requests.Responses public Country Country { - get => country ??= (Enum.TryParse(userCountry.Code, out Country result) ? result : default); + get => country ??= (Enum.TryParse(userCountry?.Code, out Country result) ? result : default); set => country = value; } #pragma warning disable 649 + [CanBeNull] [JsonProperty(@"country")] private UserCountry userCountry; From 4968859e694770186cf5e09a88ee2d91195efc44 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 06:07:53 +0300 Subject: [PATCH 047/481] Rename placeholder display flag property to make sense --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs | 2 +- osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs | 2 +- osu.Game/Overlays/Rankings/Tables/RankingsTable.cs | 2 +- osu.Game/Users/Drawables/UpdateableFlag.cs | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index b2e5be6601..669b701d1d 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -168,7 +168,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new UpdateableFlag(score.User.Country) { Size = new Vector2(19, 14), - ShowPlaceholderOnNull = false, + ShowPlaceholderOnUnknown = false, }, username, new OsuSpriteText diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index f093c6b53f..85c8f92a76 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -120,7 +120,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Origin = Anchor.CentreLeft, Size = new Vector2(19, 14), Margin = new MarginPadding { Top = 3 }, // makes spacing look more even - ShowPlaceholderOnNull = false, + ShowPlaceholderOnUnknown = false, }, } } diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 818a84b9aa..a213f99701 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -137,7 +137,7 @@ namespace osu.Game.Overlays.Profile.Header userFlag = new UpdateableFlag { Size = new Vector2(28, 20), - ShowPlaceholderOnNull = false, + ShowPlaceholderOnUnknown = false, }, userCountryText = new OsuSpriteText { diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 56e318637a..1e33b96ef1 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -100,7 +100,7 @@ namespace osu.Game.Overlays.Rankings.Tables new UpdateableFlag(GetCountry(item)) { Size = new Vector2(28, 20), - ShowPlaceholderOnNull = false, + ShowPlaceholderOnUnknown = false, }, CreateFlagContent(item) } diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index 2f08f1c787..88c08d4bb1 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -22,9 +22,9 @@ namespace osu.Game.Users.Drawables } /// - /// Whether to show a place holder on null country. + /// Whether to show a place holder on unknown country. /// - public bool ShowPlaceholderOnNull = true; + public bool ShowPlaceholderOnUnknown = true; /// /// Perform an action in addition to showing the country ranking. @@ -39,7 +39,7 @@ namespace osu.Game.Users.Drawables protected override Drawable CreateDrawable(Country country) { - if (country == default && !ShowPlaceholderOnNull) + if (country == default && !ShowPlaceholderOnUnknown) return null; return new Container From 4e7156cee8f66d057cacc7caf812518fcd7b3587 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 06:30:25 +0300 Subject: [PATCH 048/481] Store user country on databased scores --- osu.Game/Database/RealmAccess.cs | 3 ++- osu.Game/Models/RealmUser.cs | 12 ++++++++++-- osu.Game/Rulesets/Mods/ICreateReplayData.cs | 1 + osu.Game/Scoring/ScoreInfo.cs | 7 +++++-- osu.Game/Users/IUser.cs | 2 ++ 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index bebc101b13..0c44436ec8 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -60,8 +60,9 @@ namespace osu.Game.Database /// 14 2022-03-01 Added BeatmapUserSettings to BeatmapInfo. /// 15 2022-07-13 Added LastPlayed to BeatmapInfo. /// 16 2022-07-15 Removed HasReplay from ScoreInfo. + /// 17 2022-07-16 Added Country to RealmUser. /// - private const int schema_version = 16; + private const int schema_version = 17; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Models/RealmUser.cs b/osu.Game/Models/RealmUser.cs index 58fd7ff2a3..1668739bb5 100644 --- a/osu.Game/Models/RealmUser.cs +++ b/osu.Game/Models/RealmUser.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Game.Database; using osu.Game.Users; @@ -17,6 +15,16 @@ namespace osu.Game.Models public string Username { get; set; } = string.Empty; + [Ignored] + public Country Country + { + get => Enum.TryParse(CountryString, out Country country) ? country : default; + set => CountryString = value.ToString(); + } + + [MapTo(nameof(Country))] + public string CountryString { get; set; } = default(Country).ToString(); + public bool IsBot => false; public bool Equals(RealmUser other) diff --git a/osu.Game/Rulesets/Mods/ICreateReplayData.cs b/osu.Game/Rulesets/Mods/ICreateReplayData.cs index 6058380eb3..6c195f623c 100644 --- a/osu.Game/Rulesets/Mods/ICreateReplayData.cs +++ b/osu.Game/Rulesets/Mods/ICreateReplayData.cs @@ -58,6 +58,7 @@ namespace osu.Game.Rulesets.Mods public class ModCreatedUser : IUser { public int OnlineID => APIUser.SYSTEM_USER_ID; + public Country Country => default; public bool IsBot => true; public string Username { get; set; } = string.Empty; diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 81ca5f0300..f5942e4639 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -85,8 +85,9 @@ namespace osu.Game.Scoring { get => user ??= new APIUser { - Username = RealmUser.Username, Id = RealmUser.OnlineID, + Username = RealmUser.Username, + Country = RealmUser.Country, }; set { @@ -95,7 +96,8 @@ namespace osu.Game.Scoring RealmUser = new RealmUser { OnlineID = user.OnlineID, - Username = user.Username + Username = user.Username, + Country = user.Country, }; } } @@ -135,6 +137,7 @@ namespace osu.Game.Scoring { OnlineID = RealmUser.OnlineID, Username = RealmUser.Username, + Country = RealmUser.Country, }; return clone; diff --git a/osu.Game/Users/IUser.cs b/osu.Game/Users/IUser.cs index 7a233b5d8b..a520660c4d 100644 --- a/osu.Game/Users/IUser.cs +++ b/osu.Game/Users/IUser.cs @@ -10,6 +10,8 @@ namespace osu.Game.Users { string Username { get; } + Country Country { get; } + bool IsBot { get; } bool IEquatable.Equals(IUser? other) From d0fe4fe15aa7270d678a674f9d8fd2735d5507ea Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 06:31:01 +0300 Subject: [PATCH 049/481] Fix user population logic not including country --- osu.Game/Scoring/ScoreImporter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 53dd511d57..4107c66dfe 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -84,7 +84,7 @@ namespace osu.Game.Scoring api.Perform(userRequest); if (userRequest.Response is APIUser user) - model.RealmUser.OnlineID = user.Id; + model.User = user; } } } From 69d967172ac3a11e34c5ea499f64dba6fd78676d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 06:32:25 +0300 Subject: [PATCH 050/481] Remove unencessary null coalesce --- osu.Game/Skinning/LegacyBeatmapSkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index 43ae0a2252..e8414b4c11 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -96,7 +96,7 @@ namespace osu.Game.Skinning new SkinInfo { Name = beatmapInfo.ToString(), - Creator = beatmapInfo.Metadata.Author.Username ?? string.Empty + Creator = beatmapInfo.Metadata.Author.Username }; } } From a10c398cd58516ce85dbc4e7072047f392b48272 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 16 Jul 2022 06:40:53 +0300 Subject: [PATCH 051/481] Remove no longer necessary DI --- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 02c0a0d7e1..0f806a2403 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -25,9 +25,6 @@ namespace osu.Game.Tournament.Screens.Editors { public class TeamEditorScreen : TournamentEditorScreen { - [Resolved] - private TournamentGameBase game { get; set; } - protected override BindableList Storage => LadderInfo.Teams; [BackgroundDependencyLoader] From 9e945197dce72e6827e67fedf41e1048d843a608 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 16 Jul 2022 14:49:14 +0900 Subject: [PATCH 052/481] Use "Unknown" instead of "Alient" for unknown countries --- osu.Game/Users/Country.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Users/Country.cs b/osu.Game/Users/Country.cs index e96b96e57c..2070bcab39 100644 --- a/osu.Game/Users/Country.cs +++ b/osu.Game/Users/Country.cs @@ -12,7 +12,7 @@ namespace osu.Game.Users [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] public enum Country { - [Description("Alien")] + [Description("Unknown")] XX = 0, [Description("Bangladesh")] From cf7af0061c606a737d6411a96f46c131668d3574 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sat, 16 Jul 2022 15:20:15 +0200 Subject: [PATCH 053/481] Add Touch input handler settings section --- osu.Desktop/OsuGameDesktop.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 314a03a73e..524436235e 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -22,10 +22,12 @@ using osu.Framework.Input.Handlers; using osu.Framework.Input.Handlers.Joystick; using osu.Framework.Input.Handlers.Mouse; using osu.Framework.Input.Handlers.Tablet; +using osu.Framework.Input.Handlers.Touch; using osu.Framework.Threading; using osu.Game.IO; using osu.Game.IPC; using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections; using osu.Game.Overlays.Settings.Sections.Input; namespace osu.Desktop @@ -156,6 +158,9 @@ namespace osu.Desktop case JoystickHandler jh: return new JoystickSettings(jh); + case TouchHandler th: + return new InputSection.HandlerSection(th); + default: return base.CreateSettingsSubsectionFor(handler); } From bbb2398a8b4cee039a96883f4fa7a0ed9e18f92d Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 00:46:29 +0100 Subject: [PATCH 054/481] change retry button icon from ArrowCircleLeft to Redo --- osu.Game/Screens/Ranking/RetryButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/RetryButton.cs b/osu.Game/Screens/Ranking/RetryButton.cs index a0ddc98943..c56f364ae8 100644 --- a/osu.Game/Screens/Ranking/RetryButton.cs +++ b/osu.Game/Screens/Ranking/RetryButton.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Ranking Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(13), - Icon = FontAwesome.Solid.ArrowCircleLeft, + Icon = FontAwesome.Solid.Redo, }, }; From 6636e462dc925f70c368ab60b5fdff3640dff331 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 17 Jul 2022 06:18:59 +0300 Subject: [PATCH 055/481] Fix `SoloScoreInfo` not carrying mod settings in conversion methods --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 6c48c7883f..cf7aa599f3 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -101,7 +101,7 @@ namespace osu.Game.Online.API.Requests.Responses var rulesetInstance = ruleset.CreateInstance(); - var mods = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray(); + var mods = Mods.Select(apiMod => apiMod.ToMod(rulesetInstance)).ToArray(); var scoreInfo = ToScoreInfo(mods); From 9382636da987c101ea0c8b90b31ce048063e7bc2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 17 Jul 2022 06:19:34 +0300 Subject: [PATCH 056/481] Catch and log exceptions from mod setting copy failure --- osu.Game/Online/API/APIMod.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index dc1db08174..8346300767 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -65,7 +65,14 @@ namespace osu.Game.Online.API if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue)) continue; - resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue); + try + { + resultMod.CopyAdjustedSetting((IBindable)property.GetValue(resultMod), settingValue); + } + catch (Exception ex) + { + Logger.Log($"Failed to copy mod setting value '{settingValue ?? "null"}' to \"{property.Name}\": {ex.Message}"); + } } } From 5532f56a30282b7a9f8b26dd80995ebfa0d2ba5a Mon Sep 17 00:00:00 2001 From: Jay L Date: Sun, 17 Jul 2022 14:10:49 +1000 Subject: [PATCH 057/481] performance points balancing --- .../Difficulty/TaikoPerformanceCalculator.cs | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index a9cde62f44..2c2dbddf13 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -35,13 +35,13 @@ namespace osu.Game.Rulesets.Taiko.Difficulty countMeh = score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = score.Statistics.GetValueOrDefault(HitResult.Miss); - double multiplier = 1.1; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things - - if (score.Mods.Any(m => m is ModNoFail)) - multiplier *= 0.90; + double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things if (score.Mods.Any(m => m is ModHidden)) - multiplier *= 1.10; + multiplier *= 1.075; + + if (score.Mods.Any(m => m is ModEasy)) + multiplier *= 0.975; double difficultyValue = computeDifficultyValue(score, taikoAttributes); double accuracyValue = computeAccuracyValue(score, taikoAttributes); @@ -61,12 +61,15 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private double computeDifficultyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) { - double difficultyValue = Math.Pow(5 * Math.Max(1.0, attributes.StarRating / 0.175) - 4.0, 2.25) / 450.0; + double difficultyValue = Math.Pow(5 * Math.Max(1.0, attributes.StarRating / 0.115) - 4.0, 2.25) / 1150.0; double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0); difficultyValue *= lengthBonus; - difficultyValue *= Math.Pow(0.985, countMiss); + difficultyValue *= Math.Pow(0.986, countMiss); + + if (score.Mods.Any(m => m is ModEasy)) + difficultyValue *= 0.980; if (score.Mods.Any(m => m is ModHidden)) difficultyValue *= 1.025; @@ -74,7 +77,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (score.Mods.Any(m => m is ModFlashlight)) difficultyValue *= 1.05 * lengthBonus; - return difficultyValue * score.Accuracy; + return difficultyValue * Math.Pow(score.Accuracy, 1.5); } private double computeAccuracyValue(ScoreInfo score, TaikoDifficultyAttributes attributes) @@ -82,10 +85,16 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (attributes.GreatHitWindow <= 0) return 0; - double accValue = Math.Pow(150.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 15) * 22.0; + double accuracyValue = Math.Pow(140.0 / attributes.GreatHitWindow, 1.1) * Math.Pow(score.Accuracy, 12.0) * 27; - // Bonus for many objects - it's harder to keep good accuracy up for longer - return accValue * Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); + double lengthBonus = Math.Min(1.15, Math.Pow(totalHits / 1500.0, 0.3)); + accuracyValue *= lengthBonus; + + // Slight HDFL Bonus for accuracy. + if (score.Mods.Any(m => m is ModFlashlight) && score.Mods.Any(m => m is ModHidden)) + accuracyValue *= 1.10 * lengthBonus; + + return accuracyValue; } private int totalHits => countGreat + countOk + countMeh + countMiss; From 8e7e1e6b516bf7dcf1a3088fef794bd116226bf4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 17 Jul 2022 05:20:05 +0900 Subject: [PATCH 058/481] Fix spectator client not correctly reconnecting after shutdown --- osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs | 1 + osu.Game/Online/Spectator/OnlineSpectatorClient.cs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index c061398209..4e6ea997c1 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -80,6 +80,7 @@ namespace osu.Game.Online.Multiplayer try { + // Importantly, use Invoke rather than Send to capture exceptions. return await connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoomWithPassword), roomId, password ?? string.Empty); } catch (HubException exception) diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index cd9f5233a2..1ed7819b05 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -56,7 +56,8 @@ namespace osu.Game.Online.Spectator try { - await connection.SendAsync(nameof(ISpectatorServer.BeginPlaySession), state); + // Importantly, use Invoke rather than Send to capture exceptions. + await connection.InvokeAsync(nameof(ISpectatorServer.BeginPlaySession), state); } catch (HubException exception) { From da7edd5d49bf25bc13fa6e722bebc436768b6603 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 17 Jul 2022 04:35:41 +0900 Subject: [PATCH 059/481] Perform actions after server reconnection --- osu.Game/Online/HubClientConnector.cs | 12 ++++++------ osu.Game/Online/IHubClientConnector.cs | 3 ++- .../Online/Multiplayer/OnlineMultiplayerClient.cs | 8 +++++++- osu.Game/Online/Spectator/OnlineSpectatorClient.cs | 9 ++++++++- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index 01f0f3a902..6bfe09e911 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -64,26 +64,26 @@ namespace osu.Game.Online this.preferMessagePack = preferMessagePack; apiState.BindTo(api.State); - apiState.BindValueChanged(_ => connectIfPossible(), true); + apiState.BindValueChanged(_ => Task.Run(connectIfPossible), true); } - public void Reconnect() + public Task Reconnect() { Logger.Log($"{clientName} reconnecting...", LoggingTarget.Network); - Task.Run(connectIfPossible); + return Task.Run(connectIfPossible); } - private void connectIfPossible() + private async Task connectIfPossible() { switch (apiState.Value) { case APIState.Failing: case APIState.Offline: - Task.Run(() => disconnect(true)); + await disconnect(true); break; case APIState.Online: - Task.Run(connect); + await connect(); break; } } diff --git a/osu.Game/Online/IHubClientConnector.cs b/osu.Game/Online/IHubClientConnector.cs index 2afab9091b..53c4897e73 100644 --- a/osu.Game/Online/IHubClientConnector.cs +++ b/osu.Game/Online/IHubClientConnector.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Bindables; using osu.Game.Online.API; @@ -32,6 +33,6 @@ namespace osu.Game.Online /// /// Reconnect if already connected. /// - void Reconnect(); + Task Reconnect(); } } diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 4e6ea997c1..6425e3c155 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -86,7 +86,13 @@ namespace osu.Game.Online.Multiplayer catch (HubException exception) { if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE) - connector?.Reconnect(); + { + Debug.Assert(connector != null); + + await connector.Reconnect(); + return await JoinRoom(roomId, password); + } + throw; } } diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index 1ed7819b05..4f9e62d0a0 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -62,7 +62,14 @@ namespace osu.Game.Online.Spectator catch (HubException exception) { if (exception.GetHubExceptionMessage() == HubClientConnector.SERVER_SHUTDOWN_MESSAGE) - connector?.Reconnect(); + { + Debug.Assert(connector != null); + + await connector.Reconnect(); + await BeginPlayingInternal(state); + return; + } + throw; } } From 55a8a3563b9f8c8f6bcda186494b986f9cc63ce1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 17 Jul 2022 18:23:15 +0900 Subject: [PATCH 060/481] Change `MultiplayerMatchSubScreen` to not immediately leave the room on connection loss Instead, we can rely on `MultiplayerClient.Room` going `null`. --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 5 ++--- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 8 ++------ 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 9832acb140..603bd10c38 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -21,7 +21,6 @@ using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Utils; -using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Online.Multiplayer { @@ -91,7 +90,7 @@ namespace osu.Game.Online.Multiplayer /// /// The joined . /// - public virtual MultiplayerRoom? Room + public virtual MultiplayerRoom? Room // virtual for moq { get { @@ -150,7 +149,7 @@ namespace osu.Game.Online.Multiplayer // clean up local room state on server disconnect. if (!connected.NewValue && Room != null) { - Logger.Log("Connection to multiplayer server was lost.", LoggingTarget.Runtime, LogLevel.Important); + Logger.Log("Clearing room due to multiplayer server connection loss.", LoggingTarget.Runtime, LogLevel.Important); LeaveRoom(); } })); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 4eb16a854b..4bbeb39063 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -70,12 +70,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer client.LoadRequested += onLoadRequested; client.RoomUpdated += onRoomUpdated; - isConnected.BindTo(client.IsConnected); - isConnected.BindValueChanged(connected => - { - if (!connected.NewValue) - handleRoomLost(); - }, true); + if (!client.IsConnected.Value) + handleRoomLost(); } protected override Drawable CreateMainContent() => new Container From 51071be3152de31ca5f793262f4dae17487df793 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 17 Jul 2022 21:20:50 +0900 Subject: [PATCH 061/481] Don't show "missing background" messages to user Bit of an oversight. As reported on [twitter](https://twitter.com/emyl___/status/1548627793075998720) and somewhere else i forgot. --- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index ce883a7092..df44f01629 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -168,7 +168,7 @@ namespace osu.Game.Beatmaps if (texture == null) { - Logger.Log($"Beatmap background failed to load (file {Metadata.BackgroundFile} not found on disk at expected location {fileStorePath}).", level: LogLevel.Error); + Logger.Log($"Beatmap background failed to load (file {Metadata.BackgroundFile} not found on disk at expected location {fileStorePath})."); return null; } From 1f288157f491bafd470388c47f65e43c896ff008 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:07:05 +0100 Subject: [PATCH 062/481] change `GetUserScoresRequest` to return `SoloScoreInfo` instead of `APIScore` --- osu.Game/Online/API/Requests/GetUserScoresRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index 9bd78b7be1..8ef797f799 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests { - public class GetUserScoresRequest : PaginatedAPIRequest> + public class GetUserScoresRequest : PaginatedAPIRequest> { private readonly long userId; private readonly ScoreType type; From e8d88e29c6331d73e9041efefbdb705cbf93bd0c Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:08:04 +0100 Subject: [PATCH 063/481] change `DrawableProfileScore` and `DrawableProfileWeightedScore` to take `SoloScoreInfo` and `APIBeatmap` instead of `APIScore` --- .../Profile/Sections/Ranks/DrawableProfileScore.cs | 12 +++++++----- .../Sections/Ranks/DrawableProfileWeightedScore.cs | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 90a357a281..1ff1e0e842 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -31,7 +31,8 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private const float performance_background_shear = 0.45f; - protected readonly APIScore Score; + protected readonly SoloScoreInfo Score; + protected readonly APIBeatmap Beatmap; [Resolved] private OsuColour colours { get; set; } @@ -39,9 +40,10 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks [Resolved] private OverlayColourProvider colourProvider { get; set; } - public DrawableProfileScore(APIScore score) + public DrawableProfileScore(SoloScoreInfo score, APIBeatmap beatmap) { Score = score; + Beatmap = beatmap; RelativeSizeAxes = Axes.X; Height = height; @@ -84,7 +86,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Spacing = new Vector2(0, 2), Children = new Drawable[] { - new ScoreBeatmapMetadataContainer(Score.Beatmap), + new ScoreBeatmapMetadataContainer(Beatmap), new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -94,11 +96,11 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { new OsuSpriteText { - Text = $"{Score.Beatmap?.DifficultyName}", + Text = $"{Beatmap.DifficultyName}", Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), Colour = colours.Yellow }, - new DrawableDate(Score.Date, 12) + new DrawableDate(Score.EndedAt ?? DateTimeOffset.Now, 12) { Colour = colourProvider.Foreground1 } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs index f8f83883fc..8d0a41ec48 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs @@ -18,8 +18,8 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { private readonly double weight; - public DrawableProfileWeightedScore(APIScore score, double weight) - : base(score) + public DrawableProfileWeightedScore(SoloScoreInfo score, APIBeatmap beatmap, double weight) + : base(score, beatmap) { this.weight = weight; } From ef4237c4ac8bf32d5a0b8bb51662aa6e3100b9ef Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:08:31 +0100 Subject: [PATCH 064/481] create special subsection class for paginated profile scores --- .../PaginatedProfileScoreSubsection.cs | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 osu.Game/Overlays/Profile/Sections/PaginatedProfileScoreSubsection.cs diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileScoreSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileScoreSubsection.cs new file mode 100644 index 0000000000..408d4a96bf --- /dev/null +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileScoreSubsection.cs @@ -0,0 +1,193 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osuTK; + +namespace osu.Game.Overlays.Profile.Sections +{ + public abstract class PaginatedProfileScoreSubsection : ProfileSubsection + { + /// + /// The number of items displayed per page. + /// + protected virtual int ItemsPerPage => 50; + + /// + /// The number of items displayed initially. + /// + protected virtual int InitialItemsCount => 5; + + [Resolved] + private IAPIProvider api { get; set; } + + protected PaginationParameters? CurrentPage { get; private set; } + + protected ReverseChildIDFillFlowContainer ItemsContainer { get; private set; } + + private APIRequest> scoreRetrievalRequest; + private APIRequest beatmapRetrievalRequest; + private CancellationTokenSource loadCancellation; + + protected List CurrentScores { get; private set; } = new List(); + + private ShowMoreButton moreButton; + private OsuSpriteText missing; + private readonly LocalisableString? missingText; + + protected PaginatedProfileScoreSubsection(Bindable user, LocalisableString? headerText = null, LocalisableString? missingText = null) + : base(user, headerText, CounterVisibilityState.AlwaysVisible) + { + this.missingText = missingText; + } + + protected override Drawable CreateContent() => new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + // reverse ID flow is required for correct Z-ordering of the items (last item should be front-most). + // particularly important in PaginatedBeatmapContainer, as it uses beatmap cards, which have expandable overhanging content. + ItemsContainer = new ReverseChildIDFillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Spacing = new Vector2(0, 2), + // ensure the container and its contents are in front of the "more" button. + Depth = float.MinValue + }, + moreButton = new ShowMoreButton + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Alpha = 0, + Margin = new MarginPadding { Top = 10 }, + Action = showMore, + }, + missing = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 15), + Text = missingText ?? string.Empty, + Alpha = 0, + } + } + }; + + protected override void LoadComplete() + { + base.LoadComplete(); + User.BindValueChanged(onUserChanged, true); + } + + private void onUserChanged(ValueChangedEvent e) + { + loadCancellation?.Cancel(); + scoreRetrievalRequest?.Cancel(); + beatmapRetrievalRequest?.Cancel(); + + CurrentPage = null; + ItemsContainer.Clear(); + CurrentScores.Clear(); + + if (e.NewValue != null) + { + showMore(); + SetCount(GetCount(e.NewValue)); + } + } + + private void showMore() + { + loadCancellation = new CancellationTokenSource(); + + CurrentPage = CurrentPage?.TakeNext(ItemsPerPage) ?? new PaginationParameters(InitialItemsCount); + + scoreRetrievalRequest = CreateScoreRequest(CurrentPage.Value); + scoreRetrievalRequest.Success += requestBeatmaps; + + api.Queue(scoreRetrievalRequest); + } + + private void requestBeatmaps(List items) + { + CurrentScores = items; + + beatmapRetrievalRequest = CreateBeatmapsRequest(items); + beatmapRetrievalRequest.Success += UpdateItems; + + api.Queue(beatmapRetrievalRequest); + } + + protected virtual APIRequest CreateBeatmapsRequest(List items) => new GetBeatmapsRequest(items.Select(i => i.BeatmapID).ToArray()); + + protected virtual void UpdateItems(GetBeatmapsResponse beatmaps) => Schedule(() => + { + var scoreBeatmapPairs = new List>(); + + foreach (var score in CurrentScores) + { + var beatmap = beatmaps.Beatmaps.Find(m => m.OnlineID == score.BeatmapID); + scoreBeatmapPairs.Add(new Tuple(score, beatmap)); + } + + OnItemsReceived(scoreBeatmapPairs); + + if (!scoreBeatmapPairs.Any() && CurrentPage?.Offset == 0) + { + moreButton.Hide(); + moreButton.IsLoading = false; + + if (missingText.HasValue) + missing.Show(); + + return; + } + + LoadComponentsAsync(scoreBeatmapPairs.Select(CreateDrawableItem).Where(d => d != null), drawables => + { + missing.Hide(); + + moreButton.FadeTo(scoreBeatmapPairs.Count == CurrentPage?.Limit ? 1 : 0); + moreButton.IsLoading = false; + + ItemsContainer.AddRange(drawables); + }, loadCancellation.Token); + }); + + protected virtual int GetCount(APIUser user) => 0; + + protected virtual void OnItemsReceived(List> scoreBeatmapPairs) + { + } + + protected abstract APIRequest> CreateScoreRequest(PaginationParameters pagination); + + protected abstract Drawable CreateDrawableItem(Tuple scoreBeatmapPair); + + protected override void Dispose(bool isDisposing) + { + scoreRetrievalRequest?.Cancel(); + loadCancellation?.Cancel(); + base.Dispose(isDisposing); + } + } +} From 71a4b8843ffc52602f1fa5e3620da84b7bb0958e Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:08:44 +0100 Subject: [PATCH 065/481] update tests to new profile score format --- .../Online/TestSceneUserProfileScores.cs | 104 +++++++++--------- 1 file changed, 54 insertions(+), 50 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs index fa28df3061..33ddf4a3a9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs @@ -21,20 +21,12 @@ namespace osu.Game.Tests.Visual.Online { public TestSceneUserProfileScores() { - var firstScore = new APIScore + var firstScore = new SoloScoreInfo { PP = 1047.21, Rank = ScoreRank.SH, - Beatmap = new APIBeatmap - { - BeatmapSet = new APIBeatmapSet - { - Title = "JUSTadICE (TV Size)", - Artist = "Oomori Seiko", - }, - DifficultyName = "Extreme" - }, - Date = DateTimeOffset.Now, + BeatmapID = 2058788, + EndedAt = DateTimeOffset.Now, Mods = new[] { new APIMod { Acronym = new OsuModHidden().Acronym }, @@ -43,21 +35,22 @@ namespace osu.Game.Tests.Visual.Online }, Accuracy = 0.9813 }; + var firstBeatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet() + { + Title = "JUSTadICE (TV Size)", + Artist = "Oomori Seiko", + }, + DifficultyName = "Extreme" + }; - var secondScore = new APIScore + var secondScore = new SoloScoreInfo { PP = 134.32, Rank = ScoreRank.A, - Beatmap = new APIBeatmap - { - BeatmapSet = new APIBeatmapSet - { - Title = "Triumph & Regret", - Artist = "typeMARS", - }, - DifficultyName = "[4K] Regret" - }, - Date = DateTimeOffset.Now, + BeatmapID = 767046, + EndedAt = DateTimeOffset.Now, Mods = new[] { new APIMod { Acronym = new OsuModHardRock().Acronym }, @@ -65,39 +58,50 @@ namespace osu.Game.Tests.Visual.Online }, Accuracy = 0.998546 }; + var secondBeatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet() + { + Title = "Triumph & Regret", + Artist = "typeMARS", + }, + DifficultyName = "[4K] Regret" + }; - var thirdScore = new APIScore + var thirdScore = new SoloScoreInfo { PP = 96.83, Rank = ScoreRank.S, - Beatmap = new APIBeatmap - { - BeatmapSet = new APIBeatmapSet - { - Title = "Idolize", - Artist = "Creo", - }, - DifficultyName = "Insane" - }, - Date = DateTimeOffset.Now, + BeatmapID = 2134713, + EndedAt = DateTimeOffset.Now, Accuracy = 0.9726 }; + var thirdBeatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet() + { + Title = "Idolize", + Artist = "Creo", + }, + DifficultyName = "Insane" + }; - var noPPScore = new APIScore + var noPPScore = new SoloScoreInfo { Rank = ScoreRank.B, - Beatmap = new APIBeatmap - { - BeatmapSet = new APIBeatmapSet - { - Title = "C18H27NO3(extend)", - Artist = "Team Grimoire", - }, - DifficultyName = "[4K] Cataclysmic Hypernova" - }, - Date = DateTimeOffset.Now, + BeatmapID = 992512, + EndedAt = DateTimeOffset.Now, Accuracy = 0.55879 }; + var noPPBeatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet() + { + Title = "Galaxy Collapse", + Artist = "Kurokotei", + }, + DifficultyName = "[4K] Cataclysmic Hypernova" + }; Add(new FillFlowContainer { @@ -109,12 +113,12 @@ namespace osu.Game.Tests.Visual.Online Spacing = new Vector2(0, 10), Children = new[] { - new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(firstScore)), - new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(secondScore)), - new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(noPPScore)), - new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(firstScore, 0.97)), - new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(secondScore, 0.85)), - new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(thirdScore, 0.66)), + new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(firstScore, firstBeatmap)), + new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(secondScore, secondBeatmap)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(noPPScore, noPPBeatmap)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(firstScore, firstBeatmap, 0.97)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(secondScore, secondBeatmap, 0.85)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(thirdScore, thirdBeatmap, 0.66)), } }); } From 3a8b5d48b9d4a3c93f83a42734a69a7cb05d6d63 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:09:14 +0100 Subject: [PATCH 066/481] update `PaginatedScoreContainer` to use new class and format --- .../Sections/Ranks/PaginatedScoreContainer.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 15c7b8f042..9af7d59d53 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -17,7 +17,7 @@ using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class PaginatedScoreContainer : PaginatedProfileSubsection + public class PaginatedScoreContainer : PaginatedProfileScoreSubsection { private readonly ScoreType type; @@ -54,28 +54,28 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks } } - protected override void OnItemsReceived(List items) + protected override void OnItemsReceived(List> scoreBeatmapPairs) { if (CurrentPage == null || CurrentPage?.Offset == 0) drawableItemIndex = 0; - base.OnItemsReceived(items); + base.OnItemsReceived(scoreBeatmapPairs); } - protected override APIRequest> CreateRequest(PaginationParameters pagination) => + protected override APIRequest> CreateScoreRequest(PaginationParameters pagination) => new GetUserScoresRequest(User.Value.Id, type, pagination); private int drawableItemIndex; - protected override Drawable CreateDrawableItem(APIScore model) + protected override Drawable CreateDrawableItem(Tuple scoreBeatmapPair) { switch (type) { default: - return new DrawableProfileScore(model); + return new DrawableProfileScore(scoreBeatmapPair.Item1, scoreBeatmapPair.Item2); case ScoreType.Best: - return new DrawableProfileWeightedScore(model, Math.Pow(0.95, drawableItemIndex++)); + return new DrawableProfileWeightedScore(scoreBeatmapPair.Item1, scoreBeatmapPair.Item2, Math.Pow(0.95, drawableItemIndex++)); } } } From c2277031f0b7b728d5d6dbdea0dac31f53c9d7e3 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:21:55 +0100 Subject: [PATCH 067/481] add `Beatmap` to SoloScoreInfo --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 6c48c7883f..963b2975d2 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -82,6 +82,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("user")] public APIUser? User { get; set; } + [JsonProperty("beatmap")] + public APIBeatmap? Beatmap { get; set; } + [JsonProperty("pp")] public double? PP { get; set; } From 7135329c8c3339e3b95861d0311c497d58a843ad Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:22:27 +0100 Subject: [PATCH 068/481] remove `PaginatedProfileScoreSubsectio, revert profile score drawables --- .../PaginatedProfileScoreSubsection.cs | 193 ------------------ .../Sections/Ranks/DrawableProfileScore.cs | 8 +- .../Ranks/DrawableProfileWeightedScore.cs | 4 +- .../Sections/Ranks/PaginatedScoreContainer.cs | 14 +- 4 files changed, 12 insertions(+), 207 deletions(-) delete mode 100644 osu.Game/Overlays/Profile/Sections/PaginatedProfileScoreSubsection.cs diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileScoreSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileScoreSubsection.cs deleted file mode 100644 index 408d4a96bf..0000000000 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileScoreSubsection.cs +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Localisation; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.API; -using osu.Game.Graphics.Containers; -using osu.Game.Online.API.Requests; -using osu.Game.Online.API.Requests.Responses; -using osuTK; - -namespace osu.Game.Overlays.Profile.Sections -{ - public abstract class PaginatedProfileScoreSubsection : ProfileSubsection - { - /// - /// The number of items displayed per page. - /// - protected virtual int ItemsPerPage => 50; - - /// - /// The number of items displayed initially. - /// - protected virtual int InitialItemsCount => 5; - - [Resolved] - private IAPIProvider api { get; set; } - - protected PaginationParameters? CurrentPage { get; private set; } - - protected ReverseChildIDFillFlowContainer ItemsContainer { get; private set; } - - private APIRequest> scoreRetrievalRequest; - private APIRequest beatmapRetrievalRequest; - private CancellationTokenSource loadCancellation; - - protected List CurrentScores { get; private set; } = new List(); - - private ShowMoreButton moreButton; - private OsuSpriteText missing; - private readonly LocalisableString? missingText; - - protected PaginatedProfileScoreSubsection(Bindable user, LocalisableString? headerText = null, LocalisableString? missingText = null) - : base(user, headerText, CounterVisibilityState.AlwaysVisible) - { - this.missingText = missingText; - } - - protected override Drawable CreateContent() => new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - // reverse ID flow is required for correct Z-ordering of the items (last item should be front-most). - // particularly important in PaginatedBeatmapContainer, as it uses beatmap cards, which have expandable overhanging content. - ItemsContainer = new ReverseChildIDFillFlowContainer - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Spacing = new Vector2(0, 2), - // ensure the container and its contents are in front of the "more" button. - Depth = float.MinValue - }, - moreButton = new ShowMoreButton - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Alpha = 0, - Margin = new MarginPadding { Top = 10 }, - Action = showMore, - }, - missing = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 15), - Text = missingText ?? string.Empty, - Alpha = 0, - } - } - }; - - protected override void LoadComplete() - { - base.LoadComplete(); - User.BindValueChanged(onUserChanged, true); - } - - private void onUserChanged(ValueChangedEvent e) - { - loadCancellation?.Cancel(); - scoreRetrievalRequest?.Cancel(); - beatmapRetrievalRequest?.Cancel(); - - CurrentPage = null; - ItemsContainer.Clear(); - CurrentScores.Clear(); - - if (e.NewValue != null) - { - showMore(); - SetCount(GetCount(e.NewValue)); - } - } - - private void showMore() - { - loadCancellation = new CancellationTokenSource(); - - CurrentPage = CurrentPage?.TakeNext(ItemsPerPage) ?? new PaginationParameters(InitialItemsCount); - - scoreRetrievalRequest = CreateScoreRequest(CurrentPage.Value); - scoreRetrievalRequest.Success += requestBeatmaps; - - api.Queue(scoreRetrievalRequest); - } - - private void requestBeatmaps(List items) - { - CurrentScores = items; - - beatmapRetrievalRequest = CreateBeatmapsRequest(items); - beatmapRetrievalRequest.Success += UpdateItems; - - api.Queue(beatmapRetrievalRequest); - } - - protected virtual APIRequest CreateBeatmapsRequest(List items) => new GetBeatmapsRequest(items.Select(i => i.BeatmapID).ToArray()); - - protected virtual void UpdateItems(GetBeatmapsResponse beatmaps) => Schedule(() => - { - var scoreBeatmapPairs = new List>(); - - foreach (var score in CurrentScores) - { - var beatmap = beatmaps.Beatmaps.Find(m => m.OnlineID == score.BeatmapID); - scoreBeatmapPairs.Add(new Tuple(score, beatmap)); - } - - OnItemsReceived(scoreBeatmapPairs); - - if (!scoreBeatmapPairs.Any() && CurrentPage?.Offset == 0) - { - moreButton.Hide(); - moreButton.IsLoading = false; - - if (missingText.HasValue) - missing.Show(); - - return; - } - - LoadComponentsAsync(scoreBeatmapPairs.Select(CreateDrawableItem).Where(d => d != null), drawables => - { - missing.Hide(); - - moreButton.FadeTo(scoreBeatmapPairs.Count == CurrentPage?.Limit ? 1 : 0); - moreButton.IsLoading = false; - - ItemsContainer.AddRange(drawables); - }, loadCancellation.Token); - }); - - protected virtual int GetCount(APIUser user) => 0; - - protected virtual void OnItemsReceived(List> scoreBeatmapPairs) - { - } - - protected abstract APIRequest> CreateScoreRequest(PaginationParameters pagination); - - protected abstract Drawable CreateDrawableItem(Tuple scoreBeatmapPair); - - protected override void Dispose(bool isDisposing) - { - scoreRetrievalRequest?.Cancel(); - loadCancellation?.Cancel(); - base.Dispose(isDisposing); - } - } -} diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 1ff1e0e842..26181e7b40 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -32,7 +32,6 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private const float performance_background_shear = 0.45f; protected readonly SoloScoreInfo Score; - protected readonly APIBeatmap Beatmap; [Resolved] private OsuColour colours { get; set; } @@ -40,10 +39,9 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks [Resolved] private OverlayColourProvider colourProvider { get; set; } - public DrawableProfileScore(SoloScoreInfo score, APIBeatmap beatmap) + public DrawableProfileScore(SoloScoreInfo score) { Score = score; - Beatmap = beatmap; RelativeSizeAxes = Axes.X; Height = height; @@ -86,7 +84,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Spacing = new Vector2(0, 2), Children = new Drawable[] { - new ScoreBeatmapMetadataContainer(Beatmap), + new ScoreBeatmapMetadataContainer(Score.Beatmap), new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -96,7 +94,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { new OsuSpriteText { - Text = $"{Beatmap.DifficultyName}", + Text = $"{Score.Beatmap?.DifficultyName}", Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), Colour = colours.Yellow }, diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs index 8d0a41ec48..94d95dc27e 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs @@ -18,8 +18,8 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { private readonly double weight; - public DrawableProfileWeightedScore(SoloScoreInfo score, APIBeatmap beatmap, double weight) - : base(score, beatmap) + public DrawableProfileWeightedScore(SoloScoreInfo score, double weight) + : base(score) { this.weight = weight; } diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 9af7d59d53..8378d61119 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -17,7 +17,7 @@ using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class PaginatedScoreContainer : PaginatedProfileScoreSubsection + public class PaginatedScoreContainer : PaginatedProfileSubsection { private readonly ScoreType type; @@ -54,28 +54,28 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks } } - protected override void OnItemsReceived(List> scoreBeatmapPairs) + protected override void OnItemsReceived(List items) { if (CurrentPage == null || CurrentPage?.Offset == 0) drawableItemIndex = 0; - base.OnItemsReceived(scoreBeatmapPairs); + base.OnItemsReceived(items); } - protected override APIRequest> CreateScoreRequest(PaginationParameters pagination) => + protected override APIRequest> CreateRequest(PaginationParameters pagination) => new GetUserScoresRequest(User.Value.Id, type, pagination); private int drawableItemIndex; - protected override Drawable CreateDrawableItem(Tuple scoreBeatmapPair) + protected override Drawable CreateDrawableItem(SoloScoreInfo item) { switch (type) { default: - return new DrawableProfileScore(scoreBeatmapPair.Item1, scoreBeatmapPair.Item2); + return new DrawableProfileScore(item); case ScoreType.Best: - return new DrawableProfileWeightedScore(scoreBeatmapPair.Item1, scoreBeatmapPair.Item2, Math.Pow(0.95, drawableItemIndex++)); + return new DrawableProfileWeightedScore(item, Math.Pow(0.95, drawableItemIndex++)); } } } From 14ae183c700b18188f2d00288b4f5c405f21493c Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:22:46 +0100 Subject: [PATCH 069/481] update tests to match `SoloScoreInfo` --- .../Online/TestSceneUserProfileScores.cs | 88 +++++++++---------- 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs index 33ddf4a3a9..0eb6ec3c04 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs @@ -25,7 +25,15 @@ namespace osu.Game.Tests.Visual.Online { PP = 1047.21, Rank = ScoreRank.SH, - BeatmapID = 2058788, + Beatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet + { + Title = "JUSTadICE (TV Size)", + Artist = "Oomori Seiko", + }, + DifficultyName = "Extreme" + }, EndedAt = DateTimeOffset.Now, Mods = new[] { @@ -35,21 +43,20 @@ namespace osu.Game.Tests.Visual.Online }, Accuracy = 0.9813 }; - var firstBeatmap = new APIBeatmap - { - BeatmapSet = new APIBeatmapSet() - { - Title = "JUSTadICE (TV Size)", - Artist = "Oomori Seiko", - }, - DifficultyName = "Extreme" - }; var secondScore = new SoloScoreInfo { PP = 134.32, Rank = ScoreRank.A, - BeatmapID = 767046, + Beatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet + { + Title = "Triumph & Regret", + Artist = "typeMARS", + }, + DifficultyName = "[4K] Regret" + }, EndedAt = DateTimeOffset.Now, Mods = new[] { @@ -58,50 +65,39 @@ namespace osu.Game.Tests.Visual.Online }, Accuracy = 0.998546 }; - var secondBeatmap = new APIBeatmap - { - BeatmapSet = new APIBeatmapSet() - { - Title = "Triumph & Regret", - Artist = "typeMARS", - }, - DifficultyName = "[4K] Regret" - }; var thirdScore = new SoloScoreInfo { PP = 96.83, Rank = ScoreRank.S, - BeatmapID = 2134713, + Beatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet + { + Title = "Idolize", + Artist = "Creo", + }, + DifficultyName = "Insane" + }, EndedAt = DateTimeOffset.Now, Accuracy = 0.9726 }; - var thirdBeatmap = new APIBeatmap - { - BeatmapSet = new APIBeatmapSet() - { - Title = "Idolize", - Artist = "Creo", - }, - DifficultyName = "Insane" - }; var noPPScore = new SoloScoreInfo { Rank = ScoreRank.B, - BeatmapID = 992512, + Beatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet + { + Title = "C18H27NO3(extend)", + Artist = "Team Grimoire", + }, + DifficultyName = "[4K] Cataclysmic Hypernova" + }, EndedAt = DateTimeOffset.Now, Accuracy = 0.55879 }; - var noPPBeatmap = new APIBeatmap - { - BeatmapSet = new APIBeatmapSet() - { - Title = "Galaxy Collapse", - Artist = "Kurokotei", - }, - DifficultyName = "[4K] Cataclysmic Hypernova" - }; Add(new FillFlowContainer { @@ -113,12 +109,12 @@ namespace osu.Game.Tests.Visual.Online Spacing = new Vector2(0, 10), Children = new[] { - new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(firstScore, firstBeatmap)), - new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(secondScore, secondBeatmap)), - new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(noPPScore, noPPBeatmap)), - new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(firstScore, firstBeatmap, 0.97)), - new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(secondScore, secondBeatmap, 0.85)), - new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(thirdScore, thirdBeatmap, 0.66)), + new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(firstScore)), + new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(secondScore)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(noPPScore)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(firstScore, 0.97)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(secondScore, 0.85)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(thirdScore, 0.66)), } }); } From c73eff7c890014fbec8a30a1304eb424216bd7d2 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:46:22 +0100 Subject: [PATCH 070/481] add `BeatmapSet` to `SoloScoreInfo` --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 963b2975d2..e7abb28286 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -85,6 +85,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("beatmap")] public APIBeatmap? Beatmap { get; set; } + [JsonProperty("beatmapset")] + public APIBeatmapSet? BeatmapSet { get; set; } + [JsonProperty("pp")] public double? PP { get; set; } From 486fbd25315c56835a1dd3ce67f2fef503aaa6cf Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:47:04 +0100 Subject: [PATCH 071/481] create instance of `BeatmapInfo` for use in `ScoreBeatmapMetadataContainer` --- .../Sections/Ranks/DrawableProfileScore.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 26181e7b40..c11a99808e 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -50,6 +50,24 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { + var beatmapSet = Score.BeatmapSet ?? Score.Beatmap?.BeatmapSet; + var beatmapInfo = new BeatmapInfo(); + + if (beatmapSet != null) + { + var beatmapMetadata = new BeatmapMetadata + { + Artist = beatmapSet.Artist, + ArtistUnicode = beatmapSet.ArtistUnicode, + Title = beatmapSet.Title, + TitleUnicode = beatmapSet.TitleUnicode, + }; + + beatmapInfo.Metadata = beatmapMetadata; + if (Score.Beatmap?.DifficultyName != null) + beatmapInfo.DifficultyName = Score.Beatmap.DifficultyName; + } + AddInternal(new ProfileItemContainer { Children = new Drawable[] @@ -84,7 +102,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Spacing = new Vector2(0, 2), Children = new Drawable[] { - new ScoreBeatmapMetadataContainer(Score.Beatmap), + new ScoreBeatmapMetadataContainer(beatmapInfo), new FillFlowContainer { AutoSizeAxes = Axes.Both, From 7a6666996fef438427666806bbfbffe9b934d872 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 14:50:53 +0100 Subject: [PATCH 072/481] rename `item` to `model` in `CreateDrawableItem` --- .../Profile/Sections/Ranks/PaginatedScoreContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 8378d61119..2564692c87 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -67,15 +67,15 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private int drawableItemIndex; - protected override Drawable CreateDrawableItem(SoloScoreInfo item) + protected override Drawable CreateDrawableItem(SoloScoreInfo model) { switch (type) { default: - return new DrawableProfileScore(item); + return new DrawableProfileScore(model); case ScoreType.Best: - return new DrawableProfileWeightedScore(item, Math.Pow(0.95, drawableItemIndex++)); + return new DrawableProfileWeightedScore(model, Math.Pow(0.95, drawableItemIndex++)); } } } From 1caab78bdc38dcc512831a88809749db334837f7 Mon Sep 17 00:00:00 2001 From: NotGumballer91 <64581009+NotGumballer91@users.noreply.github.com> Date: Mon, 18 Jul 2022 00:09:31 +0800 Subject: [PATCH 073/481] Update ModAutoplay.cs --- osu.Game/Rulesets/Mods/ModAutoplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 6d786fc8e2..b4ab0500c7 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mods public override bool ValidForMultiplayer => false; public override bool ValidForMultiplayerAsFreeMod => false; - public override Type[] IncompatibleMods => new[] { typeof(ModCinema), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail) }; + public override Type[] IncompatibleMods => new[] { typeof(ModCinema), typeof(ModRelax), typeof(ModFailCondition), typeof(ModNoFail), typeof(ModAdaptiveSpeed) }; public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; From 028653eb97ef47051b6c21dc7156ecbb45a24eed Mon Sep 17 00:00:00 2001 From: NotGumballer91 <64581009+NotGumballer91@users.noreply.github.com> Date: Mon, 18 Jul 2022 00:10:49 +0800 Subject: [PATCH 074/481] Update ModAdaptiveSpeed.cs --- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index aea6e12a07..eab0f8dc26 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Mods public override bool ValidForMultiplayer => false; public override bool ValidForMultiplayerAsFreeMod => false; - public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp) }; + public override Type[] IncompatibleMods => new[] { typeof(ModRateAdjust), typeof(ModTimeRamp), typeof(ModAutoplay) }; [SettingSource("Initial rate", "The starting speed of the track")] public BindableNumber InitialRate { get; } = new BindableDouble From 4e8bf1da52b2c410bd5739a8b14eb0926cf9b7e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 01:23:46 +0900 Subject: [PATCH 075/481] Don't sent ruleset configuration failures to sentry --- osu.Game/Overlays/Settings/Sections/RulesetSection.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/RulesetSection.cs b/osu.Game/Overlays/Settings/Sections/RulesetSection.cs index 2368459b4e..a5f5810214 100644 --- a/osu.Game/Overlays/Settings/Sections/RulesetSection.cs +++ b/osu.Game/Overlays/Settings/Sections/RulesetSection.cs @@ -1,17 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Framework.Logging; -using osu.Game.Rulesets; using osu.Game.Localisation; +using osu.Game.Rulesets; namespace osu.Game.Overlays.Settings.Sections { @@ -36,9 +33,9 @@ namespace osu.Game.Overlays.Settings.Sections if (section != null) Add(section); } - catch (Exception e) + catch { - Logger.Error(e, "Failed to load ruleset settings"); + Logger.Log($"Failed to load ruleset settings for {ruleset.RulesetInfo.Name}. Please check for an update from the developer.", level: LogLevel.Error); } } } From 8791edf84c1ca5b2500dcb7b80ddcd058b952f69 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 18:10:33 +0100 Subject: [PATCH 076/481] set `Beatmap.BeatmapSet` to `BeatmapSet` property in `SoloScoreInfo` --- .../API/Requests/Responses/SoloScoreInfo.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index e7abb28286..d8adbbb9ad 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using osu.Game.Beatmaps; @@ -86,7 +87,19 @@ namespace osu.Game.Online.API.Requests.Responses public APIBeatmap? Beatmap { get; set; } [JsonProperty("beatmapset")] - public APIBeatmapSet? BeatmapSet { get; set; } + [CanBeNull] + public APIBeatmapSet BeatmapSet + { + set + { + // in the deserialisation case we need to ferry this data across. + // the order of properties returned by the API guarantees that the beatmap is populated by this point. + if (!(Beatmap is APIBeatmap apiBeatmap)) + throw new InvalidOperationException("Beatmap set metadata arrived before beatmap metadata in response"); + + apiBeatmap.BeatmapSet = value; + } + } [JsonProperty("pp")] public double? PP { get; set; } From a5d7075ef171b2d0ba50a9ac6b6c3f4e3c48331b Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 18:10:47 +0100 Subject: [PATCH 077/481] simplify beatmap metadata logic in `DrawableProfileScore` --- .../Sections/Ranks/DrawableProfileScore.cs | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index c11a99808e..26181e7b40 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -50,24 +50,6 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - var beatmapSet = Score.BeatmapSet ?? Score.Beatmap?.BeatmapSet; - var beatmapInfo = new BeatmapInfo(); - - if (beatmapSet != null) - { - var beatmapMetadata = new BeatmapMetadata - { - Artist = beatmapSet.Artist, - ArtistUnicode = beatmapSet.ArtistUnicode, - Title = beatmapSet.Title, - TitleUnicode = beatmapSet.TitleUnicode, - }; - - beatmapInfo.Metadata = beatmapMetadata; - if (Score.Beatmap?.DifficultyName != null) - beatmapInfo.DifficultyName = Score.Beatmap.DifficultyName; - } - AddInternal(new ProfileItemContainer { Children = new Drawable[] @@ -102,7 +84,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Spacing = new Vector2(0, 2), Children = new Drawable[] { - new ScoreBeatmapMetadataContainer(beatmapInfo), + new ScoreBeatmapMetadataContainer(Score.Beatmap), new FillFlowContainer { AutoSizeAxes = Axes.Both, From 032cc6c670869cf2414f398c9ed1d8cdda4c3eac Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 18:15:36 +0100 Subject: [PATCH 078/481] use type annotation for nullable `BeatmapSet` --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index d8adbbb9ad..21443280b6 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -87,8 +87,7 @@ namespace osu.Game.Online.API.Requests.Responses public APIBeatmap? Beatmap { get; set; } [JsonProperty("beatmapset")] - [CanBeNull] - public APIBeatmapSet BeatmapSet + public APIBeatmapSet? BeatmapSet { set { From 5875f53e07198ec5b367e0325a6ad0cd092b991e Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 18:16:30 +0100 Subject: [PATCH 079/481] remove unused import --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index 21443280b6..393597dbfe 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using osu.Game.Beatmaps; From e13c1254e5211c459c00a4a84a4a63151d66035a Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 20:41:43 +0100 Subject: [PATCH 080/481] make perfect incompatible with autopilot --- osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs index c5795177d0..1b12aab150 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs @@ -3,11 +3,14 @@ #nullable disable +using System; +using System.Linq; using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Osu.Mods { public class OsuModPerfect : ModPerfect { + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new Type[] { typeof(OsuModAutopilot) }).ToArray(); } } From 491558261f4163f1f06e0b16f7cffe7a255e9af1 Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 20:44:06 +0100 Subject: [PATCH 081/481] remove unnecessary type-specification --- osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs index 1b12aab150..bde7718da5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs @@ -11,6 +11,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModPerfect : ModPerfect { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new Type[] { typeof(OsuModAutopilot) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModAutopilot) }).ToArray(); } } From 83429d2f221567198f19b1a618f785a260ad578f Mon Sep 17 00:00:00 2001 From: tsunyoku Date: Sun, 17 Jul 2022 20:45:17 +0100 Subject: [PATCH 082/481] make cinema incompatible with repel --- osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs index 704b922ee5..d5096619b9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModCinema : ModCinema { - public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate), typeof(OsuModSingleTap) }).ToArray(); + public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModMagnetised), typeof(OsuModAutopilot), typeof(OsuModSpunOut), typeof(OsuModAlternate), typeof(OsuModSingleTap), typeof(OsuModRepel) }).ToArray(); public override ModReplayData CreateReplayData(IBeatmap beatmap, IReadOnlyList mods) => new ModReplayData(new OsuAutoGenerator(beatmap, mods).Generate(), new ModCreatedUser { Username = "Autoplay" }); From 7e4ce899814167ee3dee340597b28e49b881c201 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 04:03:18 +0300 Subject: [PATCH 083/481] Include mod settings in profile score mod icons --- .../Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 26181e7b40..c5e2b6df0c 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -138,7 +138,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { var ruleset = rulesets.GetRuleset(Score.RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {Score.RulesetID} not found locally"); - return new ModIcon(ruleset.CreateInstance().CreateModFromAcronym(mod.Acronym)) + return new ModIcon(mod.ToMod(ruleset.CreateInstance())) { Scale = new Vector2(0.35f) }; From b33f8aa0fcdb3d4ace4d3054d4811c86e1f72aec Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 06:45:00 +0300 Subject: [PATCH 084/481] Fix "Start Chat" on multiplayer/playlist chat not opening overlay --- osu.Game/Overlays/Chat/ChatLine.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 5961298485..4c425d3d4c 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -257,10 +257,14 @@ namespace osu.Game.Overlays.Chat } [BackgroundDependencyLoader] - private void load(UserProfileOverlay? profile, ChannelManager? chatManager) + private void load(UserProfileOverlay? profile, ChannelManager? chatManager, ChatOverlay? chatOverlay) { Action = () => profile?.ShowUser(sender); - startChatAction = () => chatManager?.OpenPrivateChannel(sender); + startChatAction = () => + { + chatManager?.OpenPrivateChannel(sender); + chatOverlay?.Show(); + }; } public MenuItem[] ContextMenuItems From 6ad7723d60700ee25c95113f6f806caaf29bdc67 Mon Sep 17 00:00:00 2001 From: Fyra Tedja Date: Sun, 17 Jul 2022 22:13:08 -0600 Subject: [PATCH 085/481] Make sure stats name are title-cased when score panel is contracted --- .../Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 7b8b4ce608..3e67868f34 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -214,7 +215,7 @@ namespace osu.Game.Screens.Ranking.Contracted { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Text = key, + Text = key.ToTitle(), Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) }, new OsuSpriteText From 6bfa5e53e0848e2d09543a75cbb2801f524c96d8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 07:21:10 +0300 Subject: [PATCH 086/481] Add property for whether mod uses default configuration --- osu.Game/Rulesets/Mods/Mod.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 17093e3033..d519256d23 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -128,6 +128,11 @@ namespace osu.Game.Rulesets.Mods .Cast() .ToList(); + /// + /// Whether all settings in this mod are set to their default state. + /// + protected virtual bool UsesDefaultConfiguration => Settings.All(s => s.IsDefault); + /// /// Creates a copy of this initialised to a default state. /// From 32ba58109b59518f784dfa1fc434f8ea44bab5bd Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 07:22:25 +0300 Subject: [PATCH 087/481] Remove score multiplier on difficulty-increasing mods with customised settings --- osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs | 2 +- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 2 +- osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs | 2 +- osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs | 2 +- osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 2 +- osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs index 4db0a53005..c58ce9b07d 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs @@ -9,6 +9,6 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModDoubleTime : ModDoubleTime { - public override double ScoreMultiplier => 1.06; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; } } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index e05932ca03..d166646eaf 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModFlashlight : ModFlashlight { - public override double ScoreMultiplier => 1.12; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableFloat SizeMultiplier { get; } = new BindableFloat diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs index 7db9bf2dfd..39b992b3f5 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModHardRock : ModHardRock, IApplicableToBeatmapProcessor { - public override double ScoreMultiplier => 1.12; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; public void ApplyToBeatmapProcessor(IBeatmapProcessor beatmapProcessor) { diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs index c02eedf936..b4fbc9d566 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Mods public class CatchModHidden : ModHidden, IApplicableToDrawableRuleset { public override string Description => @"Play with fading fruits."; - public override double ScoreMultiplier => 1.06; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; private const double fade_out_offset_multiplier = 0.6; private const double fade_out_duration_multiplier = 0.44; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs index 365d987794..1fd2227eb7 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Catch.Mods { public class CatchModNightcore : ModNightcore { - public override double ScoreMultiplier => 1.06; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index 199c735787..17a9a81de8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override IconUsage? Icon => FontAwesome.Solid.Adjust; public override ModType Type => ModType.DifficultyIncrease; - public override double ScoreMultiplier => 1.12; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight) }; private DrawableOsuBlinds blinds; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs index 2d19305509..b86efe84ee 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs @@ -9,6 +9,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModDoubleTime : ModDoubleTime { - public override double ScoreMultiplier => 1.12; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index c082805a0e..b72e6b4dcb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModFlashlight : ModFlashlight, IApplicableToDrawableHitObject { - public override double ScoreMultiplier => 1.12; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModBlinds)).ToArray(); private const double default_follow_delay = 120; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs index fdddfed4d5..1f25655c8c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModHardRock : ModHardRock, IApplicableToHitObject { - public override double ScoreMultiplier => 1.06; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModMirror)).ToArray(); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 11ceb0f710..253eaf473b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Mods public Bindable OnlyFadeApproachCircles { get; } = new BindableBool(); public override string Description => @"Play with no approach circles and fading circles/sliders."; - public override double ScoreMultiplier => 1.06; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn) }; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs index e9be56fcc5..b1fe066a1e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Osu.Mods { public class OsuModNightcore : ModNightcore { - public override double ScoreMultiplier => 1.12; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs index 01f1632ae2..b19c2eaccf 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs @@ -9,6 +9,6 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModDoubleTime : ModDoubleTime { - public override double ScoreMultiplier => 1.12; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; } } diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 2f9cccfe86..fe02a6caf9 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModFlashlight : ModFlashlight { - public override double ScoreMultiplier => 1.12; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; [SettingSource("Flashlight size", "Multiplier applied to the default flashlight size.")] public override BindableFloat SizeMultiplier { get; } = new BindableFloat diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs index 7fcd925c04..7780936e7d 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs @@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModHardRock : ModHardRock { - public override double ScoreMultiplier => 1.06; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; /// /// Multiplier factor added to the scrolling speed. diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index e065bb43fd..fe3e5ca11c 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public class TaikoModHidden : ModHidden, IApplicableToDrawableRuleset { public override string Description => @"Beats fade out before you hit them!"; - public override double ScoreMultiplier => 1.06; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1; /// /// How far away from the hit target should hitobjects start to fade out. diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs index f2a57ecf88..e02a16f62f 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModNightcore : ModNightcore { - public override double ScoreMultiplier => 1.12; + public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; } } From eddae7b14395485c8bd58d0f2a04c1b43c81ae39 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 07:38:56 +0300 Subject: [PATCH 088/481] Fix mod overlay and footer not updating multiplayer on settings change --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 7 +++++++ osu.Game/Screens/Select/FooterButtonMods.cs | 15 ++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 04c424461e..ea152f58ad 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -222,6 +222,8 @@ namespace osu.Game.Overlays.Mods globalAvailableMods.BindTo(game.AvailableMods); } + private ModSettingChangeTracker? modSettingChangeTracker; + protected override void LoadComplete() { // this is called before base call so that the mod state is populated early, and the transition in `PopIn()` can play out properly. @@ -238,9 +240,14 @@ namespace osu.Game.Overlays.Mods SelectedMods.BindValueChanged(val => { + modSettingChangeTracker?.Dispose(); + updateMultiplier(); updateCustomisation(val); updateFromExternalSelection(); + + modSettingChangeTracker = new ModSettingChangeTracker(val.NewValue); + modSettingChangeTracker.SettingChanged += _ => updateMultiplier(); }, true); customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true); diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index c938f58984..1fb73c2333 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -9,9 +9,11 @@ using osu.Game.Screens.Play.HUD; using osu.Game.Rulesets.Mods; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.UserInterface; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osuTK; @@ -61,11 +63,22 @@ namespace osu.Game.Screens.Select Hotkey = GlobalAction.ToggleModSelection; } + [CanBeNull] + private ModSettingChangeTracker modSettingChangeTracker; + protected override void LoadComplete() { base.LoadComplete(); - Current.BindValueChanged(_ => updateMultiplierText(), true); + Current.BindValueChanged(mods => + { + modSettingChangeTracker?.Dispose(); + + updateMultiplierText(); + + modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue); + modSettingChangeTracker.SettingChanged += _ => updateMultiplierText(); + }, true); } private void updateMultiplierText() => Schedule(() => From 0533249d11ac220e223369f774fafcabc3f30c51 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 14:34:02 +0900 Subject: [PATCH 089/481] Update all `OnlineSpectatorCalls` to `InvokeAsync` --- osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs | 1 - osu.Game/Online/Spectator/OnlineSpectatorClient.cs | 9 ++++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 4e6ea997c1..c061398209 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -80,7 +80,6 @@ namespace osu.Game.Online.Multiplayer try { - // Importantly, use Invoke rather than Send to capture exceptions. return await connection.InvokeAsync(nameof(IMultiplayerServer.JoinRoomWithPassword), roomId, password ?? string.Empty); } catch (HubException exception) diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index 1ed7819b05..2a05fb1bc0 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -56,7 +56,6 @@ namespace osu.Game.Online.Spectator try { - // Importantly, use Invoke rather than Send to capture exceptions. await connection.InvokeAsync(nameof(ISpectatorServer.BeginPlaySession), state); } catch (HubException exception) @@ -74,7 +73,7 @@ namespace osu.Game.Online.Spectator Debug.Assert(connection != null); - return connection.SendAsync(nameof(ISpectatorServer.SendFrameData), bundle); + return connection.InvokeAsync(nameof(ISpectatorServer.SendFrameData), bundle); } protected override Task EndPlayingInternal(SpectatorState state) @@ -84,7 +83,7 @@ namespace osu.Game.Online.Spectator Debug.Assert(connection != null); - return connection.SendAsync(nameof(ISpectatorServer.EndPlaySession), state); + return connection.InvokeAsync(nameof(ISpectatorServer.EndPlaySession), state); } protected override Task WatchUserInternal(int userId) @@ -94,7 +93,7 @@ namespace osu.Game.Online.Spectator Debug.Assert(connection != null); - return connection.SendAsync(nameof(ISpectatorServer.StartWatchingUser), userId); + return connection.InvokeAsync(nameof(ISpectatorServer.StartWatchingUser), userId); } protected override Task StopWatchingUserInternal(int userId) @@ -104,7 +103,7 @@ namespace osu.Game.Online.Spectator Debug.Assert(connection != null); - return connection.SendAsync(nameof(ISpectatorServer.EndWatchingUser), userId); + return connection.InvokeAsync(nameof(ISpectatorServer.EndWatchingUser), userId); } } } From 100c53f9ef24e2fd66743aa81dcc0624ae5f23df Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 08:40:34 +0300 Subject: [PATCH 090/481] `Country` -> `CountryCode` --- .../Visual/Online/TestSceneFriendDisplay.cs | 6 ++--- .../Online/TestSceneRankingsCountryFilter.cs | 6 ++--- .../Visual/Online/TestSceneRankingsHeader.cs | 6 ++--- .../Visual/Online/TestSceneRankingsOverlay.cs | 8 +++---- .../Visual/Online/TestSceneRankingsTables.cs | 10 ++++---- .../Visual/Online/TestSceneScoresContainer.cs | 12 +++++----- .../Visual/Online/TestSceneUserPanel.cs | 6 ++--- .../Online/TestSceneUserProfileOverlay.cs | 8 +++---- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 24 +++++++++---------- .../TestSceneUserTopScoreContainer.cs | 6 ++--- osu.Game.Tournament/Models/TournamentUser.cs | 4 ++-- osu.Game.Tournament/TournamentGameBase.cs | 4 ++-- .../API/Requests/GetUserRankingsRequest.cs | 10 ++++---- .../Online/Leaderboards/LeaderboardScore.cs | 2 +- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- .../BeatmapSet/Scores/TopScoreUserSection.cs | 2 +- .../Profile/Header/TopHeaderContainer.cs | 4 ++-- osu.Game/Overlays/Rankings/CountryFilter.cs | 8 +++---- osu.Game/Overlays/Rankings/CountryPill.cs | 10 ++++---- .../Rankings/RankingsOverlayHeader.cs | 2 +- .../Rankings/Tables/CountriesTable.cs | 10 ++++---- .../Overlays/Rankings/Tables/RankingsTable.cs | 4 ++-- .../Rankings/Tables/UserBasedTable.cs | 2 +- osu.Game/Overlays/RankingsOverlay.cs | 6 ++--- .../Participants/ParticipantPanel.cs | 2 +- osu.Game/Users/{Country.cs => CountryCode.cs} | 2 +- osu.Game/Users/CountryStatistics.cs | 2 +- osu.Game/Users/Drawables/DrawableFlag.cs | 10 ++++---- osu.Game/Users/Drawables/UpdateableFlag.cs | 16 ++++++------- osu.Game/Users/ExtendedUserPanel.cs | 2 +- 30 files changed, 98 insertions(+), 98 deletions(-) rename osu.Game/Users/{Country.cs => CountryCode.cs} (99%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs index 43d41e9fb9..5454c87dff 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFriendDisplay.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Online Id = 3103765, IsOnline = true, Statistics = new UserStatistics { GlobalRank = 1111 }, - Country = Country.JP, + CountryCode = CountryCode.JP, CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" }, new APIUser @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online Id = 2, IsOnline = false, Statistics = new UserStatistics { GlobalRank = 2222 }, - Country = Country.AU, + CountryCode = CountryCode.AU, CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", IsSupporter = true, SupportLevel = 3, @@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Online { Username = "Evast", Id = 8195163, - Country = Country.BY, + CountryCode = CountryCode.BY, CoverUrl = "https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg", IsOnline = false, LastVisit = DateTimeOffset.Now diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsCountryFilter.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsCountryFilter.cs index ce0ca0b6c9..6a39db4870 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsCountryFilter.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsCountryFilter.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Online public TestSceneRankingsCountryFilter() { - var countryBindable = new Bindable(); + var countryBindable = new Bindable(); AddRange(new Drawable[] { @@ -56,8 +56,8 @@ namespace osu.Game.Tests.Visual.Online } }); - const Country country = Country.BY; - const Country unknown_country = Country.CK; + const CountryCode country = CountryCode.BY; + const CountryCode unknown_country = CountryCode.CK; AddStep("Set country", () => countryBindable.Value = country); AddStep("Set default country", () => countryBindable.Value = default); diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs index 9aedfb0a14..c776cfe377 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsHeader.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Online public TestSceneRankingsHeader() { - var countryBindable = new Bindable(); + var countryBindable = new Bindable(); var ruleset = new Bindable(); var scope = new Bindable(); @@ -30,8 +30,8 @@ namespace osu.Game.Tests.Visual.Online Ruleset = { BindTarget = ruleset } }); - const Country country = Country.BY; - const Country unknown_country = Country.CK; + const CountryCode country = CountryCode.BY; + const CountryCode unknown_country = CountryCode.CK; AddStep("Set country", () => countryBindable.Value = country); AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs index ea7eb14c8e..7f4b1284a0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Online private TestRankingsOverlay rankingsOverlay; - private readonly Bindable countryBindable = new Bindable(); + private readonly Bindable countryBindable = new Bindable(); private readonly Bindable scope = new Bindable(); [SetUp] @@ -49,14 +49,14 @@ namespace osu.Game.Tests.Visual.Online { AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); AddAssert("Check country is default", () => countryBindable.Value == default); - AddStep("Set country", () => countryBindable.Value = Country.US); + AddStep("Set country", () => countryBindable.Value = CountryCode.US); AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance); } [Test] public void TestShowCountry() { - AddStep("Show US", () => rankingsOverlay.ShowCountry(Country.US)); + AddStep("Show US", () => rankingsOverlay.ShowCountry(CountryCode.US)); } private void loadRankingsOverlay() @@ -71,7 +71,7 @@ namespace osu.Game.Tests.Visual.Online private class TestRankingsOverlay : RankingsOverlay { - public new Bindable Country => base.Country; + public new Bindable Country => base.Country; } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs index f889e3f7dd..81b76d19ac 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Online { new CountryStatistics { - Country = Country.US, + Code = CountryCode.US, ActiveUsers = 2_972_623, PlayCount = 3_086_515_743, RankedScore = 449_407_643_332_546, @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.Online }, new CountryStatistics { - Country = Country.RU, + Code = CountryCode.RU, ActiveUsers = 1_609_989, PlayCount = 1_637_052_841, RankedScore = 221_660_827_473_004, @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Online User = new APIUser { Username = "first active user", - Country = Country.JP, + CountryCode = CountryCode.JP, Active = true, }, Accuracy = 0.9972, @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.Online User = new APIUser { Username = "inactive user", - Country = Country.AU, + CountryCode = CountryCode.AU, Active = false, }, Accuracy = 0.9831, @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.Online User = new APIUser { Username = "second active user", - Country = Country.PL, + CountryCode = CountryCode.PL, Active = true, }, Accuracy = 0.9584, diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 6ac3d5cb86..beca3a8700 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -157,7 +157,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 6602580, Username = @"waaiiru", - Country = Country.ES, + CountryCode = CountryCode.ES, }, Mods = new[] { @@ -180,7 +180,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 4608074, Username = @"Skycries", - Country = Country.BR, + CountryCode = CountryCode.BR, }, Mods = new[] { @@ -202,7 +202,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 1014222, Username = @"eLy", - Country = Country.JP, + CountryCode = CountryCode.JP, }, Mods = new[] { @@ -223,7 +223,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 1541390, Username = @"Toukai", - Country = Country.CA, + CountryCode = CountryCode.CA, }, Mods = new[] { @@ -243,7 +243,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 7151382, Username = @"Mayuri Hana", - Country = Country.TH, + CountryCode = CountryCode.TH, }, Rank = ScoreRank.D, PP = 160, @@ -282,7 +282,7 @@ namespace osu.Game.Tests.Visual.Online { Id = 7151382, Username = @"Mayuri Hana", - Country = Country.TH, + CountryCode = CountryCode.TH, }, Rank = ScoreRank.D, PP = 160, diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs index addcf799e3..2a70fd7df3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserPanel.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"flyte", Id = 3103765, - Country = Country.JP, + CountryCode = CountryCode.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg", Status = { Value = new UserStatusOnline() } }) { Width = 300 }, @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"peppy", Id = 2, - Country = Country.AU, + CountryCode = CountryCode.AU, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", IsSupporter = true, SupportLevel = 3, @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"Evast", Id = 8195163, - Country = Country.BY, + CountryCode = CountryCode.BY, CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg", IsOnline = false, LastVisit = DateTimeOffset.Now diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index dd6b022624..c2766e2142 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"Somebody", Id = 1, - Country = Country.XX, + CountryCode = CountryCode.XX, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", JoinDate = DateTimeOffset.Now.AddDays(-1), LastVisit = DateTimeOffset.Now, @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Online Username = @"peppy", Id = 2, IsSupporter = true, - Country = Country.AU, + CountryCode = CountryCode.AU, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg" })); @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"flyte", Id = 3103765, - Country = Country.JP, + CountryCode = CountryCode.JP, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg" })); @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Online Username = @"BanchoBot", Id = 3, IsBot = true, - Country = Country.SH, + CountryCode = CountryCode.SH, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c4.jpg" })); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 48fdf671f1..abcb888cd4 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -140,7 +140,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6602580, Username = @"waaiiru", - Country = Country.ES, + CountryCode = CountryCode.ES, }, }); } @@ -160,7 +160,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6602580, Username = @"waaiiru", - Country = Country.ES, + CountryCode = CountryCode.ES, } }); } @@ -217,7 +217,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6602580, Username = @"waaiiru", - Country = Country.ES, + CountryCode = CountryCode.ES, }, }, new ScoreInfo @@ -234,7 +234,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 4608074, Username = @"Skycries", - Country = Country.BR, + CountryCode = CountryCode.BR, }, }, new ScoreInfo @@ -252,7 +252,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 1014222, Username = @"eLy", - Country = Country.JP, + CountryCode = CountryCode.JP, }, }, new ScoreInfo @@ -270,7 +270,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 1541390, Username = @"Toukai", - Country = Country.CA, + CountryCode = CountryCode.CA, }, }, new ScoreInfo @@ -288,7 +288,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 2243452, Username = @"Satoruu", - Country = Country.VE, + CountryCode = CountryCode.VE, }, }, new ScoreInfo @@ -306,7 +306,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 2705430, Username = @"Mooha", - Country = Country.FR, + CountryCode = CountryCode.FR, }, }, new ScoreInfo @@ -324,7 +324,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 7151382, Username = @"Mayuri Hana", - Country = Country.TH, + CountryCode = CountryCode.TH, }, }, new ScoreInfo @@ -342,7 +342,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 2051389, Username = @"FunOrange", - Country = Country.CA, + CountryCode = CountryCode.CA, }, }, new ScoreInfo @@ -360,7 +360,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6169483, Username = @"-Hebel-", - Country = Country.MX, + CountryCode = CountryCode.MX, }, }, new ScoreInfo @@ -378,7 +378,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6702666, Username = @"prhtnsm", - Country = Country.DE, + CountryCode = CountryCode.DE, }, }, }; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs index 5356e74eae..39fd9fda2b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUserTopScoreContainer.cs @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 6602580, Username = @"waaiiru", - Country = Country.ES, + CountryCode = CountryCode.ES, }, }, new ScoreInfo @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 4608074, Username = @"Skycries", - Country = Country.BR, + CountryCode = CountryCode.BR, }, }, new ScoreInfo @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.SongSelect { Id = 1541390, Username = @"Toukai", - Country = Country.CA, + CountryCode = CountryCode.CA, }, } }; diff --git a/osu.Game.Tournament/Models/TournamentUser.cs b/osu.Game.Tournament/Models/TournamentUser.cs index 7faf6d1798..78ca014860 100644 --- a/osu.Game.Tournament/Models/TournamentUser.cs +++ b/osu.Game.Tournament/Models/TournamentUser.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tournament.Models /// The player's country. /// [JsonProperty("country_code")] - public Country Country { get; set; } + public CountryCode CountryCode { get; set; } /// /// The player's global rank, or null if not available. @@ -41,7 +41,7 @@ namespace osu.Game.Tournament.Models { Id = OnlineID, Username = Username, - Country = Country, + CountryCode = CountryCode, CoverUrl = CoverUrl, }; diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 853ccec83c..afeb58fde8 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -187,7 +187,7 @@ namespace osu.Game.Tournament var playersRequiringPopulation = ladder.Teams .SelectMany(t => t.Players) .Where(p => string.IsNullOrEmpty(p.Username) - || p.Country == default + || p.CountryCode == default || p.Rank == null).ToList(); if (playersRequiringPopulation.Count == 0) @@ -290,7 +290,7 @@ namespace osu.Game.Tournament user.Username = res.Username; user.CoverUrl = res.CoverUrl; - user.Country = res.Country; + user.CountryCode = res.CountryCode; user.Rank = res.Statistics?.GlobalRank; success?.Invoke(); diff --git a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs index 75088675bd..c55e7d65b6 100644 --- a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs @@ -13,21 +13,21 @@ namespace osu.Game.Online.API.Requests { public readonly UserRankingsType Type; - private readonly Country country; + private readonly CountryCode countryCode; - public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, Country country = default) + public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, CountryCode countryCode = default) : base(ruleset, page) { Type = type; - this.country = country; + this.countryCode = countryCode; } protected override WebRequest CreateWebRequest() { var req = base.CreateWebRequest(); - if (country != default) - req.AddParameter("country", country.ToString()); + if (countryCode != default) + req.AddParameter("country", countryCode.ToString()); return req; } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 62827f50aa..a7b6bd044d 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -181,7 +181,7 @@ namespace osu.Game.Online.Leaderboards Masking = true, Children = new Drawable[] { - new UpdateableFlag(user.Country) + new UpdateableFlag(user.CountryCode) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 669b701d1d..6acc9bf002 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -165,7 +165,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Font = OsuFont.GetFont(size: text_size), Colour = score.Accuracy == 1 ? highAccuracyColour : Color4.White }, - new UpdateableFlag(score.User.Country) + new UpdateableFlag(score.User.CountryCode) { Size = new Vector2(19, 14), ShowPlaceholderOnUnknown = false, diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs index 85c8f92a76..2eaa03a05d 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreUserSection.cs @@ -141,7 +141,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores set { avatar.User = value.User; - flag.Country = value.User.Country; + flag.CountryCode = value.User.CountryCode; achievedOn.Date = value.Date; usernameText.Clear(); diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index a213f99701..7e079c8341 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -175,8 +175,8 @@ namespace osu.Game.Overlays.Profile.Header avatar.User = user; usernameText.Text = user?.Username ?? string.Empty; openUserExternally.Link = $@"{api.WebsiteRootUrl}/users/{user?.Id ?? 0}"; - userFlag.Country = user?.Country ?? default; - userCountryText.Text = (user?.Country ?? default).GetDescription(); + userFlag.CountryCode = user?.CountryCode ?? default; + userCountryText.Text = (user?.CountryCode ?? default).GetDescription(); supporterTag.SupportLevel = user?.SupportLevel ?? 0; titleText.Text = user?.Title ?? string.Empty; titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff"); diff --git a/osu.Game/Overlays/Rankings/CountryFilter.cs b/osu.Game/Overlays/Rankings/CountryFilter.cs index 469d92a771..3ad677e976 100644 --- a/osu.Game/Overlays/Rankings/CountryFilter.cs +++ b/osu.Game/Overlays/Rankings/CountryFilter.cs @@ -16,14 +16,14 @@ using osuTK; namespace osu.Game.Overlays.Rankings { - public class CountryFilter : CompositeDrawable, IHasCurrentValue + public class CountryFilter : CompositeDrawable, IHasCurrentValue { private const int duration = 200; private const int height = 70; - private readonly BindableWithCurrent current = new BindableWithCurrent(); + private readonly BindableWithCurrent current = new BindableWithCurrent(); - public Bindable Current + public Bindable Current { get => current.Current; set => current.Current = value; @@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Rankings Current.BindValueChanged(onCountryChanged, true); } - private void onCountryChanged(ValueChangedEvent country) + private void onCountryChanged(ValueChangedEvent country) { if (country.NewValue == default) { diff --git a/osu.Game/Overlays/Rankings/CountryPill.cs b/osu.Game/Overlays/Rankings/CountryPill.cs index 96d677611e..610efbcc60 100644 --- a/osu.Game/Overlays/Rankings/CountryPill.cs +++ b/osu.Game/Overlays/Rankings/CountryPill.cs @@ -22,13 +22,13 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Rankings { - public class CountryPill : CompositeDrawable, IHasCurrentValue + public class CountryPill : CompositeDrawable, IHasCurrentValue { private const int duration = 200; - private readonly BindableWithCurrent current = new BindableWithCurrent(); + private readonly BindableWithCurrent current = new BindableWithCurrent(); - public Bindable Current + public Bindable Current { get => current.Current; set => current.Current = value; @@ -131,12 +131,12 @@ namespace osu.Game.Overlays.Rankings this.FadeOut(duration, Easing.OutQuint); } - private void onCountryChanged(ValueChangedEvent country) + private void onCountryChanged(ValueChangedEvent country) { if (country.NewValue == default) return; - flag.Country = country.NewValue; + flag.CountryCode = country.NewValue; countryName.Text = country.NewValue.GetDescription(); } diff --git a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs index 9b0b75f663..936545bd49 100644 --- a/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs +++ b/osu.Game/Overlays/Rankings/RankingsOverlayHeader.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Rankings { public Bindable Ruleset => rulesetSelector.Current; - public Bindable Country => countryFilter.Current; + public Bindable Country => countryFilter.Current; private OverlayRulesetSelector rulesetSelector; private CountryFilter countryFilter; diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index 27d4a6b6d3..d00a3680d8 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -34,9 +34,9 @@ namespace osu.Game.Overlays.Rankings.Tables new RankingsTableColumn(RankingsStrings.StatAveragePerformance, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), }; - protected override Country GetCountry(CountryStatistics item) => item.Country; + protected override CountryCode GetCountryCode(CountryStatistics item) => item.Code; - protected override Drawable CreateFlagContent(CountryStatistics item) => new CountryName(item.Country); + protected override Drawable CreateFlagContent(CountryStatistics item) => new CountryName(item.Code); protected override Drawable[] CreateAdditionalContent(CountryStatistics item) => new Drawable[] { @@ -71,15 +71,15 @@ namespace osu.Game.Overlays.Rankings.Tables [Resolved(canBeNull: true)] private RankingsOverlay rankings { get; set; } - public CountryName(Country country) + public CountryName(CountryCode countryCode) : base(t => t.Font = OsuFont.GetFont(size: 12)) { AutoSizeAxes = Axes.X; RelativeSizeAxes = Axes.Y; TextAnchor = Anchor.CentreLeft; - if (country != default) - AddLink(country.GetDescription(), () => rankings?.ShowCountry(country)); + if (countryCode != default) + AddLink(countryCode.GetDescription(), () => rankings?.ShowCountry(countryCode)); } } } diff --git a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs index 1e33b96ef1..073bf86a7a 100644 --- a/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/RankingsTable.cs @@ -79,7 +79,7 @@ namespace osu.Game.Overlays.Rankings.Tables protected sealed override Drawable CreateHeader(int index, TableColumn column) => (column as RankingsTableColumn)?.CreateHeaderText() ?? new HeaderText(column?.Header ?? default, false); - protected abstract Country GetCountry(TModel item); + protected abstract CountryCode GetCountryCode(TModel item); protected abstract Drawable CreateFlagContent(TModel item); @@ -97,7 +97,7 @@ namespace osu.Game.Overlays.Rankings.Tables Margin = new MarginPadding { Bottom = row_spacing }, Children = new[] { - new UpdateableFlag(GetCountry(item)) + new UpdateableFlag(GetCountryCode(item)) { Size = new Vector2(28, 20), ShowPlaceholderOnUnknown = false, diff --git a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs index 2edd1b21ba..48185e6083 100644 --- a/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/UserBasedTable.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.Rankings.Tables .Concat(GradeColumns.Select(grade => new GradeTableColumn(grade, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)))) .ToArray(); - protected sealed override Country GetCountry(UserStatistics item) => item.User.Country; + protected sealed override CountryCode GetCountryCode(UserStatistics item) => item.User.CountryCode; protected sealed override Drawable CreateFlagContent(UserStatistics item) { diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 9894df1c9d..586b883604 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -17,7 +17,7 @@ namespace osu.Game.Overlays { public class RankingsOverlay : TabbableOnlineOverlay { - protected Bindable Country => Header.Country; + protected Bindable Country => Header.Country; private APIRequest lastRequest; @@ -85,7 +85,7 @@ namespace osu.Game.Overlays protected override RankingsOverlayHeader CreateHeader() => new RankingsOverlayHeader(); - public void ShowCountry(Country requested) + public void ShowCountry(CountryCode requested) { if (requested == default) return; @@ -128,7 +128,7 @@ namespace osu.Game.Overlays switch (Header.Current.Value) { case RankingsScope.Performance: - return new GetUserRankingsRequest(ruleset.Value, country: Country.Value); + return new GetUserRankingsRequest(ruleset.Value, countryCode: Country.Value); case RankingsScope.Country: return new GetCountryRankingsRequest(ruleset.Value); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 652a832689..592be2ebac 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -128,7 +128,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Size = new Vector2(28, 20), - Country = user?.Country ?? default + CountryCode = user?.CountryCode ?? default }, new OsuSpriteText { diff --git a/osu.Game/Users/Country.cs b/osu.Game/Users/CountryCode.cs similarity index 99% rename from osu.Game/Users/Country.cs rename to osu.Game/Users/CountryCode.cs index 2070bcab39..11e9806ec5 100644 --- a/osu.Game/Users/Country.cs +++ b/osu.Game/Users/CountryCode.cs @@ -10,7 +10,7 @@ namespace osu.Game.Users { [JsonConverter(typeof(StringEnumConverter))] [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)] - public enum Country + public enum CountryCode { [Description("Unknown")] XX = 0, diff --git a/osu.Game/Users/CountryStatistics.cs b/osu.Game/Users/CountryStatistics.cs index eaee13a37b..03d455bc04 100644 --- a/osu.Game/Users/CountryStatistics.cs +++ b/osu.Game/Users/CountryStatistics.cs @@ -10,7 +10,7 @@ namespace osu.Game.Users public class CountryStatistics { [JsonProperty(@"code")] - public Country Country; + public CountryCode Code; [JsonProperty(@"active_users")] public long ActiveUsers; diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index 84b56a8d16..f7718c4f63 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -15,13 +15,13 @@ namespace osu.Game.Users.Drawables { public class DrawableFlag : Sprite, IHasTooltip { - private readonly Country country; + private readonly CountryCode countryCode; - public LocalisableString TooltipText => country == default ? string.Empty : country.GetDescription(); + public LocalisableString TooltipText => countryCode == default ? string.Empty : countryCode.GetDescription(); - public DrawableFlag(Country country) + public DrawableFlag(CountryCode countryCode) { - this.country = country; + this.countryCode = countryCode; } [BackgroundDependencyLoader] @@ -30,7 +30,7 @@ namespace osu.Game.Users.Drawables if (ts == null) throw new ArgumentNullException(nameof(ts)); - string textureName = country == default ? "__" : country.ToString(); + string textureName = countryCode == default ? "__" : countryCode.ToString(); Texture = ts.Get($@"Flags/{textureName}") ?? ts.Get(@"Flags/__"); } } diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index 88c08d4bb1..808bfad565 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -13,9 +13,9 @@ using osu.Game.Overlays; namespace osu.Game.Users.Drawables { - public class UpdateableFlag : ModelBackedDrawable + public class UpdateableFlag : ModelBackedDrawable { - public Country Country + public CountryCode CountryCode { get => Model; set => Model = value; @@ -32,14 +32,14 @@ namespace osu.Game.Users.Drawables /// public Action Action; - public UpdateableFlag(Country country = default) + public UpdateableFlag(CountryCode countryCode = default) { - Country = country; + CountryCode = countryCode; } - protected override Drawable CreateDrawable(Country country) + protected override Drawable CreateDrawable(CountryCode countryCode) { - if (country == default && !ShowPlaceholderOnUnknown) + if (countryCode == default && !ShowPlaceholderOnUnknown) return null; return new Container @@ -47,7 +47,7 @@ namespace osu.Game.Users.Drawables RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new DrawableFlag(country) + new DrawableFlag(countryCode) { RelativeSizeAxes = Axes.Both }, @@ -62,7 +62,7 @@ namespace osu.Game.Users.Drawables protected override bool OnClick(ClickEvent e) { Action?.Invoke(); - rankingsOverlay?.ShowCountry(Country); + rankingsOverlay?.ShowCountry(CountryCode); return true; } } diff --git a/osu.Game/Users/ExtendedUserPanel.cs b/osu.Game/Users/ExtendedUserPanel.cs index 7d475c545a..a4cba8b920 100644 --- a/osu.Game/Users/ExtendedUserPanel.cs +++ b/osu.Game/Users/ExtendedUserPanel.cs @@ -53,7 +53,7 @@ namespace osu.Game.Users protected UpdateableAvatar CreateAvatar() => new UpdateableAvatar(User, false); - protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.Country) + protected UpdateableFlag CreateFlag() => new UpdateableFlag(User.CountryCode) { Size = new Vector2(36, 26), Action = Action, From ef6e16b1cb668451d03962ae42b3d3d17d745efc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 08:40:43 +0300 Subject: [PATCH 091/481] `UserCountry` -> `Country` --- osu.Game/Online/API/Requests/Responses/APIUser.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index e2e1288b8c..6901b4595e 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -34,20 +34,20 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"previous_usernames")] public string[] PreviousUsernames; - private Country? country; + private CountryCode? countryCode; - public Country Country + public CountryCode CountryCode { - get => country ??= (Enum.TryParse(userCountry?.Code, out Country result) ? result : default); - set => country = value; + get => countryCode ??= (Enum.TryParse(country?.Code, out CountryCode result) ? result : default); + set => countryCode = value; } #pragma warning disable 649 [CanBeNull] [JsonProperty(@"country")] - private UserCountry userCountry; + private Country country; - private class UserCountry + private class Country { [JsonProperty(@"code")] public string Code; From 566bad0b5f4a73291628d07ebc9c2ac067b48eab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 14:42:41 +0900 Subject: [PATCH 092/481] Make `SoloScoreInfo.EndedAt` non-null Seems to already be the case for databased scores. Will be assured by https://github.com/ppy/osu-web/issues/9116. I've updated the `osu-score-statistics-processor` with this consideration. --- osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index cf7aa599f3..d46869c9f5 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -54,7 +54,7 @@ namespace osu.Game.Online.API.Requests.Responses public DateTimeOffset? StartedAt { get; set; } [JsonProperty("ended_at")] - public DateTimeOffset? EndedAt { get; set; } + public DateTimeOffset EndedAt { get; set; } [JsonProperty("mods")] public APIMod[] Mods { get; set; } = Array.Empty(); @@ -128,7 +128,7 @@ namespace osu.Game.Online.API.Requests.Responses MaxCombo = MaxCombo, Rank = Rank, Statistics = Statistics, - Date = EndedAt ?? DateTimeOffset.Now, + Date = EndedAt, Hash = HasReplay ? "online" : string.Empty, // TODO: temporary? Mods = mods, PP = PP, From 05d692bd5539e6d7ab5da778cb48a97ae7908c3e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 08:43:41 +0300 Subject: [PATCH 093/481] Move `Country` to end of class --- osu.Game/Online/API/Requests/Responses/APIUser.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 6901b4595e..5f843e9a7b 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -46,12 +46,6 @@ namespace osu.Game.Online.API.Requests.Responses [CanBeNull] [JsonProperty(@"country")] private Country country; - - private class Country - { - [JsonProperty(@"code")] - public string Code; - } #pragma warning restore 649 public readonly Bindable Status = new Bindable(); @@ -273,5 +267,13 @@ namespace osu.Game.Online.API.Requests.Responses public int OnlineID => Id; public bool Equals(APIUser other) => this.MatchesOnlineID(other); + +#pragma warning disable 649 + private class Country + { + [JsonProperty(@"code")] + public string Code; + } +#pragma warning restore 649 } } From cf99849478b644360afacb0dcf87bb380ae204a7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 08:45:54 +0300 Subject: [PATCH 094/481] `CountryCode.XX` -> `CountryCode.Unknown` --- osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs | 2 +- osu.Game/Users/CountryCode.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index c2766e2142..caa2d2571d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Online { Username = @"Somebody", Id = 1, - CountryCode = CountryCode.XX, + CountryCode = CountryCode.Unknown, CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", JoinDate = DateTimeOffset.Now.AddDays(-1), LastVisit = DateTimeOffset.Now, diff --git a/osu.Game/Users/CountryCode.cs b/osu.Game/Users/CountryCode.cs index 11e9806ec5..775de2bcf5 100644 --- a/osu.Game/Users/CountryCode.cs +++ b/osu.Game/Users/CountryCode.cs @@ -13,7 +13,7 @@ namespace osu.Game.Users public enum CountryCode { [Description("Unknown")] - XX = 0, + Unknown = 0, [Description("Bangladesh")] BD, From 018da74fe804928e83e249919ef3ed998e0d4854 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 08:54:35 +0300 Subject: [PATCH 095/481] Replace `default` with `CountryCode.Unknown` --- osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs | 2 +- osu.Game.Tournament/TournamentGameBase.cs | 3 ++- osu.Game/Online/API/Requests/GetUserRankingsRequest.cs | 4 ++-- osu.Game/Overlays/Rankings/CountryFilter.cs | 2 +- osu.Game/Overlays/Rankings/CountryPill.cs | 2 +- osu.Game/Overlays/Rankings/Tables/CountriesTable.cs | 2 +- osu.Game/Users/Drawables/DrawableFlag.cs | 4 ++-- osu.Game/Users/Drawables/UpdateableFlag.cs | 4 ++-- 8 files changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs index 7f4b1284a0..5476049882 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsOverlay.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Online public void TestFlagScopeDependency() { AddStep("Set scope to Score", () => scope.Value = RankingsScope.Score); - AddAssert("Check country is default", () => countryBindable.Value == default); + AddAssert("Check country is default", () => countryBindable.IsDefault); AddStep("Set country", () => countryBindable.Value = CountryCode.US); AddAssert("Check scope is Performance", () => scope.Value == RankingsScope.Performance); } diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index afeb58fde8..063b62bf08 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -21,6 +21,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Tournament.IO; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; +using osu.Game.Users; using osuTK.Input; namespace osu.Game.Tournament @@ -187,7 +188,7 @@ namespace osu.Game.Tournament var playersRequiringPopulation = ladder.Teams .SelectMany(t => t.Players) .Where(p => string.IsNullOrEmpty(p.Username) - || p.CountryCode == default + || p.CountryCode == CountryCode.Unknown || p.Rank == null).ToList(); if (playersRequiringPopulation.Count == 0) diff --git a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs index c55e7d65b6..c27a83b695 100644 --- a/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRankingsRequest.cs @@ -15,7 +15,7 @@ namespace osu.Game.Online.API.Requests private readonly CountryCode countryCode; - public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, CountryCode countryCode = default) + public GetUserRankingsRequest(RulesetInfo ruleset, UserRankingsType type = UserRankingsType.Performance, int page = 1, CountryCode countryCode = CountryCode.Unknown) : base(ruleset, page) { Type = type; @@ -26,7 +26,7 @@ namespace osu.Game.Online.API.Requests { var req = base.CreateWebRequest(); - if (countryCode != default) + if (countryCode != CountryCode.Unknown) req.AddParameter("country", countryCode.ToString()); return req; diff --git a/osu.Game/Overlays/Rankings/CountryFilter.cs b/osu.Game/Overlays/Rankings/CountryFilter.cs index 3ad677e976..9376bafdff 100644 --- a/osu.Game/Overlays/Rankings/CountryFilter.cs +++ b/osu.Game/Overlays/Rankings/CountryFilter.cs @@ -91,7 +91,7 @@ namespace osu.Game.Overlays.Rankings private void onCountryChanged(ValueChangedEvent country) { - if (country.NewValue == default) + if (Current.Value == CountryCode.Unknown) { countryPill.Collapse(); this.ResizeHeightTo(0, duration, Easing.OutQuint); diff --git a/osu.Game/Overlays/Rankings/CountryPill.cs b/osu.Game/Overlays/Rankings/CountryPill.cs index 610efbcc60..ee4c4f08af 100644 --- a/osu.Game/Overlays/Rankings/CountryPill.cs +++ b/osu.Game/Overlays/Rankings/CountryPill.cs @@ -133,7 +133,7 @@ namespace osu.Game.Overlays.Rankings private void onCountryChanged(ValueChangedEvent country) { - if (country.NewValue == default) + if (Current.Value == CountryCode.Unknown) return; flag.CountryCode = country.NewValue; diff --git a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs index d00a3680d8..a6f0c7a123 100644 --- a/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs +++ b/osu.Game/Overlays/Rankings/Tables/CountriesTable.cs @@ -78,7 +78,7 @@ namespace osu.Game.Overlays.Rankings.Tables RelativeSizeAxes = Axes.Y; TextAnchor = Anchor.CentreLeft; - if (countryCode != default) + if (countryCode != CountryCode.Unknown) AddLink(countryCode.GetDescription(), () => rankings?.ShowCountry(countryCode)); } } diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index f7718c4f63..253835b415 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -17,7 +17,7 @@ namespace osu.Game.Users.Drawables { private readonly CountryCode countryCode; - public LocalisableString TooltipText => countryCode == default ? string.Empty : countryCode.GetDescription(); + public LocalisableString TooltipText => countryCode == CountryCode.Unknown ? string.Empty : countryCode.GetDescription(); public DrawableFlag(CountryCode countryCode) { @@ -30,7 +30,7 @@ namespace osu.Game.Users.Drawables if (ts == null) throw new ArgumentNullException(nameof(ts)); - string textureName = countryCode == default ? "__" : countryCode.ToString(); + string textureName = countryCode == CountryCode.Unknown ? "__" : countryCode.ToString(); Texture = ts.Get($@"Flags/{textureName}") ?? ts.Get(@"Flags/__"); } } diff --git a/osu.Game/Users/Drawables/UpdateableFlag.cs b/osu.Game/Users/Drawables/UpdateableFlag.cs index 808bfad565..9b101131b3 100644 --- a/osu.Game/Users/Drawables/UpdateableFlag.cs +++ b/osu.Game/Users/Drawables/UpdateableFlag.cs @@ -32,14 +32,14 @@ namespace osu.Game.Users.Drawables /// public Action Action; - public UpdateableFlag(CountryCode countryCode = default) + public UpdateableFlag(CountryCode countryCode = CountryCode.Unknown) { CountryCode = countryCode; } protected override Drawable CreateDrawable(CountryCode countryCode) { - if (countryCode == default && !ShowPlaceholderOnUnknown) + if (countryCode == CountryCode.Unknown && !ShowPlaceholderOnUnknown) return null; return new Container From 21bf7ee448bcbc328707a049292630dcfde1c731 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 15:27:55 +0900 Subject: [PATCH 096/481] Turn on nullability in `ParticipantPanel` --- osu.Game/Online/API/APIMod.cs | 3 ++- .../Participants/ParticipantPanel.cs | 22 +++++++++---------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 8346300767..3bd0c91f8e 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Humanizer; using MessagePack; @@ -48,7 +49,7 @@ namespace osu.Game.Online.API } } - public Mod ToMod(Ruleset ruleset) + public Mod ToMod([NotNull] Ruleset ruleset) { Mod resultMod = ruleset.CreateModFromAcronym(Acronym); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index f632951f41..539502e631 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.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; @@ -35,18 +33,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants public readonly MultiplayerRoomUser User; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; [Resolved] - private IRulesetStore rulesets { get; set; } + private IRulesetStore rulesets { get; set; } = null!; - private SpriteIcon crown; + private SpriteIcon crown = null!; - private OsuSpriteText userRankText; - private ModDisplay userModsDisplay; - private StateDisplay userStateDisplay; + private OsuSpriteText userRankText = null!; + private ModDisplay userModsDisplay = null!; + private StateDisplay userStateDisplay = null!; - private IconButton kickButton; + private IconButton kickButton = null!; public ParticipantPanel(MultiplayerRoomUser user) { @@ -135,7 +133,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 18), - Text = user?.Username + Text = user?.Username ?? string.Empty }, userRankText = new OsuSpriteText { @@ -188,7 +186,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants const double fade_time = 50; var currentItem = Playlist.GetCurrentItem(); - var ruleset = currentItem != null ? rulesets.GetRuleset(currentItem.RulesetID)?.CreateInstance() : null; + Ruleset? ruleset = currentItem != null ? rulesets.GetRuleset(currentItem.RulesetID)?.CreateInstance() : null; int? currentModeRank = ruleset != null ? User.User?.RulesetsStatistics?.GetValueOrDefault(ruleset.ShortName)?.GlobalRank : null; userRankText.Text = currentModeRank != null ? $"#{currentModeRank.Value:N0}" : string.Empty; @@ -208,7 +206,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants Schedule(() => userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList()); } - public MenuItem[] ContextMenuItems + public MenuItem[]? ContextMenuItems { get { From a036632c8be8c61ff496e732d0c8bd2ef1667bde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 15:30:21 +0900 Subject: [PATCH 097/481] Fix potential crash when attempting to create mod display using null ruleset --- .../Multiplayer/Participants/ParticipantPanel.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs index 539502e631..e3a20cd652 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantPanel.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -20,6 +21,7 @@ using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play.HUD; using osu.Game.Users; using osu.Game.Users.Drawables; @@ -203,7 +205,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants // If the mods are updated at the end of the frame, the flow container will skip a reflow cycle: https://github.com/ppy/osu-framework/issues/4187 // This looks particularly jarring here, so re-schedule the update to that start of our frame as a fix. - Schedule(() => userModsDisplay.Current.Value = User.Mods.Select(m => m.ToMod(ruleset)).ToList()); + Schedule(() => + { + userModsDisplay.Current.Value = ruleset != null ? User.Mods.Select(m => m.ToMod(ruleset)).ToList() : Array.Empty(); + }); } public MenuItem[]? ContextMenuItems From e4d11febc538d046d8194f9853127af7d820af74 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 09:47:40 +0300 Subject: [PATCH 098/481] Remove no longer necessary fallback --- .../Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index c5e2b6df0c..5d8f8c8326 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -98,7 +98,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), Colour = colours.Yellow }, - new DrawableDate(Score.EndedAt ?? DateTimeOffset.Now, 12) + new DrawableDate(Score.EndedAt, 12) { Colour = colourProvider.Foreground1 } From e86a35fe334fecd3eb1784284b0b73ab380c7993 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 18 Jul 2022 10:09:14 +0300 Subject: [PATCH 099/481] Fix NRE on footer button mods --- osu.Game/Screens/Select/FooterButtonMods.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/FooterButtonMods.cs b/osu.Game/Screens/Select/FooterButtonMods.cs index 1fb73c2333..21f81995be 100644 --- a/osu.Game/Screens/Select/FooterButtonMods.cs +++ b/osu.Game/Screens/Select/FooterButtonMods.cs @@ -76,8 +76,11 @@ namespace osu.Game.Screens.Select updateMultiplierText(); - modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue); - modSettingChangeTracker.SettingChanged += _ => updateMultiplierText(); + if (mods.NewValue != null) + { + modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue); + modSettingChangeTracker.SettingChanged += _ => updateMultiplierText(); + } }, true); } From 51f91fe62e37ebc2ebcb28af62a8b7e82569b979 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 16:16:59 +0900 Subject: [PATCH 100/481] Update naming --- osu.Game/Database/RealmAccess.cs | 2 +- osu.Game/Models/RealmUser.cs | 8 ++++---- osu.Game/Rulesets/Mods/ICreateReplayData.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 6 +++--- osu.Game/Users/IUser.cs | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 0c44436ec8..c4d65f4f10 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -60,7 +60,7 @@ namespace osu.Game.Database /// 14 2022-03-01 Added BeatmapUserSettings to BeatmapInfo. /// 15 2022-07-13 Added LastPlayed to BeatmapInfo. /// 16 2022-07-15 Removed HasReplay from ScoreInfo. - /// 17 2022-07-16 Added Country to RealmUser. + /// 17 2022-07-16 Added CountryCode to RealmUser. /// private const int schema_version = 17; diff --git a/osu.Game/Models/RealmUser.cs b/osu.Game/Models/RealmUser.cs index 1668739bb5..4b9269a922 100644 --- a/osu.Game/Models/RealmUser.cs +++ b/osu.Game/Models/RealmUser.cs @@ -16,14 +16,14 @@ namespace osu.Game.Models public string Username { get; set; } = string.Empty; [Ignored] - public Country Country + public CountryCode CountryCode { - get => Enum.TryParse(CountryString, out Country country) ? country : default; + get => Enum.TryParse(CountryString, out CountryCode country) ? country : default; set => CountryString = value.ToString(); } - [MapTo(nameof(Country))] - public string CountryString { get; set; } = default(Country).ToString(); + [MapTo(nameof(CountryCode))] + public string CountryString { get; set; } = default(CountryCode).ToString(); public bool IsBot => false; diff --git a/osu.Game/Rulesets/Mods/ICreateReplayData.cs b/osu.Game/Rulesets/Mods/ICreateReplayData.cs index 6c195f623c..3ed5c2b7f8 100644 --- a/osu.Game/Rulesets/Mods/ICreateReplayData.cs +++ b/osu.Game/Rulesets/Mods/ICreateReplayData.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Mods public class ModCreatedUser : IUser { public int OnlineID => APIUser.SYSTEM_USER_ID; - public Country Country => default; + public CountryCode CountryCode => default; public bool IsBot => true; public string Username { get; set; } = string.Empty; diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index f5942e4639..d32d611a27 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -87,7 +87,7 @@ namespace osu.Game.Scoring { Id = RealmUser.OnlineID, Username = RealmUser.Username, - Country = RealmUser.Country, + CountryCode = RealmUser.CountryCode, }; set { @@ -97,7 +97,7 @@ namespace osu.Game.Scoring { OnlineID = user.OnlineID, Username = user.Username, - Country = user.Country, + CountryCode = user.CountryCode, }; } } @@ -137,7 +137,7 @@ namespace osu.Game.Scoring { OnlineID = RealmUser.OnlineID, Username = RealmUser.Username, - Country = RealmUser.Country, + CountryCode = RealmUser.CountryCode, }; return clone; diff --git a/osu.Game/Users/IUser.cs b/osu.Game/Users/IUser.cs index a520660c4d..b7f545f68b 100644 --- a/osu.Game/Users/IUser.cs +++ b/osu.Game/Users/IUser.cs @@ -10,7 +10,7 @@ namespace osu.Game.Users { string Username { get; } - Country Country { get; } + CountryCode CountryCode { get; } bool IsBot { get; } From 1e151baae8cbf2e0c6dc60e20aa8dd90658a290f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 16:24:08 +0900 Subject: [PATCH 101/481] Use `Unknown` instead of `default` --- osu.Game/Models/RealmUser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Models/RealmUser.cs b/osu.Game/Models/RealmUser.cs index 4b9269a922..e20ffc0808 100644 --- a/osu.Game/Models/RealmUser.cs +++ b/osu.Game/Models/RealmUser.cs @@ -18,7 +18,7 @@ namespace osu.Game.Models [Ignored] public CountryCode CountryCode { - get => Enum.TryParse(CountryString, out CountryCode country) ? country : default; + get => Enum.TryParse(CountryString, out CountryCode country) ? country : CountryCode.Unknown; set => CountryString = value.ToString(); } From caa44ce01e6ac413a0366b0ab22e1cce363de791 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 16:40:11 +0900 Subject: [PATCH 102/481] Update naming --- osu.Game.Tournament/CountryExtensions.cs | 502 +++++++++--------- .../Screens/Editors/TeamEditorScreen.cs | 2 +- 2 files changed, 252 insertions(+), 252 deletions(-) diff --git a/osu.Game.Tournament/CountryExtensions.cs b/osu.Game.Tournament/CountryExtensions.cs index 180c7a96af..f2a583c8a5 100644 --- a/osu.Game.Tournament/CountryExtensions.cs +++ b/osu.Game.Tournament/CountryExtensions.cs @@ -8,758 +8,758 @@ namespace osu.Game.Tournament { public static class CountryExtensions { - public static string GetAcronym(this Country country) + public static string GetAcronym(this CountryCode country) { switch (country) { - case Country.BD: + case CountryCode.BD: return "BGD"; - case Country.BE: + case CountryCode.BE: return "BEL"; - case Country.BF: + case CountryCode.BF: return "BFA"; - case Country.BG: + case CountryCode.BG: return "BGR"; - case Country.BA: + case CountryCode.BA: return "BIH"; - case Country.BB: + case CountryCode.BB: return "BRB"; - case Country.WF: + case CountryCode.WF: return "WLF"; - case Country.BL: + case CountryCode.BL: return "BLM"; - case Country.BM: + case CountryCode.BM: return "BMU"; - case Country.BN: + case CountryCode.BN: return "BRN"; - case Country.BO: + case CountryCode.BO: return "BOL"; - case Country.BH: + case CountryCode.BH: return "BHR"; - case Country.BI: + case CountryCode.BI: return "BDI"; - case Country.BJ: + case CountryCode.BJ: return "BEN"; - case Country.BT: + case CountryCode.BT: return "BTN"; - case Country.JM: + case CountryCode.JM: return "JAM"; - case Country.BV: + case CountryCode.BV: return "BVT"; - case Country.BW: + case CountryCode.BW: return "BWA"; - case Country.WS: + case CountryCode.WS: return "WSM"; - case Country.BQ: + case CountryCode.BQ: return "BES"; - case Country.BR: + case CountryCode.BR: return "BRA"; - case Country.BS: + case CountryCode.BS: return "BHS"; - case Country.JE: + case CountryCode.JE: return "JEY"; - case Country.BY: + case CountryCode.BY: return "BLR"; - case Country.BZ: + case CountryCode.BZ: return "BLZ"; - case Country.RU: + case CountryCode.RU: return "RUS"; - case Country.RW: + case CountryCode.RW: return "RWA"; - case Country.RS: + case CountryCode.RS: return "SRB"; - case Country.TL: + case CountryCode.TL: return "TLS"; - case Country.RE: + case CountryCode.RE: return "REU"; - case Country.TM: + case CountryCode.TM: return "TKM"; - case Country.TJ: + case CountryCode.TJ: return "TJK"; - case Country.RO: + case CountryCode.RO: return "ROU"; - case Country.TK: + case CountryCode.TK: return "TKL"; - case Country.GW: + case CountryCode.GW: return "GNB"; - case Country.GU: + case CountryCode.GU: return "GUM"; - case Country.GT: + case CountryCode.GT: return "GTM"; - case Country.GS: + case CountryCode.GS: return "SGS"; - case Country.GR: + case CountryCode.GR: return "GRC"; - case Country.GQ: + case CountryCode.GQ: return "GNQ"; - case Country.GP: + case CountryCode.GP: return "GLP"; - case Country.JP: + case CountryCode.JP: return "JPN"; - case Country.GY: + case CountryCode.GY: return "GUY"; - case Country.GG: + case CountryCode.GG: return "GGY"; - case Country.GF: + case CountryCode.GF: return "GUF"; - case Country.GE: + case CountryCode.GE: return "GEO"; - case Country.GD: + case CountryCode.GD: return "GRD"; - case Country.GB: + case CountryCode.GB: return "GBR"; - case Country.GA: + case CountryCode.GA: return "GAB"; - case Country.SV: + case CountryCode.SV: return "SLV"; - case Country.GN: + case CountryCode.GN: return "GIN"; - case Country.GM: + case CountryCode.GM: return "GMB"; - case Country.GL: + case CountryCode.GL: return "GRL"; - case Country.GI: + case CountryCode.GI: return "GIB"; - case Country.GH: + case CountryCode.GH: return "GHA"; - case Country.OM: + case CountryCode.OM: return "OMN"; - case Country.TN: + case CountryCode.TN: return "TUN"; - case Country.JO: + case CountryCode.JO: return "JOR"; - case Country.HR: + case CountryCode.HR: return "HRV"; - case Country.HT: + case CountryCode.HT: return "HTI"; - case Country.HU: + case CountryCode.HU: return "HUN"; - case Country.HK: + case CountryCode.HK: return "HKG"; - case Country.HN: + case CountryCode.HN: return "HND"; - case Country.HM: + case CountryCode.HM: return "HMD"; - case Country.VE: + case CountryCode.VE: return "VEN"; - case Country.PR: + case CountryCode.PR: return "PRI"; - case Country.PS: + case CountryCode.PS: return "PSE"; - case Country.PW: + case CountryCode.PW: return "PLW"; - case Country.PT: + case CountryCode.PT: return "PRT"; - case Country.SJ: + case CountryCode.SJ: return "SJM"; - case Country.PY: + case CountryCode.PY: return "PRY"; - case Country.IQ: + case CountryCode.IQ: return "IRQ"; - case Country.PA: + case CountryCode.PA: return "PAN"; - case Country.PF: + case CountryCode.PF: return "PYF"; - case Country.PG: + case CountryCode.PG: return "PNG"; - case Country.PE: + case CountryCode.PE: return "PER"; - case Country.PK: + case CountryCode.PK: return "PAK"; - case Country.PH: + case CountryCode.PH: return "PHL"; - case Country.PN: + case CountryCode.PN: return "PCN"; - case Country.PL: + case CountryCode.PL: return "POL"; - case Country.PM: + case CountryCode.PM: return "SPM"; - case Country.ZM: + case CountryCode.ZM: return "ZMB"; - case Country.EH: + case CountryCode.EH: return "ESH"; - case Country.EE: + case CountryCode.EE: return "EST"; - case Country.EG: + case CountryCode.EG: return "EGY"; - case Country.ZA: + case CountryCode.ZA: return "ZAF"; - case Country.EC: + case CountryCode.EC: return "ECU"; - case Country.IT: + case CountryCode.IT: return "ITA"; - case Country.VN: + case CountryCode.VN: return "VNM"; - case Country.SB: + case CountryCode.SB: return "SLB"; - case Country.ET: + case CountryCode.ET: return "ETH"; - case Country.SO: + case CountryCode.SO: return "SOM"; - case Country.ZW: + case CountryCode.ZW: return "ZWE"; - case Country.SA: + case CountryCode.SA: return "SAU"; - case Country.ES: + case CountryCode.ES: return "ESP"; - case Country.ER: + case CountryCode.ER: return "ERI"; - case Country.ME: + case CountryCode.ME: return "MNE"; - case Country.MD: + case CountryCode.MD: return "MDA"; - case Country.MG: + case CountryCode.MG: return "MDG"; - case Country.MF: + case CountryCode.MF: return "MAF"; - case Country.MA: + case CountryCode.MA: return "MAR"; - case Country.MC: + case CountryCode.MC: return "MCO"; - case Country.UZ: + case CountryCode.UZ: return "UZB"; - case Country.MM: + case CountryCode.MM: return "MMR"; - case Country.ML: + case CountryCode.ML: return "MLI"; - case Country.MO: + case CountryCode.MO: return "MAC"; - case Country.MN: + case CountryCode.MN: return "MNG"; - case Country.MH: + case CountryCode.MH: return "MHL"; - case Country.MK: + case CountryCode.MK: return "MKD"; - case Country.MU: + case CountryCode.MU: return "MUS"; - case Country.MT: + case CountryCode.MT: return "MLT"; - case Country.MW: + case CountryCode.MW: return "MWI"; - case Country.MV: + case CountryCode.MV: return "MDV"; - case Country.MQ: + case CountryCode.MQ: return "MTQ"; - case Country.MP: + case CountryCode.MP: return "MNP"; - case Country.MS: + case CountryCode.MS: return "MSR"; - case Country.MR: + case CountryCode.MR: return "MRT"; - case Country.IM: + case CountryCode.IM: return "IMN"; - case Country.UG: + case CountryCode.UG: return "UGA"; - case Country.TZ: + case CountryCode.TZ: return "TZA"; - case Country.MY: + case CountryCode.MY: return "MYS"; - case Country.MX: + case CountryCode.MX: return "MEX"; - case Country.IL: + case CountryCode.IL: return "ISR"; - case Country.FR: + case CountryCode.FR: return "FRA"; - case Country.IO: + case CountryCode.IO: return "IOT"; - case Country.SH: + case CountryCode.SH: return "SHN"; - case Country.FI: + case CountryCode.FI: return "FIN"; - case Country.FJ: + case CountryCode.FJ: return "FJI"; - case Country.FK: + case CountryCode.FK: return "FLK"; - case Country.FM: + case CountryCode.FM: return "FSM"; - case Country.FO: + case CountryCode.FO: return "FRO"; - case Country.NI: + case CountryCode.NI: return "NIC"; - case Country.NL: + case CountryCode.NL: return "NLD"; - case Country.NO: + case CountryCode.NO: return "NOR"; - case Country.NA: + case CountryCode.NA: return "NAM"; - case Country.VU: + case CountryCode.VU: return "VUT"; - case Country.NC: + case CountryCode.NC: return "NCL"; - case Country.NE: + case CountryCode.NE: return "NER"; - case Country.NF: + case CountryCode.NF: return "NFK"; - case Country.NG: + case CountryCode.NG: return "NGA"; - case Country.NZ: + case CountryCode.NZ: return "NZL"; - case Country.NP: + case CountryCode.NP: return "NPL"; - case Country.NR: + case CountryCode.NR: return "NRU"; - case Country.NU: + case CountryCode.NU: return "NIU"; - case Country.CK: + case CountryCode.CK: return "COK"; - case Country.XK: + case CountryCode.XK: return "XKX"; - case Country.CI: + case CountryCode.CI: return "CIV"; - case Country.CH: + case CountryCode.CH: return "CHE"; - case Country.CO: + case CountryCode.CO: return "COL"; - case Country.CN: + case CountryCode.CN: return "CHN"; - case Country.CM: + case CountryCode.CM: return "CMR"; - case Country.CL: + case CountryCode.CL: return "CHL"; - case Country.CC: + case CountryCode.CC: return "CCK"; - case Country.CA: + case CountryCode.CA: return "CAN"; - case Country.CG: + case CountryCode.CG: return "COG"; - case Country.CF: + case CountryCode.CF: return "CAF"; - case Country.CD: + case CountryCode.CD: return "COD"; - case Country.CZ: + case CountryCode.CZ: return "CZE"; - case Country.CY: + case CountryCode.CY: return "CYP"; - case Country.CX: + case CountryCode.CX: return "CXR"; - case Country.CR: + case CountryCode.CR: return "CRI"; - case Country.CW: + case CountryCode.CW: return "CUW"; - case Country.CV: + case CountryCode.CV: return "CPV"; - case Country.CU: + case CountryCode.CU: return "CUB"; - case Country.SZ: + case CountryCode.SZ: return "SWZ"; - case Country.SY: + case CountryCode.SY: return "SYR"; - case Country.SX: + case CountryCode.SX: return "SXM"; - case Country.KG: + case CountryCode.KG: return "KGZ"; - case Country.KE: + case CountryCode.KE: return "KEN"; - case Country.SS: + case CountryCode.SS: return "SSD"; - case Country.SR: + case CountryCode.SR: return "SUR"; - case Country.KI: + case CountryCode.KI: return "KIR"; - case Country.KH: + case CountryCode.KH: return "KHM"; - case Country.KN: + case CountryCode.KN: return "KNA"; - case Country.KM: + case CountryCode.KM: return "COM"; - case Country.ST: + case CountryCode.ST: return "STP"; - case Country.SK: + case CountryCode.SK: return "SVK"; - case Country.KR: + case CountryCode.KR: return "KOR"; - case Country.SI: + case CountryCode.SI: return "SVN"; - case Country.KP: + case CountryCode.KP: return "PRK"; - case Country.KW: + case CountryCode.KW: return "KWT"; - case Country.SN: + case CountryCode.SN: return "SEN"; - case Country.SM: + case CountryCode.SM: return "SMR"; - case Country.SL: + case CountryCode.SL: return "SLE"; - case Country.SC: + case CountryCode.SC: return "SYC"; - case Country.KZ: + case CountryCode.KZ: return "KAZ"; - case Country.KY: + case CountryCode.KY: return "CYM"; - case Country.SG: + case CountryCode.SG: return "SGP"; - case Country.SE: + case CountryCode.SE: return "SWE"; - case Country.SD: + case CountryCode.SD: return "SDN"; - case Country.DO: + case CountryCode.DO: return "DOM"; - case Country.DM: + case CountryCode.DM: return "DMA"; - case Country.DJ: + case CountryCode.DJ: return "DJI"; - case Country.DK: + case CountryCode.DK: return "DNK"; - case Country.VG: + case CountryCode.VG: return "VGB"; - case Country.DE: + case CountryCode.DE: return "DEU"; - case Country.YE: + case CountryCode.YE: return "YEM"; - case Country.DZ: + case CountryCode.DZ: return "DZA"; - case Country.US: + case CountryCode.US: return "USA"; - case Country.UY: + case CountryCode.UY: return "URY"; - case Country.YT: + case CountryCode.YT: return "MYT"; - case Country.UM: + case CountryCode.UM: return "UMI"; - case Country.LB: + case CountryCode.LB: return "LBN"; - case Country.LC: + case CountryCode.LC: return "LCA"; - case Country.LA: + case CountryCode.LA: return "LAO"; - case Country.TV: + case CountryCode.TV: return "TUV"; - case Country.TW: + case CountryCode.TW: return "TWN"; - case Country.TT: + case CountryCode.TT: return "TTO"; - case Country.TR: + case CountryCode.TR: return "TUR"; - case Country.LK: + case CountryCode.LK: return "LKA"; - case Country.LI: + case CountryCode.LI: return "LIE"; - case Country.LV: + case CountryCode.LV: return "LVA"; - case Country.TO: + case CountryCode.TO: return "TON"; - case Country.LT: + case CountryCode.LT: return "LTU"; - case Country.LU: + case CountryCode.LU: return "LUX"; - case Country.LR: + case CountryCode.LR: return "LBR"; - case Country.LS: + case CountryCode.LS: return "LSO"; - case Country.TH: + case CountryCode.TH: return "THA"; - case Country.TF: + case CountryCode.TF: return "ATF"; - case Country.TG: + case CountryCode.TG: return "TGO"; - case Country.TD: + case CountryCode.TD: return "TCD"; - case Country.TC: + case CountryCode.TC: return "TCA"; - case Country.LY: + case CountryCode.LY: return "LBY"; - case Country.VA: + case CountryCode.VA: return "VAT"; - case Country.VC: + case CountryCode.VC: return "VCT"; - case Country.AE: + case CountryCode.AE: return "ARE"; - case Country.AD: + case CountryCode.AD: return "AND"; - case Country.AG: + case CountryCode.AG: return "ATG"; - case Country.AF: + case CountryCode.AF: return "AFG"; - case Country.AI: + case CountryCode.AI: return "AIA"; - case Country.VI: + case CountryCode.VI: return "VIR"; - case Country.IS: + case CountryCode.IS: return "ISL"; - case Country.IR: + case CountryCode.IR: return "IRN"; - case Country.AM: + case CountryCode.AM: return "ARM"; - case Country.AL: + case CountryCode.AL: return "ALB"; - case Country.AO: + case CountryCode.AO: return "AGO"; - case Country.AQ: + case CountryCode.AQ: return "ATA"; - case Country.AS: + case CountryCode.AS: return "ASM"; - case Country.AR: + case CountryCode.AR: return "ARG"; - case Country.AU: + case CountryCode.AU: return "AUS"; - case Country.AT: + case CountryCode.AT: return "AUT"; - case Country.AW: + case CountryCode.AW: return "ABW"; - case Country.IN: + case CountryCode.IN: return "IND"; - case Country.AX: + case CountryCode.AX: return "ALA"; - case Country.AZ: + case CountryCode.AZ: return "AZE"; - case Country.IE: + case CountryCode.IE: return "IRL"; - case Country.ID: + case CountryCode.ID: return "IDN"; - case Country.UA: + case CountryCode.UA: return "UKR"; - case Country.QA: + case CountryCode.QA: return "QAT"; - case Country.MZ: + case CountryCode.MZ: return "MOZ"; default: diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index 0f806a2403..da27c09e01 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tournament.Screens.Editors { var countries = new List(); - foreach (var country in Enum.GetValues(typeof(Country)).Cast().Skip(1)) + foreach (var country in Enum.GetValues(typeof(CountryCode)).Cast().Skip(1)) { countries.Add(new TournamentTeam { From 18da9ddfbfc5343f37e8bf69be7ef5617c0e389b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 17:01:45 +0900 Subject: [PATCH 103/481] Add test coverage of two beatmaps in same set with different audio files --- .../NonVisual/BeatmapSetInfoEqualityTest.cs | 40 ++++++++++++++++++- osu.Game.Tests/Resources/TestResources.cs | 2 +- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs index c887105da6..461102124a 100644 --- a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs +++ b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs @@ -62,9 +62,45 @@ namespace osu.Game.Tests.NonVisual Assert.IsTrue(beatmapSetA.Beatmaps.Single().AudioEquals(beatmapSetB.Beatmaps.Single())); } - private static void addAudioFile(BeatmapSetInfo beatmapSetInfo, string hash = null) + [Test] + public void TestAudioEqualityBeatmapInfoSameHash() { - beatmapSetInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = hash ?? Guid.NewGuid().ToString() }, "audio.mp3")); + var beatmapSet = TestResources.CreateTestBeatmapSetInfo(2); + + addAudioFile(beatmapSet); + + var beatmap1 = beatmapSet.Beatmaps.First(); + var beatmap2 = beatmapSet.Beatmaps.Last(); + + Assert.AreNotEqual(beatmap1, beatmap2); + Assert.IsTrue(beatmap1.AudioEquals(beatmap2)); + } + + [Test] + public void TestAudioEqualityBeatmapInfoDifferentHash() + { + var beatmapSet = TestResources.CreateTestBeatmapSetInfo(2); + + const string filename1 = "audio1.mp3"; + const string filename2 = "audio2.mp3"; + + addAudioFile(beatmapSet, filename: filename1); + addAudioFile(beatmapSet, filename: filename2); + + var beatmap1 = beatmapSet.Beatmaps.First(); + var beatmap2 = beatmapSet.Beatmaps.Last(); + + Assert.AreNotEqual(beatmap1, beatmap2); + + beatmap1.Metadata.AudioFile = filename1; + beatmap2.Metadata.AudioFile = filename2; + + Assert.IsFalse(beatmap1.AudioEquals(beatmap2)); + } + + private static void addAudioFile(BeatmapSetInfo beatmapSetInfo, string hash = null, string filename = null) + { + beatmapSetInfo.Files.Add(new RealmNamedFileUsage(new RealmFile { Hash = hash ?? Guid.NewGuid().ToString() }, filename ?? "audio.mp3")); } [Test] diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 41404b2636..ee29cc8644 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -138,7 +138,7 @@ namespace osu.Game.Tests.Resources BPM = bpm, Hash = Guid.NewGuid().ToString().ComputeMD5Hash(), Ruleset = rulesetInfo, - Metadata = metadata, + Metadata = metadata.DeepClone(), Difficulty = new BeatmapDifficulty { OverallDifficulty = diff, From 22a9e7e2753c5e09aad0fcdecba470f913ef5a82 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 16:56:08 +0900 Subject: [PATCH 104/481] Fix audio/background equality not correctly using `BeatmapInfo` local filenames --- osu.Game/Beatmaps/BeatmapInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 45d76259fc..41e89d864e 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -169,8 +169,8 @@ namespace osu.Game.Beatmaps Debug.Assert(x.BeatmapSet != null); Debug.Assert(y.BeatmapSet != null); - string? fileHashX = x.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(x.BeatmapSet.Metadata))?.File.Hash; - string? fileHashY = y.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(y.BeatmapSet.Metadata))?.File.Hash; + string? fileHashX = x.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(x.Metadata))?.File.Hash; + string? fileHashY = y.BeatmapSet.Files.FirstOrDefault(f => f.Filename == getFilename(y.Metadata))?.File.Hash; return fileHashX == fileHashY; } From afa831f6fef58e984fecc568378ca88759daa47a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 01:14:53 +0900 Subject: [PATCH 105/481] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 04b16c5b0f..3b14d85e53 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1d3cd39cf3..6120d3d600 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index dfd229755f..8a36ad6e3d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From ffa9a83a4f2e12eb18125e2372ca5a766662f718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Jun 2022 14:58:23 +0200 Subject: [PATCH 106/481] Add own fixed copy of defective Humanizer methods --- .../StringDehumanizeExtensionsTest.cs | 85 +++++++++++++++++ .../Extensions/StringDehumanizeExtensions.cs | 94 +++++++++++++++++++ 2 files changed, 179 insertions(+) create mode 100644 osu.Game.Tests/Extensions/StringDehumanizeExtensionsTest.cs create mode 100644 osu.Game/Extensions/StringDehumanizeExtensions.cs diff --git a/osu.Game.Tests/Extensions/StringDehumanizeExtensionsTest.cs b/osu.Game.Tests/Extensions/StringDehumanizeExtensionsTest.cs new file mode 100644 index 0000000000..e7490b461b --- /dev/null +++ b/osu.Game.Tests/Extensions/StringDehumanizeExtensionsTest.cs @@ -0,0 +1,85 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Globalization; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Extensions; + +namespace osu.Game.Tests.Extensions +{ + [TestFixture] + public class StringDehumanizeExtensionsTest + { + [Test] + [TestCase("single", "Single")] + [TestCase("example word", "ExampleWord")] + [TestCase("mixed Casing test", "MixedCasingTest")] + [TestCase("PascalCase", "PascalCase")] + [TestCase("camelCase", "CamelCase")] + [TestCase("snake_case", "SnakeCase")] + [TestCase("kebab-case", "KebabCase")] + [TestCase("i will not break in a different culture", "IWillNotBreakInADifferentCulture", "tr-TR")] + public void TestToPascalCase(string input, string expectedOutput, string? culture = null) + { + using (temporaryCurrentCulture(culture)) + Assert.That(input.ToPascalCase(), Is.EqualTo(expectedOutput)); + } + + [Test] + [TestCase("single", "single")] + [TestCase("example word", "exampleWord")] + [TestCase("mixed Casing test", "mixedCasingTest")] + [TestCase("PascalCase", "pascalCase")] + [TestCase("camelCase", "camelCase")] + [TestCase("snake_case", "snakeCase")] + [TestCase("kebab-case", "kebabCase")] + [TestCase("I will not break in a different culture", "iWillNotBreakInADifferentCulture", "tr-TR")] + public void TestToCamelCase(string input, string expectedOutput, string? culture = null) + { + using (temporaryCurrentCulture(culture)) + Assert.That(input.ToCamelCase(), Is.EqualTo(expectedOutput)); + } + + [Test] + [TestCase("single", "single")] + [TestCase("example word", "example_word")] + [TestCase("mixed Casing test", "mixed_casing_test")] + [TestCase("PascalCase", "pascal_case")] + [TestCase("camelCase", "camel_case")] + [TestCase("snake_case", "snake_case")] + [TestCase("kebab-case", "kebab_case")] + [TestCase("I will not break in a different culture", "i_will_not_break_in_a_different_culture", "tr-TR")] + public void TestToSnakeCase(string input, string expectedOutput, string? culture = null) + { + using (temporaryCurrentCulture(culture)) + Assert.That(input.ToSnakeCase(), Is.EqualTo(expectedOutput)); + } + + [Test] + [TestCase("single", "single")] + [TestCase("example word", "example-word")] + [TestCase("mixed Casing test", "mixed-casing-test")] + [TestCase("PascalCase", "pascal-case")] + [TestCase("camelCase", "camel-case")] + [TestCase("snake_case", "snake-case")] + [TestCase("kebab-case", "kebab-case")] + [TestCase("I will not break in a different culture", "i-will-not-break-in-a-different-culture", "tr-TR")] + public void TestToKebabCase(string input, string expectedOutput, string? culture = null) + { + using (temporaryCurrentCulture(culture)) + Assert.That(input.ToKebabCase(), Is.EqualTo(expectedOutput)); + } + + private IDisposable temporaryCurrentCulture(string? cultureName) + { + var storedCulture = CultureInfo.CurrentCulture; + + if (cultureName != null) + CultureInfo.CurrentCulture = new CultureInfo(cultureName); + + return new InvokeOnDisposal(() => CultureInfo.CurrentCulture = storedCulture); + } + } +} diff --git a/osu.Game/Extensions/StringDehumanizeExtensions.cs b/osu.Game/Extensions/StringDehumanizeExtensions.cs new file mode 100644 index 0000000000..6f0d7622d3 --- /dev/null +++ b/osu.Game/Extensions/StringDehumanizeExtensions.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. + +// Based on code from the Humanizer library (https://github.com/Humanizr/Humanizer/blob/606e958cb83afc9be5b36716ac40d4daa9fa73a7/src/Humanizer/InflectorExtensions.cs) +// +// Humanizer is licenced under the MIT License (MIT) +// +// Copyright (c) .NET Foundation and Contributors +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +using System.Text.RegularExpressions; + +namespace osu.Game.Extensions +{ + /// + /// Class with extension methods used to turn human-readable strings to casing conventions frequently used in code. + /// Often used for communicating with other systems (web API, spectator server). + /// All of the operations in this class are intentionally culture-invariant. + /// + public static class StringDehumanizeExtensions + { + /// + /// Converts the string to "Pascal case" (also known as "upper camel case"). + /// + /// + /// + /// "this is a test string".ToPascalCase() == "ThisIsATestString" + /// + /// + public static string ToPascalCase(this string input) + { + return Regex.Replace(input, "(?:^|_|-| +)(.)", match => match.Groups[1].Value.ToUpperInvariant()); + } + + /// + /// Converts the string to (lower) "camel case". + /// + /// + /// + /// "this is a test string".ToCamelCase() == "thisIsATestString" + /// + /// + public static string ToCamelCase(this string input) + { + string word = input.ToPascalCase(); + return word.Length > 0 ? word.Substring(0, 1).ToLowerInvariant() + word.Substring(1) : word; + } + + /// + /// Converts the string to "snake case". + /// + /// + /// + /// "this is a test string".ToSnakeCase() == "this_is_a_test_string" + /// + /// + public static string ToSnakeCase(this string input) + { + return Regex.Replace( + Regex.Replace( + Regex.Replace(input, @"([\p{Lu}]+)([\p{Lu}][\p{Ll}])", "$1_$2"), @"([\p{Ll}\d])([\p{Lu}])", "$1_$2"), @"[-\s]", "_").ToLowerInvariant(); + } + + /// + /// Converts the string to "kebab case". + /// + /// + /// + /// "this is a test string".ToKebabCase() == "this-is-a-test-string" + /// + /// + public static string ToKebabCase(this string input) + { + return ToSnakeCase(input).Replace('_', '-'); + } + } +} From 6f37487528a3a5207f73706968d067c5f87a69c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Jun 2022 15:02:15 +0200 Subject: [PATCH 107/481] Replace calls to defective Humanizer methods with correct version --- CodeAnalysis/BannedSymbols.txt | 4 ++++ .../UserInterface/TestSceneBeatmapListingSearchControl.cs | 4 ++-- osu.Game/Beatmaps/BeatmapStatisticIcon.cs | 4 ++-- osu.Game/Extensions/DrawableExtensions.cs | 3 +-- osu.Game/IO/Serialization/SnakeCaseKeyContractResolver.cs | 4 ++-- osu.Game/Online/API/APIMod.cs | 6 +++--- osu.Game/Online/API/Requests/GetCommentsRequest.cs | 4 ++-- osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs | 4 ++-- osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs | 4 ++-- osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs | 3 +-- osu.Game/Online/Rooms/GetRoomsRequest.cs | 4 ++-- osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 3 +-- 12 files changed, 24 insertions(+), 23 deletions(-) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index 8b5431e2d6..e779ee6658 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -19,3 +19,7 @@ P:System.Threading.Tasks.Task`1.Result;Don't use Task.Result. Use Task.GetResult M:System.Threading.ManualResetEventSlim.Wait();Specify a timeout to avoid waiting forever. M:System.String.ToLower();string.ToLower() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToLowerInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString. M:System.String.ToUpper();string.ToUpper() changes behaviour depending on CultureInfo.CurrentCulture. Use string.ToUpperInvariant() instead. If wanting culture-sensitive behaviour, explicitly provide CultureInfo.CurrentCulture or use LocalisableString. +M:Humanizer.InflectorExtensions.Pascalize(System.String);Humanizer's .Pascalize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToPascalCase() instead. +M:Humanizer.InflectorExtensions.Camelize(System.String);Humanizer's .Camelize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToCamelCase() instead. +M:Humanizer.InflectorExtensions.Underscore(System.String);Humanizer's .Underscore() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToSnakeCase() instead. +M:Humanizer.InflectorExtensions.Kebaberize(System.String);Humanizer's .Kebaberize() extension method changes behaviour depending on CultureInfo.CurrentCulture. Use StringDehumanizeExtensions.ToKebabCase() instead. diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index 44f2da2b95..e8454e8d0f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -4,13 +4,13 @@ #nullable disable using System.Linq; -using Humanizer; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Extensions; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; control.Query.BindValueChanged(q => query.Text = $"Query: {q.NewValue}", true); - control.General.BindCollectionChanged((_, _) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().Underscore())) : "")}", true); + control.General.BindCollectionChanged((_, _) => general.Text = $"General: {(control.General.Any() ? string.Join('.', control.General.Select(i => i.ToString().ToSnakeCase())) : "")}", true); control.Ruleset.BindValueChanged(r => ruleset.Text = $"Ruleset: {r.NewValue}", true); control.Category.BindValueChanged(c => category.Text = $"Category: {c.NewValue}", true); control.Genre.BindValueChanged(g => genre.Text = $"Genre: {g.NewValue}", true); diff --git a/osu.Game/Beatmaps/BeatmapStatisticIcon.cs b/osu.Game/Beatmaps/BeatmapStatisticIcon.cs index 58d13a3172..8002910b52 100644 --- a/osu.Game/Beatmaps/BeatmapStatisticIcon.cs +++ b/osu.Game/Beatmaps/BeatmapStatisticIcon.cs @@ -3,10 +3,10 @@ #nullable disable -using Humanizer; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Game.Extensions; namespace osu.Game.Beatmaps { @@ -25,7 +25,7 @@ namespace osu.Game.Beatmaps [BackgroundDependencyLoader] private void load(TextureStore textures) { - Texture = textures.Get($"Icons/BeatmapDetails/{iconType.ToString().Kebaberize()}"); + Texture = textures.Get($"Icons/BeatmapDetails/{iconType.ToString().ToKebabCase()}"); } } diff --git a/osu.Game/Extensions/DrawableExtensions.cs b/osu.Game/Extensions/DrawableExtensions.cs index d1aba2bfe3..35f2d61437 100644 --- a/osu.Game/Extensions/DrawableExtensions.cs +++ b/osu.Game/Extensions/DrawableExtensions.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using Humanizer; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -67,7 +66,7 @@ namespace osu.Game.Extensions foreach (var (_, property) in component.GetSettingsSourceProperties()) { - if (!info.Settings.TryGetValue(property.Name.Underscore(), out object settingValue)) + if (!info.Settings.TryGetValue(property.Name.ToSnakeCase(), out object settingValue)) continue; skinnable.CopyAdjustedSetting((IBindable)property.GetValue(component), settingValue); diff --git a/osu.Game/IO/Serialization/SnakeCaseKeyContractResolver.cs b/osu.Game/IO/Serialization/SnakeCaseKeyContractResolver.cs index 4808ac1384..b51a8473ca 100644 --- a/osu.Game/IO/Serialization/SnakeCaseKeyContractResolver.cs +++ b/osu.Game/IO/Serialization/SnakeCaseKeyContractResolver.cs @@ -3,8 +3,8 @@ #nullable disable -using Humanizer; using Newtonsoft.Json.Serialization; +using osu.Game.Extensions; namespace osu.Game.IO.Serialization { @@ -12,7 +12,7 @@ namespace osu.Game.IO.Serialization { protected override string ResolvePropertyName(string propertyName) { - return propertyName.Underscore(); + return propertyName.ToSnakeCase(); } } } diff --git a/osu.Game/Online/API/APIMod.cs b/osu.Game/Online/API/APIMod.cs index 3bd0c91f8e..900f59290c 100644 --- a/osu.Game/Online/API/APIMod.cs +++ b/osu.Game/Online/API/APIMod.cs @@ -7,12 +7,12 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -using Humanizer; using MessagePack; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Logging; using osu.Game.Configuration; +using osu.Game.Extensions; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -45,7 +45,7 @@ namespace osu.Game.Online.API var bindable = (IBindable)property.GetValue(mod); if (!bindable.IsDefault) - Settings.Add(property.Name.Underscore(), bindable.GetUnderlyingSettingValue()); + Settings.Add(property.Name.ToSnakeCase(), bindable.GetUnderlyingSettingValue()); } } @@ -63,7 +63,7 @@ namespace osu.Game.Online.API { foreach (var (_, property) in resultMod.GetSettingsSourceProperties()) { - if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue)) + if (!Settings.TryGetValue(property.Name.ToSnakeCase(), out object settingValue)) continue; try diff --git a/osu.Game/Online/API/Requests/GetCommentsRequest.cs b/osu.Game/Online/API/Requests/GetCommentsRequest.cs index c63c574124..1aa08f2ed8 100644 --- a/osu.Game/Online/API/Requests/GetCommentsRequest.cs +++ b/osu.Game/Online/API/Requests/GetCommentsRequest.cs @@ -4,7 +4,7 @@ #nullable disable using osu.Framework.IO.Network; -using Humanizer; +using osu.Game.Extensions; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Comments; @@ -32,7 +32,7 @@ namespace osu.Game.Online.API.Requests var req = base.CreateWebRequest(); req.AddParameter("commentable_id", commentableId.ToString()); - req.AddParameter("commentable_type", type.ToString().Underscore().ToLowerInvariant()); + req.AddParameter("commentable_type", type.ToString().ToSnakeCase().ToLowerInvariant()); req.AddParameter("page", page.ToString()); req.AddParameter("sort", sort.ToString().ToLowerInvariant()); diff --git a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs index 3ec60cd06c..d723786f23 100644 --- a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs @@ -3,8 +3,8 @@ #nullable disable -using Humanizer; using System.Collections.Generic; +using osu.Game.Extensions; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.API.Requests @@ -22,7 +22,7 @@ namespace osu.Game.Online.API.Requests this.type = type; } - protected override string Target => $@"users/{userId}/beatmapsets/{type.ToString().Underscore()}"; + protected override string Target => $@"users/{userId}/beatmapsets/{type.ToString().ToSnakeCase()}"; } public enum BeatmapSetType diff --git a/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs b/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs index 8fefe4d9c2..2def18926f 100644 --- a/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs +++ b/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs @@ -4,8 +4,8 @@ #nullable disable using System; -using Humanizer; using Newtonsoft.Json; +using osu.Game.Extensions; using osu.Game.Scoring; namespace osu.Game.Online.API.Requests.Responses @@ -21,7 +21,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty] private string type { - set => Type = (RecentActivityType)Enum.Parse(typeof(RecentActivityType), value.Pascalize()); + set => Type = (RecentActivityType)Enum.Parse(typeof(RecentActivityType), value.ToPascalCase()); } public RecentActivityType Type; diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 082f9bb371..c303c410ec 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; -using Humanizer; using JetBrains.Annotations; using osu.Framework.IO.Network; using osu.Game.Extensions; @@ -86,7 +85,7 @@ namespace osu.Game.Online.API.Requests req.AddParameter("q", query); if (General != null && General.Any()) - req.AddParameter("c", string.Join('.', General.Select(e => e.ToString().Underscore()))); + req.AddParameter("c", string.Join('.', General.Select(e => e.ToString().ToSnakeCase()))); if (ruleset.OnlineID >= 0) req.AddParameter("m", ruleset.OnlineID.ToString()); diff --git a/osu.Game/Online/Rooms/GetRoomsRequest.cs b/osu.Game/Online/Rooms/GetRoomsRequest.cs index 5ae9d58189..afab83b5be 100644 --- a/osu.Game/Online/Rooms/GetRoomsRequest.cs +++ b/osu.Game/Online/Rooms/GetRoomsRequest.cs @@ -4,8 +4,8 @@ #nullable disable using System.Collections.Generic; -using Humanizer; using osu.Framework.IO.Network; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Screens.OnlinePlay.Lounge.Components; @@ -27,7 +27,7 @@ namespace osu.Game.Online.Rooms var req = base.CreateWebRequest(); if (status != RoomStatusFilter.Open) - req.AddParameter("mode", status.ToString().Underscore().ToLowerInvariant()); + req.AddParameter("mode", status.ToString().ToSnakeCase().ToLowerInvariant()); if (!string.IsNullOrEmpty(category)) req.AddParameter("category", category); diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index f045ab661e..ee29241321 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Humanizer; using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -71,7 +70,7 @@ namespace osu.Game.Screens.Play.HUD var bindable = (IBindable)property.GetValue(component); if (!bindable.IsDefault) - Settings.Add(property.Name.Underscore(), bindable.GetUnderlyingSettingValue()); + Settings.Add(property.Name.ToSnakeCase(), bindable.GetUnderlyingSettingValue()); } if (component is Container container) From 5055e327699829b820231598009be8b6e1433468 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jul 2022 03:42:20 +0300 Subject: [PATCH 108/481] Add benchmark for `HitObject` --- osu.Game.Benchmarks/BenchmarkHitObject.cs | 116 ++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 osu.Game.Benchmarks/BenchmarkHitObject.cs diff --git a/osu.Game.Benchmarks/BenchmarkHitObject.cs b/osu.Game.Benchmarks/BenchmarkHitObject.cs new file mode 100644 index 0000000000..d7731e0cfd --- /dev/null +++ b/osu.Game.Benchmarks/BenchmarkHitObject.cs @@ -0,0 +1,116 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using BenchmarkDotNet.Attributes; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Benchmarks +{ + public class BenchmarkHitObject : BenchmarkTest + { + [Params(1, 100, 1000)] + public int Count { get; set; } + + [Params(false, true)] + public bool WithBindableAccess { get; set; } + + [Benchmark] + public HitCircle[] OsuCircle() + { + var circles = new HitCircle[Count]; + + for (int i = 0; i < Count; i++) + { + circles[i] = new HitCircle(); + + if (WithBindableAccess) + { + _ = circles[i].PositionBindable; + _ = circles[i].ScaleBindable; + _ = circles[i].ComboIndexBindable; + _ = circles[i].ComboOffsetBindable; + _ = circles[i].StackHeightBindable; + _ = circles[i].LastInComboBindable; + _ = circles[i].ComboIndexWithOffsetsBindable; + _ = circles[i].IndexInCurrentComboBindable; + _ = circles[i].SamplesBindable; + _ = circles[i].StartTimeBindable; + } + } + + return circles; + } + + [Benchmark] + public Hit[] TaikoHit() + { + var hits = new Hit[Count]; + + for (int i = 0; i < Count; i++) + { + hits[i] = new Hit(); + + if (WithBindableAccess) + { + _ = hits[i].TypeBindable; + _ = hits[i].IsStrongBindable; + _ = hits[i].SamplesBindable; + _ = hits[i].StartTimeBindable; + } + } + + return hits; + } + + [Benchmark] + public Fruit[] CatchFruit() + { + var fruit = new Fruit[Count]; + + for (int i = 0; i < Count; i++) + { + fruit[i] = new Fruit(); + + if (WithBindableAccess) + { + _ = fruit[i].OriginalXBindable; + _ = fruit[i].XOffsetBindable; + _ = fruit[i].ScaleBindable; + _ = fruit[i].ComboIndexBindable; + _ = fruit[i].HyperDashBindable; + _ = fruit[i].LastInComboBindable; + _ = fruit[i].ComboIndexWithOffsetsBindable; + _ = fruit[i].IndexInCurrentComboBindable; + _ = fruit[i].IndexInBeatmapBindable; + _ = fruit[i].SamplesBindable; + _ = fruit[i].StartTimeBindable; + } + } + + return fruit; + } + + [Benchmark] + public Note[] ManiaNote() + { + var notes = new Note[Count]; + + for (int i = 0; i < Count; i++) + { + notes[i] = new Note(); + + if (WithBindableAccess) + { + _ = notes[i].ColumnBindable; + _ = notes[i].SamplesBindable; + _ = notes[i].StartTimeBindable; + } + } + + return notes; + } + } +} From 5ddb5a3d741e2d1f64ad7ddd964502a3767206a3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jul 2022 04:23:30 +0300 Subject: [PATCH 109/481] Introduce `HitObjectProperty` --- .../Rulesets/Objects/HitObjectProperty.cs | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 osu.Game/Rulesets/Objects/HitObjectProperty.cs diff --git a/osu.Game/Rulesets/Objects/HitObjectProperty.cs b/osu.Game/Rulesets/Objects/HitObjectProperty.cs new file mode 100644 index 0000000000..e765dd6cb0 --- /dev/null +++ b/osu.Game/Rulesets/Objects/HitObjectProperty.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using JetBrains.Annotations; +using osu.Framework.Bindables; + +namespace osu.Game.Rulesets.Objects +{ + /// + /// Represents a wrapper containing a lazily-initialised , backed by a temporary field used for storage until initialisation. + /// + public struct HitObjectProperty + { + [CanBeNull] + private Bindable backingBindable; + + /// + /// A temporary field to store the current value to, prior to 's initialisation. + /// + private T backingValue; + + /// + /// The underlying , only initialised on first access. + /// + public Bindable Bindable => backingBindable ??= new Bindable { Value = backingValue }; + + /// + /// The current value, derived from and delegated to if initialised, or a temporary field otherwise. + /// + public T Value + { + get => backingBindable != null ? backingBindable.Value : backingValue; + set + { + if (backingBindable != null) + backingBindable.Value = value; + else + backingValue = value; + } + } + } +} From 1051009827b081d15686d307603b510f9e1e1a4e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jul 2022 04:35:07 +0300 Subject: [PATCH 110/481] Change bindable properties in all `HitObject`s to be lazily initialised --- .../Objects/CatchHitObject.cs | 66 ++++++++++++------- .../Objects/PalpableCatchHitObject.cs | 7 +- .../Objects/ManiaHitObject.cs | 8 ++- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 42 ++++++++---- osu.Game.Rulesets.Taiko/Objects/BarLine.cs | 10 +-- osu.Game.Rulesets.Taiko/Objects/Hit.cs | 11 ++-- 6 files changed, 93 insertions(+), 51 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index f5a3426305..7b871177f1 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -19,7 +19,9 @@ namespace osu.Game.Rulesets.Catch.Objects { public const float OBJECT_RADIUS = 64; - public readonly Bindable OriginalXBindable = new Bindable(); + private HitObjectProperty originalX; + + public Bindable OriginalXBindable => originalX.Bindable; /// /// The horizontal position of the hit object between 0 and . @@ -31,18 +33,20 @@ namespace osu.Game.Rulesets.Catch.Objects [JsonIgnore] public float X { - set => OriginalXBindable.Value = value; + set => originalX.Value = value; } - public readonly Bindable XOffsetBindable = new Bindable(); + private HitObjectProperty xOffset; + + public Bindable XOffsetBindable => xOffset.Bindable; /// /// A random offset applied to the horizontal position, set by the beatmap processing. /// public float XOffset { - get => XOffsetBindable.Value; - set => XOffsetBindable.Value = value; + get => xOffset.Value; + set => xOffset.Value = value; } /// @@ -54,8 +58,8 @@ namespace osu.Game.Rulesets.Catch.Objects /// public float OriginalX { - get => OriginalXBindable.Value; - set => OriginalXBindable.Value = value; + get => originalX.Value; + set => originalX.Value = value; } /// @@ -69,59 +73,71 @@ namespace osu.Game.Rulesets.Catch.Objects public double TimePreempt { get; set; } = 1000; - public readonly Bindable IndexInBeatmapBindable = new Bindable(); + private HitObjectProperty indexInBeatmap; + + public Bindable IndexInBeatmapBindable => indexInBeatmap.Bindable; public int IndexInBeatmap { - get => IndexInBeatmapBindable.Value; - set => IndexInBeatmapBindable.Value = value; + get => indexInBeatmap.Value; + set => indexInBeatmap.Value = value; } public virtual bool NewCombo { get; set; } public int ComboOffset { get; set; } - public Bindable IndexInCurrentComboBindable { get; } = new Bindable(); + private HitObjectProperty indexInCurrentCombo; + + public Bindable IndexInCurrentComboBindable => indexInCurrentCombo.Bindable; public int IndexInCurrentCombo { - get => IndexInCurrentComboBindable.Value; - set => IndexInCurrentComboBindable.Value = value; + get => indexInCurrentCombo.Value; + set => indexInCurrentCombo.Value = value; } - public Bindable ComboIndexBindable { get; } = new Bindable(); + private HitObjectProperty comboIndex; + + public Bindable ComboIndexBindable => comboIndex.Bindable; public int ComboIndex { - get => ComboIndexBindable.Value; - set => ComboIndexBindable.Value = value; + get => comboIndex.Value; + set => comboIndex.Value = value; } - public Bindable ComboIndexWithOffsetsBindable { get; } = new Bindable(); + private HitObjectProperty comboIndexWithOffsets; + + public Bindable ComboIndexWithOffsetsBindable => comboIndexWithOffsets.Bindable; public int ComboIndexWithOffsets { - get => ComboIndexWithOffsetsBindable.Value; - set => ComboIndexWithOffsetsBindable.Value = value; + get => comboIndexWithOffsets.Value; + set => comboIndexWithOffsets.Value = value; } - public Bindable LastInComboBindable { get; } = new Bindable(); + private HitObjectProperty lastInCombo; + + public Bindable LastInComboBindable => lastInCombo.Bindable; /// /// The next fruit starts a new combo. Used for explodey. /// public virtual bool LastInCombo { - get => LastInComboBindable.Value; - set => LastInComboBindable.Value = value; + get => lastInCombo.Value; + set => lastInCombo.Value = value; } - public readonly Bindable ScaleBindable = new Bindable(1); + private HitObjectProperty scale; + + public Bindable ScaleBindable => scale.Bindable; public float Scale { - get => ScaleBindable.Value; - set => ScaleBindable.Value = value; + get => scale.Value; + set => scale.Value = value; } /// diff --git a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs index 1ededa1438..c9bc9ca2ac 100644 --- a/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/PalpableCatchHitObject.cs @@ -5,6 +5,7 @@ using Newtonsoft.Json; using osu.Framework.Bindables; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Skinning; using osuTK.Graphics; @@ -24,12 +25,14 @@ namespace osu.Game.Rulesets.Catch.Objects /// public float DistanceToHyperDash { get; set; } - public readonly Bindable HyperDashBindable = new Bindable(); + private HitObjectProperty hyperDash; + + public Bindable HyperDashBindable => hyperDash.Bindable; /// /// Whether this fruit can initiate a hyperdash. /// - public bool HyperDash => HyperDashBindable.Value; + public bool HyperDash => hyperDash.Value; private CatchHitObject hyperDashTarget; diff --git a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs index 0efaeac026..ebff5cf4e9 100644 --- a/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/ManiaHitObject.cs @@ -13,12 +13,14 @@ namespace osu.Game.Rulesets.Mania.Objects { public abstract class ManiaHitObject : HitObject, IHasColumn, IHasXPosition { - public readonly Bindable ColumnBindable = new Bindable(); + private HitObjectProperty column; + + public Bindable ColumnBindable => column.Bindable; public virtual int Column { - get => ColumnBindable.Value; - set => ColumnBindable.Value = value; + get => column.Value; + set => column.Value = value; } protected override HitWindows CreateHitWindows() => new ManiaHitWindows(); diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 387342b4a9..6708c061e7 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -7,12 +7,12 @@ using System; using System.Linq; using osu.Framework.Bindables; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects; -using osuTK; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects { @@ -36,12 +36,14 @@ namespace osu.Game.Rulesets.Osu.Objects public double TimePreempt = 600; public double TimeFadeIn = 400; - public readonly Bindable PositionBindable = new Bindable(); + private HitObjectProperty position; + + public Bindable PositionBindable => position.Bindable; public virtual Vector2 Position { - get => PositionBindable.Value; - set => PositionBindable.Value = value; + get => position.Value; + set => position.Value = value; } public float X => Position.X; @@ -53,7 +55,9 @@ namespace osu.Game.Rulesets.Osu.Objects public Vector2 StackedEndPosition => EndPosition + StackOffset; - public readonly Bindable StackHeightBindable = new Bindable(); + private HitObjectProperty stackHeightProperty; + + public Bindable StackHeightBindable => stackHeightProperty.Bindable; public int StackHeight { @@ -65,7 +69,9 @@ namespace osu.Game.Rulesets.Osu.Objects public double Radius => OBJECT_RADIUS * Scale; - public readonly Bindable ScaleBindable = new BindableFloat(1); + private HitObjectProperty scaleProperty; + + public Bindable ScaleBindable => scaleProperty.Bindable; public float Scale { @@ -75,7 +81,9 @@ namespace osu.Game.Rulesets.Osu.Objects public virtual bool NewCombo { get; set; } - public readonly Bindable ComboOffsetBindable = new Bindable(); + private HitObjectProperty comboOffsetProperty; + + public Bindable ComboOffsetBindable => comboOffsetProperty.Bindable; public int ComboOffset { @@ -83,7 +91,9 @@ namespace osu.Game.Rulesets.Osu.Objects set => ComboOffsetBindable.Value = value; } - public Bindable IndexInCurrentComboBindable { get; } = new Bindable(); + private HitObjectProperty indexInCurrentComboProperty; + + public Bindable IndexInCurrentComboBindable => indexInCurrentComboProperty.Bindable; public virtual int IndexInCurrentCombo { @@ -91,7 +101,9 @@ namespace osu.Game.Rulesets.Osu.Objects set => IndexInCurrentComboBindable.Value = value; } - public Bindable ComboIndexBindable { get; } = new Bindable(); + private HitObjectProperty comboIndexProperty; + + public Bindable ComboIndexBindable => comboIndexProperty.Bindable; public virtual int ComboIndex { @@ -99,7 +111,9 @@ namespace osu.Game.Rulesets.Osu.Objects set => ComboIndexBindable.Value = value; } - public Bindable ComboIndexWithOffsetsBindable { get; } = new Bindable(); + private HitObjectProperty comboIndexWithOffsetsProperty; + + public Bindable ComboIndexWithOffsetsBindable => comboIndexWithOffsetsProperty.Bindable; public int ComboIndexWithOffsets { @@ -107,7 +121,9 @@ namespace osu.Game.Rulesets.Osu.Objects set => ComboIndexWithOffsetsBindable.Value = value; } - public Bindable LastInComboBindable { get; } = new Bindable(); + private HitObjectProperty lastInComboProperty; + + public Bindable LastInComboBindable => lastInComboProperty.Bindable; public bool LastInCombo { diff --git a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs index 382035119e..d2eba0eb54 100644 --- a/osu.Game.Rulesets.Taiko/Objects/BarLine.cs +++ b/osu.Game.Rulesets.Taiko/Objects/BarLine.cs @@ -11,14 +11,16 @@ namespace osu.Game.Rulesets.Taiko.Objects { public class BarLine : TaikoHitObject, IBarLine { + private HitObjectProperty major; + + public Bindable MajorBindable => major.Bindable; + public bool Major { - get => MajorBindable.Value; - set => MajorBindable.Value = value; + get => major.Value; + set => major.Value = value; } - public readonly Bindable MajorBindable = new BindableBool(); - public override Judgement CreateJudgement() => new IgnoreJudgement(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs index 20f3304c30..787079bfee 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs @@ -7,6 +7,7 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Audio; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osuTK.Graphics; @@ -14,19 +15,21 @@ namespace osu.Game.Rulesets.Taiko.Objects { public class Hit : TaikoStrongableHitObject, IHasDisplayColour { - public readonly Bindable TypeBindable = new Bindable(); + private HitObjectProperty type; - public Bindable DisplayColour { get; } = new Bindable(COLOUR_CENTRE); + public Bindable TypeBindable => type.Bindable; /// /// The that actuates this . /// public HitType Type { - get => TypeBindable.Value; - set => TypeBindable.Value = value; + get => type.Value; + set => type.Value = value; } + public Bindable DisplayColour { get; } = new Bindable(COLOUR_CENTRE); + public static readonly Color4 COLOUR_CENTRE = Color4Extensions.FromHex(@"bb1177"); public static readonly Color4 COLOUR_RIM = Color4Extensions.FromHex(@"2299bb"); From 8f80a22ef9ab510f02e98fcbfa7f9765ee229b61 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jul 2022 06:57:31 +0300 Subject: [PATCH 111/481] Fix osu! and catch hitobjects no longer scaled to 1 by default --- osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs | 2 +- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 2 +- osu.Game/Rulesets/Objects/HitObjectProperty.cs | 10 +++++++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index 7b871177f1..6e01c44e1f 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Catch.Objects set => lastInCombo.Value = value; } - private HitObjectProperty scale; + private HitObjectProperty scale = new HitObjectProperty(1); public Bindable ScaleBindable => scale.Bindable; diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 6708c061e7..1e7cce162e 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects public double Radius => OBJECT_RADIUS * Scale; - private HitObjectProperty scaleProperty; + private HitObjectProperty scaleProperty = new HitObjectProperty(1); public Bindable ScaleBindable => scaleProperty.Bindable; diff --git a/osu.Game/Rulesets/Objects/HitObjectProperty.cs b/osu.Game/Rulesets/Objects/HitObjectProperty.cs index e765dd6cb0..f1df83f80c 100644 --- a/osu.Game/Rulesets/Objects/HitObjectProperty.cs +++ b/osu.Game/Rulesets/Objects/HitObjectProperty.cs @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Objects /// /// The underlying , only initialised on first access. /// - public Bindable Bindable => backingBindable ??= new Bindable { Value = backingValue }; + public Bindable Bindable => backingBindable ??= new Bindable(defaultValue) { Value = backingValue }; /// /// The current value, derived from and delegated to if initialised, or a temporary field otherwise. @@ -40,5 +40,13 @@ namespace osu.Game.Rulesets.Objects backingValue = value; } } + + private readonly T defaultValue; + + public HitObjectProperty(T value = default) + { + backingValue = defaultValue = value; + backingBindable = null; + } } } From 59018ab5ba28a21e2a2ede263dc2a9df66d5012d Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Mon, 18 Jul 2022 23:21:16 -0500 Subject: [PATCH 112/481] Fix multiplayer queue edit button opening to the wrong beatmap --- .../Multiplayer/TestSceneMultiplayer.cs | 60 ++++++++++--------- .../Multiplayer/MultiplayerMatchSubScreen.cs | 12 +++- 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index a2793acba7..d35887c443 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -402,16 +402,18 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestPlayStartsWithCorrectBeatmapWhileAtSongSelect() { - createRoom(() => new Room + PlaylistItem? item = null; + createRoom(() => { - Name = { Value = "Test Room" }, - Playlist = + item = new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - } - } + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + }; + return new Room + { + Name = { Value = "Test Room" }, + Playlist = { item } + }; }); pressReadyButton(); @@ -419,7 +421,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; - ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.ClientRoom?.Settings.PlaylistItemId); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(item); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); @@ -440,16 +442,18 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestPlayStartsWithCorrectRulesetWhileAtSongSelect() { - createRoom(() => new Room + PlaylistItem? item = null; + createRoom(() => { - Name = { Value = "Test Room" }, - Playlist = + item = new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - } - } + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + }; + return new Room + { + Name = { Value = "Test Room" }, + Playlist = { item } + }; }); pressReadyButton(); @@ -457,7 +461,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; - ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.ClientRoom?.Settings.PlaylistItemId); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(item); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); @@ -478,16 +482,18 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestPlayStartsWithCorrectModsWhileAtSongSelect() { - createRoom(() => new Room + PlaylistItem? item = null; + createRoom(() => { - Name = { Value = "Test Room" }, - Playlist = + item = new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) { - new PlaylistItem(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0)).BeatmapInfo) - { - RulesetID = new OsuRuleset().RulesetInfo.OnlineID - } - } + RulesetID = new OsuRuleset().RulesetInfo.OnlineID + }; + return new Room + { + Name = { Value = "Test Room" }, + Playlist = { item } + }; }); pressReadyButton(); @@ -495,7 +501,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerComponents.CurrentScreen).CurrentSubScreen; - ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(multiplayerClient.ClientRoom?.Settings.PlaylistItemId); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(item); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 4eb16a854b..9ed67bd142 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -49,6 +49,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private MultiplayerClient client { get; set; } + [Resolved] + private BeatmapManager beatmapManager { get; set; } + private readonly IBindable isConnected = new Bindable(); private AddItemButton addItemButton; @@ -145,7 +148,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new MultiplayerPlaylist { RelativeSizeAxes = Axes.Both, - RequestEdit = item => OpenSongSelection(item.ID) + RequestEdit = item => OpenSongSelection(item) } }, new[] @@ -223,12 +226,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer /// Opens the song selection screen to add or edit an item. /// /// An optional playlist item to edit. If null, a new item will be added instead. - internal void OpenSongSelection(long? itemToEdit = null) + internal void OpenSongSelection(PlaylistItem itemToEdit = null) { if (!this.IsCurrentScreen()) return; - this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit)); + var localBeatmap = itemToEdit == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineID == itemToEdit.Beatmap.OnlineID); + var workingBeatmap = localBeatmap == null ? null : beatmapManager.GetWorkingBeatmap(localBeatmap); + + this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit?.ID, workingBeatmap)); } protected override Drawable CreateFooter() => new MultiplayerMatchFooter(); From 46efce8a67c059ee5673682df65ea736fa1fe662 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 13:39:26 +0900 Subject: [PATCH 113/481] Equalise work done in benchmarks to cover accessing normal properties --- osu.Game.Benchmarks/BenchmarkHitObject.cs | 50 +++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/osu.Game.Benchmarks/BenchmarkHitObject.cs b/osu.Game.Benchmarks/BenchmarkHitObject.cs index d7731e0cfd..65c78e39b3 100644 --- a/osu.Game.Benchmarks/BenchmarkHitObject.cs +++ b/osu.Game.Benchmarks/BenchmarkHitObject.cs @@ -39,6 +39,29 @@ namespace osu.Game.Benchmarks _ = circles[i].SamplesBindable; _ = circles[i].StartTimeBindable; } + else + { + _ = circles[i].Position; + _ = circles[i].Scale; + _ = circles[i].ComboIndex; + _ = circles[i].ComboOffset; + _ = circles[i].StackHeight; + _ = circles[i].LastInCombo; + _ = circles[i].ComboIndexWithOffsets; + _ = circles[i].IndexInCurrentCombo; + _ = circles[i].Samples; + _ = circles[i].StartTime; + _ = circles[i].Position; + _ = circles[i].Scale; + _ = circles[i].ComboIndex; + _ = circles[i].ComboOffset; + _ = circles[i].StackHeight; + _ = circles[i].LastInCombo; + _ = circles[i].ComboIndexWithOffsets; + _ = circles[i].IndexInCurrentCombo; + _ = circles[i].Samples; + _ = circles[i].StartTime; + } } return circles; @@ -60,6 +83,13 @@ namespace osu.Game.Benchmarks _ = hits[i].SamplesBindable; _ = hits[i].StartTimeBindable; } + else + { + _ = hits[i].Type; + _ = hits[i].IsStrong; + _ = hits[i].Samples; + _ = hits[i].StartTime; + } } return hits; @@ -88,6 +118,20 @@ namespace osu.Game.Benchmarks _ = fruit[i].SamplesBindable; _ = fruit[i].StartTimeBindable; } + else + { + _ = fruit[i].OriginalX; + _ = fruit[i].XOffset; + _ = fruit[i].Scale; + _ = fruit[i].ComboIndex; + _ = fruit[i].HyperDash; + _ = fruit[i].LastInCombo; + _ = fruit[i].ComboIndexWithOffsets; + _ = fruit[i].IndexInCurrentCombo; + _ = fruit[i].IndexInBeatmap; + _ = fruit[i].Samples; + _ = fruit[i].StartTime; + } } return fruit; @@ -108,6 +152,12 @@ namespace osu.Game.Benchmarks _ = notes[i].SamplesBindable; _ = notes[i].StartTimeBindable; } + else + { + _ = notes[i].Column; + _ = notes[i].Samples; + _ = notes[i].StartTime; + } } return notes; From d8cce5fe363ecad7c6fb80d40e161de4c76497a6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jul 2022 07:52:12 +0300 Subject: [PATCH 114/481] Fix `OsuHitObject` not using property wrapper properly --- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 1e7cce162e..7b98fc48e0 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -55,80 +55,80 @@ namespace osu.Game.Rulesets.Osu.Objects public Vector2 StackedEndPosition => EndPosition + StackOffset; - private HitObjectProperty stackHeightProperty; + private HitObjectProperty stackHeight; - public Bindable StackHeightBindable => stackHeightProperty.Bindable; + public Bindable StackHeightBindable => stackHeight.Bindable; public int StackHeight { - get => StackHeightBindable.Value; - set => StackHeightBindable.Value = value; + get => stackHeight.Value; + set => stackHeight.Value = value; } public virtual Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f); public double Radius => OBJECT_RADIUS * Scale; - private HitObjectProperty scaleProperty = new HitObjectProperty(1); + private HitObjectProperty scale = new HitObjectProperty(1); - public Bindable ScaleBindable => scaleProperty.Bindable; + public Bindable ScaleBindable => scale.Bindable; public float Scale { - get => ScaleBindable.Value; - set => ScaleBindable.Value = value; + get => scale.Value; + set => scale.Value = value; } public virtual bool NewCombo { get; set; } - private HitObjectProperty comboOffsetProperty; + private HitObjectProperty comboOffset; - public Bindable ComboOffsetBindable => comboOffsetProperty.Bindable; + public Bindable ComboOffsetBindable => comboOffset.Bindable; public int ComboOffset { - get => ComboOffsetBindable.Value; - set => ComboOffsetBindable.Value = value; + get => comboOffset.Value; + set => comboOffset.Value = value; } - private HitObjectProperty indexInCurrentComboProperty; + private HitObjectProperty indexInCurrentCombo; - public Bindable IndexInCurrentComboBindable => indexInCurrentComboProperty.Bindable; + public Bindable IndexInCurrentComboBindable => indexInCurrentCombo.Bindable; public virtual int IndexInCurrentCombo { - get => IndexInCurrentComboBindable.Value; - set => IndexInCurrentComboBindable.Value = value; + get => indexInCurrentCombo.Value; + set => indexInCurrentCombo.Value = value; } - private HitObjectProperty comboIndexProperty; + private HitObjectProperty comboIndex; - public Bindable ComboIndexBindable => comboIndexProperty.Bindable; + public Bindable ComboIndexBindable => comboIndex.Bindable; public virtual int ComboIndex { - get => ComboIndexBindable.Value; - set => ComboIndexBindable.Value = value; + get => comboIndex.Value; + set => comboIndex.Value = value; } - private HitObjectProperty comboIndexWithOffsetsProperty; + private HitObjectProperty comboIndexWithOffsets; - public Bindable ComboIndexWithOffsetsBindable => comboIndexWithOffsetsProperty.Bindable; + public Bindable ComboIndexWithOffsetsBindable => comboIndexWithOffsets.Bindable; public int ComboIndexWithOffsets { - get => ComboIndexWithOffsetsBindable.Value; - set => ComboIndexWithOffsetsBindable.Value = value; + get => comboIndexWithOffsets.Value; + set => comboIndexWithOffsets.Value = value; } - private HitObjectProperty lastInComboProperty; + private HitObjectProperty lastInCombo; - public Bindable LastInComboBindable => lastInComboProperty.Bindable; + public Bindable LastInComboBindable => lastInCombo.Bindable; public bool LastInCombo { - get => LastInComboBindable.Value; - set => LastInComboBindable.Value = value; + get => lastInCombo.Value; + set => lastInCombo.Value = value; } protected OsuHitObject() From 2716bd41d9f34d57733943bc7ec10f9fecd2ffbe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 15:33:02 +0900 Subject: [PATCH 115/481] Use more correct json casing in `APIScoresCollection` osu-web API is already returning both of these casings for backwards compatibility, but the former will be removed at some point. https://github.com/ppy/osu-web/blob/e540276721951b72bd1b6625260da5e6b33110b0/app/Http/Controllers/BeatmapsController.php#L314-L315 --- osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs b/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs index 38c67d92f4..4ef39be5e5 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs @@ -13,7 +13,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"scores")] public List Scores; - [JsonProperty(@"userScore")] + [JsonProperty(@"user_score")] public APIScoreWithPosition UserScore; } } From e346624b14e78d4165618e3536de904001941aeb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 15:51:02 +0900 Subject: [PATCH 116/481] Fix animation changes incorrectly applying to successful completion of sliders --- .../Skinning/Default/DefaultFollowCircle.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 07b99560e5..b77d4addee 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -33,8 +34,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default protected override void OnTrackingChanged(ValueChangedEvent tracking) { + Debug.Assert(ParentObject != null); + const float duration = 300f; + if (ParentObject.Judged) + return; + if (tracking.NewValue) { if (Precision.AlmostEquals(0, Alpha)) @@ -52,10 +58,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default protected override void OnSliderEnd() { - const float fade_duration = 450f; + const float fade_duration = 300; // intentionally pile on an extra FadeOut to make it happen much faster - this.FadeOut(fade_duration / 4, Easing.Out); + this.ScaleTo(1, fade_duration, Easing.OutQuint); + this.FadeOut(fade_duration / 2, Easing.OutQuint); } } } From 5008a737747a51ecef4a8df9203767902277550b Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Tue, 19 Jul 2022 02:04:19 -0500 Subject: [PATCH 117/481] Make add item button open to the last beatmap in queue --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 9ed67bd142..04d800a10e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -231,7 +231,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!this.IsCurrentScreen()) return; - var localBeatmap = itemToEdit == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineID == itemToEdit.Beatmap.OnlineID); + int id = itemToEdit?.Beatmap.OnlineID ?? Room.Playlist.LastOrDefault().Beatmap.OnlineID; + var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == id); + var workingBeatmap = localBeatmap == null ? null : beatmapManager.GetWorkingBeatmap(localBeatmap); this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit?.ID, workingBeatmap)); From 12e5bc3f3d2ea4c109f1b77edbd88b12ef15e224 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 17:14:57 +0900 Subject: [PATCH 118/481] Fix `BeginPlayingInternal` firing actual errors when beatmap not available online --- osu.Game/Online/Spectator/OnlineSpectatorClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index 9bbc2a11c7..030ca724c4 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -66,10 +66,10 @@ namespace osu.Game.Online.Spectator await connector.Reconnect(); await BeginPlayingInternal(state); - return; } - throw; + // Exceptions can occur if, for instance, the locally played beatmap doesn't have a server-side counterpart. + // For now, let's ignore these so they don't cause unobserved exceptions to appear to the user (and sentry). } } From 09613f1af3bf92b2fb072b5b909b45b4d8f173ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 17:25:52 +0900 Subject: [PATCH 119/481] Add mention of "compatibility mode" in windows version check error message --- osu.Desktop/Program.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index cebbcb40b7..19cf7f5d46 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -37,9 +37,15 @@ namespace osu.Desktop // See https://www.mongodb.com/docs/realm/sdk/dotnet/#supported-platforms if (windowsVersion.Major < 6 || (windowsVersion.Major == 6 && windowsVersion.Minor <= 2)) { + // If users running in compatibility mode becomes more of a common thing, we may want to provide better guidance or even consider + // disabling it ourselves. + // We could also better detect compatibility mode if required: + // https://stackoverflow.com/questions/10744651/how-i-can-detect-if-my-application-is-running-under-compatibility-mode#comment58183249_10744730 SDL.SDL_ShowSimpleMessageBox(SDL.SDL_MessageBoxFlags.SDL_MESSAGEBOX_ERROR, "Your operating system is too old to run osu!", - "This version of osu! requires at least Windows 8.1 to run.\nPlease upgrade your operating system or consider using an older version of osu!.", IntPtr.Zero); + "This version of osu! requires at least Windows 8.1 to run.\n" + + "Please upgrade your operating system or consider using an older version of osu!.\n\n" + + "If you are running a newer version of windows, please check you don't have \"Compatibility mode\" turned on for osu!", IntPtr.Zero); return; } From 0bd4aee66cb48b4fdd88937d665af076f7f3beef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jul 2022 15:23:06 +0900 Subject: [PATCH 120/481] Add ignore rule for `System.ComponentModel.Component` --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 0794095854..b16e309e52 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -809,6 +809,7 @@ See the LICENCE file in the repository root for full licence text. True True True + True True True True From 6ea380d6498583d6a242e2385e6491c057c01b04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 18:59:08 +0900 Subject: [PATCH 121/481] Add new properties to `BeatmapInfo` to track online hash and updates --- osu.Game/Beatmaps/BeatmapInfo.cs | 10 ++++++++++ osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs | 7 +++++-- osu.Game/Beatmaps/BeatmapUpdater.cs | 2 ++ osu.Game/Database/RealmAccess.cs | 3 ++- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 41e89d864e..3ee306cc9a 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -92,6 +92,16 @@ namespace osu.Game.Beatmaps [Indexed] public string MD5Hash { get; set; } = string.Empty; + public string OnlineMD5Hash { get; set; } = string.Empty; + + public DateTimeOffset? LastOnlineUpdate { get; set; } + + /// + /// Whether this beatmap matches the online version, based on fetched online metadata. + /// Will return true if no online metadata is available. + /// + public bool MatchesOnlineVersion => LastOnlineUpdate == null || MD5Hash == OnlineMD5Hash; + [JsonIgnore] public bool Hidden { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs index a2eb76cafa..e07f18bdfb 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs @@ -102,10 +102,13 @@ namespace osu.Game.Beatmaps beatmapInfo.BeatmapSet.Status = res.BeatmapSet?.Status ?? BeatmapOnlineStatus.None; beatmapInfo.BeatmapSet.OnlineID = res.OnlineBeatmapSetID; + beatmapInfo.OnlineMD5Hash = res.MD5Hash; beatmapInfo.OnlineID = res.OnlineID; beatmapInfo.Metadata.Author.OnlineID = res.AuthorID; + beatmapInfo.LastOnlineUpdate = DateTimeOffset.Now; + logForModel(set, $"Online retrieval mapped {beatmapInfo} to {res.OnlineBeatmapSetID} / {res.OnlineID}."); } } @@ -190,7 +193,7 @@ namespace osu.Game.Beatmaps using (var cmd = db.CreateCommand()) { - cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; + cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id, checksum FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmapInfo.MD5Hash)); cmd.Parameters.Add(new SqliteParameter("@OnlineID", beatmapInfo.OnlineID)); @@ -209,8 +212,8 @@ namespace osu.Game.Beatmaps beatmapInfo.BeatmapSet.Status = status; beatmapInfo.BeatmapSet.OnlineID = reader.GetInt32(0); beatmapInfo.OnlineID = reader.GetInt32(1); - beatmapInfo.Metadata.Author.OnlineID = reader.GetInt32(3); + beatmapInfo.OnlineMD5Hash = reader.GetString(4); logForModel(set, $"Cached local retrieval for {beatmapInfo}."); return true; diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index 20fa0bc7c6..d1d0cd9623 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -56,6 +56,8 @@ namespace osu.Game.Beatmaps // Before we use below, we want to invalidate. workingBeatmapCache.Invalidate(beatmapSet); + // TODO: this call currently uses the local `online.db` lookup. + // We probably don't want this to happen after initial import (as the data may be stale). onlineLookupQueue.Update(beatmapSet); foreach (var beatmap in beatmapSet.Beatmaps) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index c4d65f4f10..28870617cc 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -61,8 +61,9 @@ namespace osu.Game.Database /// 15 2022-07-13 Added LastPlayed to BeatmapInfo. /// 16 2022-07-15 Removed HasReplay from ScoreInfo. /// 17 2022-07-16 Added CountryCode to RealmUser. + /// 18 2022-07-19 Added OnlineMD5Hash and LastOnlineUpdate to BeatmapInfo. /// - private const int schema_version = 17; + private const int schema_version = 18; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. From cd39f444efe4124f7db47276ec4b69c65c1b4453 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 14 Jul 2022 15:18:12 +0900 Subject: [PATCH 122/481] Expose event from `OnlineMetadataClient` rather than calling `BeatmapUpdater` directly --- osu.Game/Online/Metadata/MetadataClient.cs | 10 ++++++++++ osu.Game/Online/Metadata/OnlineMetadataClient.cs | 16 +--------------- osu.Game/OsuGameBase.cs | 2 +- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/osu.Game/Online/Metadata/MetadataClient.cs b/osu.Game/Online/Metadata/MetadataClient.cs index 1e5eeb4eb0..60867da2d7 100644 --- a/osu.Game/Online/Metadata/MetadataClient.cs +++ b/osu.Game/Online/Metadata/MetadataClient.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Linq; using System.Threading.Tasks; using osu.Framework.Graphics; @@ -11,5 +13,13 @@ namespace osu.Game.Online.Metadata public abstract Task BeatmapSetsUpdated(BeatmapUpdates updates); public abstract Task GetChangesSince(int queueId); + + public Action? ChangedBeatmapSetsArrived; + + protected Task ProcessChanges(int[] beatmapSetIDs) + { + ChangedBeatmapSetsArrived?.Invoke(beatmapSetIDs.Distinct().ToArray()); + return Task.CompletedTask; + } } } diff --git a/osu.Game/Online/Metadata/OnlineMetadataClient.cs b/osu.Game/Online/Metadata/OnlineMetadataClient.cs index 1b0d1884dc..95228c380f 100644 --- a/osu.Game/Online/Metadata/OnlineMetadataClient.cs +++ b/osu.Game/Online/Metadata/OnlineMetadataClient.cs @@ -7,7 +7,6 @@ using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Logging; -using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Online.API; @@ -15,7 +14,6 @@ namespace osu.Game.Online.Metadata { public class OnlineMetadataClient : MetadataClient { - private readonly BeatmapUpdater beatmapUpdater; private readonly string endpoint; private IHubClientConnector? connector; @@ -24,9 +22,8 @@ namespace osu.Game.Online.Metadata private HubConnection? connection => connector?.CurrentConnection; - public OnlineMetadataClient(EndpointConfiguration endpoints, BeatmapUpdater beatmapUpdater) + public OnlineMetadataClient(EndpointConfiguration endpoints) { - this.beatmapUpdater = beatmapUpdater; endpoint = endpoints.MetadataEndpointUrl; } @@ -102,17 +99,6 @@ namespace osu.Game.Online.Metadata await ProcessChanges(updates.BeatmapSetIDs); } - protected Task ProcessChanges(int[] beatmapSetIDs) - { - foreach (int id in beatmapSetIDs) - { - Logger.Log($"Processing {id}..."); - beatmapUpdater.Queue(id); - } - - return Task.CompletedTask; - } - public override Task GetChangesSince(int queueId) { if (connector?.IsConnected.Value != true) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 4b5c9c0815..c060723152 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -287,7 +287,7 @@ namespace osu.Game dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints)); dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints)); - dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints, beatmapUpdater)); + dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); BeatmapManager.ProcessBeatmap = set => beatmapUpdater.Process(set); From 6adcf82d2ed7270a840b8be7190997830ba8de00 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Jun 2022 18:40:05 +0900 Subject: [PATCH 123/481] Add change ingester to handle passing of online changes to correct target components --- .../Beatmaps/BeatmapOnlineChangeIngest.cs | 52 +++++++++++++++++++ osu.Game/Beatmaps/BeatmapUpdater.cs | 12 +---- osu.Game/OsuGameBase.cs | 2 + 3 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs diff --git a/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs b/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs new file mode 100644 index 0000000000..937d4358d5 --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System.Linq; +using osu.Framework.Graphics; +using osu.Game.Database; +using osu.Game.Online.Metadata; + +namespace osu.Game.Beatmaps +{ + /// + /// Ingests any changes that happen externally to the client, reprocessing as required. + /// + public class BeatmapOnlineChangeIngest : Component + { + private readonly BeatmapUpdater beatmapUpdater; + private readonly RealmAccess realm; + private readonly MetadataClient metadataClient; + + public BeatmapOnlineChangeIngest(BeatmapUpdater beatmapUpdater, RealmAccess realm, MetadataClient metadataClient) + { + this.beatmapUpdater = beatmapUpdater; + this.realm = realm; + this.metadataClient = metadataClient; + + metadataClient.ChangedBeatmapSetsArrived += changesDetected; + } + + private void changesDetected(int[] beatmapSetIds) + { + // May want to batch incoming updates further if the background realm operations ever becomes a concern. + realm.Run(r => + { + foreach (int id in beatmapSetIds) + { + var matchingSet = r.All().FirstOrDefault(s => s.OnlineID == id); + + if (matchingSet != null) + beatmapUpdater.Queue(matchingSet.ToLive(realm)); + } + }); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + metadataClient.ChangedBeatmapSetsArrived -= changesDetected; + } + } +} diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index d1d0cd9623..d2c5e5616a 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Database; using osu.Game.Online.API; @@ -30,21 +31,12 @@ namespace osu.Game.Beatmaps onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); } - /// - /// Queue a beatmap for background processing. - /// - public void Queue(int beatmapSetId) - { - // TODO: implement - } - /// /// Queue a beatmap for background processing. /// public void Queue(Live beatmap) { - // For now, just fire off a task. - // TODO: Add actual queueing probably. + Logger.Log($"Queueing change for local beatmap {beatmap}"); Task.Factory.StartNew(() => beatmap.PerformRead(Process)); } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index c060723152..a53ad48a40 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -289,6 +289,8 @@ namespace osu.Game dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints)); dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); + AddInternal(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient)); + BeatmapManager.ProcessBeatmap = set => beatmapUpdater.Process(set); dependencies.Cache(userCache = new UserLookupCache()); From d213f56f79001d79267940372a472238a58d7df8 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Tue, 19 Jul 2022 02:08:53 -0700 Subject: [PATCH 124/481] Align legacy followcircle anims to slider ticks --- .../Skinning/Default/DefaultFollowCircle.cs | 42 ++++++-------- .../Skinning/FollowCircle.cs | 31 +++++++--- .../Skinning/Legacy/LegacyFollowCircle.cs | 39 +++++++------ .../Skinning/TickFollowCircle.cs | 58 +++++++++++++++++++ 4 files changed, 119 insertions(+), 51 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index b77d4addee..0acd1a56b6 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Diagnostics; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -32,37 +30,31 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default }; } - protected override void OnTrackingChanged(ValueChangedEvent tracking) + protected override void OnSliderPress() { - Debug.Assert(ParentObject != null); - const float duration = 300f; - if (ParentObject.Judged) - return; + if (Precision.AlmostEquals(0, Alpha)) + this.ScaleTo(1); - if (tracking.NewValue) - { - if (Precision.AlmostEquals(0, Alpha)) - this.ScaleTo(1); - - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA, duration, Easing.OutQuint) - .FadeTo(1f, duration, Easing.OutQuint); - } - else - { - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.2f, duration / 2, Easing.OutQuint) - .FadeTo(0, duration / 2, Easing.OutQuint); - } + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA, duration, Easing.OutQuint) + .FadeIn(duration, Easing.OutQuint); } - protected override void OnSliderEnd() + protected override void OnSliderRelease() { - const float fade_duration = 300; + const float duration = 150; - // intentionally pile on an extra FadeOut to make it happen much faster - this.ScaleTo(1, fade_duration, Easing.OutQuint); - this.FadeOut(fade_duration / 2, Easing.OutQuint); + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.2f, duration, Easing.OutQuint) + .FadeTo(0, duration, Easing.OutQuint); + } + + protected override void OnSliderTail() + { + const float duration = 300; + + this.ScaleTo(1, duration, Easing.OutQuint) + .FadeOut(duration / 2, Easing.OutQuint); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs index 321705d25e..ca903b678d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -1,8 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; @@ -23,7 +23,17 @@ namespace osu.Game.Rulesets.Osu.Skinning [BackgroundDependencyLoader] private void load() { - ((DrawableSlider?)ParentObject)?.Tracking.BindValueChanged(OnTrackingChanged, true); + ((DrawableSlider?)ParentObject)?.Tracking.BindValueChanged(tracking => + { + Debug.Assert(ParentObject != null); + if (ParentObject.Judged) + return; + + if (tracking.NewValue) + OnSliderPress(); + else + OnSliderRelease(); + }, true); } protected override void LoadComplete() @@ -48,13 +58,18 @@ namespace osu.Game.Rulesets.Osu.Skinning private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { - // Gets called by slider ticks, tails, etc., leading to duplicated - // animations which may negatively affect performance if (drawableObject is not DrawableSlider) return; using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) - OnSliderEnd(); + { + switch (state) + { + case ArmedState.Hit: + OnSliderTail(); + break; + } + } } protected override void Dispose(bool isDisposing) @@ -68,8 +83,10 @@ namespace osu.Game.Rulesets.Osu.Skinning } } - protected abstract void OnTrackingChanged(ValueChangedEvent tracking); + protected abstract void OnSliderPress(); - protected abstract void OnSliderEnd(); + protected abstract void OnSliderRelease(); + + protected abstract void OnSliderTail(); } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index 5b7da5a1ba..965f2a9cbe 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -3,12 +3,11 @@ using System; using System.Diagnostics; -using osu.Framework.Bindables; using osu.Framework.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public class LegacyFollowCircle : FollowCircle + public class LegacyFollowCircle : TickFollowCircle { public LegacyFollowCircle(Drawable animationContent) { @@ -21,35 +20,37 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy InternalChild = animationContent; } - protected override void OnTrackingChanged(ValueChangedEvent tracking) + protected override void OnSliderPress() { Debug.Assert(ParentObject != null); - if (ParentObject.Judged) - return; - double remainingTime = Math.Max(0, ParentObject.HitStateUpdateTime - Time.Current); // Note that the scale adjust here is 2 instead of DrawableSliderBall.FOLLOW_AREA to match legacy behaviour. // This means the actual tracking area for gameplay purposes is larger than the sprite (but skins may be accounting for this). - if (tracking.NewValue) - { - // TODO: Follow circle should bounce on each slider tick. - this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out) - .FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime)); - } - else - { - // TODO: Should animate only at the next slider tick if we want to match stable perfectly. - this.ScaleTo(4f, 100) - .FadeTo(0f, 100); - } + this.ScaleTo(0.5f).ScaleTo(2f, Math.Min(180f, remainingTime), Easing.Out) + .FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime)); } - protected override void OnSliderEnd() + protected override void OnSliderTail() { this.ScaleTo(1.6f, 200, Easing.Out) .FadeOut(200, Easing.In); } + + protected override void OnSliderTick() + { + // TODO: Follow circle should bounce on each slider tick. + + // TEMP DUMMY ANIMS + this.ScaleTo(2.2f) + .ScaleTo(2f, 175f); + } + + protected override void OnSliderBreak() + { + this.ScaleTo(4f, 100) + .FadeTo(0f, 100); + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs new file mode 100644 index 0000000000..39d62064ab --- /dev/null +++ b/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs @@ -0,0 +1,58 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; + +namespace osu.Game.Rulesets.Osu.Skinning +{ + public abstract class TickFollowCircle : FollowCircle + { + protected override void LoadComplete() + { + base.LoadComplete(); + + if (ParentObject != null) + ParentObject.ApplyCustomUpdateState += updateStateTransforms; + } + + private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) + { + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + { + switch (state) + { + case ArmedState.Hit: + if (drawableObject is DrawableSliderTick or DrawableSliderRepeat) + OnSliderTick(); + break; + + case ArmedState.Miss: + if (drawableObject is DrawableSlider or DrawableSliderTick or DrawableSliderRepeat) + OnSliderBreak(); + break; + } + } + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (ParentObject != null) + ParentObject.ApplyCustomUpdateState -= updateStateTransforms; + } + + /// + /// Sealed empty. Override instead, since animations + /// should only play on slider ticks. + /// + protected sealed override void OnSliderRelease() + { + } + + protected abstract void OnSliderTick(); + + protected abstract void OnSliderBreak(); + } +} From e1f7db6e7df40777fb743e90e50cf51251c301aa Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Tue, 19 Jul 2022 02:25:14 -0700 Subject: [PATCH 125/481] Fix around some comments --- osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs | 3 +++ osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs index ca903b678d..ec9d188c6b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -58,6 +58,9 @@ namespace osu.Game.Rulesets.Osu.Skinning private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { + // We only want DrawableSlider here. DrawableSliderTail doesn't quite work because its + // HitStateUpdateTime is ~36ms before DrawableSlider's HitStateUpdateTime (aka slider + // end leniency). if (drawableObject is not DrawableSlider) return; diff --git a/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs index 39d62064ab..3aeb0c7c0a 100644 --- a/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs @@ -44,8 +44,8 @@ namespace osu.Game.Rulesets.Osu.Skinning } /// - /// Sealed empty. Override instead, since animations - /// should only play on slider ticks. + /// Sealed empty intentionally. Override instead, since + /// animations should only play on slider ticks. /// protected sealed override void OnSliderRelease() { From 5cb0920cfbdd18da5b3254f27bdbeb94dd281880 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Tue, 19 Jul 2022 02:27:04 -0700 Subject: [PATCH 126/481] Revert OnSliderTail() to OnSliderEnd() In light of the comment added in the previous commit, slider tail and end are not actually the same. --- osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs | 2 +- osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs | 4 ++-- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 0acd1a56b6..51cfb2568b 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default .FadeTo(0, duration, Easing.OutQuint); } - protected override void OnSliderTail() + protected override void OnSliderEnd() { const float duration = 300; diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs index ec9d188c6b..d7685c1724 100644 --- a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Skinning switch (state) { case ArmedState.Hit: - OnSliderTail(); + OnSliderEnd(); break; } } @@ -90,6 +90,6 @@ namespace osu.Game.Rulesets.Osu.Skinning protected abstract void OnSliderRelease(); - protected abstract void OnSliderTail(); + protected abstract void OnSliderEnd(); } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index 965f2a9cbe..9e1c2e7e9d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy .FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime)); } - protected override void OnSliderTail() + protected override void OnSliderEnd() { this.ScaleTo(1.6f, 200, Easing.Out) .FadeOut(200, Easing.In); From eaf4f6dbb7fab886f28b1db98346196d06ac691d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 Jul 2022 18:06:11 +0900 Subject: [PATCH 127/481] Add beatmap update button --- .../SongSelect/TestSceneBeatmapCarousel.cs | 27 ++++- osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 + .../Carousel/DrawableCarouselBeatmap.cs | 11 +- .../Select/Carousel/SetPanelContent.cs | 7 +- .../Select/Carousel/UpdateRequiredIcon.cs | 105 ++++++++++++++++++ 5 files changed, 141 insertions(+), 11 deletions(-) create mode 100644 osu.Game/Screens/Select/Carousel/UpdateRequiredIcon.cs diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 66b9fa990a..453869f721 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -43,6 +44,29 @@ namespace osu.Game.Tests.Visual.SongSelect this.rulesets = rulesets; } + [Test] + public void TestBeatmapWithOnlineUpdates() + { + var testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(); + + createCarousel(new List + { + testBeatmapSetInfo, + }); + + AddAssert("update button not visible", () => !carousel.ChildrenOfType().Any()); + + AddStep("update online hash", () => + { + testBeatmapSetInfo.Beatmaps.First().OnlineMD5Hash = "different hash"; + testBeatmapSetInfo.Beatmaps.First().LastOnlineUpdate = DateTimeOffset.Now; + + carousel.UpdateBeatmapSet(testBeatmapSetInfo); + }); + + AddUntilStep("update button visible", () => carousel.ChildrenOfType().Any()); + } + [Test] public void TestExternalRulesetChange() { @@ -825,7 +849,8 @@ namespace osu.Game.Tests.Visual.SongSelect checkVisibleItemCount(true, 15); } - private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null, Action carouselAdjust = null, int? count = null, bool randomDifficulties = false) + private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null, Action carouselAdjust = null, int? count = null, + bool randomDifficulties = false) { bool changed = false; diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 96d95b1a12..ead280a75e 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -93,5 +93,7 @@ namespace osu.Game.Beatmaps IEnumerable IBeatmapSetInfo.Beatmaps => Beatmaps; IEnumerable IHasNamedFiles.Files => Files; + + public bool AllBeatmapsUpToDate => Beatmaps.All(b => b.MatchesOnlineVersion); } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index a6532ee145..50e30c68d5 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -153,17 +153,12 @@ namespace osu.Game.Screens.Select.Carousel { Direction = FillDirection.Horizontal, Spacing = new Vector2(4, 0), + Scale = new Vector2(0.8f), AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new TopLocalRank(beatmapInfo) - { - Scale = new Vector2(0.8f), - }, - starCounter = new StarCounter - { - Scale = new Vector2(0.8f), - } + new TopLocalRank(beatmapInfo), + starCounter = new StarCounter() } } } diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 577b3f5f64..050425f9f1 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -61,14 +61,15 @@ namespace osu.Game.Screens.Select.Carousel Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Top = 5 }, - Children = new Drawable[] + Spacing = new Vector2(5), + Children = new[] { + beatmapSet.AllBeatmapsUpToDate ? Empty() : new UpdateRequiredIcon(beatmapSet), new BeatmapSetOnlineStatusPill { AutoSizeAxes = Axes.Both, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, - Margin = new MarginPadding { Right = 5 }, TextSize = 11, TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 }, Status = beatmapSet.Status @@ -76,6 +77,8 @@ namespace osu.Game.Screens.Select.Carousel new FillFlowContainer { AutoSizeAxes = Axes.Both, + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, Spacing = new Vector2(3), ChildrenEnumerable = getDifficultyIcons(), }, diff --git a/osu.Game/Screens/Select/Carousel/UpdateRequiredIcon.cs b/osu.Game/Screens/Select/Carousel/UpdateRequiredIcon.cs new file mode 100644 index 0000000000..42539f2836 --- /dev/null +++ b/osu.Game/Screens/Select/Carousel/UpdateRequiredIcon.cs @@ -0,0 +1,105 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; + +namespace osu.Game.Screens.Select.Carousel +{ + public class UpdateRequiredIcon : OsuAnimatedButton + { + private readonly BeatmapSetInfo beatmapSetInfo; + private SpriteIcon icon; + + public UpdateRequiredIcon(BeatmapSetInfo beatmapSetInfo) + { + this.beatmapSetInfo = beatmapSetInfo; + + AutoSizeAxes = Axes.Both; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + } + + [Resolved] + private BeatmapModelDownloader beatmaps { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load() + { + const float icon_size = 14; + + Content.Anchor = Anchor.CentreLeft; + Content.Origin = Anchor.CentreLeft; + + Content.AddRange(new Drawable[] + { + new FillFlowContainer + { + Padding = new MarginPadding { Horizontal = 5, Vertical = 3 }, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(4), + Children = new Drawable[] + { + new Container + { + Size = new Vector2(icon_size), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new Drawable[] + { + icon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.Solid.SyncAlt, + Size = new Vector2(icon_size), + }, + } + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.Default.With(weight: FontWeight.Bold), + Text = "Update", + } + } + }, + }); + + TooltipText = "Update beatmap with online changes"; + + Action = () => beatmaps.Download(beatmapSetInfo); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + icon.Spin(4000, RotationDirection.Clockwise); + } + + protected override bool OnHover(HoverEvent e) + { + icon.Spin(400, RotationDirection.Clockwise); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + icon.Spin(4000, RotationDirection.Clockwise); + base.OnHoverLost(e); + } + } +} From da360af15af89dc660cbc948a4a195cf0e9515bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 18:01:25 +0900 Subject: [PATCH 128/481] Fix vertical centering of button --- osu.Game/Screens/Select/Carousel/SetPanelContent.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 050425f9f1..8b921fe400 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -64,7 +64,17 @@ namespace osu.Game.Screens.Select.Carousel Spacing = new Vector2(5), Children = new[] { - beatmapSet.AllBeatmapsUpToDate ? Empty() : new UpdateRequiredIcon(beatmapSet), + beatmapSet.AllBeatmapsUpToDate + ? Empty() + : new Container + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Children = new Drawable[] + { + new UpdateRequiredIcon(beatmapSet), + } + }, new BeatmapSetOnlineStatusPill { AutoSizeAxes = Axes.Both, From a16bf35581551befae89f3db8db66b6854b6811e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 18:16:30 +0900 Subject: [PATCH 129/481] Rename button class and add basic progress display --- .../Select/Carousel/SetPanelContent.cs | 2 +- ...uiredIcon.cs => UpdateBeatmapSetButton.cs} | 38 +++++++++++++++++-- 2 files changed, 35 insertions(+), 5 deletions(-) rename osu.Game/Screens/Select/Carousel/{UpdateRequiredIcon.cs => UpdateBeatmapSetButton.cs} (73%) diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 8b921fe400..a95d9078a2 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Select.Carousel RelativeSizeAxes = Axes.Y, Children = new Drawable[] { - new UpdateRequiredIcon(beatmapSet), + new UpdateBeatmapSetButton(beatmapSet), } }, new BeatmapSetOnlineStatusPill diff --git a/osu.Game/Screens/Select/Carousel/UpdateRequiredIcon.cs b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs similarity index 73% rename from osu.Game/Screens/Select/Carousel/UpdateRequiredIcon.cs rename to osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs index 42539f2836..a11a29a5c6 100644 --- a/osu.Game/Screens/Select/Carousel/UpdateRequiredIcon.cs +++ b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Beatmaps; @@ -12,15 +13,18 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Select.Carousel { - public class UpdateRequiredIcon : OsuAnimatedButton + public class UpdateBeatmapSetButton : OsuAnimatedButton { private readonly BeatmapSetInfo beatmapSetInfo; private SpriteIcon icon; - public UpdateRequiredIcon(BeatmapSetInfo beatmapSetInfo) + private Box progressFill; + + public UpdateBeatmapSetButton(BeatmapSetInfo beatmapSetInfo) { this.beatmapSetInfo = beatmapSetInfo; @@ -31,7 +35,7 @@ namespace osu.Game.Screens.Select.Carousel } [Resolved] - private BeatmapModelDownloader beatmaps { get; set; } = null!; + private BeatmapModelDownloader beatmapDownloader { get; set; } = null!; [BackgroundDependencyLoader] private void load() @@ -43,6 +47,14 @@ namespace osu.Game.Screens.Select.Carousel Content.AddRange(new Drawable[] { + progressFill = new Box + { + Colour = Color4.White, + Alpha = 0.2f, + Blending = BlendingParameters.Additive, + RelativeSizeAxes = Axes.Both, + Width = 0, + }, new FillFlowContainer { Padding = new MarginPadding { Horizontal = 5, Vertical = 3 }, @@ -80,7 +92,12 @@ namespace osu.Game.Screens.Select.Carousel TooltipText = "Update beatmap with online changes"; - Action = () => beatmaps.Download(beatmapSetInfo); + Action = () => + { + beatmapDownloader.Download(beatmapSetInfo); + + attachExistingDownload(); + }; } protected override void LoadComplete() @@ -90,6 +107,19 @@ namespace osu.Game.Screens.Select.Carousel icon.Spin(4000, RotationDirection.Clockwise); } + private void attachExistingDownload() + { + var download = beatmapDownloader.GetExistingDownload(beatmapSetInfo); + + if (download != null) + { + Enabled.Value = false; + TooltipText = string.Empty; + + download.DownloadProgressed += progress => progressFill.ResizeWidthTo(progress, 100); + } + } + protected override bool OnHover(HoverEvent e) { icon.Spin(400, RotationDirection.Clockwise); From f6de76e057ba83855b9b662aed1db1751d08f824 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 18:50:27 +0900 Subject: [PATCH 130/481] Move test to stand-alone class and add full ui testing --- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 2 +- .../SongSelect/TestSceneBeatmapCarousel.cs | 24 ---- .../TestSceneUpdateBeatmapSetButton.cs | 104 ++++++++++++++++++ 3 files changed, 105 insertions(+), 25 deletions(-) create mode 100644 osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index fcf69bf6f2..31bc6dacf8 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -246,7 +246,7 @@ namespace osu.Game.Tests.Online => new TestDownloadRequest(set); } - private class TestDownloadRequest : ArchiveDownloadRequest + internal class TestDownloadRequest : ArchiveDownloadRequest { public new void SetProgress(float progress) => base.SetProgress(progress); public new void TriggerSuccess(string filename) => base.TriggerSuccess(filename); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 453869f721..e574ee30fb 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -11,7 +11,6 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -44,29 +43,6 @@ namespace osu.Game.Tests.Visual.SongSelect this.rulesets = rulesets; } - [Test] - public void TestBeatmapWithOnlineUpdates() - { - var testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(); - - createCarousel(new List - { - testBeatmapSetInfo, - }); - - AddAssert("update button not visible", () => !carousel.ChildrenOfType().Any()); - - AddStep("update online hash", () => - { - testBeatmapSetInfo.Beatmaps.First().OnlineMD5Hash = "different hash"; - testBeatmapSetInfo.Beatmaps.First().LastOnlineUpdate = DateTimeOffset.Now; - - carousel.UpdateBeatmapSet(testBeatmapSetInfo); - }); - - AddUntilStep("update button visible", () => carousel.ChildrenOfType().Any()); - } - [Test] public void TestExternalRulesetChange() { diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs new file mode 100644 index 0000000000..03336ef488 --- /dev/null +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs @@ -0,0 +1,104 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Carousel; +using osu.Game.Tests.Online; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Visual.SongSelect +{ + [TestFixture] + public class TestSceneUpdateBeatmapSetButton : OsuManualInputManagerTestScene + { + private BeatmapCarousel carousel = null!; + + private TestSceneOnlinePlayBeatmapAvailabilityTracker.TestBeatmapModelDownloader beatmapDownloader = null!; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + + var importer = parent.Get(); + + dependencies.CacheAs(beatmapDownloader = new TestSceneOnlinePlayBeatmapAvailabilityTracker.TestBeatmapModelDownloader(importer, API)); + return dependencies; + } + + [Test] + public void TestBeatmapWithOnlineUpdates() + { + ArchiveDownloadRequest? downloadRequest = null; + + UpdateBeatmapSetButton? getUpdateButton() => carousel.ChildrenOfType().SingleOrDefault(); + + var testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(); + + AddStep("create carousel", () => + { + Child = carousel = new BeatmapCarousel + { + RelativeSizeAxes = Axes.Both, + BeatmapSets = new List + { + testBeatmapSetInfo, + } + }; + }); + + AddUntilStep("wait for load", () => carousel.BeatmapSetsLoaded); + + AddAssert("update button not visible", () => getUpdateButton() == null); + + AddStep("update online hash", () => + { + testBeatmapSetInfo.Beatmaps.First().OnlineMD5Hash = "different hash"; + testBeatmapSetInfo.Beatmaps.First().LastOnlineUpdate = DateTimeOffset.Now; + + carousel.UpdateBeatmapSet(testBeatmapSetInfo); + }); + + AddUntilStep("update button visible", () => getUpdateButton() != null); + + AddStep("click button", () => getUpdateButton()?.TriggerClick()); + + AddUntilStep("wait for download started", () => + { + downloadRequest = beatmapDownloader.GetExistingDownload(testBeatmapSetInfo); + return downloadRequest != null; + }); + + AddUntilStep("progress download to completion", () => + { + if (downloadRequest is TestSceneOnlinePlayBeatmapAvailabilityTracker.TestDownloadRequest testRequest) + { + testRequest.SetProgress(testRequest.Progress + 0.1f); + + if (testRequest.Progress >= 1) + { + testRequest.TriggerSuccess(); + + // usually this would be done by the import process. + testBeatmapSetInfo.Beatmaps.First().MD5Hash = "different hash"; + testBeatmapSetInfo.Beatmaps.First().LastOnlineUpdate = DateTimeOffset.Now; + + // usually this would be done by a realm subscription. + carousel.UpdateBeatmapSet(testBeatmapSetInfo); + return true; + } + } + + return false; + }); + } + } +} From 17046b0553ef0240cb91b3289a5cff70fb718082 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 19:01:23 +0900 Subject: [PATCH 131/481] Add basic handling of download failures --- .../TestSceneUpdateBeatmapSetButton.cs | 68 ++++++++++++++++--- osu.Game/Database/ModelDownloader.cs | 2 +- .../Select/Carousel/UpdateBeatmapSetButton.cs | 13 ++-- 3 files changed, 69 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs index 03336ef488..bae3b66ed9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs @@ -24,6 +24,8 @@ namespace osu.Game.Tests.Visual.SongSelect private TestSceneOnlinePlayBeatmapAvailabilityTracker.TestBeatmapModelDownloader beatmapDownloader = null!; + private BeatmapSetInfo testBeatmapSetInfo = null!; + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -34,15 +36,11 @@ namespace osu.Game.Tests.Visual.SongSelect return dependencies; } - [Test] - public void TestBeatmapWithOnlineUpdates() + private UpdateBeatmapSetButton? getUpdateButton() => carousel.ChildrenOfType().SingleOrDefault(); + + [SetUpSteps] + public void SetUpSteps() { - ArchiveDownloadRequest? downloadRequest = null; - - UpdateBeatmapSetButton? getUpdateButton() => carousel.ChildrenOfType().SingleOrDefault(); - - var testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo(); - AddStep("create carousel", () => { Child = carousel = new BeatmapCarousel @@ -50,7 +48,7 @@ namespace osu.Game.Tests.Visual.SongSelect RelativeSizeAxes = Axes.Both, BeatmapSets = new List { - testBeatmapSetInfo, + (testBeatmapSetInfo = TestResources.CreateTestBeatmapSetInfo()), } }; }); @@ -58,6 +56,12 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for load", () => carousel.BeatmapSetsLoaded); AddAssert("update button not visible", () => getUpdateButton() == null); + } + + [Test] + public void TestDownloadToCompletion() + { + ArchiveDownloadRequest? downloadRequest = null; AddStep("update online hash", () => { @@ -77,6 +81,8 @@ namespace osu.Game.Tests.Visual.SongSelect return downloadRequest != null; }); + AddUntilStep("wait for button disabled", () => getUpdateButton()?.Enabled.Value == false); + AddUntilStep("progress download to completion", () => { if (downloadRequest is TestSceneOnlinePlayBeatmapAvailabilityTracker.TestDownloadRequest testRequest) @@ -100,5 +106,49 @@ namespace osu.Game.Tests.Visual.SongSelect return false; }); } + + [Test] + public void TestDownloadFailed() + { + ArchiveDownloadRequest? downloadRequest = null; + + AddStep("update online hash", () => + { + testBeatmapSetInfo.Beatmaps.First().OnlineMD5Hash = "different hash"; + testBeatmapSetInfo.Beatmaps.First().LastOnlineUpdate = DateTimeOffset.Now; + + carousel.UpdateBeatmapSet(testBeatmapSetInfo); + }); + + AddUntilStep("update button visible", () => getUpdateButton() != null); + + AddStep("click button", () => getUpdateButton()?.TriggerClick()); + + AddUntilStep("wait for download started", () => + { + downloadRequest = beatmapDownloader.GetExistingDownload(testBeatmapSetInfo); + return downloadRequest != null; + }); + + AddUntilStep("wait for button disabled", () => getUpdateButton()?.Enabled.Value == false); + + AddUntilStep("progress download to failure", () => + { + if (downloadRequest is TestSceneOnlinePlayBeatmapAvailabilityTracker.TestDownloadRequest testRequest) + { + testRequest.SetProgress(testRequest.Progress + 0.1f); + + if (testRequest.Progress >= 0.5f) + { + testRequest.TriggerFailure(new Exception()); + return true; + } + } + + return false; + }); + + AddUntilStep("wait for button enabled", () => getUpdateButton()?.Enabled.Value == true); + } } } diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index 76717fd46f..a4d52426ab 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -101,7 +101,7 @@ namespace osu.Game.Database notification.State = ProgressNotificationState.Cancelled; if (!(error is OperationCanceledException)) - Logger.Error(error, $"{importer.HumanisedModelName.Titleize()} download failed!"); + Logger.Error(error, $"{importer?.HumanisedModelName.Titleize()} download failed!"); } } diff --git a/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs index a11a29a5c6..89404b2878 100644 --- a/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs +++ b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs @@ -90,12 +90,9 @@ namespace osu.Game.Screens.Select.Carousel }, }); - TooltipText = "Update beatmap with online changes"; - Action = () => { beatmapDownloader.Download(beatmapSetInfo); - attachExistingDownload(); }; } @@ -116,7 +113,15 @@ namespace osu.Game.Screens.Select.Carousel Enabled.Value = false; TooltipText = string.Empty; - download.DownloadProgressed += progress => progressFill.ResizeWidthTo(progress, 100); + download.DownloadProgressed += progress => progressFill.ResizeWidthTo(progress, 100, Easing.OutQuint); + download.Failure += _ => attachExistingDownload(); + } + else + { + Enabled.Value = true; + TooltipText = "Update beatmap with online changes"; + + progressFill.ResizeWidthTo(0, 100, Easing.OutQuint); } } From 07874efa7f732ea1a2abb6a7a5f7f67d4a11a09b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 19:39:51 +0900 Subject: [PATCH 132/481] Set last online update to actual value provided by data source --- osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs | 9 ++++++--- osu.Game/Online/API/Requests/Responses/APIBeatmap.cs | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs index e07f18bdfb..580dcee18c 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs @@ -102,13 +102,14 @@ namespace osu.Game.Beatmaps beatmapInfo.BeatmapSet.Status = res.BeatmapSet?.Status ?? BeatmapOnlineStatus.None; beatmapInfo.BeatmapSet.OnlineID = res.OnlineBeatmapSetID; + beatmapInfo.OnlineMD5Hash = res.MD5Hash; + beatmapInfo.LastOnlineUpdate = res.LastUpdated; + beatmapInfo.OnlineID = res.OnlineID; beatmapInfo.Metadata.Author.OnlineID = res.AuthorID; - beatmapInfo.LastOnlineUpdate = DateTimeOffset.Now; - logForModel(set, $"Online retrieval mapped {beatmapInfo} to {res.OnlineBeatmapSetID} / {res.OnlineID}."); } } @@ -193,7 +194,7 @@ namespace osu.Game.Beatmaps using (var cmd = db.CreateCommand()) { - cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id, checksum FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; + cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmapInfo.MD5Hash)); cmd.Parameters.Add(new SqliteParameter("@OnlineID", beatmapInfo.OnlineID)); @@ -213,7 +214,9 @@ namespace osu.Game.Beatmaps beatmapInfo.BeatmapSet.OnlineID = reader.GetInt32(0); beatmapInfo.OnlineID = reader.GetInt32(1); beatmapInfo.Metadata.Author.OnlineID = reader.GetInt32(3); + beatmapInfo.OnlineMD5Hash = reader.GetString(4); + beatmapInfo.LastOnlineUpdate = reader.GetDateTimeOffset(5); logForModel(set, $"Cached local retrieval for {beatmapInfo}."); return true; diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index 735fde333d..3fee81cf33 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -81,6 +81,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"max_combo")] public int? MaxCombo { get; set; } + [JsonProperty(@"last_updated")] + public DateTimeOffset LastUpdated { get; set; } + public double BPM { get; set; } #region Implementation of IBeatmapInfo From 30daa0fd447282ec9ecaeb3755bc6ec0d6479332 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 19:37:04 +0900 Subject: [PATCH 133/481] Add ranked and submitted date storage and filtering --- osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs | 6 +++++- osu.Game/Beatmaps/BeatmapSetInfo.cs | 10 ++++++++++ osu.Game/Database/RealmAccess.cs | 3 ++- osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs | 2 ++ osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs | 7 +++++++ osu.Game/Screens/Select/Filter/SortMode.cs | 3 +++ 6 files changed, 29 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs index 580dcee18c..d3be240d4c 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs @@ -102,6 +102,8 @@ namespace osu.Game.Beatmaps beatmapInfo.BeatmapSet.Status = res.BeatmapSet?.Status ?? BeatmapOnlineStatus.None; beatmapInfo.BeatmapSet.OnlineID = res.OnlineBeatmapSetID; + beatmapInfo.BeatmapSet.DateRanked = res.BeatmapSet?.Ranked; + beatmapInfo.BeatmapSet.DateSubmitted = res.BeatmapSet?.Submitted; beatmapInfo.OnlineMD5Hash = res.MD5Hash; beatmapInfo.LastOnlineUpdate = res.LastUpdated; @@ -194,7 +196,8 @@ namespace osu.Game.Beatmaps using (var cmd = db.CreateCommand()) { - cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; + cmd.CommandText = + "SELECT beatmapset_id, beatmap_id, approved, user_id, checksum, last_update FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmapInfo.MD5Hash)); cmd.Parameters.Add(new SqliteParameter("@OnlineID", beatmapInfo.OnlineID)); @@ -212,6 +215,7 @@ namespace osu.Game.Beatmaps beatmapInfo.BeatmapSet.Status = status; beatmapInfo.BeatmapSet.OnlineID = reader.GetInt32(0); + // TODO: DateSubmitted and DateRanked are not provided by local cache. beatmapInfo.OnlineID = reader.GetInt32(1); beatmapInfo.Metadata.Author.OnlineID = reader.GetInt32(3); diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index ead280a75e..b404f0b34d 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -26,6 +26,16 @@ namespace osu.Game.Beatmaps public DateTimeOffset DateAdded { get; set; } + /// + /// The date this beatmap set was first submitted. + /// + public DateTimeOffset? DateSubmitted { get; set; } + + /// + /// The date this beatmap set was ranked. + /// + public DateTimeOffset? DateRanked { get; set; } + [JsonIgnore] public IBeatmapMetadataInfo Metadata => Beatmaps.FirstOrDefault()?.Metadata ?? new BeatmapMetadata(); diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 28870617cc..dff2bdddbd 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -62,8 +62,9 @@ namespace osu.Game.Database /// 16 2022-07-15 Removed HasReplay from ScoreInfo. /// 17 2022-07-16 Added CountryCode to RealmUser. /// 18 2022-07-19 Added OnlineMD5Hash and LastOnlineUpdate to BeatmapInfo. + /// 19 2022-07-19 Added DateSubmitted and DateRanked to BeatmapSetInfo. /// - private const int schema_version = 18; + private const int schema_version = 19; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index c57cbcfba4..81734745c4 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -55,6 +55,8 @@ namespace osu.Game.Screens.Select.Carousel match &= !criteria.Artist.HasFilter || criteria.Artist.Matches(BeatmapInfo.Metadata.Artist) || criteria.Artist.Matches(BeatmapInfo.Metadata.ArtistUnicode); + match &= criteria.Sort != SortMode.DateRanked || BeatmapInfo.BeatmapSet?.DateRanked != null; + match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarRating); if (match && criteria.SearchTerms.Length > 0) diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 94d911692c..bd7b1f12a4 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -81,6 +81,13 @@ namespace osu.Game.Screens.Select.Carousel case SortMode.DateAdded: return otherSet.BeatmapSet.DateAdded.CompareTo(BeatmapSet.DateAdded); + case SortMode.DateRanked: + // Beatmaps which have no ranked date should already be filtered away in this mode. + if (BeatmapSet.DateRanked == null || otherSet.BeatmapSet.DateRanked == null) + return 0; + + return otherSet.BeatmapSet.DateRanked.Value.CompareTo(BeatmapSet.DateRanked.Value); + case SortMode.LastPlayed: return -compareUsingAggregateMax(otherSet, b => (b.LastPlayed ?? DateTimeOffset.MinValue).ToUnixTimeSeconds()); diff --git a/osu.Game/Screens/Select/Filter/SortMode.cs b/osu.Game/Screens/Select/Filter/SortMode.cs index 4227114618..1e60ea3bac 100644 --- a/osu.Game/Screens/Select/Filter/SortMode.cs +++ b/osu.Game/Screens/Select/Filter/SortMode.cs @@ -23,6 +23,9 @@ namespace osu.Game.Screens.Select.Filter [Description("Date Added")] DateAdded, + [Description("Date Ranked")] + DateRanked, + [Description("Last Played")] LastPlayed, From 842fe32003244c5934cf0f1b8bbeb8648c74f08d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 19 Jul 2022 19:57:36 +0900 Subject: [PATCH 134/481] Update test values --- .../OsuDifficultyCalculatorTest.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index bb593c2fb3..46f7c461f8 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -17,18 +17,18 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [TestCase(6.6972307565739273d, 206, "diffcalc-test")] - [TestCase(1.4484754139145539d, 45, "zero-length-sliders")] + [TestCase(6.6369583000323935d, 206, "diffcalc-test")] + [TestCase(1.4476531024675374d, 45, "zero-length-sliders")] public void Test(double expectedStarRating, int expectedMaxCombo, string name) => base.Test(expectedStarRating, expectedMaxCombo, name); - [TestCase(8.9382559208689809d, 206, "diffcalc-test")] - [TestCase(1.7548875851757628d, 45, "zero-length-sliders")] + [TestCase(8.8816128335486386d, 206, "diffcalc-test")] + [TestCase(1.7540389962596916d, 45, "zero-length-sliders")] public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name) => Test(expectedStarRating, expectedMaxCombo, name, new OsuModDoubleTime()); - [TestCase(6.6972307218715166d, 239, "diffcalc-test")] - [TestCase(1.4484754139145537d, 54, "zero-length-sliders")] + [TestCase(6.6369583000323935d, 239, "diffcalc-test")] + [TestCase(1.4476531024675374d, 54, "zero-length-sliders")] public void TestClassicMod(double expectedStarRating, int expectedMaxCombo, string name) => Test(expectedStarRating, expectedMaxCombo, name, new OsuModClassic()); From 6357223341200e1b5ed37c03dd4ae21b7b706952 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 20:06:19 +0900 Subject: [PATCH 135/481] Fix incorrect DI fetch and apply nullability to `ModelDownloader` --- .../TestSceneUpdateBeatmapSetButton.cs | 2 +- osu.Game/Beatmaps/BeatmapModelDownloader.cs | 4 +--- osu.Game/Database/ModelDownloader.cs | 18 ++++++++---------- osu.Game/Scoring/ScoreModelDownloader.cs | 2 -- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs index bae3b66ed9..a95f145897 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.SongSelect { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - var importer = parent.Get(); + var importer = parent.Get(); dependencies.CacheAs(beatmapDownloader = new TestSceneOnlinePlayBeatmapAvailabilityTracker.TestBeatmapModelDownloader(importer, API)); return dependencies; diff --git a/osu.Game/Beatmaps/BeatmapModelDownloader.cs b/osu.Game/Beatmaps/BeatmapModelDownloader.cs index 74d583fe7e..4295def5c3 100644 --- a/osu.Game/Beatmaps/BeatmapModelDownloader.cs +++ b/osu.Game/Beatmaps/BeatmapModelDownloader.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -14,7 +12,7 @@ namespace osu.Game.Beatmaps protected override ArchiveDownloadRequest CreateDownloadRequest(IBeatmapSetInfo set, bool minimiseDownloadSize) => new DownloadBeatmapSetRequest(set, minimiseDownloadSize); - public override ArchiveDownloadRequest GetExistingDownload(IBeatmapSetInfo model) + public override ArchiveDownloadRequest? GetExistingDownload(IBeatmapSetInfo model) => CurrentDownloads.Find(r => r.Model.OnlineID == model.OnlineID); public BeatmapModelDownloader(IModelImporter beatmapImporter, IAPIProvider api) diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index a4d52426ab..02bcb342e4 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -19,18 +17,18 @@ namespace osu.Game.Database where TModel : class, IHasGuidPrimaryKey, ISoftDelete, IEquatable, T where T : class { - public Action PostNotification { protected get; set; } + public Action? PostNotification { protected get; set; } - public event Action> DownloadBegan; + public event Action>? DownloadBegan; - public event Action> DownloadFailed; + public event Action>? DownloadFailed; private readonly IModelImporter importer; - private readonly IAPIProvider api; + private readonly IAPIProvider? api; protected readonly List> CurrentDownloads = new List>(); - protected ModelDownloader(IModelImporter importer, IAPIProvider api) + protected ModelDownloader(IModelImporter importer, IAPIProvider? api) { this.importer = importer; this.api = api; @@ -87,7 +85,7 @@ namespace osu.Game.Database CurrentDownloads.Add(request); PostNotification?.Invoke(notification); - api.PerformAsync(request); + api?.PerformAsync(request); DownloadBegan?.Invoke(request); return true; @@ -101,11 +99,11 @@ namespace osu.Game.Database notification.State = ProgressNotificationState.Cancelled; if (!(error is OperationCanceledException)) - Logger.Error(error, $"{importer?.HumanisedModelName.Titleize()} download failed!"); + Logger.Error(error, $"{importer.HumanisedModelName.Titleize()} download failed!"); } } - public abstract ArchiveDownloadRequest GetExistingDownload(T model); + public abstract ArchiveDownloadRequest? GetExistingDownload(T model); private bool canDownload(T model) => GetExistingDownload(model) == null && api != null; diff --git a/osu.Game/Scoring/ScoreModelDownloader.cs b/osu.Game/Scoring/ScoreModelDownloader.cs index 8625c6c5d0..514b7a57de 100644 --- a/osu.Game/Scoring/ScoreModelDownloader.cs +++ b/osu.Game/Scoring/ScoreModelDownloader.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Database; using osu.Game.Extensions; using osu.Game.Online.API; From 1f9f2b413e2218bc6353ffc496c319f35eb7ea96 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 3 Jul 2022 22:19:35 +0800 Subject: [PATCH 136/481] Remove the nullable disable annotation. Also, mark as nullable for some properties. --- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 4 +--- .../Rulesets/Mods/IApplicableAfterBeatmapConversion.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableFailOverride.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableMod.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToAudio.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs | 2 -- .../Rulesets/Mods/IApplicableToBeatmapConverter.cs | 2 -- .../Rulesets/Mods/IApplicableToBeatmapProcessor.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs | 2 -- .../Rulesets/Mods/IApplicableToDrawableHitObject.cs | 2 -- .../Rulesets/Mods/IApplicableToDrawableHitObjects.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToHUD.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToHealthProcessor.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToHitObject.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToPlayer.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToRate.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToSample.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs | 2 -- osu.Game/Rulesets/Mods/IApplicableToTrack.cs | 2 -- osu.Game/Rulesets/Mods/ICreateReplay.cs | 2 -- osu.Game/Rulesets/Mods/ICreateReplayData.cs | 4 +--- osu.Game/Rulesets/Mods/IHasSeed.cs | 2 -- osu.Game/Rulesets/Mods/IMod.cs | 2 -- osu.Game/Rulesets/Mods/IReadFromConfig.cs | 2 -- osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs | 2 -- osu.Game/Rulesets/Mods/MetronomeBeat.cs | 2 -- osu.Game/Rulesets/Mods/Mod.cs | 4 +--- osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs | 4 +--- osu.Game/Rulesets/Mods/ModAutoplay.cs | 2 -- osu.Game/Rulesets/Mods/ModBarrelRoll.cs | 2 -- osu.Game/Rulesets/Mods/ModBlockFail.cs | 2 -- osu.Game/Rulesets/Mods/ModCinema.cs | 2 -- osu.Game/Rulesets/Mods/ModClassic.cs | 2 -- osu.Game/Rulesets/Mods/ModDaycore.cs | 2 -- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 2 -- osu.Game/Rulesets/Mods/ModDoubleTime.cs | 2 -- osu.Game/Rulesets/Mods/ModEasy.cs | 2 -- osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs | 2 -- osu.Game/Rulesets/Mods/ModExtensions.cs | 2 -- osu.Game/Rulesets/Mods/ModFailCondition.cs | 2 -- osu.Game/Rulesets/Mods/ModFlashlight.cs | 2 -- osu.Game/Rulesets/Mods/ModHalfTime.cs | 2 -- osu.Game/Rulesets/Mods/ModHardRock.cs | 2 -- osu.Game/Rulesets/Mods/ModHidden.cs | 2 -- osu.Game/Rulesets/Mods/ModMirror.cs | 2 -- osu.Game/Rulesets/Mods/ModMuted.cs | 2 -- osu.Game/Rulesets/Mods/ModNightcore.cs | 10 ++++------ osu.Game/Rulesets/Mods/ModNoFail.cs | 2 -- osu.Game/Rulesets/Mods/ModNoMod.cs | 2 -- osu.Game/Rulesets/Mods/ModNoScope.cs | 2 -- osu.Game/Rulesets/Mods/ModPerfect.cs | 2 -- osu.Game/Rulesets/Mods/ModRandom.cs | 2 -- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 2 -- osu.Game/Rulesets/Mods/ModRelax.cs | 2 -- osu.Game/Rulesets/Mods/ModSuddenDeath.cs | 2 -- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 4 +--- osu.Game/Rulesets/Mods/ModType.cs | 2 -- osu.Game/Rulesets/Mods/ModWindDown.cs | 2 -- osu.Game/Rulesets/Mods/ModWindUp.cs | 2 -- osu.Game/Rulesets/Mods/ModWithVisibilityAdjustment.cs | 8 +++----- osu.Game/Rulesets/Mods/MultiMod.cs | 2 -- osu.Game/Rulesets/Mods/UnknownMod.cs | 2 -- 63 files changed, 12 insertions(+), 138 deletions(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index eb5f97bcf7..34e9fe40a3 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.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.Bindables; using osu.Game.Beatmaps; @@ -29,7 +27,7 @@ namespace osu.Game.Rulesets.Mods /// /// A function that can extract the current value of this setting from a beatmap difficulty for display purposes. /// - public Func ReadCurrentFromDifficulty; + public Func? ReadCurrentFromDifficulty; public float Precision { diff --git a/osu.Game/Rulesets/Mods/IApplicableAfterBeatmapConversion.cs b/osu.Game/Rulesets/Mods/IApplicableAfterBeatmapConversion.cs index 9286f682d1..d45311675d 100644 --- a/osu.Game/Rulesets/Mods/IApplicableAfterBeatmapConversion.cs +++ b/osu.Game/Rulesets/Mods/IApplicableAfterBeatmapConversion.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs index d0b54f835b..8c99d739cb 100644 --- a/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs +++ b/osu.Game/Rulesets/Mods/IApplicableFailOverride.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mods { /// diff --git a/osu.Game/Rulesets/Mods/IApplicableMod.cs b/osu.Game/Rulesets/Mods/IApplicableMod.cs index 7675bd89ef..8ca1a3f8a5 100644 --- a/osu.Game/Rulesets/Mods/IApplicableMod.cs +++ b/osu.Game/Rulesets/Mods/IApplicableMod.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mods { /// diff --git a/osu.Game/Rulesets/Mods/IApplicableToAudio.cs b/osu.Game/Rulesets/Mods/IApplicableToAudio.cs index de76790aee..901da7af55 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToAudio.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToAudio.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mods { public interface IApplicableToAudio : IApplicableToTrack, IApplicableToSample diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs index 278b4794c5..cff669bf53 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmap.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs index a5ccea1873..8cefb02904 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmapConverter.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToBeatmapProcessor.cs b/osu.Game/Rulesets/Mods/IApplicableToBeatmapProcessor.cs index c653a674ef..e23a5d8d99 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToBeatmapProcessor.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToBeatmapProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs b/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs index 1447511de9..42b520ab26 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDifficulty.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs index f559ed04d7..c8a9ff2f9a 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObject.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Objects.Drawables; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObjects.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObjects.cs index 8bf2c3810e..7f926dd8b8 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObjects.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableHitObjects.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Framework.Extensions.IEnumerableExtensions; diff --git a/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs b/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs index ace3af62a1..b012beb0c0 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToDrawableRuleset.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; diff --git a/osu.Game/Rulesets/Mods/IApplicableToHUD.cs b/osu.Game/Rulesets/Mods/IApplicableToHUD.cs index b5fe299b24..4fb535a0b3 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToHUD.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToHUD.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToHealthProcessor.cs b/osu.Game/Rulesets/Mods/IApplicableToHealthProcessor.cs index a58f8640fd..2676060efa 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToHealthProcessor.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToHealthProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs index d9fa993393..f7f81c92c0 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToHitObject.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToPlayer.cs b/osu.Game/Rulesets/Mods/IApplicableToPlayer.cs index c28935607f..bf78428470 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToPlayer.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToPlayer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Screens.Play; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToRate.cs b/osu.Game/Rulesets/Mods/IApplicableToRate.cs index c66c8f49a1..f613867132 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToRate.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToRate.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mods { /// diff --git a/osu.Game/Rulesets/Mods/IApplicableToSample.cs b/osu.Game/Rulesets/Mods/IApplicableToSample.cs index 97ed0fbf7e..efd88f2399 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToSample.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToSample.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.Audio; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs b/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs index 24c1ac9afe..b93e50921f 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToScoreProcessor.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; diff --git a/osu.Game/Rulesets/Mods/IApplicableToTrack.cs b/osu.Game/Rulesets/Mods/IApplicableToTrack.cs index 358ef71cc0..deecd4bf1f 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToTrack.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToTrack.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.Audio; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/ICreateReplay.cs b/osu.Game/Rulesets/Mods/ICreateReplay.cs index e77f4c49b9..1e5eeca92c 100644 --- a/osu.Game/Rulesets/Mods/ICreateReplay.cs +++ b/osu.Game/Rulesets/Mods/ICreateReplay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Game.Beatmaps; diff --git a/osu.Game/Rulesets/Mods/ICreateReplayData.cs b/osu.Game/Rulesets/Mods/ICreateReplayData.cs index 6058380eb3..d4587b673c 100644 --- a/osu.Game/Rulesets/Mods/ICreateReplayData.cs +++ b/osu.Game/Rulesets/Mods/ICreateReplayData.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; @@ -45,7 +43,7 @@ namespace osu.Game.Rulesets.Mods /// public readonly ModCreatedUser User; - public ModReplayData(Replay replay, ModCreatedUser user = null) + public ModReplayData(Replay replay, ModCreatedUser? user = null) { Replay = replay; User = user ?? new ModCreatedUser(); diff --git a/osu.Game/Rulesets/Mods/IHasSeed.cs b/osu.Game/Rulesets/Mods/IHasSeed.cs index fd2161ac09..001a9d214c 100644 --- a/osu.Game/Rulesets/Mods/IHasSeed.cs +++ b/osu.Game/Rulesets/Mods/IHasSeed.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; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IMod.cs b/osu.Game/Rulesets/Mods/IMod.cs index 349cc7dd5a..30fa1ea8cb 100644 --- a/osu.Game/Rulesets/Mods/IMod.cs +++ b/osu.Game/Rulesets/Mods/IMod.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.Sprites; diff --git a/osu.Game/Rulesets/Mods/IReadFromConfig.cs b/osu.Game/Rulesets/Mods/IReadFromConfig.cs index ee6fb6364f..d66fabce70 100644 --- a/osu.Game/Rulesets/Mods/IReadFromConfig.cs +++ b/osu.Game/Rulesets/Mods/IReadFromConfig.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Configuration; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs b/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs index 3aad858af5..7cf480a11b 100644 --- a/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs +++ b/osu.Game/Rulesets/Mods/IUpdatableByPlayfield.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/MetronomeBeat.cs b/osu.Game/Rulesets/Mods/MetronomeBeat.cs index b26052a37e..149af1e30a 100644 --- a/osu.Game/Rulesets/Mods/MetronomeBeat.cs +++ b/osu.Game/Rulesets/Mods/MetronomeBeat.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.Audio; using osu.Framework.Audio.Track; using osu.Framework.Bindables; diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 17093e3033..7fdb03a7f3 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -117,7 +115,7 @@ namespace osu.Game.Rulesets.Mods [JsonIgnore] public virtual Type[] IncompatibleMods => Array.Empty(); - private IReadOnlyList settingsBacking; + private IReadOnlyList? settingsBacking; /// /// A list of the all settings within this mod. diff --git a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs index aea6e12a07..54ee4554b1 100644 --- a/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs +++ b/osu.Game/Rulesets/Mods/ModAdaptiveSpeed.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -79,7 +77,7 @@ namespace osu.Game.Rulesets.Mods // Apply a fixed rate change when missing, allowing the player to catch up when the rate is too fast. private const double rate_change_on_miss = 0.95d; - private IAdjustableAudioComponent track; + private IAdjustableAudioComponent? track; private double targetRate = 1d; /// diff --git a/osu.Game/Rulesets/Mods/ModAutoplay.cs b/osu.Game/Rulesets/Mods/ModAutoplay.cs index 6d786fc8e2..0ebe11b393 100644 --- a/osu.Game/Rulesets/Mods/ModAutoplay.cs +++ b/osu.Game/Rulesets/Mods/ModAutoplay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs b/osu.Game/Rulesets/Mods/ModBarrelRoll.cs index bd0f2bfe59..bacb953f76 100644 --- a/osu.Game/Rulesets/Mods/ModBarrelRoll.cs +++ b/osu.Game/Rulesets/Mods/ModBarrelRoll.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.Bindables; using osu.Framework.Extensions; diff --git a/osu.Game/Rulesets/Mods/ModBlockFail.cs b/osu.Game/Rulesets/Mods/ModBlockFail.cs index a6a8244480..8a9b0cddc8 100644 --- a/osu.Game/Rulesets/Mods/ModBlockFail.cs +++ b/osu.Game/Rulesets/Mods/ModBlockFail.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.Configuration; using osu.Game.Screens.Play; diff --git a/osu.Game/Rulesets/Mods/ModCinema.cs b/osu.Game/Rulesets/Mods/ModCinema.cs index 6e7bd6350e..99c4e71d1f 100644 --- a/osu.Game/Rulesets/Mods/ModCinema.cs +++ b/osu.Game/Rulesets/Mods/ModCinema.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game/Rulesets/Mods/ModClassic.cs b/osu.Game/Rulesets/Mods/ModClassic.cs index b4885ff16e..1159955e11 100644 --- a/osu.Game/Rulesets/Mods/ModClassic.cs +++ b/osu.Game/Rulesets/Mods/ModClassic.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics.Sprites; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/ModDaycore.cs b/osu.Game/Rulesets/Mods/ModDaycore.cs index 262aeb07ac..9e8e44229e 100644 --- a/osu.Game/Rulesets/Mods/ModDaycore.cs +++ b/osu.Game/Rulesets/Mods/ModDaycore.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.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index c0e2c75aca..eefa1531c4 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.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.Bindables; diff --git a/osu.Game/Rulesets/Mods/ModDoubleTime.cs b/osu.Game/Rulesets/Mods/ModDoubleTime.cs index 389d0db261..1c71f5d055 100644 --- a/osu.Game/Rulesets/Mods/ModDoubleTime.cs +++ b/osu.Game/Rulesets/Mods/ModDoubleTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index bc5988174b..0f51e2a6d5 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.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.Sprites; using osu.Game.Beatmaps; diff --git a/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs b/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs index 984892de51..2ac0f59d84 100644 --- a/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs +++ b/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.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 Humanizer; using osu.Framework.Bindables; using osu.Game.Beatmaps; diff --git a/osu.Game/Rulesets/Mods/ModExtensions.cs b/osu.Game/Rulesets/Mods/ModExtensions.cs index ad61404972..b22030414b 100644 --- a/osu.Game/Rulesets/Mods/ModExtensions.cs +++ b/osu.Game/Rulesets/Mods/ModExtensions.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Online.API.Requests.Responses; diff --git a/osu.Game/Rulesets/Mods/ModFailCondition.cs b/osu.Game/Rulesets/Mods/ModFailCondition.cs index e24746ebd9..4425ece513 100644 --- a/osu.Game/Rulesets/Mods/ModFailCondition.cs +++ b/osu.Game/Rulesets/Mods/ModFailCondition.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.Bindables; using osu.Game.Configuration; diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 649d5480bf..b449f3f64d 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Framework.Allocation; diff --git a/osu.Game/Rulesets/Mods/ModHalfTime.cs b/osu.Game/Rulesets/Mods/ModHalfTime.cs index 48fa7c13b4..13d89e30d6 100644 --- a/osu.Game/Rulesets/Mods/ModHalfTime.cs +++ b/osu.Game/Rulesets/Mods/ModHalfTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index 030520fccc..0a5348a8cf 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.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.Sprites; using osu.Game.Beatmaps; diff --git a/osu.Game/Rulesets/Mods/ModHidden.cs b/osu.Game/Rulesets/Mods/ModHidden.cs index 35107762aa..5a8226115f 100644 --- a/osu.Game/Rulesets/Mods/ModHidden.cs +++ b/osu.Game/Rulesets/Mods/ModHidden.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Scoring; diff --git a/osu.Game/Rulesets/Mods/ModMirror.cs b/osu.Game/Rulesets/Mods/ModMirror.cs index 00a1d4a9c6..3c4b7d0c60 100644 --- a/osu.Game/Rulesets/Mods/ModMirror.cs +++ b/osu.Game/Rulesets/Mods/ModMirror.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mods { public abstract class ModMirror : Mod diff --git a/osu.Game/Rulesets/Mods/ModMuted.cs b/osu.Game/Rulesets/Mods/ModMuted.cs index 88e49f41b0..84341faab7 100644 --- a/osu.Game/Rulesets/Mods/ModMuted.cs +++ b/osu.Game/Rulesets/Mods/ModMuted.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.Audio; using osu.Framework.Bindables; diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index 7c2201cd98..c4417ec509 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; @@ -57,10 +55,10 @@ namespace osu.Game.Rulesets.Mods public class NightcoreBeatContainer : BeatSyncedContainer { - private PausableSkinnableSound hatSample; - private PausableSkinnableSound clapSample; - private PausableSkinnableSound kickSample; - private PausableSkinnableSound finishSample; + private PausableSkinnableSound? hatSample; + private PausableSkinnableSound? clapSample; + private PausableSkinnableSound? kickSample; + private PausableSkinnableSound? finishSample; private int? firstBeat; diff --git a/osu.Game/Rulesets/Mods/ModNoFail.cs b/osu.Game/Rulesets/Mods/ModNoFail.cs index 27dbb53e6c..5ebae17228 100644 --- a/osu.Game/Rulesets/Mods/ModNoFail.cs +++ b/osu.Game/Rulesets/Mods/ModNoFail.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.Sprites; using osu.Game.Graphics; diff --git a/osu.Game/Rulesets/Mods/ModNoMod.cs b/osu.Game/Rulesets/Mods/ModNoMod.cs index cc0b38cbc0..1009c5bc42 100644 --- a/osu.Game/Rulesets/Mods/ModNoMod.cs +++ b/osu.Game/Rulesets/Mods/ModNoMod.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics.Sprites; namespace osu.Game.Rulesets.Mods diff --git a/osu.Game/Rulesets/Mods/ModNoScope.cs b/osu.Game/Rulesets/Mods/ModNoScope.cs index c43ac33b3f..7a935eb38f 100644 --- a/osu.Game/Rulesets/Mods/ModNoScope.cs +++ b/osu.Game/Rulesets/Mods/ModNoScope.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.Bindables; using osu.Framework.Graphics; diff --git a/osu.Game/Rulesets/Mods/ModPerfect.cs b/osu.Game/Rulesets/Mods/ModPerfect.cs index b6daf54fa0..9016a24f8d 100644 --- a/osu.Game/Rulesets/Mods/ModPerfect.cs +++ b/osu.Game/Rulesets/Mods/ModPerfect.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game/Rulesets/Mods/ModRandom.cs b/osu.Game/Rulesets/Mods/ModRandom.cs index 6654bff04b..1f7742b075 100644 --- a/osu.Game/Rulesets/Mods/ModRandom.cs +++ b/osu.Game/Rulesets/Mods/ModRandom.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index 643280623c..7b55ba4ad0 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.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.Audio; using osu.Framework.Bindables; diff --git a/osu.Game/Rulesets/Mods/ModRelax.cs b/osu.Game/Rulesets/Mods/ModRelax.cs index 4829d10ddb..e5995ff180 100644 --- a/osu.Game/Rulesets/Mods/ModRelax.cs +++ b/osu.Game/Rulesets/Mods/ModRelax.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.Sprites; using osu.Game.Graphics; diff --git a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs index 48ab888a90..c8b835f78a 100644 --- a/osu.Game/Rulesets/Mods/ModSuddenDeath.cs +++ b/osu.Game/Rulesets/Mods/ModSuddenDeath.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index daa4b7c797..7031489d0e 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.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.Audio; @@ -46,7 +44,7 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - private IAdjustableAudioComponent track; + private IAdjustableAudioComponent? track; protected ModTimeRamp() { diff --git a/osu.Game/Rulesets/Mods/ModType.cs b/osu.Game/Rulesets/Mods/ModType.cs index 5a405b7632..e3c82e42f5 100644 --- a/osu.Game/Rulesets/Mods/ModType.cs +++ b/osu.Game/Rulesets/Mods/ModType.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mods { public enum ModType diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index 2150264a0c..08bd44f7bd 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.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.Bindables; diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index d9ee561ef8..df8f781148 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.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.Bindables; diff --git a/osu.Game/Rulesets/Mods/ModWithVisibilityAdjustment.cs b/osu.Game/Rulesets/Mods/ModWithVisibilityAdjustment.cs index d84695fff8..2e3619ec63 100644 --- a/osu.Game/Rulesets/Mods/ModWithVisibilityAdjustment.cs +++ b/osu.Game/Rulesets/Mods/ModWithVisibilityAdjustment.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Framework.Bindables; using osu.Game.Beatmaps; @@ -21,7 +19,7 @@ namespace osu.Game.Rulesets.Mods /// /// The first adjustable object. /// - protected HitObject FirstObject { get; private set; } + protected HitObject? FirstObject { get; private set; } /// /// Whether the visibility of should be increased. @@ -59,7 +57,7 @@ namespace osu.Game.Rulesets.Mods { FirstObject = getFirstAdjustableObjectRecursive(beatmap.HitObjects); - HitObject getFirstAdjustableObjectRecursive(IReadOnlyList hitObjects) + HitObject? getFirstAdjustableObjectRecursive(IReadOnlyList hitObjects) { foreach (var h in hitObjects) { @@ -93,7 +91,7 @@ namespace osu.Game.Rulesets.Mods /// The to check. /// The which may be equal to or contain as a nested object. /// Whether is equal to or nested within . - private bool isObjectEqualToOrNestedIn(HitObject toCheck, HitObject target) + private bool isObjectEqualToOrNestedIn(HitObject toCheck, HitObject? target) { if (target == null) return false; diff --git a/osu.Game/Rulesets/Mods/MultiMod.cs b/osu.Game/Rulesets/Mods/MultiMod.cs index d62dc34d3b..1c41c6b8b3 100644 --- a/osu.Game/Rulesets/Mods/MultiMod.cs +++ b/osu.Game/Rulesets/Mods/MultiMod.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; diff --git a/osu.Game/Rulesets/Mods/UnknownMod.cs b/osu.Game/Rulesets/Mods/UnknownMod.cs index e058fba566..72de0ad653 100644 --- a/osu.Game/Rulesets/Mods/UnknownMod.cs +++ b/osu.Game/Rulesets/Mods/UnknownMod.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mods { public class UnknownMod : Mod From ce1bb206c860e3ca53bb04ac6f797e308ab68d5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 23:16:51 +0800 Subject: [PATCH 137/481] Initialize some bindables for prevent get the null instance. --- osu.Game/Rulesets/Mods/ModBlockFail.cs | 4 ++-- osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs | 4 ++-- osu.Game/Rulesets/Mods/ModMuted.cs | 4 ++-- osu.Game/Rulesets/Mods/ModNoScope.cs | 8 ++++---- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModBlockFail.cs b/osu.Game/Rulesets/Mods/ModBlockFail.cs index 8a9b0cddc8..cdfb36ebbc 100644 --- a/osu.Game/Rulesets/Mods/ModBlockFail.cs +++ b/osu.Game/Rulesets/Mods/ModBlockFail.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Mods { public abstract class ModBlockFail : Mod, IApplicableFailOverride, IApplicableToHUD, IReadFromConfig { - private Bindable showHealthBar; + private readonly Bindable showHealthBar = new Bindable(); /// /// We never fail, 'yo. @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Mods public void ReadFromConfig(OsuConfigManager config) { - showHealthBar = config.GetBindable(OsuSetting.ShowHealthDisplayWhenCantFail); + config.BindWith(OsuSetting.ShowHealthDisplayWhenCantFail, showHealthBar); } public void ApplyToHUD(HUDOverlay overlay) diff --git a/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs b/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs index 2ac0f59d84..c4396e440e 100644 --- a/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs +++ b/osu.Game/Rulesets/Mods/ModEasyWithExtraLives.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Mods private int retries; - private BindableNumber health; + private readonly BindableNumber health = new BindableDouble(); public override void ApplyToDifficulty(BeatmapDifficulty difficulty) { @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Mods public void ApplyToHealthProcessor(HealthProcessor healthProcessor) { - health = healthProcessor.Health.GetBoundCopy(); + health.BindTo(healthProcessor.Health); } } } diff --git a/osu.Game/Rulesets/Mods/ModMuted.cs b/osu.Game/Rulesets/Mods/ModMuted.cs index 84341faab7..55d5abfa82 100644 --- a/osu.Game/Rulesets/Mods/ModMuted.cs +++ b/osu.Game/Rulesets/Mods/ModMuted.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Mods private readonly BindableNumber mainVolumeAdjust = new BindableDouble(0.5); private readonly BindableNumber metronomeVolumeAdjust = new BindableDouble(0.5); - private BindableNumber currentCombo; + private readonly BindableNumber currentCombo = new BindableInt(); [SettingSource("Enable metronome", "Add a metronome beat to help you keep track of the rhythm.")] public BindableBool EnableMetronome { get; } = new BindableBool @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Mods public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { - currentCombo = scoreProcessor.Combo.GetBoundCopy(); + currentCombo.BindTo(scoreProcessor.Combo); currentCombo.BindValueChanged(combo => { double dimFactor = MuteComboCount.Value == 0 ? 1 : (double)combo.NewValue / MuteComboCount.Value; diff --git a/osu.Game/Rulesets/Mods/ModNoScope.cs b/osu.Game/Rulesets/Mods/ModNoScope.cs index 7a935eb38f..1b9ce833ad 100644 --- a/osu.Game/Rulesets/Mods/ModNoScope.cs +++ b/osu.Game/Rulesets/Mods/ModNoScope.cs @@ -28,9 +28,9 @@ namespace osu.Game.Rulesets.Mods protected const float TRANSITION_DURATION = 100; - protected BindableNumber CurrentCombo; + protected readonly BindableNumber CurrentCombo = new BindableInt(); - protected IBindable IsBreakTime; + protected readonly IBindable IsBreakTime = new Bindable(); protected float ComboBasedAlpha; @@ -40,14 +40,14 @@ namespace osu.Game.Rulesets.Mods public void ApplyToPlayer(Player player) { - IsBreakTime = player.IsBreakTime.GetBoundCopy(); + IsBreakTime.BindTo(player.IsBreakTime); } public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { if (HiddenComboCount.Value == 0) return; - CurrentCombo = scoreProcessor.Combo.GetBoundCopy(); + CurrentCombo.BindTo(scoreProcessor.Combo); CurrentCombo.BindValueChanged(combo => { ComboBasedAlpha = Math.Max(MIN_ALPHA, 1 - (float)combo.NewValue / HiddenComboCount.Value); From 3af093cb2776039e9e1a05860865064b59075b8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 23:33:31 +0800 Subject: [PATCH 138/481] Remove the null check because bindable should always have the value. --- osu.Game/Rulesets/Mods/Mod.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/Mod.cs b/osu.Game/Rulesets/Mods/Mod.cs index 7fdb03a7f3..abba83ce59 100644 --- a/osu.Game/Rulesets/Mods/Mod.cs +++ b/osu.Game/Rulesets/Mods/Mod.cs @@ -214,8 +214,8 @@ namespace osu.Game.Rulesets.Mods public bool Equals(IBindable x, IBindable y) { - object xValue = x?.GetUnderlyingSettingValue(); - object yValue = y?.GetUnderlyingSettingValue(); + object xValue = x.GetUnderlyingSettingValue(); + object yValue = y.GetUnderlyingSettingValue(); return EqualityComparer.Default.Equals(xValue, yValue); } From 317558f8769dc217fd8caf8ec8c8b3a5e2106181 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 23:37:11 +0800 Subject: [PATCH 139/481] Mark the shader as non-nullable because shader should always has the value. And initialize the breaks to avoid get the null instance. --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index b449f3f64d..e8bc6c2026 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -94,13 +94,13 @@ namespace osu.Game.Rulesets.Mods { public readonly BindableInt Combo = new BindableInt(); - private IShader shader; + private IShader shader = null!; protected override DrawNode CreateDrawNode() => new FlashlightDrawNode(this); public override bool RemoveCompletedTransforms => false; - public List Breaks; + public List Breaks = new List(); private readonly float defaultFlashlightSize; private readonly float sizeMultiplier; @@ -205,7 +205,7 @@ namespace osu.Game.Rulesets.Mods { protected new Flashlight Source => (Flashlight)base.Source; - private IShader shader; + private IShader shader = null!; private Quad screenSpaceDrawQuad; private Vector2 flashlightPosition; private Vector2 flashlightSize; @@ -253,7 +253,7 @@ namespace osu.Game.Rulesets.Mods protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - quadBatch?.Dispose(); + quadBatch.Dispose(); } } } From d9addebc93a4b3570c027258bdeebdaf725025a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Mon, 11 Jul 2022 00:07:17 +0800 Subject: [PATCH 140/481] Remove the nullable disable annotation in the test project and fix the api broken. --- osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs | 4 +--- osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs | 2 -- osu.Game.Tests/Mods/ModSettingsTest.cs | 2 -- osu.Game.Tests/Mods/ModUtilsTest.cs | 2 -- osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs | 6 ++---- osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs | 2 -- osu.Game.Tests/Mods/TestCustomisableModRuleset.cs | 4 +--- osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs | 2 -- 8 files changed, 4 insertions(+), 20 deletions(-) diff --git a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs index a6f68b2836..efb04978a5 100644 --- a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs +++ b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using NUnit.Framework; using osu.Game.Beatmaps; @@ -148,7 +146,7 @@ namespace osu.Game.Tests.Mods yield return new TestModDifficultyAdjust(); } - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) { throw new System.NotImplementedException(); } diff --git a/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs b/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs index e94ee40acd..cd6879cf01 100644 --- a/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs +++ b/osu.Game.Tests/Mods/ModSettingsEqualityComparison.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Game.Online.API; using osu.Game.Rulesets.Osu; diff --git a/osu.Game.Tests/Mods/ModSettingsTest.cs b/osu.Game.Tests/Mods/ModSettingsTest.cs index 607b585d33..b9ea1f2567 100644 --- a/osu.Game.Tests/Mods/ModSettingsTest.cs +++ b/osu.Game.Tests/Mods/ModSettingsTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index 22be1a3f01..6c9dddf51f 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.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 Moq; diff --git a/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs b/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs index 3c69adcb59..b8a3828a64 100644 --- a/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs +++ b/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -29,10 +27,10 @@ namespace osu.Game.Tests.Mods [TestCase(typeof(ManiaRuleset))] public void TestAllMultiModsFromRulesetAreIncompatible(Type rulesetType) { - var ruleset = (Ruleset)Activator.CreateInstance(rulesetType); + var ruleset = Activator.CreateInstance(rulesetType) as Ruleset; Assert.That(ruleset, Is.Not.Null); - var allMultiMods = getMultiMods(ruleset); + var allMultiMods = getMultiMods(ruleset!); Assert.Multiple(() => { diff --git a/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs index f608d020d4..dd105787fa 100644 --- a/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs +++ b/osu.Game.Tests/Mods/SettingsSourceAttributeTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Bindables; diff --git a/osu.Game.Tests/Mods/TestCustomisableModRuleset.cs b/osu.Game.Tests/Mods/TestCustomisableModRuleset.cs index 08007503c6..9e3354935a 100644 --- a/osu.Game.Tests/Mods/TestCustomisableModRuleset.cs +++ b/osu.Game.Tests/Mods/TestCustomisableModRuleset.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Framework.Bindables; @@ -33,7 +31,7 @@ namespace osu.Game.Tests.Mods return Array.Empty(); } - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList? mods = null) => throw new NotImplementedException(); public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); diff --git a/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs index 2622db464f..51163efd6a 100644 --- a/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs +++ b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Framework.Audio.Track; using osu.Framework.Timing; From ee7e7f2d3a27e5c38b52808d937a9a6072d1bdca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Mon, 11 Jul 2022 00:09:54 +0800 Subject: [PATCH 141/481] Mark the property as non-nullable. --- osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs | 2 +- osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs index efb04978a5..4101652c49 100644 --- a/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs +++ b/osu.Game.Tests/Mods/ModDifficultyAdjustTest.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Mods [TestFixture] public class ModDifficultyAdjustTest { - private TestModDifficultyAdjust testMod; + private TestModDifficultyAdjust testMod = null!; [SetUp] public void Setup() diff --git a/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs index 51163efd6a..4601737558 100644 --- a/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs +++ b/osu.Game.Tests/Rulesets/Mods/ModTimeRampTest.cs @@ -17,8 +17,8 @@ namespace osu.Game.Tests.Rulesets.Mods private const double start_time = 1000; private const double duration = 9000; - private TrackVirtual track; - private OsuPlayfield playfield; + private TrackVirtual track = null!; + private OsuPlayfield playfield = null!; [SetUp] public void SetUp() From 2a83404dbe1e19316b3044f7b4c14a4c7cf882fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Mon, 11 Jul 2022 00:20:56 +0800 Subject: [PATCH 142/481] Use array.empty instead of null value. --- osu.Game.Tests/Mods/ModUtilsTest.cs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Mods/ModUtilsTest.cs b/osu.Game.Tests/Mods/ModUtilsTest.cs index 6c9dddf51f..3b391f6756 100644 --- a/osu.Game.Tests/Mods/ModUtilsTest.cs +++ b/osu.Game.Tests/Mods/ModUtilsTest.cs @@ -162,19 +162,19 @@ namespace osu.Game.Tests.Mods new object[] { new Mod[] { new OsuModHidden(), new InvalidMultiplayerMod() }, - null + Array.Empty() }, // invalid free mod is valid for local. new object[] { new Mod[] { new OsuModHidden(), new InvalidMultiplayerFreeMod() }, - null + Array.Empty() }, // valid pair. new object[] { new Mod[] { new OsuModHidden(), new OsuModHardRock() }, - null + Array.Empty() }, }; @@ -214,13 +214,13 @@ namespace osu.Game.Tests.Mods new object[] { new Mod[] { new OsuModHidden(), new InvalidMultiplayerFreeMod() }, - null + Array.Empty() }, // valid pair. new object[] { new Mod[] { new OsuModHidden(), new OsuModHardRock() }, - null + Array.Empty() }, }; @@ -254,19 +254,19 @@ namespace osu.Game.Tests.Mods new object[] { new Mod[] { new OsuModHidden(), new OsuModApproachDifferent() }, - null, + Array.Empty(), }, // incompatible pair with derived class is valid for free mods. new object[] { new Mod[] { new OsuModDeflate(), new OsuModSpinIn() }, - null, + Array.Empty(), }, // valid pair. new object[] { new Mod[] { new OsuModHidden(), new OsuModHardRock() }, - null + Array.Empty() }, }; @@ -275,12 +275,12 @@ namespace osu.Game.Tests.Mods { bool isValid = ModUtils.CheckValidForGameplay(inputMods, out var invalid); - Assert.That(isValid, Is.EqualTo(expectedInvalid == null)); + Assert.That(isValid, Is.EqualTo(expectedInvalid.Length == 0)); if (isValid) Assert.IsNull(invalid); else - Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); + Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); } [TestCaseSource(nameof(invalid_multiplayer_mod_test_scenarios))] @@ -288,12 +288,12 @@ namespace osu.Game.Tests.Mods { bool isValid = ModUtils.CheckValidRequiredModsForMultiplayer(inputMods, out var invalid); - Assert.That(isValid, Is.EqualTo(expectedInvalid == null)); + Assert.That(isValid, Is.EqualTo(expectedInvalid.Length == 0)); if (isValid) Assert.IsNull(invalid); else - Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); + Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); } [TestCaseSource(nameof(invalid_free_mod_test_scenarios))] @@ -301,12 +301,12 @@ namespace osu.Game.Tests.Mods { bool isValid = ModUtils.CheckValidFreeModsForMultiplayer(inputMods, out var invalid); - Assert.That(isValid, Is.EqualTo(expectedInvalid == null)); + Assert.That(isValid, Is.EqualTo(expectedInvalid.Length == 0)); if (isValid) Assert.IsNull(invalid); else - Assert.That(invalid.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); + Assert.That(invalid?.Select(t => t.GetType()), Is.EquivalentTo(expectedInvalid)); } public abstract class CustomMod1 : Mod, IModCompatibilitySpecification From 4164f260b387b3251294d83d237dfa97ba67bcc8 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Tue, 19 Jul 2022 08:12:12 -0500 Subject: [PATCH 143/481] Fix code quality errors --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 04d800a10e..410ac9438f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -148,7 +148,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new MultiplayerPlaylist { RelativeSizeAxes = Axes.Both, - RequestEdit = item => OpenSongSelection(item) + RequestEdit = OpenSongSelection } }, new[] @@ -231,7 +231,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!this.IsCurrentScreen()) return; - int id = itemToEdit?.Beatmap.OnlineID ?? Room.Playlist.LastOrDefault().Beatmap.OnlineID; + int id = itemToEdit?.Beatmap.OnlineID ?? Room.Playlist.LastOrDefault()!.Beatmap.OnlineID; var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == id); var workingBeatmap = localBeatmap == null ? null : beatmapManager.GetWorkingBeatmap(localBeatmap); From 25028bb7fab0edd964d304e6fc00409c2faf8240 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 22:36:07 +0900 Subject: [PATCH 144/481] Fix editor clap/finish buttons being ordered against expectations --- osu.Game/Audio/HitSampleInfo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Audio/HitSampleInfo.cs b/osu.Game/Audio/HitSampleInfo.cs index 6aaf3d5cc2..efa5562cb8 100644 --- a/osu.Game/Audio/HitSampleInfo.cs +++ b/osu.Game/Audio/HitSampleInfo.cs @@ -14,15 +14,15 @@ namespace osu.Game.Audio [Serializable] public class HitSampleInfo : ISampleInfo, IEquatable { + public const string HIT_NORMAL = @"hitnormal"; public const string HIT_WHISTLE = @"hitwhistle"; public const string HIT_FINISH = @"hitfinish"; - public const string HIT_NORMAL = @"hitnormal"; public const string HIT_CLAP = @"hitclap"; /// /// All valid sample addition constants. /// - public static IEnumerable AllAdditions => new[] { HIT_WHISTLE, HIT_CLAP, HIT_FINISH }; + public static IEnumerable AllAdditions => new[] { HIT_WHISTLE, HIT_FINISH, HIT_CLAP }; /// /// The name of the sample to load. From 06ae30a7d270c7f089fb5341cc8dbcfb98e94cf7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 22:54:11 +0900 Subject: [PATCH 145/481] Fix slider velocity not using previous value if slider is not adjacent --- .../Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 139bfe7dd3..59be93530c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit; using osuTK; using osuTK.Input; @@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders { public class SliderPlacementBlueprint : PlacementBlueprint { - public new Objects.Slider HitObject => (Objects.Slider)base.HitObject; + public new Slider HitObject => (Slider)base.HitObject; private SliderBodyPiece bodyPiece; private HitCirclePiece headCirclePiece; @@ -42,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders private IDistanceSnapProvider snapProvider { get; set; } public SliderPlacementBlueprint() - : base(new Objects.Slider()) + : base(new Slider()) { RelativeSizeAxes = Axes.Both; @@ -82,7 +83,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders case SliderPlacementState.Initial: BeginPlacement(); - var nearestDifficultyPoint = editorBeatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < HitObject.StartTime)?.DifficultyControlPoint?.DeepClone() as DifficultyControlPoint; + var nearestDifficultyPoint = editorBeatmap.HitObjects + .LastOrDefault(h => h is Slider && h.GetEndTime() < HitObject.StartTime)? + .DifficultyControlPoint?.DeepClone() as DifficultyControlPoint; HitObject.DifficultyControlPoint = nearestDifficultyPoint ?? new DifficultyControlPoint(); HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); From 87afa7317b141c37bbc5d0d498c0149be729a9e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 Jul 2022 23:12:49 +0900 Subject: [PATCH 146/481] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 3b14d85e53..013a7d1419 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6120d3d600..40e01d5f2e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 8a36ad6e3d..0e2e7d57b7 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 7be5c638e46b14db6b8a9257e5e47bd17ebb5d1a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 00:22:31 +0900 Subject: [PATCH 147/481] Fix floating mouse position not running correctly in single thread mode Noticed while testing on iOS. Previously, the interpolation was being done in input handling but using the update thread clock, leading to incorrect application. --- osu.Game/Graphics/Cursor/MenuCursor.cs | 34 +++++++++++++++++--------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 10ed76ebdd..862a10208c 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -3,21 +3,21 @@ #nullable disable -using osuTK; +using System; +using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; -using osu.Game.Configuration; -using System; -using JetBrains.Annotations; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Bindables; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Configuration; +using osuTK; namespace osu.Game.Graphics.Cursor { @@ -35,6 +35,7 @@ namespace osu.Game.Graphics.Cursor private Vector2 positionMouseDown; private Sample tapSample; + private Vector2 lastMovePosition; [BackgroundDependencyLoader(true)] private void load([NotNull] OsuConfigManager config, [CanBeNull] ScreenshotManager screenshotManager, AudioManager audio) @@ -47,16 +48,25 @@ namespace osu.Game.Graphics.Cursor tapSample = audio.Samples.Get(@"UI/cursor-tap"); } + protected override void Update() + { + base.Update(); + + if (dragRotationState != DragRotationState.NotDragging + && Vector2.Distance(positionMouseDown, lastMovePosition) > 60) + { + // make the rotation centre point floating. + positionMouseDown = Interpolation.ValueAt(0.04f, positionMouseDown, lastMovePosition, 0, Clock.ElapsedFrameTime); + } + } + protected override bool OnMouseMove(MouseMoveEvent e) { if (dragRotationState != DragRotationState.NotDragging) { - // make the rotation centre point floating. - if (Vector2.Distance(positionMouseDown, e.MousePosition) > 60) - positionMouseDown = Interpolation.ValueAt(0.005f, positionMouseDown, e.MousePosition, 0, Clock.ElapsedFrameTime); + lastMovePosition = e.MousePosition; - var position = e.MousePosition; - float distance = Vector2Extensions.Distance(position, positionMouseDown); + float distance = Vector2Extensions.Distance(lastMovePosition, positionMouseDown); // don't start rotating until we're moved a minimum distance away from the mouse down location, // else it can have an annoying effect. From 89653b74c797e417d88fc0235b29be964d2d70e8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 19 Jul 2022 19:21:16 +0300 Subject: [PATCH 148/481] Only add setting tracker when customisation is permitted --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index ea152f58ad..afc6775e83 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -246,8 +246,11 @@ namespace osu.Game.Overlays.Mods updateCustomisation(val); updateFromExternalSelection(); - modSettingChangeTracker = new ModSettingChangeTracker(val.NewValue); - modSettingChangeTracker.SettingChanged += _ => updateMultiplier(); + if (AllowCustomisation) + { + modSettingChangeTracker = new ModSettingChangeTracker(val.NewValue); + modSettingChangeTracker.SettingChanged += _ => updateMultiplier(); + } }, true); customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true); From a00da279b7bfd02730ddfb52b347ab93847464b6 Mon Sep 17 00:00:00 2001 From: LukynkaCZE <48604271+LukynkaCZE@users.noreply.github.com> Date: Tue, 19 Jul 2022 21:38:23 +0200 Subject: [PATCH 149/481] Beatmap Editor Save Toast --- osu.Game/Localisation/ToastStrings.cs | 11 +++++++++++ osu.Game/Screens/Edit/Editor.cs | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 52e75425bf..7c5fdb4b22 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -34,6 +34,17 @@ namespace osu.Game.Localisation /// public static LocalisableString RestartTrack => new TranslatableString(getKey(@"restart_track"), @"Restart track"); + /// + /// "Beatmap Editor" + /// r + public static LocalisableString BeatmapEditor => new TranslatableString(getKey(@"beatmap_editor"), @"Beatmap Editor"); + + /// + /// "Beatmap Saved" + /// + public static LocalisableString EditorSaveBeatmap => new TranslatableString(getKey(@"beatmap_editor_save"), @"Beatmap Saved"); + + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 48576b81e2..bfc6abfc32 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -18,6 +18,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Screens; @@ -31,10 +32,11 @@ using osu.Game.Database; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; +using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; -using osu.Game.Resources.Localisation.Web; +using osu.Game.Overlays.OSD; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; @@ -50,6 +52,7 @@ using osu.Game.Screens.Play; using osu.Game.Users; using osuTK.Graphics; using osuTK.Input; +using CommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings; namespace osu.Game.Screens.Edit { @@ -169,6 +172,9 @@ namespace osu.Game.Screens.Edit [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + [Resolved(canBeNull: true)] + private OnScreenDisplay onScreenDisplay { get; set; } + public Editor(EditorLoader loader = null) { this.loader = loader; @@ -405,6 +411,7 @@ namespace osu.Game.Screens.Edit // no longer new after first user-triggered save. isNewBeatmap = false; updateLastSavedHash(); + onScreenDisplay?.Display(new BeatmapEditorToast(ToastStrings.EditorSaveBeatmap, editorBeatmap.BeatmapInfo.GetDisplayTitle())); return true; } @@ -934,5 +941,13 @@ namespace osu.Game.Screens.Edit ControlPointInfo IBeatSyncProvider.ControlPoints => editorBeatmap.ControlPointInfo; IClock IBeatSyncProvider.Clock => clock; ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : null; + + + private class BeatmapEditorToast : Toast + { + public BeatmapEditorToast(LocalisableString value, string beatmapDisplayName) + : base(ToastStrings.BeatmapEditor, value, beatmapDisplayName) { } + + } } } From 51a0b5afdc4950195363efaa50b9ee1890436623 Mon Sep 17 00:00:00 2001 From: LukynkaCZE <48604271+LukynkaCZE@users.noreply.github.com> Date: Tue, 19 Jul 2022 22:18:19 +0200 Subject: [PATCH 150/481] Skin Editor --- osu.Game/Localisation/ToastStrings.cs | 11 +++++++++++ osu.Game/Skinning/Editor/SkinEditor.cs | 16 ++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 7c5fdb4b22..519c47e7c4 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -44,6 +44,17 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorSaveBeatmap => new TranslatableString(getKey(@"beatmap_editor_save"), @"Beatmap Saved"); + /// + /// "Skin Editor" + /// + public static LocalisableString SkinEditor => new TranslatableString(getKey(@"skin_editor"), @"Skin Editor"); + + /// + /// "Skin Saved" + /// + public static LocalisableString EditorSaveSkin => new TranslatableString(getKey(@"skin_editor_save"), @"Skin Saved"); + + private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 649b63dda4..02c0350d2c 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -14,13 +14,16 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; using osu.Game.Overlays; +using osu.Game.Overlays.OSD; using osu.Game.Screens.Edit.Components; using osu.Game.Screens.Edit.Components.Menus; @@ -68,6 +71,9 @@ namespace osu.Game.Skinning.Editor private EditorSidebar componentsSidebar; private EditorSidebar settingsSidebar; + [Resolved(canBeNull: true)] + private OnScreenDisplay onScreenDisplay { get; set; } + public SkinEditor() { } @@ -316,6 +322,7 @@ namespace osu.Game.Skinning.Editor currentSkin.Value.UpdateDrawableTarget(t); skins.Save(skins.CurrentSkin.Value); + onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.EditorSaveSkin, currentSkin.Value.SkinInfo.ToString())); } protected override bool OnHover(HoverEvent e) => true; @@ -395,5 +402,14 @@ namespace osu.Game.Skinning.Editor game?.UnregisterImportHandler(this); } + + + private class SkinEditorToast : Toast + { + public SkinEditorToast(LocalisableString value, string skinDisplayName) + : base(ToastStrings.SkinEditor, value, skinDisplayName) { } + + } + } } From 5987acfbca821b3e5f71157509382d9bdc0e2a04 Mon Sep 17 00:00:00 2001 From: LukynkaCZE <48604271+LukynkaCZE@users.noreply.github.com> Date: Tue, 19 Jul 2022 22:59:25 +0200 Subject: [PATCH 151/481] Fixed code formatting --- osu.Game/Localisation/ToastStrings.cs | 2 -- osu.Game/Screens/Edit/Editor.cs | 2 -- osu.Game/Skinning/Editor/SkinEditor.cs | 3 --- 3 files changed, 7 deletions(-) diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 519c47e7c4..4169a23798 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -54,8 +54,6 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorSaveSkin => new TranslatableString(getKey(@"skin_editor_save"), @"Skin Saved"); - - private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index bfc6abfc32..bf9785063b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -942,12 +942,10 @@ namespace osu.Game.Screens.Edit IClock IBeatSyncProvider.Clock => clock; ChannelAmplitudes? IBeatSyncProvider.Amplitudes => Beatmap.Value.TrackLoaded ? Beatmap.Value.Track.CurrentAmplitudes : null; - private class BeatmapEditorToast : Toast { public BeatmapEditorToast(LocalisableString value, string beatmapDisplayName) : base(ToastStrings.BeatmapEditor, value, beatmapDisplayName) { } - } } } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 02c0350d2c..326574f2da 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -403,13 +403,10 @@ namespace osu.Game.Skinning.Editor game?.UnregisterImportHandler(this); } - private class SkinEditorToast : Toast { public SkinEditorToast(LocalisableString value, string skinDisplayName) : base(ToastStrings.SkinEditor, value, skinDisplayName) { } - } - } } From 1270abdf42214e144ad5cf260d0ec9d8a3e46470 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 20 Jul 2022 00:50:28 +0300 Subject: [PATCH 152/481] Highlight perfect slider tick/end values in beatmap info leaderboards --- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 6acc9bf002..c46c5cde43 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -23,6 +23,7 @@ using osuTK; using osuTK.Graphics; using osu.Framework.Localisation; using osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Graphics.Cursor; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapSet.Scores @@ -38,8 +39,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private readonly FillFlowContainer backgroundFlow; - private Color4 highAccuracyColour; - public ScoreTable() { RelativeSizeAxes = Axes.X; @@ -57,12 +56,6 @@ namespace osu.Game.Overlays.BeatmapSet.Scores }); } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - highAccuracyColour = colours.GreenLight; - } - /// /// The statistics that appear in the table, in order of appearance. /// @@ -158,12 +151,10 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Current = scoreManager.GetBindableTotalScoreString(score), Font = OsuFont.GetFont(size: text_size, weight: index == 0 ? FontWeight.Bold : FontWeight.Medium) }, - new OsuSpriteText + new StatisticText(score.Accuracy, 1, showTooltip: false) { Margin = new MarginPadding { Right = horizontal_inset }, Text = score.DisplayAccuracy, - Font = OsuFont.GetFont(size: text_size), - Colour = score.Accuracy == 1 ? highAccuracyColour : Color4.White }, new UpdateableFlag(score.User.CountryCode) { @@ -171,14 +162,9 @@ namespace osu.Game.Overlays.BeatmapSet.Scores ShowPlaceholderOnUnknown = false, }, username, - new OsuSpriteText - { - Text = score.MaxCombo.ToLocalisableString(@"0\x"), - Font = OsuFont.GetFont(size: text_size), #pragma warning disable 618 - Colour = score.MaxCombo == score.BeatmapInfo.MaxCombo ? highAccuracyColour : Color4.White + new StatisticText(score.MaxCombo, score.BeatmapInfo.MaxCombo, @"0\x"), #pragma warning restore 618 - } }; var availableStatistics = score.GetStatisticsForDisplay().ToDictionary(tuple => tuple.Result); @@ -188,23 +174,13 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (!availableStatistics.TryGetValue(result.result, out var stat)) stat = new HitResultDisplayStatistic(result.result, 0, null, result.displayName); - content.Add(new OsuSpriteText - { - Text = stat.MaxCount == null ? stat.Count.ToLocalisableString(@"N0") : (LocalisableString)$"{stat.Count}/{stat.MaxCount}", - Font = OsuFont.GetFont(size: text_size), - Colour = stat.Count == 0 ? Color4.Gray : Color4.White - }); + content.Add(new StatisticText(stat.Count, stat.MaxCount, @"N0") { Colour = stat.Count == 0 ? Color4.Gray : Color4.White }); } if (showPerformancePoints) { Debug.Assert(score.PP != null); - - content.Add(new OsuSpriteText - { - Text = score.PP.ToLocalisableString(@"N0"), - Font = OsuFont.GetFont(size: text_size) - }); + content.Add(new StatisticText(score.PP.Value, format: @"N0")); } content.Add(new ScoreboardTime(score.Date, text_size) @@ -243,5 +219,31 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Colour = colourProvider.Foreground1; } } + + private class StatisticText : OsuSpriteText, IHasTooltip + { + private readonly double count; + private readonly double? maxCount; + private readonly bool showTooltip; + + public LocalisableString TooltipText => maxCount == null || !showTooltip ? string.Empty : $"{count}/{maxCount}"; + + public StatisticText(double count, double? maxCount = null, string format = null, bool showTooltip = true) + { + this.count = count; + this.maxCount = maxCount; + this.showTooltip = showTooltip; + + Text = count.ToLocalisableString(format); + Font = OsuFont.GetFont(size: text_size); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + if (count == maxCount) + Colour = colours.GreenLight; + } + } } } From 4d1f9a13296a1d8115f2fd22354a133f74762dba Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 20 Jul 2022 00:50:37 +0300 Subject: [PATCH 153/481] Adjust test scene to cover slider ticks --- .../Visual/Online/TestSceneScoresContainer.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index beca3a8700..864b2b6878 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -255,18 +255,25 @@ namespace osu.Game.Tests.Visual.Online }; const int initial_great_count = 2000; + const int initial_tick_count = 100; int greatCount = initial_great_count; + int tickCount = initial_tick_count; foreach (var s in scores.Scores) { s.Statistics = new Dictionary { - { HitResult.Great, greatCount -= 100 }, + { HitResult.Great, greatCount }, + { HitResult.LargeTickHit, tickCount }, { HitResult.Ok, RNG.Next(100) }, { HitResult.Meh, RNG.Next(100) }, - { HitResult.Miss, initial_great_count - greatCount } + { HitResult.Miss, initial_great_count - greatCount }, + { HitResult.LargeTickMiss, initial_tick_count - tickCount }, }; + + greatCount -= 100; + tickCount -= RNG.Next(1, 5); } return scores; From cecf654a7b1c265a3bbdfbbb965bf3808b29d5ee Mon Sep 17 00:00:00 2001 From: Adam Baker <42323315+Cwazywierdo@users.noreply.github.com> Date: Tue, 19 Jul 2022 18:58:59 -0500 Subject: [PATCH 154/481] Update osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs Co-authored-by: Salman Ahmed --- .../Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 285dfc2b60..ceadfa1527 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -227,7 +227,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!this.IsCurrentScreen()) return; - int id = itemToEdit?.Beatmap.OnlineID ?? Room.Playlist.LastOrDefault()!.Beatmap.OnlineID; + int id = itemToEdit?.Beatmap.OnlineID ?? Room.Playlist.Last().Beatmap.OnlineID; var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == id); var workingBeatmap = localBeatmap == null ? null : beatmapManager.GetWorkingBeatmap(localBeatmap); From 2a76a046198adb09379e78743852a6910a4f0588 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 13:41:36 +0900 Subject: [PATCH 155/481] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 013a7d1419..c83b7872ac 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 40e01d5f2e..4fa4b804ab 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 0e2e7d57b7..dc012ab2fa 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From e7f35701dbdc4203636edbffef9f17ca1ac160c7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 20 Jul 2022 08:47:20 +0300 Subject: [PATCH 156/481] Add failing test case --- .../Visual/Online/TestSceneWikiOverlay.cs | 46 ++++++++++++++++--- .../Online/API/Requests/GetWikiRequest.cs | 7 +-- 2 files changed, 43 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs index 8889cb3e37..558bff2f3c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Net; using NUnit.Framework; using osu.Game.Online.API; @@ -25,24 +26,40 @@ namespace osu.Game.Tests.Visual.Online public void TestMainPage() { setUpWikiResponse(responseMainPage); - AddStep("Show Main Page", () => wiki.Show()); + AddStep("Show main page", () => wiki.Show()); } [Test] public void TestArticlePage() { setUpWikiResponse(responseArticlePage); - AddStep("Show Article Page", () => wiki.ShowPage("Article_styling_criteria/Formatting")); + AddStep("Show article page", () => wiki.ShowPage("Article_styling_criteria/Formatting")); + } + + [Test] + public void TestRedirection() + { + const string redirection_path = "Redirection_path_for_article"; + + setUpWikiResponse(responseArticlePage, redirection_path); + AddStep("Show article page", () => wiki.ShowPage(redirection_path)); + + AddUntilStep("Current page is article", () => wiki.Header.Current.Value == "Formatting"); + + setUpWikiResponse(responseArticleParentPage); + AddStep("Show parent page", () => wiki.Header.ShowParentPage?.Invoke()); + + AddUntilStep("Current page is parent", () => wiki.Header.Current.Value == "Article styling criteria"); } [Test] public void TestErrorPage() { - setUpWikiResponse(null, true); + setUpWikiResponse(responseArticlePage); AddStep("Show Error Page", () => wiki.ShowPage("Error")); } - private void setUpWikiResponse(APIWikiPage r, bool isFailed = false) + private void setUpWikiResponse(APIWikiPage r, string redirectionPath = null) => AddStep("set up response", () => { dummyAPI.HandleRequest = request => @@ -50,10 +67,13 @@ namespace osu.Game.Tests.Visual.Online if (!(request is GetWikiRequest getWikiRequest)) return false; - if (isFailed) - getWikiRequest.TriggerFailure(new WebException()); - else + if (getWikiRequest.Path.Equals(r.Path, StringComparison.OrdinalIgnoreCase) || + getWikiRequest.Path.Equals(redirectionPath, StringComparison.OrdinalIgnoreCase)) + { getWikiRequest.TriggerSuccess(r); + } + else + getWikiRequest.TriggerFailure(new WebException()); return true; }; @@ -82,5 +102,17 @@ namespace osu.Game.Tests.Visual.Online Markdown = "# Formatting\n\n*For the writing standards, see: [Article style criteria/Writing](../Writing)*\n\n*Notice: This article uses [RFC 2119](https://tools.ietf.org/html/rfc2119 \"IETF Tools\") to describe requirement levels.*\n\n## Locales\n\nListed below are the properly-supported locales for the wiki:\n\n| File Name | Locale Name | Native Script |\n| :-- | :-- | :-- |\n| `en.md` | English | English |\n| `ar.md` | Arabic | اَلْعَرَبِيَّةُ |\n| `be.md` | Belarusian | Беларуская мова |\n| `bg.md` | Bulgarian | Български |\n| `cs.md` | Czech | Česky |\n| `da.md` | Danish | Dansk |\n| `de.md` | German | Deutsch |\n| `gr.md` | Greek | Ελληνικά |\n| `es.md` | Spanish | Español |\n| `fi.md` | Finnish | Suomi |\n| `fr.md` | French | Français |\n| `hu.md` | Hungarian | Magyar |\n| `id.md` | Indonesian | Bahasa Indonesia |\n| `it.md` | Italian | Italiano |\n| `ja.md` | Japanese | 日本語 |\n| `ko.md` | Korean | 한국어 |\n| `nl.md` | Dutch | Nederlands |\n| `no.md` | Norwegian | Norsk |\n| `pl.md` | Polish | Polski |\n| `pt.md` | Portuguese | Português |\n| `pt-br.md` | Brazilian Portuguese | Português (Brasil) |\n| `ro.md` | Romanian | Română |\n| `ru.md` | Russian | Русский |\n| `sk.md` | Slovak | Slovenčina |\n| `sv.md` | Swedish | Svenska |\n| `th.md` | Thai | ไทย |\n| `tr.md` | Turkish | Türkçe |\n| `uk.md` | Ukrainian | Українська мова |\n| `vi.md` | Vietnamese | Tiếng Việt |\n| `zh.md` | Chinese (Simplified) | 简体中文 |\n| `zh-tw.md` | Traditional Chinese (Taiwan) | 繁體中文(台灣) |\n\n*Note: The website will give readers their selected language's version of an article. If it is not available, the English version will be given.*\n\n### Content parity\n\nTranslations are subject to strict content parity with their English article, in the sense that they must have the same message, regardless of grammar and syntax. Any changes to the translations' meanings must be accompanied by equivalent changes to the English article.\n\nThere are some cases where the content is allowed to differ:\n\n- Articles originally written in a language other than English (in this case, English should act as the translation)\n- Explanations of English words that are common terms in the osu! community\n- External links\n- Tags\n- Subcommunity-specific explanations\n\n## Front matter\n\nFront matter must be placed at the very top of the file. It is written in [YAML](https://en.wikipedia.org/wiki/YAML#Example \"YAML Wikipedia article\") and describes additional information about the article. This must be surrounded by three hyphens (`---`) on the lines above and below it, and an empty line must follow it before the title heading.\n\n### Articles that need help\n\n*Note: Avoid translating English articles with this tag. In addition to this, this tag should be added when the translation needs its own clean up.*\n\nThe `needs_cleanup` tag may be added to articles that need rewriting or formatting help. It is also acceptable to open an issue on GitHub for this purpose. This tag must be written as shown below:\n\n```yaml\nneeds_cleanup: true\n```\n\nWhen adding this tag to an article, [comments](#comments) should also be added to explain what needs to be done to remove the tag.\n\n### Outdated articles\n\n*Note: Avoid translating English articles with this tag. If the English article has this tag, the translation must also have this tag.*\n\nTranslated articles that are outdated must use the `outdated` tag when the English variant is updated. English articles may also become outdated when the content they contain is misleading or no longer relevant. This tag must be written as shown below:\n\n```yaml\noutdated: true\n```\n\nWhen adding this tag to an article, [comments](#comments) should also be added to explain what needs to be updated to remove the tag.\n\n### Tagging articles\n\nTags help the website's search engine query articles better. Tags should be written in the same language as the article and include the original list of tags. Tags should use lowercase letters where applicable.\n\nFor example, an article called \"Beatmap discussion\" may include the following tags:\n\n```yaml\ntags:\n - beatmap discussions\n - modding V2\n - MV2\n```\n\n### Translations without reviews\n\n*Note: Wiki maintainers will determine and apply this mark prior to merging.*\n\nSometimes, translations are added to the wiki without review from other native speakers of the language. In this case, the `no_native_review` mark is added to let future translators know that it may need to be checked again. This tag must be written as shown below:\n\n```yaml\nno_native_review: true\n```\n\n## Article naming\n\n*See also: [Folder names](#folder-names) and [Titles](#titles)*\n\nArticle titles should be singular and use sentence case. See [Wikipedia's naming conventions article](https://en.wikipedia.org/wiki/Wikipedia:Naming_conventions_(plurals) \"Wikipedia\") for more details.\n\nArticle titles should match the folder name it is in (spaces may replace underscores (`_`) where appropriate). If the folder name changes, the article title should be changed to match it and vice versa.\n\n---\n\nContest and tournament articles are an exception. The folder name must use abbreviations, acronyms, or initialisms. The article's title must be the full name of the contest or tournament.\n\n## Folder and file structure\n\n### Folder names\n\n*See also: [Article naming](#article-naming)*\n\nFolder names must be in English and use sentence case.\n\nFolder names must only use these characters:\n\n- uppercase and lowercase letters\n- numbers\n- underscores (`_`)\n- hyphens (`-`)\n- exclamation marks (`!`)\n\n### Article file names\n\nThe file name of an article can be found in the `File Name` column of the [locales section](#locales). The location of a translated article must be placed in the same folder as the English article.\n\n### Index articles\n\nAn index article must be created if the folder is intended to only hold other articles. Index articles must contain a list of articles that are inside its own folder. They may also contain other information, such as a lead paragraph or descriptions of the linked articles.\n\n### Disambiguation articles\n\n[Disambiguation](/wiki/Disambiguation) articles must be placed in the `/wiki/Disambiguation` folder. The main page must be updated to include the disambiguation article. Refer to [Disambiguation/Mod](/wiki/Disambiguation/Mod) as an example.\n\nRedirects must be updated to have the ambiguous keyword(s) redirect to the disambiguation article.\n\nArticles linked from a disambiguation article must have a [For other uses](#for-other-uses) hatnote.\n\n## HTML\n\nHTML must not be used, with exception for [comments](#comments). The structure of the article must be redone if HTML is used.\n\n### Comments\n\nHTML comments should be used for marking to-dos, but may also be used to annotate text. They should be on their own line, but can be placed inline in a paragraph. If placed inline, the start of the comment must not have a space.\n\nBad example:\n\n```markdown\nHTML comments should be used for marking to-dos or annotate text.\n```\n\nGood example:\n\n```markdown\nHTML comments should be used for marking to-dos or annotate text.\n```\n\n## Editing\n\n### End of line sequence\n\n*Caution: Uploading Markdown files using `CRLF` (carriage return and line feed) via GitHub will result in those files using `CRLF`. To prevent this, set the line ending to `LF` (line feed) before uploading.*\n\nMarkdown files must be checked in using the `LF` end of line sequence.\n\n### Escaping\n\nMarkdown syntax should be escaped as needed. However, article titles are parsed as plain text and so must not be escaped.\n\n### Paragraphs\n\nEach paragraph must be followed by one empty line.\n\n### Line breaks\n\nLine breaks must use a backslash (`\\`).\n\nLine breaks must be used sparingly.\n\n## Hatnote\n\n*Not to be confused with [Notice](#notice).*\n\nHatnotes are short notes placed at the top of an article or section to help readers navigate to related articles or inform them about related topics.\n\nHatnotes must be italicised and be placed immediately after the heading. If multiple hatnotes are used, they must be on the same paragraph separated with a line break.\n\n### Main page\n\n*Main page* hatnotes direct the reader to the main article of a topic. When this hatnote is used, it implies that the section it is on is a summary of what the linked page is about. This hatnote should have only one link. These must be formatted as follows:\n\n```markdown\n*Main page: {article}*\n\n*Main pages: {article} and {article}*\n```\n\n### See also\n\n*See also* hatnotes suggest to readers other points of interest from a given article or section. These must be formatted as follows:\n\n```markdown\n*See also: {article}*\n\n*See also: {article} and {article}*\n```\n\n### For see\n\n*For see* hatnotes are similar to *see also* hatnotes, but are generally more descriptive and direct. This hatnote may use more than one link if necessary. These must be formatted as follows:\n\n```markdown\n*For {description}, see: {article}`*\n\n*For {description}, see: {article} and {article}`*\n```\n\n### Not to be confused with\n\n*Not to be confused with* hatnotes help distinguish ambiguous or misunderstood article titles or sections. This hatnote may use more than one link if necessary. These must be formatted as follows:\n\n```markdown\n*Not to be confused with {article}.*\n\n*Not to be confused with {article} or {article}.*\n```\n\n### For other uses\n\n*For other uses* hatnotes are similar to *not to be confused with* hatnotes, but links directly to the [disambiguation article](#disambiguation-articles). This hatnote must only link to the disambiguation article. These must be formatted as follows:\n\n```markdown\n*For other uses, see {disambiguation article}.*\n```\n\n## Notice\n\n*Not to be confused with [Hatnote](#hatnote).*\n\nA notice should be placed where appropriate in a section, but must start off the paragraph and use italics. Notices may contain bolding where appropriate, but should be kept to a minimum. Notices must be written as complete sentences. Thus, unlike most [hatnotes](#hatnotes), must use a full stop (`.`) or an exclamation mark (`!`) if appropriate. Anything within the same paragraph of a notice must also be italicised. These must be formatted as follows:\n\n```markdown\n*Note: {note}.*\n\n*Notice: {notice}.*\n\n*Caution: {caution}.*\n\n*Warning: {warning}.*\n```\n\n- `Note` should be used for factual or trivial details.\n- `Notice` should be used for reminders or to draw attention to something that the reader should be made aware of.\n- `Caution` should be used to warn the reader to avoid unintended consequences.\n- `Warning` should be used to warn the reader that action may be taken against them.\n\n## Emphasising\n\n### Bold\n\nBold must use double asterisks (`**`).\n\nLead paragraphs may bold the first occurrence of the article's title.\n\n### Italics\n\nItalics must use single asterisks (`*`).\n\nNames of work or video games should be italicised. osu!—the game—is exempt from this.\n\nThe first occurrence of an abbreviation, acronym, or initialism may be italicised.\n\nItalics may also be used to provide emphasis or help with readability.\n\n## Headings\n\nAll headings must use sentence case.\n\nHeadings must use the [ATX (hash) style](https://github.github.com/gfm/#atx-headings \"GitHub\") and must have an empty line before and after the heading. The title heading is an exception when it is on the first line. If this is the case, there only needs to be an empty line after the title heading.\n\nHeadings must not exceed a heading level of 5 and must not be used to style or format text.\n\n### Titles\n\n*See also: [Article naming](#article-naming)*\n\n*Caution: Titles are parsed as plain text; they must not be escaped.*\n\nThe first heading in all articles must be a level 1 heading, being the article's title. All headings afterwards must be [section headings](#sections). Titles must not contain formatting, links, or images.\n\nThe title heading must be on the first line, unless [front matter](#front-matter) is being used. If that is the case, the title heading must go after it and have an empty line before the title heading.\n\n### Sections\n\nSection headings must use levels 2 to 5. The section heading proceeding the [title heading](#titles) must be a level 2 heading. Unlike titles, section headings may have small image icons.\n\nSection headings must not skip a heading level (i.e. do not go from a level 2 heading to a level 4 heading) and must not contain formatting or links.\n\n*Notice: On the website, heading levels 4 and 5 will not appear in the table of contents. They cannot be linked to directly either.*\n\n## Lists\n\nLists should not go over 4 levels of indentation and should not have an empty line in between each item.\n\nFor nested lists, bullets or numbers must align with the item content of their parent lists.\n\nThe following example was done incorrectly (take note of the spacing before the bullet):\n\n```markdown\n1. Fly a kite\n - Don't fly a kite if it's raining\n```\n\nThe following example was done correctly:\n\n```markdown\n1. Fly a kite\n - Don't fly a kite if it's raining\n```\n\n### Bulleted\n\nBulleted lists must use a hyphen (`-`). These must then be followed by one space. (Example shown below.)\n\n```markdown\n- osu!\n - Hit circle\n - Combo number\n - Approach circle\n - Slider\n - Hit circles\n - Slider body\n - Slider ticks\n - Spinner\n- osu!taiko\n```\n\n### Numbered\n\nThe numbers in a numbered list must be incremented to represent their step.\n\n```markdown\n1. Download the osu! installer.\n2. Run the installer.\n 1. To change the installation location, click the text underneath the progression bar.\n 2. The installer will prompt for a new location, choose the installation folder.\n3. osu! will start up once installation is complete.\n4. Sign in.\n```\n\n### Mixed\n\nCombining both bulleted and numbered lists should be done sparingly.\n\n```markdown\n1. Download a skin from the forums.\n2. Load the skin file into osu!.\n - If the file is a `.zip`, unzip it and move the contents into the `Skins/` folder (found in your osu! installation folder).\n - If the file is a `.osk`, open it on your desktop or drag-and-drop it into the game client.\n3. Open osu!, if it is not opened, and select the skin in the options.\n - This may have been completed if you opened the `.osk` file or drag-and-dropped it into the game client.\n```\n\n## Code\n\nThe markup for code is a grave mark (`` ` ``). To put grave marks in code, use double grave marks instead. If the grave mark is at the start or end, pad it with one space. (Example shown below.)\n\n```markdown\n`` ` ``\n`` `Space` ``\n```\n\n### Keyboard keys\n\n*Notice: When denoting the letter itself, and not the keyboard key, use quotation marks instead.*\n\nWhen representing keyboard keys, use capital letters for single characters and title case for modifiers. Use the plus symbol (`+`) (without code) to represent key combinations. (Example shown below.)\n\n```markdown\npippi is spelt with a lowercase \"p\" like peppy.\n\nPress `Ctrl` + `O` to open the open dialog.\n```\n\nWhen representing a space or the spacebar, use `` `Space` ``.\n\n### Button and menu text\n\nWhen copying the text from a menu or button, the letter casing should be copied as it appears. (Example shown below.)\n\n```markdown\nThe `osu!direct` button is visible in the main menu on the right side, if you have an active osu!supporter tag.\n```\n\n### Folder and directory names\n\nWhen copying the name of a folder or directory, the letter casing should be copied as it appears, but prefer lowercased paths when possible. Directory paths must not be absolute (i.e. do not start the directory name from the drive letter or from the root folder). (Example shown below.)\n\n```markdown\nosu! is installed in the `AppData/Local` folder by default, unless specified otherwise during installation.\n```\n\n### Keywords and commands\n\nWhen copying a keyword or command, the letter casing should be copied as it appears or how someone normally would type it. If applicable, prefer lowercase letters. (Example shown below.)\n\n```markdown\nAs of now, the `Name` and `Author` commands in the skin configuration file (`skin.ini`) do nothing.\n```\n\n### File names\n\nWhen copying the name of a file, the letter casing should be copied as it appears. If applicable, prefer lowercase letters. (Example shown below.)\n\n```markdown\nTo play osu!, double click the `osu!.exe` icon.\n```\n\n### File extensions\n\n*Notice: File formats (not to be confused with file extensions) must be written in capital letters without the prefixed fullstop (`.`).*\n\nFile extensions must be prefixed with a fullstop (`.`) and be followed by the file extension in lowercase letters. (Example shown below.)\n\n```markdown\nThe JPG (or JPEG) file format has the `.jpg` (or `.jpeg`) extension.\n```\n\n### Chat channels\n\nWhen copying the name of a chat channel, start it with a hash (`#`), followed by the channel name in lowercase letters. (Example shown below.)\n\n```markdown\n`#lobby` is where you can advertise your multi room.\n```\n\n## Preformatted text (code blocks)\n\n*Notice: Syntax highlighting for preformatted text is not implemented on the website yet.*\n\nPreformatted text (also known as code blocks) must be fenced using three grave marks. They should set the language identifier for syntax highlighting.\n\n## Links\n\nThere are two types of links: inline and reference. Inline has two styles.\n\nThe following is an example of both inline styles:\n\n```markdown\n[Game Modifiers](/wiki/Game_Modifiers)\n\n\n```\n\nThe following is an example of the reference style:\n\n```markdown\n[Game Modifiers][game mods link]\n\n[game mods link]: /wiki/Game_Modifiers\n```\n\n---\n\nLinks must use the inline style if they are only referenced once. The inline angle brackets style should be avoided. References to reference links must be placed at the bottom of the article.\n\n### Internal links\n\n*Note: Internal links refer to links that stay inside the `https://osu.ppy.sh/` domain.*\n\n#### Wiki links\n\nAll links that point to an wiki article should start with `/wiki/` followed by the path to get to the article you are targeting. Relative links may also be used. Some examples include the following:\n\n```markdown\n[FAQ](/wiki/FAQ)\n[pippi](/wiki/Mascots#-pippi)\n[Beatmaps](../)\n[Pattern](./Pattern)\n```\n\nWiki links must not use redirects and must not have a trailing forward slash (`/`).\n\nBad examples include the following:\n\n```markdown\n[Article styling criteria](/wiki/ASC)\n[Developers](/wiki/Developers/)\n[Developers](/wiki/Developers/#game-client-developers)\n```\n\nGood examples include the following:\n\n```markdown\n[Article styling criteria](/wiki/Article_styling_criteria)\n[Developers](/wiki/Developers)\n[Developers](/wiki/Developers#game-client-developers)\n```\n\n##### Sub-article links\n\nWiki links that point to a sub-article should include the parent article's folder name in its link text. See the following example:\n\n```markdown\n*See also: [Beatmap Editor/Design](/wiki/Beatmap_Editor/Design)*\n```\n\n##### Section links\n\n*Notice: On the website, heading levels 4 and 5 are not given the id attribute. This means that they can not be linked to directly.*\n\nWiki links that point to a section of an article may use the section sign symbol (`§`). See the following example:\n\n```markdown\n*For timing rules, see: [Ranking Criteria § Timing](/wiki/Ranking_Criteria#timing)*\n```\n\n#### Other osu! links\n\nThe URL from the address bar of your web browser should be copied as it is when linking to other osu! web pages. The `https://osu.ppy.sh` part of the URL must be kept.\n\n##### User profiles\n\nAll usernames must be linked on first occurrence. Other occurrences are optional, but must be consistent throughout the entire article for all usernames. If it is difficult to determine the user's id, it may be skipped over.\n\nWhen linking to a user profile, the user's id number must be used. Use the new website (`https://osu.ppy.sh/users/{username})`) to get the user's id.\n\nThe link text of the user link should be the user's current name.\n\n##### Difficulties\n\nWhenever linking to a single difficulty, use this format as the link text:\n\n```\n{artist} - {title} ({creator}) [{difficuty_name}]\n```\n\nThe link must actually link to that difficulty. Beatmap difficulty URLs must be formatted as follows:\n\n```\nhttps://osu.ppy.sh/beatmapsets/{BeatmapSetID}#{mode}/{BeatmapID}\n```\n\nThe difficulty name may be left outside of the link text, but doing so must be consistent throughout the entire article.\n\n##### Beatmaps\n\nWhenever linking to a beatmap, use this format as the link text:\n\n```\n{artist} - {title} ({creator})\n```\n\nAll beatmap URLs must be formatted as follows:\n\n```\nhttps://osu.ppy.sh/beatmapsets/{BeatmapSetID}\n```\n\n### External links\n\n*Notice: External links refers to links that go outside the `https://osu.ppy.sh/` domain.*\n\nThe `https` protocol must be used, unless the site does not support it. External links must be a clean and direct link to a reputable source. The link text should be the title of the page it is linking to. The URL from the address bar of your web browser should be copied as it is when linking to other external pages.\n\nThere are no visual differences between external and osu! web links. Due to this, the website name should be included in the title text. See the following example:\n\n```markdown\n*For more information about music theory, see: [Music theory](https://en.wikipedia.org/wiki/Music_theory \"Wikipedia\")*\n```\n\n## Images\n\nThere are two types of image links: inline and reference. Examples:\n\n**Inline style:**\n\n```markdown\n![](/wiki/shared/flag/AU.gif)\n```\n\n**Reference style:**\n\n```markdown\n![][flag_AU]\n\n[flag_AU]: /wiki/shared/flag/AU.gif\n```\n\nImages should use the inline linking style. References to reference links must be placed at the bottom of the article.\n\nImages must be placed in a folder named `img`, located in the article's folder. Images that are used in multiple articles should be stored in the `/wiki/shared/` folder.\n\n### Image caching\n\nImages on the website are cached for up to 60 days. The cached image is matched with the image link's URL.\n\nWhen updating an image, either change the image's name or append a query string to the URL. In both cases, all translations linking to the updated image should also be updated.\n\n### Formats and quality\n\nImages should use the JPG format at quality 8 (80 or 80%, depending on the program). If the image contains transparency or has text that must be readable, use the PNG format instead. If the image contains an animation, the GIF format can be used; however, this should be used sparingly as these may take longer to load or can be bigger then the [max file size](#file-size).\n\n### File size\n\nImages must be under 1 megabyte, otherwise they will fail to load. Downscaling and using JPG at 80% is almost always under the size limit.\n\nAll images should be optimised as much as possible. Use [jpeg-archive](https://github.com/danielgtaylor/jpeg-archive \"GitHub\") to compress JPEG images. For consistency, use the following command for jpeg-archive:\n\n```sh\njpeg-recompress -am smallfry \n```\n\nWhere `` is the file name to be compressed and `` is the compressed file name.\n\n### File names\n\n*Notice: File extensions must use lowercase letters, otherwise they will fail to load!*\n\nUse hyphens (`-`) when spacing words. When naming an image, the file name should be meaningful or descriptive but short.\n\n### Formatting and positioning\n\n*Note: It is currently not possible to float an image or have text wrap around it.*\n\nImages on the website will be centred when it is on a single line, by themself. Otherwise, they will be positioned inline with the paragraph. The following example will place the image in the center:\n\n```markdown\nInstalling osu! is easy. First, download the installer from the download page.\n\n![](img/download-page.jpg)\n\nThen locate the installer and run it.\n```\n\n### Alt text\n\nImages should have alt text unless it is for decorative purposes.\n\n### Captions\n\nImages are given captions on the website if they fulfill these conditions:\n\n1. The image is by itself.\n2. The image is not inside a heading.\n3. The image has title text.\n\nCaptions are assumed via the title text, which must be in plain text. Images with captions are also centred with the image on the website.\n\n### Max image width\n\nThe website's max image width is the width of the article body. Images should be no wider than 800 pixels.\n\n### Annotating images\n\nWhen annotating images, use *Torus Regular*. For Chinese, Korean, Japanese characters, use *Microsoft YaHei*.\n\nAnnotating images should be avoided, as it is difficult for translators (and other editors) to edit them.\n\n#### Translating annotated images\n\nWhen translating annotated images, the localised image version must be placed in the same directory as the original version (i.e. the English version). The filename of a localised image version must start with the original version's name, followed by a hyphen, followed by the locale name (in capital letters). See the following examples:\n\n- `hardrock-mod-vs-easy-mod.jpg` for English\n- `hardrock-mod-vs-easy-mod-DE.jpg` for German\n- `hardrock-mod-vs-easy-mod-ZH-TW.jpg` for Traditional Chinese\n\n### Screenshots of gameplay\n\nAll screenshots of gameplay must be done in the stable build, unless it is for a specific feature that is unavailable in the stable build. You should use the in-game screenshot feature (`F12`).\n\n#### Game client settings\n\n*Note: If you do not want to change your current settings for the wiki, you can move your `osu!..cfg` out of the osu! folder and move it back later.*\n\nYou must set these settings before taking a screenshot of the game client (settings not stated below are assumed to be at their defaults):\n\n- Select language: `English`\n- Prefer metadata in original language: `Enabled`\n- Resolution: `1280x720`\n- Fullscreen mode: `Disabled`\n- Parallax: `Disabled`\n- Menu tips: `Disabled`\n- Seasonal backgrounds: `Never`\n- Always show key overlay: `Enabled`\n- Current skin: `Default` (first option)\n\n*Notice to translators: If you are translating an article containing screenshots of the game, you may set the game client's language to the language you are translating in.*\n\n### Image links\n\nImages must not be part of a link text.\n\nFlag icons next to user links must be separate from the link text. See the following example:\n\n```markdown\n![][flag_AU] [peppy](https://osu.ppy.sh/users/2)\n```\n\n### Flag icons\n\n*For a list of flag icons, see: [issue \\#328](https://github.com/ppy/osu-wiki/issues/328 \"GitHub\")*\n\nThe flag icons use the two letter code (in all capital letters) and end with `.gif`. When adding a flag inline, use this format:\n\n```markdown\n![](/wiki/shared/flag/xx.gif)\n```\n\nWhere `xx` is the [ISO 3166-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 \"Wikipedia\") two-lettered country code for the flag.\n\nThe full country name should be added in the title text. The country code in the alternate text is optional, but must be applied to all flag icons in the article.\n\n## Tables\n\nTables on the website only support headings along the first row.\n\nTables must not be beautified (do not pad cells with extra spaces to make their widths uniform). They must have a vertical bar (`|`) on the left and right sides and the text of each cell must be padded with one space on both sides. Empty cells must use a vertical bar (`|`) followed by two spaces then another vertical bar (`|`).\n\nThe delimiter row (the next line after the table heading) must use only three characters per column (and be padded with a space on both sides), which must look like one of the following:\n\n- `:--` (for left align)\n- `:-:` (for centre align)\n- `--:` (for right align)\n\n---\n\nThe following is an example of what a table should look like:\n\n```markdown\n| Team \"Picturesque\" Red | Score | Team \"Statuesque\" Blue | Average Beatmap Stars |\n| :-- | :-: | --: | :-- |\n| **peppy** | 5 - 2 | pippi | 9.3 stars |\n| Aiko | 1 - 6 | **Alisa** | 4.2 stars |\n| Ryūta | 3 - 4 | **Yuzu** | 5.1 stars |\n| **Taikonator** | 7 - 0 | Tama | 13.37 stars |\n| Maria | No Contest | Mocha | |\n```\n\n## Blockquotes\n\nThe blockquote is limited to quoting text from someone. It must not be used to format text otherwise.\n\n## Thematic breaks\n\nThe thematic break (also known as the horizontal rule or line) should be used sparingly. A few uses of the thematic break may include (but is not limited to):\n\n- separating images from text\n- separating multiple images that follow one another\n- shifting the topic within a section\n\nThese must have an empty line before and after the markup. Thematic breaks must use only three hyphens, as depicted below:\n\n```markdown\n---\n```\n" }; + + // From https://osu.ppy.sh/api/v2/wiki/en/Article_styling_criteria + private APIWikiPage responseArticleParentPage => new APIWikiPage + { + Title = "Article styling criteria", + Layout = "markdown_page", + Path = "Article_styling_criteria", + Locale = "en", + Subtitle = null, + Markdown = + "---\ntags:\n - wiki standards\n---\n\n# Article styling criteria\n\n*For news posts, see: [News Styling Criteria](/wiki/News_styling_criteria)*\n\nThe article styling criteria (ASC) serve as the osu! wiki's enforced styling standards to keep consistency in clarity, formatting, and layout in all articles, and to help them strive for proper grammar, correct spelling, and correct information.\n\nThese articles are primarily tools to aid in reviewing and represent the consensus of osu! wiki contributors formed over the years. Since the wiki is a collaborative effort through the review process, it is not necessary to read or memorise all of the ASC at once. If you are looking to contribute, read the [contribution guide](/wiki/osu!_wiki/Contribution_guide).\n\nTo suggest changes regarding the article styling criteria, [open an issue on GitHub](https://github.com/ppy/osu-wiki/issues/new).\n\n## Standards\n\n*Notice: The articles below use [RFC 2119](https://tools.ietf.org/html/rfc2119) to describe requirement levels.*\n\nThe article styling criteria are split up into two articles:\n\n- [Formatting](Formatting): includes Markdown and other formatting rules\n- [Writing](Writing): includes writing practices and other grammar rules\n" + }; } } diff --git a/osu.Game/Online/API/Requests/GetWikiRequest.cs b/osu.Game/Online/API/Requests/GetWikiRequest.cs index e0f967ec15..7c84e1f790 100644 --- a/osu.Game/Online/API/Requests/GetWikiRequest.cs +++ b/osu.Game/Online/API/Requests/GetWikiRequest.cs @@ -11,15 +11,16 @@ namespace osu.Game.Online.API.Requests { public class GetWikiRequest : APIRequest { - private readonly string path; + public readonly string Path; + private readonly Language language; public GetWikiRequest(string path, Language language = Language.en) { - this.path = path; + Path = path; this.language = language; } - protected override string Target => $"wiki/{language.ToCultureCode()}/{path}"; + protected override string Target => $"wiki/{language.ToCultureCode()}/{Path}"; } } From 474c1a8a7a9f2a1d91d60182596604333890f202 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 20 Jul 2022 08:58:24 +0300 Subject: [PATCH 157/481] Fix wiki overlay not handling path redirection properly --- osu.Game/Overlays/WikiOverlay.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index 3c5cb82a88..b54aeb4aa3 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -100,6 +100,11 @@ namespace osu.Game.Overlays private void onPathChanged(ValueChangedEvent e) { + // the path could change as a result of redirecting to another path cof the same page. + // we already have the correct wiki data, so we can safely return here. + if (e.NewValue == wikiData.Value?.Path) + return; + cancellationToken?.Cancel(); request?.Cancel(); @@ -121,6 +126,7 @@ namespace osu.Game.Overlays private void onSuccess(APIWikiPage response) { wikiData.Value = response; + path.Value = response.Path; if (response.Layout == index_path) { From de29078db2b869f2af498fb2a33ba5b4443d361a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 20 Jul 2022 15:16:40 +0900 Subject: [PATCH 158/481] Remove nullable disables --- osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs | 2 -- osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs | 6 ++---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs b/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs index 937d4358d5..b6968f4e06 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.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 enable - using System.Linq; using osu.Framework.Graphics; using osu.Game.Database; diff --git a/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs index 89404b2878..b80eb40018 100644 --- a/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs +++ b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,9 +19,8 @@ namespace osu.Game.Screens.Select.Carousel public class UpdateBeatmapSetButton : OsuAnimatedButton { private readonly BeatmapSetInfo beatmapSetInfo; - private SpriteIcon icon; - - private Box progressFill; + private SpriteIcon icon = null!; + private Box progressFill = null!; public UpdateBeatmapSetButton(BeatmapSetInfo beatmapSetInfo) { From 5e933cb4668946a2d8a34540b83c5345aba220f0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 20 Jul 2022 09:43:32 +0300 Subject: [PATCH 159/481] Improve comment wording Co-authored-by: Dean Herbert --- osu.Game/Overlays/WikiOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index b54aeb4aa3..148d2977c7 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -100,7 +100,7 @@ namespace osu.Game.Overlays private void onPathChanged(ValueChangedEvent e) { - // the path could change as a result of redirecting to another path cof the same page. + // the path could change as a result of redirecting to a newer location of the same page. // we already have the correct wiki data, so we can safely return here. if (e.NewValue == wikiData.Value?.Path) return; From d2a3c2594d04a15d575f33e7afff637abaec8e80 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 20 Jul 2022 16:33:52 +0900 Subject: [PATCH 160/481] Fix inspections --- osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs | 2 +- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs b/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs index d0176da0e9..e7a6e9a543 100644 --- a/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs +++ b/osu.Game.Tests/Online/TestSceneBeatmapDownloading.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Online { AddStep("download beatmap", () => beatmaps.Download(test_db_model)); - AddStep("cancel download from request", () => beatmaps.GetExistingDownload(test_db_model).Cancel()); + AddStep("cancel download from request", () => beatmaps.GetExistingDownload(test_db_model)!.Cancel()); AddUntilStep("is removed from download list", () => beatmaps.GetExistingDownload(test_db_model) == null); AddAssert("is notification cancelled", () => recentNotification.State == ProgressNotificationState.Cancelled); diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 31bc6dacf8..536322805b 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -126,10 +126,10 @@ namespace osu.Game.Tests.Online AddStep("start downloading", () => beatmapDownloader.Download(testBeatmapSet)); addAvailabilityCheckStep("state downloading 0%", () => BeatmapAvailability.Downloading(0.0f)); - AddStep("set progress 40%", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet)).SetProgress(0.4f)); + AddStep("set progress 40%", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet))!.SetProgress(0.4f)); addAvailabilityCheckStep("state downloading 40%", () => BeatmapAvailability.Downloading(0.4f)); - AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet)).TriggerSuccess(testBeatmapFile)); + AddStep("finish download", () => ((TestDownloadRequest)beatmapDownloader.GetExistingDownload(testBeatmapSet))!.TriggerSuccess(testBeatmapFile)); addAvailabilityCheckStep("state importing", BeatmapAvailability.Importing); AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); From 53e61c5041954c570fb1b5f08a73dbc43da5ed8a Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 3 Jul 2022 22:42:41 +0800 Subject: [PATCH 161/481] Remove the nullable annotation in the catch ruleset. --- osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModClassic.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModMuted.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 2 -- osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs | 2 -- 20 files changed, 40 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs index c5ca595fd6..50e48101d3 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Replays; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs index 10a0809e05..7eda6b37d3 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModClassic.cs b/osu.Game.Rulesets.Catch/Mods/CatchModClassic.cs index 904656993e..9624e84018 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModClassic.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModClassic.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Mods diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs index 8d4b57c244..cae19e9468 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDaycore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Mods diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs index 6927d7953f..e59a0a0431 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDifficultyAdjust.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.Bindables; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs b/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs index c58ce9b07d..57c06e1cd1 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModDoubleTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Mods diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs index bea9b094fa..16ef56d845 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModEasy.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Mods diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index d166646eaf..7f7c04fb52 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Configuration; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs index 1fe892c9b5..63203dd57c 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFloatingFruits.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Catch.Objects; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs index 0c7886be10..ce06b841aa 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHalfTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Mods diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs index 39b992b3f5..93eadcc13e 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHardRock.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Beatmaps; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs index b4fbc9d566..51516edacd 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModHidden.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModHidden.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.Graphics; using osu.Game.Rulesets.Catch.Objects; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs b/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs index 89fc40356d..a97e940a64 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModMirror.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModMirror.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.Game.Beatmaps; using osu.Game.Rulesets.Catch.Beatmaps; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModMuted.cs b/osu.Game.Rulesets.Catch/Mods/CatchModMuted.cs index 6b28d1a127..6d2565440a 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModMuted.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModMuted.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs index 1fd2227eb7..9e38913be7 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNightcore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs index 89e7e4bcd6..3c02646e99 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoFail.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Mods diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs index 385d4c50c0..a24a6227fe 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.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.Bindables; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs b/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs index 0a74ee4fbb..fb92399102 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModPerfect.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Mods diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index f4d6fb9ab3..d0a94767d1 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs b/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs index d98829137c..68e01391ce 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModSuddenDeath.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Catch.Mods From 91bc7b9381093ca30fb0e501be9a7b6dad4440d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 23:18:27 +0800 Subject: [PATCH 162/481] Mark the class as non-nullable. Not the safe way but got no better idea. --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 2 +- osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 7f7c04fb52..abe391ba4e 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Mods protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield); - private CatchPlayfield playfield; + private CatchPlayfield playfield = null!; public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index d0a94767d1..60f1614d98 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public override string Description => @"Use the mouse to control the catcher."; - private DrawableRuleset drawableRuleset; + private DrawableRuleset drawableRuleset = null!; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { From 6a096cf11fad6feb5847266a7f66bb113573b7c6 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Wed, 20 Jul 2022 20:30:04 +0800 Subject: [PATCH 163/481] Remove nullable disable annotation in the Catch test case. --- osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs | 2 -- osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs | 2 -- osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs | 2 -- osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs | 2 -- 4 files changed, 8 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs b/osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs index 19321a48b9..fbbfee6b60 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/CatchModMirrorTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Utils; diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs index ffc5734f01..bbe543e73e 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Utils; diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs index 886822f9a5..3e06e78dba 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Catch.Objects; diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs index 3209be12d5..c01aff0aa0 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using NUnit.Framework; From 0c3d43026d956be5b9e6ba8ed0534eaf99a0f378 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 02:53:29 +0900 Subject: [PATCH 164/481] Add initial structure for fps counter --- .../UserInterface/TestSceneFPSCounter.cs | 51 ++++++ osu.Game/Graphics/UserInterface/FPSCounter.cs | 149 ++++++++++++++++++ .../UserInterface/FPSCounterTooltip.cs | 96 +++++++++++ 3 files changed, 296 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneFPSCounter.cs create mode 100644 osu.Game/Graphics/UserInterface/FPSCounter.cs create mode 100644 osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneFPSCounter.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneFPSCounter.cs new file mode 100644 index 0000000000..d78707045b --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneFPSCounter.cs @@ -0,0 +1,51 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneFPSCounter : OsuTestScene + { + [SetUpSteps] + public void SetUpSteps() + { + AddStep("create display", () => + { + Children = new Drawable[] + { + new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new FPSCounter(), + new FPSCounter { Scale = new Vector2(2) }, + new FPSCounter { Scale = new Vector2(4) }, + } + }, + }; + }); + } + + [Test] + public void TestBasic() + { + } + } +} diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs new file mode 100644 index 0000000000..222c30c1dc --- /dev/null +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -0,0 +1,149 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Localisation; +using osu.Framework.Platform; +using osu.Framework.Threading; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Graphics.UserInterface +{ + public class FPSCounter : CompositeDrawable, IHasCustomTooltip + { + private RollingCounter msCounter = null!; + private RollingCounter fpsCounter = null!; + + private Container mainContent = null!; + + public FPSCounter() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + InternalChildren = new Drawable[] + { + mainContent = new Container + { + Alpha = 0, + Size = new Vector2(30), + Children = new Drawable[] + { + msCounter = new FrameTimeCounter + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Colour = colours.Orange2, + }, + fpsCounter = new FramesPerSecondCounter + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Y = 11, + Scale = new Vector2(0.8f), + Colour = colours.Lime3, + } + } + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + displayTemporarily(); + } + + private bool isDisplayed; + + private ScheduledDelegate? fadeOutDelegate; + + private void displayTemporarily() + { + if (!isDisplayed) + mainContent.FadeTo(1, 300, Easing.OutQuint); + + fadeOutDelegate?.Cancel(); + fadeOutDelegate = Scheduler.AddDelayed(() => + { + mainContent.FadeTo(0, 1000, Easing.In); + isDisplayed = false; + }, 2000); + } + + [Resolved] + private GameHost gameHost { get; set; } = null!; + + protected override void Update() + { + base.Update(); + + // TODO: this is wrong (elapsed clock time, not actual run time). + double newFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; + double newFps = gameHost.DrawThread.Clock.FramesPerSecond; + + bool hasSignificantChanges = + Math.Abs(msCounter.Current.Value - newFrameTime) > 5 || + Math.Abs(fpsCounter.Current.Value - newFps) > 10; + + if (hasSignificantChanges) + displayTemporarily(); + + msCounter.Current.Value = newFrameTime; + fpsCounter.Current.Value = newFps; + } + + public ITooltip GetCustomTooltip() => new FPSCounterTooltip(); + + public object TooltipContent => this; + + public class FramesPerSecondCounter : RollingCounter + { + protected override double RollingDuration => 400; + + protected override OsuSpriteText CreateSpriteText() + { + return new OsuSpriteText + { + Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), + Spacing = new Vector2(-2), + }; + } + + protected override LocalisableString FormatCount(double count) + { + return $"{count:#,0}fps"; + } + } + + public class FrameTimeCounter : RollingCounter + { + protected override double RollingDuration => 1000; + + protected override OsuSpriteText CreateSpriteText() + { + return new OsuSpriteText + { + Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), + Spacing = new Vector2(-1), + }; + } + + protected override LocalisableString FormatCount(double count) + { + if (count < 1) + return $"{count:N1}ms"; + + return $"{count:N0}ms"; + } + } + } +} diff --git a/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs b/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs new file mode 100644 index 0000000000..af95fbe556 --- /dev/null +++ b/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs @@ -0,0 +1,96 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Platform; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Graphics.UserInterface +{ + public class FPSCounterTooltip : CompositeDrawable, ITooltip + { + private OsuTextFlowContainer textFlow = null!; + + [Resolved] + private GameHost gameHost { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AutoSizeAxes = Axes.Both; + + CornerRadius = 15; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Gray1, + Alpha = 1, + RelativeSizeAxes = Axes.Both, + }, + new OsuTextFlowContainer(cp => + { + cp.Font = OsuFont.Default.With(weight: FontWeight.SemiBold); + cp.Spacing = new Vector2(-1); + }) + { + AutoSizeAxes = Axes.Both, + TextAnchor = Anchor.TopRight, + Margin = new MarginPadding { Left = 5, Vertical = 10 }, + Text = string.Join('\n', gameHost.Threads.Select(t => t.Name)) + }, + textFlow = new OsuTextFlowContainer(cp => + { + cp.Font = OsuFont.Default.With(fixedWidth: true, weight: FontWeight.Regular); + cp.Spacing = new Vector2(-1); + }) + { + Width = 190, + Margin = new MarginPadding { Left = 35, Right = 10, Vertical = 10 }, + AutoSizeAxes = Axes.Y, + TextAnchor = Anchor.TopRight, + }, + }; + } + + private int lastUpdate; + + protected override void Update() + { + int currentSecond = (int)(Clock.CurrentTime / 100); + + if (currentSecond != lastUpdate) + { + lastUpdate = currentSecond; + + textFlow.Clear(); + + foreach (var thread in gameHost.Threads) + { + var clock = thread.Clock; + + string maximum = $"{(clock.MaximumUpdateHz > 0 && clock.MaximumUpdateHz < 10000 ? clock.MaximumUpdateHz.ToString("0") : "∞"),4}"; + + textFlow.AddParagraph($"{clock.FramesPerSecond:0}/{maximum}fps ({clock.ElapsedFrameTime:0.00}ms)"); + } + } + } + + public void SetContent(object content) + { + } + + public void Move(Vector2 pos) + { + Position = pos; + } + } +} From 03e644e548a066efe5c957b6f536b951883fcb8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 20:28:58 +0900 Subject: [PATCH 165/481] Choose colours based on relative performance goals --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 30 ++++++++++++++++--- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 222c30c1dc..a999c1338a 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -4,11 +4,13 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Graphics.Sprites; using osuTK; @@ -21,13 +23,16 @@ namespace osu.Game.Graphics.UserInterface private Container mainContent = null!; + [Resolved] + private OsuColour colours { get; set; } = null!; + public FPSCounter() { AutoSizeAxes = Axes.Both; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { InternalChildren = new Drawable[] { @@ -41,7 +46,6 @@ namespace osu.Game.Graphics.UserInterface { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Colour = colours.Orange2, }, fpsCounter = new FramesPerSecondCounter { @@ -49,7 +53,6 @@ namespace osu.Game.Graphics.UserInterface Origin = Anchor.TopRight, Y = 11, Scale = new Vector2(0.8f), - Colour = colours.Lime3, } } }, @@ -97,8 +100,27 @@ namespace osu.Game.Graphics.UserInterface if (hasSignificantChanges) displayTemporarily(); - msCounter.Current.Value = newFrameTime; + // If the frame time spikes up, make sure it shows immediately on the counter. + if (msCounter.Current.Value < 20 && newFrameTime > 20) + msCounter.SetCountWithoutRolling(newFrameTime); + else + msCounter.Current.Value = newFrameTime; + fpsCounter.Current.Value = newFps; + + fpsCounter.Colour = getColour(fpsCounter.DisplayedCount / gameHost.DrawThread.Clock.MaximumUpdateHz); + + double equivalentHz = 1000 / msCounter.DisplayedCount; + + msCounter.Colour = getColour(equivalentHz / gameHost.UpdateThread.Clock.MaximumUpdateHz); + } + + private ColourInfo getColour(double performanceRatio) + { + if (performanceRatio < 0.5f) + return Interpolation.ValueAt(performanceRatio, colours.Red, colours.Orange2, 0, 0.5, Easing.Out); + + return Interpolation.ValueAt(performanceRatio, colours.Orange2, colours.Lime3, 0.5, 1, Easing.Out); } public ITooltip GetCustomTooltip() => new FPSCounterTooltip(); From 0fb959a565023b4939588b4d97e713bbd6da6606 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 20:29:18 +0900 Subject: [PATCH 166/481] Stay displayed while hovering --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 49 +++++++++++++++++-- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index a999c1338a..58be2ff5a2 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Framework.Threading; @@ -23,6 +25,10 @@ namespace osu.Game.Graphics.UserInterface private Container mainContent = null!; + private Container background = null!; + + private const float idle_background_alpha = 0.4f; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -39,9 +45,24 @@ namespace osu.Game.Graphics.UserInterface mainContent = new Container { Alpha = 0, - Size = new Vector2(30), + AutoSizeAxes = Axes.Both, Children = new Drawable[] { + background = new Container + { + RelativeSizeAxes = Axes.Both, + CornerRadius = 5, + Masking = true, + Alpha = idle_background_alpha, + Children = new Drawable[] + { + new Box + { + Colour = colours.Gray0, + RelativeSizeAxes = Axes.Both, + }, + } + }, msCounter = new FrameTimeCounter { Anchor = Anchor.TopRight, @@ -65,6 +86,20 @@ namespace osu.Game.Graphics.UserInterface displayTemporarily(); } + protected override bool OnHover(HoverEvent e) + { + background.FadeTo(1, 200); + displayTemporarily(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + background.FadeTo(idle_background_alpha, 200); + displayTemporarily(); + base.OnHoverLost(e); + } + private bool isDisplayed; private ScheduledDelegate? fadeOutDelegate; @@ -75,11 +110,15 @@ namespace osu.Game.Graphics.UserInterface mainContent.FadeTo(1, 300, Easing.OutQuint); fadeOutDelegate?.Cancel(); - fadeOutDelegate = Scheduler.AddDelayed(() => + + if (!IsHovered) { - mainContent.FadeTo(0, 1000, Easing.In); - isDisplayed = false; - }, 2000); + fadeOutDelegate = Scheduler.AddDelayed(() => + { + mainContent.FadeTo(0, 1000, Easing.In); + isDisplayed = false; + }, 2000); + } } [Resolved] From 0a1744facaffd299e72d2c8ec8fe30ce29412ae1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 20:49:57 +0900 Subject: [PATCH 167/481] Add to game and bind with configuration setting --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 15 ++++++++++++++- osu.Game/OsuGame.cs | 7 +++++++ osu.Game/OsuGameBase.cs | 16 ---------------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 58be2ff5a2..2dd945d375 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -13,6 +14,7 @@ using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Framework.Utils; +using osu.Game.Configuration; using osu.Game.Graphics.Sprites; using osuTK; @@ -29,6 +31,8 @@ namespace osu.Game.Graphics.UserInterface private const float idle_background_alpha = 0.4f; + private Bindable showFpsDisplay = null!; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -38,7 +42,7 @@ namespace osu.Game.Graphics.UserInterface } [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { InternalChildren = new Drawable[] { @@ -78,12 +82,21 @@ namespace osu.Game.Graphics.UserInterface } }, }; + + showFpsDisplay = config.GetBindable(OsuSetting.ShowFpsDisplay); } protected override void LoadComplete() { base.LoadComplete(); + displayTemporarily(); + + showFpsDisplay.BindValueChanged(showFps => + { + this.FadeTo(showFps.NewValue ? 1 : 0, 100); + displayTemporarily(); + }, true); } protected override bool OnHover(HoverEvent e) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index bd0a2680ae..23af401dbd 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -814,6 +814,13 @@ namespace osu.Game ScreenStack.ScreenPushed += screenPushed; ScreenStack.ScreenExited += screenExited; + loadComponentSingleFile(new FPSCounter + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding(10), + }, topMostOverlayContent.Add); + if (!args?.Any(a => a == @"--no-version-overlay") ?? true) loadComponentSingleFile(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 4b5c9c0815..d07d9379f4 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -17,7 +17,6 @@ using osu.Framework.Development; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Input.Handlers; @@ -192,8 +191,6 @@ namespace osu.Game private DependencyContainer dependencies; - private Bindable fpsDisplayVisible; - private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(global_track_volume_adjust); /// @@ -404,19 +401,6 @@ namespace osu.Game AddFont(Resources, @"Fonts/Venera/Venera-Black"); } - protected override void LoadComplete() - { - base.LoadComplete(); - - // TODO: This is temporary until we reimplement the local FPS display. - // It's just to allow end-users to access the framework FPS display without knowing the shortcut key. - fpsDisplayVisible = LocalConfig.GetBindable(OsuSetting.ShowFpsDisplay); - fpsDisplayVisible.ValueChanged += visible => { FrameStatistics.Value = visible.NewValue ? FrameStatisticsMode.Minimal : FrameStatisticsMode.None; }; - fpsDisplayVisible.TriggerChange(); - - FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None; - } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); From f54aff2ecef798c940a5579ad451ce0ed1b31f8b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 21:05:20 +0900 Subject: [PATCH 168/481] Add global key binding for FPS toggle --- osu.Game/Configuration/OsuConfigManager.cs | 6 ++++++ osu.Game/Graphics/UserInterface/FPSCounter.cs | 17 ++++++++++++----- .../Input/Bindings/GlobalActionContainer.cs | 4 ++++ .../GlobalActionKeyBindingStrings.cs | 5 +++++ osu.Game/OsuGame.cs | 8 +++++++- 5 files changed, 34 insertions(+), 6 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index a523507205..e816fd50f3 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -224,6 +224,12 @@ namespace osu.Game.Configuration return new TrackedSettings { + new TrackedSetting(OsuSetting.ShowFpsDisplay, state => new SettingDescription( + rawValue: state, + name: GlobalActionKeyBindingStrings.ToggleFPSCounter, + value: state ? CommonStrings.Enabled.ToLower() : CommonStrings.Disabled.ToLower(), + shortcut: LookupKeyBindings(GlobalAction.ToggleFPSDisplay)) + ), new TrackedSetting(OsuSetting.MouseDisableButtons, disabledState => new SettingDescription( rawValue: !disabledState, name: GlobalActionKeyBindingStrings.ToggleGameplayMouseButtons, diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 2dd945d375..1d72f772db 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -20,7 +20,7 @@ using osuTK; namespace osu.Game.Graphics.UserInterface { - public class FPSCounter : CompositeDrawable, IHasCustomTooltip + public class FPSCounter : VisibilityContainer, IHasCustomTooltip { private RollingCounter msCounter = null!; private RollingCounter fpsCounter = null!; @@ -31,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface private const float idle_background_alpha = 0.4f; - private Bindable showFpsDisplay = null!; + private readonly BindableBool showFpsDisplay = new BindableBool(true); [Resolved] private OsuColour colours { get; set; } = null!; @@ -83,7 +83,7 @@ namespace osu.Game.Graphics.UserInterface }, }; - showFpsDisplay = config.GetBindable(OsuSetting.ShowFpsDisplay); + config.BindWith(OsuSetting.ShowFpsDisplay, showFpsDisplay); } protected override void LoadComplete() @@ -94,11 +94,18 @@ namespace osu.Game.Graphics.UserInterface showFpsDisplay.BindValueChanged(showFps => { - this.FadeTo(showFps.NewValue ? 1 : 0, 100); - displayTemporarily(); + State.Value = showFps.NewValue ? Visibility.Visible : Visibility.Hidden; + if (showFps.NewValue) + displayTemporarily(); }, true); + + State.BindValueChanged(state => showFpsDisplay.Value = state.NewValue == Visibility.Visible); } + protected override void PopIn() => this.FadeIn(100); + + protected override void PopOut() => this.FadeOut(100); + protected override bool OnHover(HoverEvent e) { background.FadeTo(1, 200); diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 21b49286ea..1ee03a6964 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -45,6 +45,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial), new KeyBinding(InputKey.F10, GlobalAction.ToggleGameplayMouseButtons), new KeyBinding(InputKey.F12, GlobalAction.TakeScreenshot), + new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.F }, GlobalAction.ToggleFPSDisplay), new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings), new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), @@ -328,5 +329,8 @@ namespace osu.Game.Input.Bindings [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTapForBPM))] EditorTapForBPM, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleFPSCounter))] + ToggleFPSDisplay, } } diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 82d03dbb5b..de1a5b189c 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -274,6 +274,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ToggleSkinEditor => new TranslatableString(getKey(@"toggle_skin_editor"), @"Toggle skin editor"); + /// + /// "Toggle FPS counter" + /// + public static LocalisableString ToggleFPSCounter => new TranslatableString(getKey(@"toggle_fps_counter"), @"Toggle FPS counter"); + /// /// "Previous volume meter" /// diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 23af401dbd..c90b4c2403 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -159,6 +159,8 @@ namespace osu.Game protected FirstRunSetupOverlay FirstRunOverlay { get; private set; } + private FPSCounter fpsCounter; + private VolumeOverlay volume; private OsuLogo osuLogo; @@ -814,7 +816,7 @@ namespace osu.Game ScreenStack.ScreenPushed += screenPushed; ScreenStack.ScreenExited += screenExited; - loadComponentSingleFile(new FPSCounter + loadComponentSingleFile(fpsCounter = new FPSCounter { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, @@ -1121,6 +1123,10 @@ namespace osu.Game switch (e.Action) { + case GlobalAction.ToggleFPSDisplay: + fpsCounter.ToggleVisibility(); + return true; + case GlobalAction.ToggleSkinEditor: skinEditor.ToggleVisibility(); return true; From 75453b78c0a39086c12ad04041503863c6d747e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Jul 2022 23:59:09 +0900 Subject: [PATCH 169/481] Adjust colours and metrics --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 14 +++++++++----- osu.Game/OsuGame.cs | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 1d72f772db..001eedff39 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -49,13 +49,14 @@ namespace osu.Game.Graphics.UserInterface mainContent = new Container { Alpha = 0, - AutoSizeAxes = Axes.Both, + Size = new Vector2(42, 26), Children = new Drawable[] { background = new Container { RelativeSizeAxes = Axes.Both, CornerRadius = 5, + CornerExponent = 5f, Masking = true, Alpha = idle_background_alpha, Children = new Drawable[] @@ -71,12 +72,15 @@ namespace osu.Game.Graphics.UserInterface { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + Margin = new MarginPadding(1), + Y = -1, }, fpsCounter = new FramesPerSecondCounter { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Y = 11, + Margin = new MarginPadding(1), + Y = 10, Scale = new Vector2(0.8f), } } @@ -135,7 +139,7 @@ namespace osu.Game.Graphics.UserInterface { fadeOutDelegate = Scheduler.AddDelayed(() => { - mainContent.FadeTo(0, 1000, Easing.In); + mainContent.FadeTo(0, 300, Easing.OutQuint); isDisplayed = false; }, 2000); } @@ -177,9 +181,9 @@ namespace osu.Game.Graphics.UserInterface private ColourInfo getColour(double performanceRatio) { if (performanceRatio < 0.5f) - return Interpolation.ValueAt(performanceRatio, colours.Red, colours.Orange2, 0, 0.5, Easing.Out); + return Interpolation.ValueAt(performanceRatio, colours.Red, colours.Orange2, 0, 0.5); - return Interpolation.ValueAt(performanceRatio, colours.Orange2, colours.Lime3, 0.5, 1, Easing.Out); + return Interpolation.ValueAt(performanceRatio, colours.Orange2, colours.Lime0, 0.5, 0.9); } public ITooltip GetCustomTooltip() => new FPSCounterTooltip(); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c90b4c2403..69d02b95ff 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -820,7 +820,7 @@ namespace osu.Game { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Margin = new MarginPadding(10), + Margin = new MarginPadding(5), }, topMostOverlayContent.Add); if (!args?.Any(a => a == @"--no-version-overlay") ?? true) From 23fd514ca3b691c6e6043288c8d49b2204030477 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Wed, 20 Jul 2022 18:07:02 -0700 Subject: [PATCH 170/481] Use DrawableSliderTail instead of DrawableSlider --- osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs | 11 ++++++----- osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs | 4 +++- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs index d7685c1724..06fa381cbb 100644 --- a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -58,13 +58,14 @@ namespace osu.Game.Rulesets.Osu.Skinning private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { - // We only want DrawableSlider here. DrawableSliderTail doesn't quite work because its - // HitStateUpdateTime is ~36ms before DrawableSlider's HitStateUpdateTime (aka slider - // end leniency). - if (drawableObject is not DrawableSlider) + if (drawableObject is not DrawableSliderTail) return; - using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + Debug.Assert(ParentObject != null); + + // Use ParentObject instead of drawableObject because slider tail hit state update time + // is ~36ms before the actual slider end (aka slider tail leniency) + using (BeginAbsoluteSequence(ParentObject.HitStateUpdateTime)) { switch (state) { diff --git a/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs index 3aeb0c7c0a..de8a8150d0 100644 --- a/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.Skinning private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { + // Fine to use drawableObject.HitStateUpdateTime even for DrawableSliderTail, since on + // stable, the break anim plays right when the tail is missed, not when the slider ends using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) { switch (state) @@ -28,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Skinning break; case ArmedState.Miss: - if (drawableObject is DrawableSlider or DrawableSliderTick or DrawableSliderRepeat) + if (drawableObject is DrawableSliderTail or DrawableSliderTick or DrawableSliderRepeat) OnSliderBreak(); break; } From c7313b4198179801e1ebdec47da70063cf5d052e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 11:58:28 +0900 Subject: [PATCH 171/481] Fix alignment glitching due to non-matching anchor/origin Co-authored-by: Salman Ahmed --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 001eedff39..aaaf6b5e9b 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -198,6 +198,8 @@ namespace osu.Game.Graphics.UserInterface { return new OsuSpriteText { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), Spacing = new Vector2(-2), }; @@ -217,6 +219,8 @@ namespace osu.Game.Graphics.UserInterface { return new OsuSpriteText { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), Spacing = new Vector2(-1), }; From 57ecc3a6dfea9b8c504da70329558d973d830d59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 11:59:26 +0900 Subject: [PATCH 172/481] Remove unnecessary negative spacing from thread names Co-authored-by: Salman Ahmed --- osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs b/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs index af95fbe556..5a26d34f8d 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs @@ -39,7 +39,6 @@ namespace osu.Game.Graphics.UserInterface new OsuTextFlowContainer(cp => { cp.Font = OsuFont.Default.With(weight: FontWeight.SemiBold); - cp.Spacing = new Vector2(-1); }) { AutoSizeAxes = Axes.Both, From e1a577ea48642cdf459309ce71ca4b3414deef4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 11:59:51 +0900 Subject: [PATCH 173/481] Adjust spacing to make things feel more even Co-authored-by: Salman Ahmed --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index aaaf6b5e9b..a73c3afaf4 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -73,13 +73,13 @@ namespace osu.Game.Graphics.UserInterface Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Margin = new MarginPadding(1), - Y = -1, + Y = -2, }, fpsCounter = new FramesPerSecondCounter { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Margin = new MarginPadding(1), + Margin = new MarginPadding(2), Y = 10, Scale = new Vector2(0.8f), } From c4089b71bd038c7694f358120b34bdd27bd83e2f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 06:00:41 +0300 Subject: [PATCH 174/481] Store maximum score results from simulated autoplay --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 56bbe031e6..031c14a091 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -126,6 +126,9 @@ namespace osu.Game.Rulesets.Scoring private bool beatmapApplied; private readonly Dictionary scoreResultCounts = new Dictionary(); + + private Dictionary? maximumResultCounts; + private readonly List hitEvents = new List(); private HitObject? lastHitObject; @@ -410,12 +413,16 @@ namespace osu.Game.Rulesets.Scoring { base.Reset(storeResults); - scoreResultCounts.Clear(); hitEvents.Clear(); lastHitObject = null; if (storeResults) + { maximumScoringValues = currentScoringValues; + maximumResultCounts = new Dictionary(scoreResultCounts); + } + + scoreResultCounts.Clear(); currentScoringValues = default; currentMaximumScoringValues = default; From 0f0b19da4a78801d12c1242ee8b4cd4b33260768 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 06:01:13 +0300 Subject: [PATCH 175/481] Populate score with remaining "miss" statistics on fail/exit --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 27 +++++++++++++++++++++ osu.Game/Screens/Play/Player.cs | 20 ++++++++------- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 031c14a091..63ad97376f 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -430,6 +430,7 @@ namespace osu.Game.Rulesets.Scoring TotalScore.Value = 0; Accuracy.Value = 1; Combo.Value = 0; + Rank.Disabled = false; Rank.Value = ScoreRank.X; HighestCombo.Value = 0; } @@ -452,6 +453,32 @@ namespace osu.Game.Rulesets.Scoring score.TotalScore = (long)Math.Round(ComputeFinalScore(ScoringMode.Standardised, score)); } + /// + /// Populates the given score with remaining statistics as "missed" and marks it with rank. + /// + public void FailScore(ScoreInfo score) + { + if (Rank.Value == ScoreRank.F) + return; + + Rank.Value = ScoreRank.F; + Rank.Disabled = true; + + Debug.Assert(maximumResultCounts != null); + + if (maximumResultCounts.TryGetValue(HitResult.LargeTickHit, out int maximumLargeTick)) + scoreResultCounts[HitResult.LargeTickMiss] = maximumLargeTick - scoreResultCounts.GetValueOrDefault(HitResult.LargeTickHit); + + if (maximumResultCounts.TryGetValue(HitResult.SmallTickHit, out int maximumSmallTick)) + scoreResultCounts[HitResult.SmallTickMiss] = maximumSmallTick - scoreResultCounts.GetValueOrDefault(HitResult.SmallTickHit); + + int maximumBasic = maximumResultCounts.Single(kvp => kvp.Key.IsBasic()).Value; + int currentBasic = scoreResultCounts.Where(kvp => kvp.Key.IsBasic() && kvp.Key != HitResult.Miss).Sum(kvp => kvp.Value); + scoreResultCounts[HitResult.Miss] = maximumBasic - currentBasic; + + PopulateScore(score); + } + public override void ResetFromReplayFrame(ReplayFrame frame) { base.ResetFromReplayFrame(frame); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 516364e13a..490252ff2b 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -267,12 +267,7 @@ namespace osu.Game.Screens.Play }, FailOverlay = new FailOverlay { - SaveReplay = () => - { - Score.ScoreInfo.Passed = false; - Score.ScoreInfo.Rank = ScoreRank.F; - return prepareAndImportScore(); - }, + SaveReplay = prepareAndImportScore, OnRetry = Restart, OnQuit = () => PerformExit(true), }, @@ -831,7 +826,6 @@ namespace osu.Game.Screens.Play return false; GameplayState.HasFailed = true; - Score.ScoreInfo.Passed = false; updateGameplayState(); @@ -849,9 +843,17 @@ namespace osu.Game.Screens.Play return true; } - // Called back when the transform finishes + /// + /// Invoked when the fail animation has finished. + /// private void onFailComplete() { + // fail completion is a good point to mark a score as failed, + // since the last judgement that caused the fail only applies to score processor after onFail. + // todo: this should probably be handled better. + Score.ScoreInfo.Passed = false; + ScoreProcessor.FailScore(Score.ScoreInfo); + GameplayClockContainer.Stop(); FailOverlay.Retries = RestartCount; @@ -1030,7 +1032,7 @@ namespace osu.Game.Screens.Play if (prepareScoreForDisplayTask == null) { Score.ScoreInfo.Passed = false; - Score.ScoreInfo.Rank = ScoreRank.F; + ScoreProcessor.FailScore(Score.ScoreInfo); } // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. From 3c2a885872865ba46d4d99c1cc89beacaa5c07a0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 06:01:48 +0300 Subject: [PATCH 176/481] Add test coverage --- .../Gameplay/TestSceneScoreProcessor.cs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index 9e1d786d87..dc6c9158d7 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -15,6 +16,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osu.Game.Tests.Visual; namespace osu.Game.Tests.Gameplay @@ -91,6 +93,41 @@ namespace osu.Game.Tests.Gameplay Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0)); } + [Test] + public void TestFailScore() + { + var beatmap = new Beatmap + { + HitObjects = + { + new TestHitObject(), + new TestHitObject(HitResult.LargeTickHit), + new TestHitObject(HitResult.SmallTickHit), + new TestHitObject(), + new TestHitObject(HitResult.LargeTickHit), + new TestHitObject(HitResult.SmallTickHit), + } + }; + + var scoreProcessor = new ScoreProcessor(new OsuRuleset()); + scoreProcessor.ApplyBeatmap(beatmap); + + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.LargeTickHit }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].CreateJudgement()) { Type = HitResult.SmallTickMiss }); + + var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo }; + scoreProcessor.FailScore(score); + + Assert.That(score.Rank, Is.EqualTo(ScoreRank.F)); + Assert.That(score.Statistics.Count(kvp => kvp.Value > 0), Is.EqualTo(5)); + Assert.That(score.Statistics[HitResult.Ok], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.Miss], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.LargeTickHit], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.LargeTickMiss], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.SmallTickMiss], Is.EqualTo(2)); + } + private class TestJudgement : Judgement { public override HitResult MaxResult { get; } @@ -100,5 +137,17 @@ namespace osu.Game.Tests.Gameplay MaxResult = maxResult; } } + + private class TestHitObject : HitObject + { + private readonly HitResult maxResult; + + public TestHitObject(HitResult maxResult = HitResult.Perfect) + { + this.maxResult = maxResult; + } + + public override Judgement CreateJudgement() => new TestJudgement(maxResult); + } } } From 728e22fbcecf36b76eb085fe822e8210ada63a22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 12:06:22 +0900 Subject: [PATCH 177/481] Improve tooltip display when running single thread --- osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs b/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs index 5a26d34f8d..bf53bff9b4 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounterTooltip.cs @@ -76,9 +76,11 @@ namespace osu.Game.Graphics.UserInterface { var clock = thread.Clock; - string maximum = $"{(clock.MaximumUpdateHz > 0 && clock.MaximumUpdateHz < 10000 ? clock.MaximumUpdateHz.ToString("0") : "∞"),4}"; + string maximum = clock.Throttling + ? $"/{(clock.MaximumUpdateHz > 0 && clock.MaximumUpdateHz < 10000 ? clock.MaximumUpdateHz.ToString("0") : "∞"),4}" + : string.Empty; - textFlow.AddParagraph($"{clock.FramesPerSecond:0}/{maximum}fps ({clock.ElapsedFrameTime:0.00}ms)"); + textFlow.AddParagraph($"{clock.FramesPerSecond:0}{maximum}fps ({clock.ElapsedFrameTime:0.00}ms)"); } } } From ed8e065a86fc963b18fe60771ad5ed748ea5446c Mon Sep 17 00:00:00 2001 From: TacoGuyAT Date: Thu, 21 Jul 2022 06:13:45 +0300 Subject: [PATCH 178/481] Logo triangles speed and beat sync tweaks --- .../Containers/BeatSyncedContainer.cs | 8 ++++--- osu.Game/Screens/Menu/OsuLogo.cs | 23 +++++++++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 4b40add87f..41fbd2bbac 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -28,7 +28,8 @@ namespace osu.Game.Graphics.Containers public class BeatSyncedContainer : Container { private int lastBeat; - private TimingControlPoint lastTimingPoint; + protected TimingControlPoint LastTimingPoint; + protected EffectControlPoint LastEffectPoint; /// /// The amount of time before a beat we should fire . @@ -127,7 +128,7 @@ namespace osu.Game.Graphics.Containers TimeSinceLastBeat = beatLength - TimeUntilNextBeat; - if (ReferenceEquals(timingPoint, lastTimingPoint) && beatIndex == lastBeat) + if (ReferenceEquals(timingPoint, LastTimingPoint) && beatIndex == lastBeat) return; // as this event is sometimes used for sound triggers where `BeginDelayedSequence` has no effect, avoid firing it if too far away from the beat. @@ -139,7 +140,8 @@ namespace osu.Game.Graphics.Containers } lastBeat = beatIndex; - lastTimingPoint = timingPoint; + LastTimingPoint = timingPoint; + LastEffectPoint = effectPoint; } } } diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index ddeb544bc5..9864fe8bed 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -90,6 +90,8 @@ namespace osu.Game.Screens.Menu private const double early_activation = 60; + private const float triangles_paused_velocity = 0.5f; + public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; public OsuLogo() @@ -319,6 +321,15 @@ namespace osu.Game.Screens.Menu .FadeTo(visualizer_default_alpha * 1.8f * amplitudeAdjust, early_activation, Easing.Out).Then() .FadeTo(visualizer_default_alpha, beatLength); } + + if (amplitudes.Maximum > 0.3f) + this.Delay(early_activation / 2).Schedule(() => + triangles.Velocity = (float)Interpolation.Damp( + triangles.Velocity, + triangles_paused_velocity * (effectPoint.KiaiMode ? 6 : 2) + amplitudeAdjust * (effectPoint.KiaiMode ? 8 : 4), + 0.3f, + Time.Elapsed + )); } public void PlayIntro() @@ -340,22 +351,17 @@ namespace osu.Game.Screens.Menu base.Update(); const float scale_adjust_cutoff = 0.4f; - const float velocity_adjust_cutoff = 0.98f; - const float paused_velocity = 0.5f; if (musicController.CurrentTrack.IsRunning) { float maxAmplitude = lastBeatIndex >= 0 ? musicController.CurrentTrack.CurrentAmplitudes.Maximum : 0; logoAmplitudeContainer.Scale = new Vector2((float)Interpolation.Damp(logoAmplitudeContainer.Scale.X, 1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 0.9f, Time.Elapsed)); - if (maxAmplitude > velocity_adjust_cutoff) - triangles.Velocity = 1 + Math.Max(0, maxAmplitude - velocity_adjust_cutoff) * 50; - else - triangles.Velocity = (float)Interpolation.Damp(triangles.Velocity, 1, 0.995f, Time.Elapsed); + triangles.Velocity = (float)Interpolation.Damp(triangles.Velocity, triangles_paused_velocity * (LastEffectPoint.KiaiMode ? 6 : 2), 0.995f, Time.Elapsed); } else { - triangles.Velocity = paused_velocity; + triangles.Velocity = (float)Interpolation.Damp(triangles.Velocity, triangles_paused_velocity, 0.9f, Time.Elapsed); } } @@ -378,6 +384,9 @@ namespace osu.Game.Screens.Menu protected override bool OnClick(ClickEvent e) { + //triangles.AccentColours = this.FindClosestParent()!.GetBackgroundColours(2).Select(x => Graphics.Backgrounds.Triangles.InRange(x, 0)).ToArray(); + //background.Colour = this.FindClosestParent()!.GetBackgroundColours()[0]; + if (Action?.Invoke() ?? true) sampleClick.Play(); From d6c3a524948267653372766bd41136f5056959e7 Mon Sep 17 00:00:00 2001 From: TacoGuyAT Date: Thu, 21 Jul 2022 06:38:33 +0300 Subject: [PATCH 179/481] Added missing braces --- osu.Game/Screens/Menu/OsuLogo.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 9864fe8bed..b538629923 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -323,13 +323,17 @@ namespace osu.Game.Screens.Menu } if (amplitudes.Maximum > 0.3f) + { this.Delay(early_activation / 2).Schedule(() => + { triangles.Velocity = (float)Interpolation.Damp( triangles.Velocity, triangles_paused_velocity * (effectPoint.KiaiMode ? 6 : 2) + amplitudeAdjust * (effectPoint.KiaiMode ? 8 : 4), 0.3f, Time.Elapsed - )); + ); + }); + } } public void PlayIntro() From 285516b11185c4b62874181e1141996d713b5626 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 12:40:27 +0900 Subject: [PATCH 180/481] Fix `isDisplayed` never actually being set --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index a73c3afaf4..e0a4decf7f 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -131,9 +131,13 @@ namespace osu.Game.Graphics.UserInterface private void displayTemporarily() { if (!isDisplayed) + { mainContent.FadeTo(1, 300, Easing.OutQuint); + isDisplayed = true; + } fadeOutDelegate?.Cancel(); + fadeOutDelegate = null; if (!IsHovered) { From 705ff06ea5fcb09d175b21c938608f3c80937cf2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 12:50:39 +0900 Subject: [PATCH 181/481] Better handle spikes and significant changes --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 68 ++++++++++++------- 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index e0a4decf7f..524a055903 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -22,8 +21,8 @@ namespace osu.Game.Graphics.UserInterface { public class FPSCounter : VisibilityContainer, IHasCustomTooltip { - private RollingCounter msCounter = null!; - private RollingCounter fpsCounter = null!; + private RollingCounter counterUpdateFrameTime = null!; + private RollingCounter counterDrawFPS = null!; private Container mainContent = null!; @@ -68,14 +67,14 @@ namespace osu.Game.Graphics.UserInterface }, } }, - msCounter = new FrameTimeCounter + counterUpdateFrameTime = new FrameTimeCounter { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Margin = new MarginPadding(1), Y = -2, }, - fpsCounter = new FramesPerSecondCounter + counterDrawFPS = new FramesPerSecondCounter { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -156,30 +155,47 @@ namespace osu.Game.Graphics.UserInterface { base.Update(); - // TODO: this is wrong (elapsed clock time, not actual run time). - double newFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; - double newFps = gameHost.DrawThread.Clock.FramesPerSecond; + double aimDrawFPS = gameHost.DrawThread.Clock.MaximumUpdateHz; + double aimUpdateFPS = gameHost.UpdateThread.Clock.MaximumUpdateHz; - bool hasSignificantChanges = - Math.Abs(msCounter.Current.Value - newFrameTime) > 5 || - Math.Abs(fpsCounter.Current.Value - newFps) > 10; + if (!gameHost.UpdateThread.Clock.Throttling) + { + aimUpdateFPS = aimDrawFPS = gameHost.InputThread.Clock.MaximumUpdateHz; + } + + // TODO: this is wrong (elapsed clock time, not actual run time). + double newUpdateFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; + // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. + double newDrawFps = 1000 / gameHost.DrawThread.Clock.ElapsedFrameTime; + + const double spike_time_ms = 20; + + bool hasUpdateSpike = counterUpdateFrameTime.Current.Value < spike_time_ms && newUpdateFrameTime > spike_time_ms; + bool hasDrawSpike = counterDrawFPS.Current.Value > (1000 / spike_time_ms) && newDrawFps <= (1000 / spike_time_ms); + + // If the frame time spikes up, make sure it shows immediately on the counter. + if (hasUpdateSpike) + counterUpdateFrameTime.SetCountWithoutRolling(newUpdateFrameTime); + else + counterUpdateFrameTime.Current.Value = newUpdateFrameTime; + + if (hasDrawSpike) + counterDrawFPS.SetCountWithoutRolling(newDrawFps); + else + counterDrawFPS.Current.Value = newDrawFps; + + counterDrawFPS.Colour = getColour(counterDrawFPS.DisplayedCount / aimDrawFPS); + + double displayedUpdateFPS = 1000 / counterUpdateFrameTime.DisplayedCount; + counterUpdateFrameTime.Colour = getColour(displayedUpdateFPS / aimUpdateFPS); + + bool hasSignificantChanges = hasDrawSpike + || hasUpdateSpike + || counterDrawFPS.DisplayedCount < aimDrawFPS * 0.8 + || displayedUpdateFPS < aimUpdateFPS * 0.8; if (hasSignificantChanges) displayTemporarily(); - - // If the frame time spikes up, make sure it shows immediately on the counter. - if (msCounter.Current.Value < 20 && newFrameTime > 20) - msCounter.SetCountWithoutRolling(newFrameTime); - else - msCounter.Current.Value = newFrameTime; - - fpsCounter.Current.Value = newFps; - - fpsCounter.Colour = getColour(fpsCounter.DisplayedCount / gameHost.DrawThread.Clock.MaximumUpdateHz); - - double equivalentHz = 1000 / msCounter.DisplayedCount; - - msCounter.Colour = getColour(equivalentHz / gameHost.UpdateThread.Clock.MaximumUpdateHz); } private ColourInfo getColour(double performanceRatio) @@ -196,7 +212,7 @@ namespace osu.Game.Graphics.UserInterface public class FramesPerSecondCounter : RollingCounter { - protected override double RollingDuration => 400; + protected override double RollingDuration => 1000; protected override OsuSpriteText CreateSpriteText() { From 311a0a3de019657adb600798e72261ea6db76cde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 12:57:38 +0900 Subject: [PATCH 182/481] Always show counter temporarily when aim FPS changes --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 44 +++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 524a055903..40aa223ddc 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -127,6 +127,9 @@ namespace osu.Game.Graphics.UserInterface private ScheduledDelegate? fadeOutDelegate; + private double aimDrawFPS; + private double aimUpdateFPS; + private void displayTemporarily() { if (!isDisplayed) @@ -155,13 +158,9 @@ namespace osu.Game.Graphics.UserInterface { base.Update(); - double aimDrawFPS = gameHost.DrawThread.Clock.MaximumUpdateHz; - double aimUpdateFPS = gameHost.UpdateThread.Clock.MaximumUpdateHz; - - if (!gameHost.UpdateThread.Clock.Throttling) - { - aimUpdateFPS = aimDrawFPS = gameHost.InputThread.Clock.MaximumUpdateHz; - } + // Handle the case where the window has become inactive or the user changed the + // frame limiter (we want to show the FPS as it's changing, even if it isn't an outlier). + bool aimRatesChanged = updateAimFPS(); // TODO: this is wrong (elapsed clock time, not actual run time). double newUpdateFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; @@ -189,7 +188,8 @@ namespace osu.Game.Graphics.UserInterface double displayedUpdateFPS = 1000 / counterUpdateFrameTime.DisplayedCount; counterUpdateFrameTime.Colour = getColour(displayedUpdateFPS / aimUpdateFPS); - bool hasSignificantChanges = hasDrawSpike + bool hasSignificantChanges = aimRatesChanged + || hasDrawSpike || hasUpdateSpike || counterDrawFPS.DisplayedCount < aimDrawFPS * 0.8 || displayedUpdateFPS < aimUpdateFPS * 0.8; @@ -198,6 +198,34 @@ namespace osu.Game.Graphics.UserInterface displayTemporarily(); } + private bool updateAimFPS() + { + if (gameHost.UpdateThread.Clock.Throttling) + { + double newAimDrawFPS = gameHost.DrawThread.Clock.MaximumUpdateHz; + double newAimUpdateFPS = gameHost.UpdateThread.Clock.MaximumUpdateHz; + + if (aimDrawFPS != newAimDrawFPS || aimUpdateFPS != newAimUpdateFPS) + { + aimDrawFPS = newAimDrawFPS; + aimUpdateFPS = newAimUpdateFPS; + return true; + } + } + else + { + double newAimFPS = gameHost.InputThread.Clock.MaximumUpdateHz; + + if (aimDrawFPS != newAimFPS || aimUpdateFPS != newAimFPS) + { + aimUpdateFPS = aimDrawFPS = newAimFPS; + return true; + } + } + + return false; + } + private ColourInfo getColour(double performanceRatio) { if (performanceRatio < 0.5f) From 56106e43d2244b03472ade2e6e375599c0ed0c5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 13:06:43 +0900 Subject: [PATCH 183/481] Avoid div-by-zero --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 40aa223ddc..9d5aa525d7 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -165,7 +166,7 @@ namespace osu.Game.Graphics.UserInterface // TODO: this is wrong (elapsed clock time, not actual run time). double newUpdateFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. - double newDrawFps = 1000 / gameHost.DrawThread.Clock.ElapsedFrameTime; + double newDrawFps = 1000 / Math.Max(0.001, gameHost.DrawThread.Clock.ElapsedFrameTime); const double spike_time_ms = 20; From c1bcbd9c8a01c37a4df9aa9f3c1bb455ea83aaec Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 07:20:58 +0300 Subject: [PATCH 184/481] Fix fail score not handling bonus/tick-only beatmaps --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 63ad97376f..7fd21ddc54 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -472,7 +472,7 @@ namespace osu.Game.Rulesets.Scoring if (maximumResultCounts.TryGetValue(HitResult.SmallTickHit, out int maximumSmallTick)) scoreResultCounts[HitResult.SmallTickMiss] = maximumSmallTick - scoreResultCounts.GetValueOrDefault(HitResult.SmallTickHit); - int maximumBasic = maximumResultCounts.Single(kvp => kvp.Key.IsBasic()).Value; + int maximumBasic = maximumResultCounts.SingleOrDefault(kvp => kvp.Key.IsBasic()).Value; int currentBasic = scoreResultCounts.Where(kvp => kvp.Key.IsBasic() && kvp.Key != HitResult.Miss).Sum(kvp => kvp.Value); scoreResultCounts[HitResult.Miss] = maximumBasic - currentBasic; From 5513a8b6b4e732e10608424099e0ab43f5c9c1e2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 07:21:27 +0300 Subject: [PATCH 185/481] Fix changelog overlay tests failing due to missing `CreatedAt` date --- osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs index 4c39dc34d5..c5ac3dd442 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChangelogOverlay.cs @@ -108,6 +108,7 @@ namespace osu.Game.Tests.Visual.Online Version = "2018.712.0", DisplayVersion = "2018.712.0", UpdateStream = streams[OsuGameBase.CLIENT_STREAM_NAME], + CreatedAt = new DateTime(2018, 7, 12), ChangelogEntries = new List { new APIChangelogEntry @@ -171,6 +172,7 @@ namespace osu.Game.Tests.Visual.Online { Version = "2019.920.0", DisplayVersion = "2019.920.0", + CreatedAt = new DateTime(2019, 9, 20), UpdateStream = new APIUpdateStream { Name = "Test", From 2f16174d3dc2ed1e0bd6653160f99a75d308f415 Mon Sep 17 00:00:00 2001 From: TacoGuyAT Date: Thu, 21 Jul 2022 07:25:44 +0300 Subject: [PATCH 186/481] Changed control points set to private; Cleanup --- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 4 ++-- osu.Game/Screens/Menu/OsuLogo.cs | 3 --- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 41fbd2bbac..e1998a1d7f 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -28,8 +28,8 @@ namespace osu.Game.Graphics.Containers public class BeatSyncedContainer : Container { private int lastBeat; - protected TimingControlPoint LastTimingPoint; - protected EffectControlPoint LastEffectPoint; + protected TimingControlPoint LastTimingPoint { get; private set; } + protected EffectControlPoint LastEffectPoint { get; private set; } /// /// The amount of time before a beat we should fire . diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index b538629923..332a0da01b 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -388,9 +388,6 @@ namespace osu.Game.Screens.Menu protected override bool OnClick(ClickEvent e) { - //triangles.AccentColours = this.FindClosestParent()!.GetBackgroundColours(2).Select(x => Graphics.Backgrounds.Triangles.InRange(x, 0)).ToArray(); - //background.Colour = this.FindClosestParent()!.GetBackgroundColours()[0]; - if (Action?.Invoke() ?? true) sampleClick.Play(); From ad09e728fd5dd5c1b1f94672f8486ec066b778f3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 08:12:06 +0300 Subject: [PATCH 187/481] Move `Passed` assignment inside `FailScore` --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 ++ osu.Game/Screens/Play/Player.cs | 4 ---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 7fd21ddc54..ce9e06ce9c 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -461,6 +461,8 @@ namespace osu.Game.Rulesets.Scoring if (Rank.Value == ScoreRank.F) return; + score.Passed = false; + Rank.Value = ScoreRank.F; Rank.Disabled = true; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 490252ff2b..9a058e45c5 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -851,7 +851,6 @@ namespace osu.Game.Screens.Play // fail completion is a good point to mark a score as failed, // since the last judgement that caused the fail only applies to score processor after onFail. // todo: this should probably be handled better. - Score.ScoreInfo.Passed = false; ScoreProcessor.FailScore(Score.ScoreInfo); GameplayClockContainer.Stop(); @@ -1030,10 +1029,7 @@ namespace osu.Game.Screens.Play // if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap. if (prepareScoreForDisplayTask == null) - { - Score.ScoreInfo.Passed = false; ScoreProcessor.FailScore(Score.ScoreInfo); - } // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. // To resolve test failures, forcefully end playing synchronously when this screen exits. From 9df49db45ff3a5a740221eab9bf618a3b98100dc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 08:12:46 +0300 Subject: [PATCH 188/481] Include bonus/ignore judgements in statistics fill logic --- osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs | 8 +++++++- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index dc6c9158d7..4123412ab6 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -103,9 +103,11 @@ namespace osu.Game.Tests.Gameplay new TestHitObject(), new TestHitObject(HitResult.LargeTickHit), new TestHitObject(HitResult.SmallTickHit), + new TestHitObject(HitResult.SmallBonus), new TestHitObject(), new TestHitObject(HitResult.LargeTickHit), new TestHitObject(HitResult.SmallTickHit), + new TestHitObject(HitResult.LargeBonus), } }; @@ -115,17 +117,21 @@ namespace osu.Game.Tests.Gameplay scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok }); scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.LargeTickHit }); scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[2], beatmap.HitObjects[2].CreateJudgement()) { Type = HitResult.SmallTickMiss }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[3], beatmap.HitObjects[3].CreateJudgement()) { Type = HitResult.SmallBonus }); var score = new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo }; scoreProcessor.FailScore(score); Assert.That(score.Rank, Is.EqualTo(ScoreRank.F)); - Assert.That(score.Statistics.Count(kvp => kvp.Value > 0), Is.EqualTo(5)); + Assert.That(score.Passed, Is.False); + Assert.That(score.Statistics.Count(kvp => kvp.Value > 0), Is.EqualTo(7)); Assert.That(score.Statistics[HitResult.Ok], Is.EqualTo(1)); Assert.That(score.Statistics[HitResult.Miss], Is.EqualTo(1)); Assert.That(score.Statistics[HitResult.LargeTickHit], Is.EqualTo(1)); Assert.That(score.Statistics[HitResult.LargeTickMiss], Is.EqualTo(1)); Assert.That(score.Statistics[HitResult.SmallTickMiss], Is.EqualTo(2)); + Assert.That(score.Statistics[HitResult.SmallBonus], Is.EqualTo(1)); + Assert.That(score.Statistics[HitResult.IgnoreMiss], Is.EqualTo(1)); } private class TestJudgement : Judgement diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index ce9e06ce9c..56d025e9a5 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -474,6 +474,10 @@ namespace osu.Game.Rulesets.Scoring if (maximumResultCounts.TryGetValue(HitResult.SmallTickHit, out int maximumSmallTick)) scoreResultCounts[HitResult.SmallTickMiss] = maximumSmallTick - scoreResultCounts.GetValueOrDefault(HitResult.SmallTickHit); + int maximumBonusOrIgnore = maximumResultCounts.Where(kvp => kvp.Key.IsBonus() || kvp.Key == HitResult.IgnoreHit).Sum(kvp => kvp.Value); + int currentBonusOrIgnore = scoreResultCounts.Where(kvp => kvp.Key.IsBonus() || kvp.Key == HitResult.IgnoreHit).Sum(kvp => kvp.Value); + scoreResultCounts[HitResult.IgnoreMiss] = maximumBonusOrIgnore - currentBonusOrIgnore; + int maximumBasic = maximumResultCounts.SingleOrDefault(kvp => kvp.Key.IsBasic()).Value; int currentBasic = scoreResultCounts.Where(kvp => kvp.Key.IsBasic() && kvp.Key != HitResult.Miss).Sum(kvp => kvp.Value); scoreResultCounts[HitResult.Miss] = maximumBasic - currentBasic; From a97170a2723e6de2f436b08485c54734c7108829 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 21 Jul 2022 08:16:17 +0300 Subject: [PATCH 189/481] Keep `Rank` bindable enabled on score fail --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 56d025e9a5..59fee0f97b 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -462,9 +462,7 @@ namespace osu.Game.Rulesets.Scoring return; score.Passed = false; - Rank.Value = ScoreRank.F; - Rank.Disabled = true; Debug.Assert(maximumResultCounts != null); From 07e1763a7079a9e20713dd9ce14cbac758900510 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 14:24:56 +0900 Subject: [PATCH 190/481] Tweak velocity a bit more (and simplify in multiple places) --- osu.Game/Screens/Menu/OsuLogo.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 332a0da01b..0909f615f2 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -322,18 +322,10 @@ namespace osu.Game.Screens.Menu .FadeTo(visualizer_default_alpha, beatLength); } - if (amplitudes.Maximum > 0.3f) + this.Delay(early_activation).Schedule(() => { - this.Delay(early_activation / 2).Schedule(() => - { - triangles.Velocity = (float)Interpolation.Damp( - triangles.Velocity, - triangles_paused_velocity * (effectPoint.KiaiMode ? 6 : 2) + amplitudeAdjust * (effectPoint.KiaiMode ? 8 : 4), - 0.3f, - Time.Elapsed - ); - }); - } + triangles.Velocity += amplitudeAdjust * (effectPoint.KiaiMode ? 6 : 3); + }); } public void PlayIntro() @@ -361,7 +353,7 @@ namespace osu.Game.Screens.Menu float maxAmplitude = lastBeatIndex >= 0 ? musicController.CurrentTrack.CurrentAmplitudes.Maximum : 0; logoAmplitudeContainer.Scale = new Vector2((float)Interpolation.Damp(logoAmplitudeContainer.Scale.X, 1 - Math.Max(0, maxAmplitude - scale_adjust_cutoff) * 0.04f, 0.9f, Time.Elapsed)); - triangles.Velocity = (float)Interpolation.Damp(triangles.Velocity, triangles_paused_velocity * (LastEffectPoint.KiaiMode ? 6 : 2), 0.995f, Time.Elapsed); + triangles.Velocity = (float)Interpolation.Damp(triangles.Velocity, triangles_paused_velocity * (LastEffectPoint.KiaiMode ? 4 : 2), 0.995f, Time.Elapsed); } else { From a05d7f4d8ca28dd7022ee94d280e267133d7e174 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 16:06:06 +0900 Subject: [PATCH 191/481] Change carousel terminology to not use `Children` / `InternalChildren` --- osu.Game/Screens/Select/BeatmapCarousel.cs | 20 ++++++------- .../Select/Carousel/CarouselBeatmapSet.cs | 12 ++++---- .../Screens/Select/Carousel/CarouselGroup.cs | 30 ++++++++++--------- .../Carousel/CarouselGroupEagerSelect.cs | 18 +++++------ .../Carousel/DrawableCarouselBeatmapSet.cs | 2 +- .../Select/Carousel/DrawableCarouselItem.cs | 4 +-- 6 files changed, 44 insertions(+), 42 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 72e6c7d159..8b8a85b243 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Select private readonly NoResultsPlaceholder noResultsPlaceholder; - private IEnumerable beatmapSets => root.Children.OfType(); + private IEnumerable beatmapSets => root.Items.OfType(); // todo: only used for testing, maybe remove. private bool loadedTestBeatmaps; @@ -300,7 +300,7 @@ namespace osu.Game.Screens.Select if (!root.BeatmapSetsByID.TryGetValue(beatmapSetID, out var existingSet)) return; - root.RemoveChild(existingSet); + root.RemoveItem(existingSet); itemsCache.Invalidate(); if (!Scroll.UserScrolling) @@ -321,7 +321,7 @@ namespace osu.Game.Screens.Select if (newSet != null) { - root.AddChild(newSet); + root.AddItem(newSet); // check if we can/need to maintain our current selection. if (previouslySelectedID != null) @@ -415,7 +415,7 @@ namespace osu.Game.Screens.Select if (selectedBeatmap == null) return; - var unfilteredDifficulties = selectedBeatmapSet.Children.Where(s => !s.Filtered.Value).ToList(); + var unfilteredDifficulties = selectedBeatmapSet.Items.Where(s => !s.Filtered.Value).ToList(); int index = unfilteredDifficulties.IndexOf(selectedBeatmap); @@ -798,7 +798,7 @@ namespace osu.Game.Screens.Select scrollTarget = null; - foreach (CarouselItem item in root.Children) + foreach (CarouselItem item in root.Items) { if (item.Filtered.Value) continue; @@ -964,26 +964,26 @@ namespace osu.Game.Screens.Select this.carousel = carousel; } - public override void AddChild(CarouselItem i) + public override void AddItem(CarouselItem i) { CarouselBeatmapSet set = (CarouselBeatmapSet)i; BeatmapSetsByID.Add(set.BeatmapSet.ID, set); - base.AddChild(i); + base.AddItem(i); } public void RemoveChild(Guid beatmapSetID) { if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSet)) - RemoveChild(carouselBeatmapSet); + RemoveItem(carouselBeatmapSet); } - public override void RemoveChild(CarouselItem i) + public override void RemoveItem(CarouselItem i) { CarouselBeatmapSet set = (CarouselBeatmapSet)i; BeatmapSetsByID.Remove(set.BeatmapSet.ID); - base.RemoveChild(i); + base.RemoveItem(i); } protected override void PerformSelection() diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index bd7b1f12a4..59d9318962 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Select.Carousel switch (State.Value) { case CarouselItemState.Selected: - return DrawableCarouselBeatmapSet.HEIGHT + Children.Count(c => c.Visible) * DrawableCarouselBeatmap.HEIGHT; + return DrawableCarouselBeatmapSet.HEIGHT + Items.Count(c => c.Visible) * DrawableCarouselBeatmap.HEIGHT; default: return DrawableCarouselBeatmapSet.HEIGHT; @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select.Carousel } } - public IEnumerable Beatmaps => InternalChildren.OfType(); + public IEnumerable Beatmaps => Items.OfType(); public BeatmapSetInfo BeatmapSet; @@ -44,15 +44,15 @@ namespace osu.Game.Screens.Select.Carousel .OrderBy(b => b.Ruleset) .ThenBy(b => b.StarRating) .Select(b => new CarouselBeatmap(b)) - .ForEach(AddChild); + .ForEach(AddItem); } protected override CarouselItem GetNextToSelect() { if (LastSelected == null || LastSelected.Filtered.Value) { - if (GetRecommendedBeatmap?.Invoke(Children.OfType().Where(b => !b.Filtered.Value).Select(b => b.BeatmapInfo)) is BeatmapInfo recommended) - return Children.OfType().First(b => b.BeatmapInfo.Equals(recommended)); + if (GetRecommendedBeatmap?.Invoke(Items.OfType().Where(b => !b.Filtered.Value).Select(b => b.BeatmapInfo)) is BeatmapInfo recommended) + return Items.OfType().First(b => b.BeatmapInfo.Equals(recommended)); } return base.GetNextToSelect(); @@ -122,7 +122,7 @@ namespace osu.Game.Screens.Select.Carousel public override void Filter(FilterCriteria criteria) { base.Filter(criteria); - Filtered.Value = InternalChildren.All(i => i.Filtered.Value); + Filtered.Value = Items.All(i => i.Filtered.Value); } public override string ToString() => BeatmapSet.ToString(); diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 1cd9674b72..e430ff3d3a 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -13,9 +13,9 @@ namespace osu.Game.Screens.Select.Carousel { public override DrawableCarouselItem? CreateDrawableRepresentation() => null; - public IReadOnlyList Children => InternalChildren; + public IReadOnlyList Items => items; - protected List InternalChildren = new List(); + private List items = new List(); /// /// Used to assign a monotonically increasing ID to children as they are added. This member is @@ -27,16 +27,18 @@ namespace osu.Game.Screens.Select.Carousel private FilterCriteria? lastCriteria; - public virtual void RemoveChild(CarouselItem i) + protected int GetIndexOfItem(CarouselItem lastSelected) => items.IndexOf(lastSelected); + + public virtual void RemoveItem(CarouselItem i) { - InternalChildren.Remove(i); + items.Remove(i); // it's important we do the deselection after removing, so any further actions based on // State.ValueChanged make decisions post-removal. i.State.Value = CarouselItemState.Collapsed; } - public virtual void AddChild(CarouselItem i) + public virtual void AddItem(CarouselItem i) { i.State.ValueChanged += state => ChildItemStateChanged(i, state.NewValue); i.ChildID = ++currentChildID; @@ -45,21 +47,21 @@ namespace osu.Game.Screens.Select.Carousel { i.Filter(lastCriteria); - int index = InternalChildren.BinarySearch(i, criteriaComparer); + int index = items.BinarySearch(i, criteriaComparer); if (index < 0) index = ~index; // BinarySearch hacks multiple return values with 2's complement. - InternalChildren.Insert(index, i); + items.Insert(index, i); } else { // criteria may be null for initial population. the filtering will be applied post-add. - InternalChildren.Add(i); + items.Add(i); } } public CarouselGroup(List? items = null) { - if (items != null) InternalChildren = items; + if (items != null) this.items = items; State.ValueChanged += state => { @@ -67,11 +69,11 @@ namespace osu.Game.Screens.Select.Carousel { case CarouselItemState.Collapsed: case CarouselItemState.NotSelected: - InternalChildren.ForEach(c => c.State.Value = CarouselItemState.Collapsed); + this.items.ForEach(c => c.State.Value = CarouselItemState.Collapsed); break; case CarouselItemState.Selected: - InternalChildren.ForEach(c => + this.items.ForEach(c => { if (c.State.Value == CarouselItemState.Collapsed) c.State.Value = CarouselItemState.NotSelected; }); @@ -84,11 +86,11 @@ namespace osu.Game.Screens.Select.Carousel { base.Filter(criteria); - InternalChildren.ForEach(c => c.Filter(criteria)); + items.ForEach(c => c.Filter(criteria)); // IEnumerable.OrderBy() is used instead of List.Sort() to ensure sorting stability criteriaComparer = Comparer.Create((x, y) => x.CompareTo(criteria, y)); - InternalChildren = InternalChildren.OrderBy(c => c, criteriaComparer).ToList(); + items = items.OrderBy(c => c, criteriaComparer).ToList(); lastCriteria = criteria; } @@ -98,7 +100,7 @@ namespace osu.Game.Screens.Select.Carousel // ensure we are the only item selected if (value == CarouselItemState.Selected) { - foreach (var b in InternalChildren) + foreach (var b in items) { if (item == b) continue; diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 4805c7e2a4..26d64a2619 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -49,9 +49,9 @@ namespace osu.Game.Screens.Select.Carousel attemptSelection(); } - public override void RemoveChild(CarouselItem i) + public override void RemoveItem(CarouselItem i) { - base.RemoveChild(i); + base.RemoveItem(i); if (i != LastSelected) updateSelectedIndex(); @@ -64,16 +64,16 @@ namespace osu.Game.Screens.Select.Carousel addingChildren = true; foreach (var i in items) - AddChild(i); + AddItem(i); addingChildren = false; attemptSelection(); } - public override void AddChild(CarouselItem i) + public override void AddItem(CarouselItem i) { - base.AddChild(i); + base.AddItem(i); if (!addingChildren) attemptSelection(); } @@ -103,15 +103,15 @@ namespace osu.Game.Screens.Select.Carousel if (State.Value != CarouselItemState.Selected) return; // we only perform eager selection if none of our children are in a selected state already. - if (Children.Any(i => i.State.Value == CarouselItemState.Selected)) return; + if (Items.Any(i => i.State.Value == CarouselItemState.Selected)) return; PerformSelection(); } protected virtual CarouselItem GetNextToSelect() { - return Children.Skip(lastSelectedIndex).FirstOrDefault(i => !i.Filtered.Value) ?? - Children.Reverse().Skip(InternalChildren.Count - lastSelectedIndex).FirstOrDefault(i => !i.Filtered.Value); + return Items.Skip(lastSelectedIndex).FirstOrDefault(i => !i.Filtered.Value) ?? + Items.Reverse().Skip(Items.Count - lastSelectedIndex).FirstOrDefault(i => !i.Filtered.Value); } protected virtual void PerformSelection() @@ -131,6 +131,6 @@ namespace osu.Game.Screens.Select.Carousel updateSelectedIndex(); } - private void updateSelectedIndex() => lastSelectedIndex = LastSelected == null ? 0 : Math.Max(0, InternalChildren.IndexOf(LastSelected)); + private void updateSelectedIndex() => lastSelectedIndex = LastSelected == null ? 0 : Math.Max(0, GetIndexOfItem(LastSelected)); } } diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index fc29f509ad..8c266c8dff 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Select.Carousel { var carouselBeatmapSet = (CarouselBeatmapSet)Item; - var visibleBeatmaps = carouselBeatmapSet.Children.Where(c => c.Visible).ToArray(); + var visibleBeatmaps = carouselBeatmapSet.Items.Where(c => c.Visible).ToArray(); // if we are already displaying all the correct beatmaps, only run animation updates. // note that the displayed beatmaps may change due to the applied filter. diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs index c92a0ac4ac..133bf5f9c3 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Select.Carousel if (item is CarouselGroup group) { - foreach (var c in group.Children) + foreach (var c in group.Items) c.Filtered.ValueChanged -= onStateChange; } } @@ -117,7 +117,7 @@ namespace osu.Game.Screens.Select.Carousel if (Item is CarouselGroup group) { - foreach (var c in group.Children) + foreach (var c in group.Items) c.Filtered.ValueChanged += onStateChange; } } From 3cfe624af1c8a5b4ec1c953640034801df1ac100 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 16:16:39 +0900 Subject: [PATCH 192/481] Fix one more missed method with incorrect terminology --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- .../Select/Carousel/CarouselGroupEagerSelect.cs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 8b8a85b243..04d77bfb0e 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -121,7 +121,7 @@ namespace osu.Game.Screens.Select { CarouselRoot newRoot = new CarouselRoot(this); - newRoot.AddChildren(beatmapSets.Select(s => createCarouselSet(s.Detach())).Where(g => g != null)); + newRoot.AddItems(beatmapSets.Select(s => createCarouselSet(s.Detach())).Where(g => g != null)); root = newRoot; diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 26d64a2619..613b3db5d5 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -57,16 +57,16 @@ namespace osu.Game.Screens.Select.Carousel updateSelectedIndex(); } - private bool addingChildren; + private bool addingItems; - public void AddChildren(IEnumerable items) + public void AddItems(IEnumerable items) { - addingChildren = true; + addingItems = true; foreach (var i in items) AddItem(i); - addingChildren = false; + addingItems = false; attemptSelection(); } @@ -74,7 +74,7 @@ namespace osu.Game.Screens.Select.Carousel public override void AddItem(CarouselItem i) { base.AddItem(i); - if (!addingChildren) + if (!addingItems) attemptSelection(); } From fc0c9f76bd8ef2a9c5250d1977782ddb9916b678 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 16:24:46 +0900 Subject: [PATCH 193/481] Fix `UpdateBeatmapSetButton` intermittent test failure Carousel would only expire items when off-screen. This meant that for a case (like a test) where items are generally always on-screen, `UpdateBeatmapSet` calls would result in panels remaining hidden but not cleaned up. --- .../TestSceneUpdateBeatmapSetButton.cs | 2 ++ osu.Game/Screens/Select/BeatmapCarousel.cs | 27 ++++++++++++++----- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs index a95f145897..70786c93e7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs @@ -71,6 +71,7 @@ namespace osu.Game.Tests.Visual.SongSelect carousel.UpdateBeatmapSet(testBeatmapSetInfo); }); + AddUntilStep("only one set visible", () => carousel.ChildrenOfType().Count() == 1); AddUntilStep("update button visible", () => getUpdateButton() != null); AddStep("click button", () => getUpdateButton()?.TriggerClick()); @@ -120,6 +121,7 @@ namespace osu.Game.Tests.Visual.SongSelect carousel.UpdateBeatmapSet(testBeatmapSetInfo); }); + AddUntilStep("only one set visible", () => carousel.ChildrenOfType().Count() == 1); AddUntilStep("update button visible", () => getUpdateButton() != null); AddStep("click button", () => getUpdateButton()?.TriggerClick()); diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 04d77bfb0e..75caa3c9a3 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -316,8 +316,13 @@ namespace osu.Game.Screens.Select previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID; var newSet = createCarouselSet(beatmapSet); + var removedSet = root.RemoveChild(beatmapSet.ID); - root.RemoveChild(beatmapSet.ID); + // If we don't remove this here, it may remain in a hidden state until scrolled off screen. + // Doesn't really affect anything during actual user interaction, but makes testing annoying. + var removedDrawable = Scroll.FirstOrDefault(c => c.Item == removedSet); + if (removedDrawable != null) + expirePanelImmediately(removedDrawable); if (newSet != null) { @@ -696,11 +701,7 @@ namespace osu.Game.Screens.Select // panel loaded as drawable but not required by visible range. // remove but only if too far off-screen if (panel.Y + panel.DrawHeight < visibleUpperBound - distance_offscreen_before_unload || panel.Y > visibleBottomBound + distance_offscreen_before_unload) - { - // may want a fade effect here (could be seen if a huge change happens, like a set with 20 difficulties becomes selected). - panel.ClearTransforms(); - panel.Expire(); - } + expirePanelImmediately(panel); } // Add those items within the previously found index range that should be displayed. @@ -730,6 +731,13 @@ namespace osu.Game.Screens.Select } } + private static void expirePanelImmediately(DrawableCarouselItem panel) + { + // may want a fade effect here (could be seen if a huge change happens, like a set with 20 difficulties becomes selected). + panel.ClearTransforms(); + panel.Expire(); + } + private readonly CarouselBoundsItem carouselBoundsItem = new CarouselBoundsItem(); private (int firstIndex, int lastIndex) getDisplayRange() @@ -972,10 +980,15 @@ namespace osu.Game.Screens.Select base.AddItem(i); } - public void RemoveChild(Guid beatmapSetID) + public CarouselBeatmapSet RemoveChild(Guid beatmapSetID) { if (BeatmapSetsByID.TryGetValue(beatmapSetID, out var carouselBeatmapSet)) + { RemoveItem(carouselBeatmapSet); + return carouselBeatmapSet; + } + + return null; } public override void RemoveItem(CarouselItem i) From aca19a005ef1db984b315d5aa529f207b0ddac7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 02:05:18 +0900 Subject: [PATCH 194/481] Add versioning to difficulty calculators --- .../Difficulty/CatchDifficultyCalculator.cs | 2 ++ .../Difficulty/ManiaDifficultyCalculator.cs | 2 ++ osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 ++ .../Difficulty/TaikoDifficultyCalculator.cs | 2 ++ osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 5 +++++ 5 files changed, 13 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index b64d860417..f37479f84a 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty private float halfCatcherWidth; + public override int Version => 20220701; + public CatchDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) { diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index 178094476f..5d30d33190 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -31,6 +31,8 @@ namespace osu.Game.Rulesets.Mania.Difficulty private readonly bool isForCurrentRuleset; private readonly double originalOverallDifficulty; + public override int Version => 20220701; + public ManiaDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) { diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 75d9469da3..9f4a405113 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -25,6 +25,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty private const double difficulty_multiplier = 0.0675; private double hitWindowGreat; + public override int Version => 20220701; + public OsuDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) { diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 228856cbe9..9267d1ee3c 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -26,6 +26,8 @@ namespace osu.Game.Rulesets.Taiko.Difficulty private const double colour_skill_multiplier = 0.01; private const double stamina_skill_multiplier = 0.021; + public override int Version => 20220701; + public TaikoDifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) : base(ruleset, beatmap) { diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 9c58a53b97..8dd1b51cae 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -34,6 +34,11 @@ namespace osu.Game.Rulesets.Difficulty private readonly IRulesetInfo ruleset; private readonly IWorkingBeatmap beatmap; + /// + /// A yymmdd version which is used to discern when reprocessing is required. + /// + public virtual int Version => 0; + protected DifficultyCalculator(IRulesetInfo ruleset, IWorkingBeatmap beatmap) { this.ruleset = ruleset; From 68f28ff660183a869fc2803b98beaec13dfdce1e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 02:27:29 +0900 Subject: [PATCH 195/481] Add last applied version to `RulesetInfo` --- osu.Game/Database/RealmAccess.cs | 3 ++- osu.Game/Rulesets/RulesetInfo.cs | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index dff2bdddbd..7aac94576f 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -63,8 +63,9 @@ namespace osu.Game.Database /// 17 2022-07-16 Added CountryCode to RealmUser. /// 18 2022-07-19 Added OnlineMD5Hash and LastOnlineUpdate to BeatmapInfo. /// 19 2022-07-19 Added DateSubmitted and DateRanked to BeatmapSetInfo. + /// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo. /// - private const int schema_version = 19; + private const int schema_version = 20; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index b5e5fa1561..91954320a4 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -4,6 +4,7 @@ using System; using JetBrains.Annotations; using osu.Framework.Testing; +using osu.Game.Rulesets.Difficulty; using Realms; namespace osu.Game.Rulesets @@ -22,6 +23,11 @@ namespace osu.Game.Rulesets public string InstantiationInfo { get; set; } = string.Empty; + /// + /// Stores the last applied + /// + public int LastAppliedDifficultyVersion { get; set; } + public RulesetInfo(string shortName, string name, string instantiationInfo, int onlineID) { ShortName = shortName; @@ -86,7 +92,8 @@ namespace osu.Game.Rulesets Name = Name, ShortName = ShortName, InstantiationInfo = InstantiationInfo, - Available = Available + Available = Available, + LastAppliedDifficultyVersion = LastAppliedDifficultyVersion, }; public Ruleset CreateInstance() From 57a41c689756f8372ea06fb9eb040bf27940ca37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 03:18:57 +0900 Subject: [PATCH 196/481] Add basic background processor --- osu.Game/BackgroundBeatmapProcessor.cs | 112 +++++++++++++++++++++++++ osu.Game/OsuGame.cs | 2 + osu.Game/OsuGameBase.cs | 3 +- 3 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 osu.Game/BackgroundBeatmapProcessor.cs diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs new file mode 100644 index 0000000000..09db107744 --- /dev/null +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -0,0 +1,112 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Logging; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets; + +namespace osu.Game +{ + public class BackgroundBeatmapProcessor : Component + { + [Resolved] + private RulesetStore rulesetStore { get; set; } = null!; + + [Resolved] + private RealmAccess realmAccess { get; set; } = null!; + + [Resolved] + private BeatmapUpdater beatmapUpdater { get; set; } = null!; + + [Resolved] + private IBindable gameBeatmap { get; set; } = null!; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Task.Run(() => + { + Logger.Log("Beginning background beatmap processing.."); + checkForOutdatedStarRatings(); + processBeatmapSetsWithMissingMetrics(); + Logger.Log("Finished background beatmap processing!"); + }); + } + + /// + /// Check whether the databased difficulty calculation version matches the latest ruleset provided version. + /// If it doesn't, clear out any existing difficulties so they can be incrementally recalculated. + /// + private void checkForOutdatedStarRatings() + { + foreach (var ruleset in rulesetStore.AvailableRulesets) + { + // beatmap being passed in is arbitrary here. just needs to be non-null. + int currentVersion = ruleset.CreateInstance().CreateDifficultyCalculator(gameBeatmap.Value).Version; + + if (ruleset.LastAppliedDifficultyVersion < currentVersion) + { + Logger.Log($"Resetting star ratings for {ruleset.Name} (difficulty calculation version updated from {ruleset.LastAppliedDifficultyVersion} to {currentVersion})"); + + int countReset = 0; + + realmAccess.Write(r => + { + foreach (var b in r.All()) + { + if (b.Ruleset.ShortName == ruleset.ShortName) + { + b.StarRating = 0; + countReset++; + } + } + + r.Find(ruleset.ShortName).LastAppliedDifficultyVersion = currentVersion; + }); + + Logger.Log($"Finished resetting {countReset} beatmap sets for {ruleset.Name}"); + } + } + } + + private void processBeatmapSetsWithMissingMetrics() + { + // TODO: rate limit and pause processing during gameplay. + + HashSet beatmapSetIds = new HashSet(); + + realmAccess.Run(r => + { + foreach (var b in r.All().Where(b => b.StarRating == 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null))) + { + Debug.Assert(b.BeatmapSet != null); + beatmapSetIds.Add(b.BeatmapSet.ID); + } + }); + + foreach (var id in beatmapSetIds) + { + realmAccess.Run(r => + { + var set = r.Find(id); + + if (set != null) + { + Logger.Log($"Background processing {set}"); + beatmapUpdater.Process(set); + } + }); + } + } + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 69d02b95ff..4f08511783 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -904,6 +904,8 @@ namespace osu.Game loadComponentSingleFile(CreateHighPerformanceSession(), Add); + loadComponentSingleFile(new BackgroundBeatmapProcessor(), Add); + chatOverlay.State.BindValueChanged(_ => updateChatPollRate()); // Multiplayer modes need to increase poll rate temporarily. API.Activity.BindValueChanged(_ => updateChatPollRate(), true); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 6a5c6835ba..3b81b5c8cd 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -280,8 +280,7 @@ namespace osu.Game AddInternal(difficultyCache); // TODO: OsuGame or OsuGameBase? - beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage); - + dependencies.CacheAs(beatmapUpdater = new BeatmapUpdater(BeatmapManager, difficultyCache, API, Storage)); dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints)); dependencies.CacheAs(multiplayerClient = new OnlineMultiplayerClient(endpoints)); dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); From 04f48d88627bf2cfe44f26871968a50cb204f8a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 03:55:05 +0900 Subject: [PATCH 197/481] Add better log output and sleeping during gameplay sections --- osu.Game/BackgroundBeatmapProcessor.cs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 09db107744..8cc725a3f8 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -13,6 +14,7 @@ using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Rulesets; +using osu.Game.Screens.Play; namespace osu.Game { @@ -30,6 +32,9 @@ namespace osu.Game [Resolved] private IBindable gameBeatmap { get; set; } = null!; + [Resolved] + private ILocalUserPlayInfo? localUserPlayInfo { get; set; } + protected override void LoadComplete() { base.LoadComplete(); @@ -81,10 +86,10 @@ namespace osu.Game private void processBeatmapSetsWithMissingMetrics() { - // TODO: rate limit and pause processing during gameplay. - HashSet beatmapSetIds = new HashSet(); + Logger.Log("Querying for beatmap sets to reprocess..."); + realmAccess.Run(r => { foreach (var b in r.All().Where(b => b.StarRating == 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null))) @@ -94,15 +99,25 @@ namespace osu.Game } }); + Logger.Log($"Found {beatmapSetIds.Count} beatmap sets which require reprocessing."); + + int i = 0; + foreach (var id in beatmapSetIds) { + while (localUserPlayInfo?.IsPlaying.Value == true) + { + Logger.Log("Background processing sleeping 30s due to active gameplay..."); + Thread.Sleep(30000); + } + realmAccess.Run(r => { var set = r.Find(id); if (set != null) { - Logger.Log($"Background processing {set}"); + Logger.Log($"Background processing {set} ({++i} / {beatmapSetIds.Count})"); beatmapUpdater.Process(set); } }); From d5e0dba9da649ebfbc19bac7e19e5b5871081231 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 17:39:07 +0900 Subject: [PATCH 198/481] Change default value of `StarRating` to `-1` --- osu.Game/BackgroundBeatmapProcessor.cs | 4 ++-- osu.Game/Beatmaps/BeatmapInfo.cs | 6 +++++- osu.Game/Database/RealmAccess.cs | 12 +++++++++++- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 8cc725a3f8..bd3fab9135 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -71,7 +71,7 @@ namespace osu.Game { if (b.Ruleset.ShortName == ruleset.ShortName) { - b.StarRating = 0; + b.StarRating = -1; countReset++; } } @@ -92,7 +92,7 @@ namespace osu.Game realmAccess.Run(r => { - foreach (var b in r.All().Where(b => b.StarRating == 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null))) + foreach (var b in r.All().Where(b => b.StarRating < 0 || (b.OnlineID > 0 && b.LastOnlineUpdate == null))) { Debug.Assert(b.BeatmapSet != null); beatmapSetIds.Add(b.BeatmapSet.ID); diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 3ee306cc9a..466448d771 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -87,7 +87,11 @@ namespace osu.Game.Beatmaps public string Hash { get; set; } = string.Empty; - public double StarRating { get; set; } + /// + /// Defaults to -1 (meaning not-yet-calculated). + /// Will likely be superseded with a better storage considering ruleset/mods. + /// + public double StarRating { get; set; } = -1; [Indexed] public string MD5Hash { get; set; } = string.Empty; diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 7aac94576f..2754696130 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -63,7 +63,7 @@ namespace osu.Game.Database /// 17 2022-07-16 Added CountryCode to RealmUser. /// 18 2022-07-19 Added OnlineMD5Hash and LastOnlineUpdate to BeatmapInfo. /// 19 2022-07-19 Added DateSubmitted and DateRanked to BeatmapSetInfo. - /// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo. + /// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo, changed default value of BeatmapInfo.StarRating to -1. /// private const int schema_version = 20; @@ -781,6 +781,16 @@ namespace osu.Game.Database case 14: foreach (var beatmap in migration.NewRealm.All()) beatmap.UserSettings = new BeatmapUserSettings(); + + break; + + case 20: + foreach (var beatmap in migration.NewRealm.All()) + { + if (beatmap.StarRating == 0) + beatmap.StarRating = -1; + } + break; } } From 1374738a0da5fbc024862819c5b09172365c88fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 18:15:21 +0900 Subject: [PATCH 199/481] Add test coverage --- .../BackgroundBeatmapProcessorTests.cs | 132 ++++++++++++++++++ osu.Game/BackgroundBeatmapProcessor.cs | 6 +- 2 files changed, 136 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs diff --git a/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs b/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs new file mode 100644 index 0000000000..4012e3f851 --- /dev/null +++ b/osu.Game.Tests/Database/BackgroundBeatmapProcessorTests.cs @@ -0,0 +1,132 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps.IO; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Database +{ + [HeadlessTest] + public class BackgroundBeatmapProcessorTests : OsuTestScene, ILocalUserPlayInfo + { + public IBindable IsPlaying => isPlaying; + + private readonly Bindable isPlaying = new Bindable(); + + private BeatmapSetInfo importedSet = null!; + + [BackgroundDependencyLoader] + private void load(OsuGameBase osu) + { + importedSet = BeatmapImportHelper.LoadQuickOszIntoOsu(osu).GetResultSafely(); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Set not playing", () => isPlaying.Value = false); + } + + [Test] + public void TestDifficultyProcessing() + { + AddAssert("Difficulty is initially set", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); + }); + }); + + AddStep("Reset difficulty", () => + { + Realm.Write(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + foreach (var b in beatmapSetInfo.Beatmaps) + b.StarRating = -1; + }); + }); + + AddStep("Run background processor", () => + { + Add(new TestBackgroundBeatmapProcessor()); + }); + + AddUntilStep("wait for difficulties repopulated", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); + }); + }); + } + + [Test] + public void TestDifficultyProcessingWhilePlaying() + { + AddAssert("Difficulty is initially set", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); + }); + }); + + AddStep("Set playing", () => isPlaying.Value = true); + + AddStep("Reset difficulty", () => + { + Realm.Write(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + foreach (var b in beatmapSetInfo.Beatmaps) + b.StarRating = -1; + }); + }); + + AddStep("Run background processor", () => + { + Add(new TestBackgroundBeatmapProcessor()); + }); + + AddWaitStep("wait some", 500); + + AddAssert("Difficulty still not populated", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating == -1); + }); + }); + + AddStep("Set not playing", () => isPlaying.Value = false); + + AddUntilStep("wait for difficulties repopulated", () => + { + return Realm.Run(r => + { + var beatmapSetInfo = r.Find(importedSet.ID); + return beatmapSetInfo.Beatmaps.All(b => b.StarRating > 0); + }); + }); + } + + public class TestBackgroundBeatmapProcessor : BackgroundBeatmapProcessor + { + protected override int TimeToSleepDuringGameplay => 10; + } + } +} diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index bd3fab9135..9b04f487a8 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -35,6 +35,8 @@ namespace osu.Game [Resolved] private ILocalUserPlayInfo? localUserPlayInfo { get; set; } + protected virtual int TimeToSleepDuringGameplay => 30000; + protected override void LoadComplete() { base.LoadComplete(); @@ -107,8 +109,8 @@ namespace osu.Game { while (localUserPlayInfo?.IsPlaying.Value == true) { - Logger.Log("Background processing sleeping 30s due to active gameplay..."); - Thread.Sleep(30000); + Logger.Log("Background processing sleeping due to active gameplay..."); + Thread.Sleep(TimeToSleepDuringGameplay); } realmAccess.Run(r => From 94cd641fb4ca493a8e30972134defcf220efce8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 18:27:31 +0900 Subject: [PATCH 200/481] Change migration to trigger reprocessing on every local beatmap Was originally relying on the fact that this would be triggered due to a null `LastOnlineUpdate`, but wouldn't cover the case of beatmaps with no `OnlineID`. --- osu.Game/Database/RealmAccess.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 2754696130..826341a5b9 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -785,11 +785,10 @@ namespace osu.Game.Database break; case 20: + // As we now have versioned difficulty calculations, let's reset + // all star ratings and have `BackgroundBeatmapProcessor` recalculate them. foreach (var beatmap in migration.NewRealm.All()) - { - if (beatmap.StarRating == 0) - beatmap.StarRating = -1; - } + beatmap.StarRating = -1; break; } From fb728fbed175f3beb717c31cfc76046292e20212 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 18:56:39 +0900 Subject: [PATCH 201/481] Fix FPS counter not being wide enough to show large fps numbers --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 9d5aa525d7..db86199667 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -29,6 +29,8 @@ namespace osu.Game.Graphics.UserInterface private Container background = null!; + private Container counters = null!; + private const float idle_background_alpha = 0.4f; private readonly BindableBool showFpsDisplay = new BindableBool(true); @@ -49,7 +51,7 @@ namespace osu.Game.Graphics.UserInterface mainContent = new Container { Alpha = 0, - Size = new Vector2(42, 26), + Height = 26, Children = new Drawable[] { background = new Container @@ -68,21 +70,30 @@ namespace osu.Game.Graphics.UserInterface }, } }, - counterUpdateFrameTime = new FrameTimeCounter + counters = new Container { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Margin = new MarginPadding(1), - Y = -2, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + counterUpdateFrameTime = new FrameTimeCounter + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Margin = new MarginPadding(1), + Y = -2, + }, + counterDrawFPS = new FramesPerSecondCounter + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Margin = new MarginPadding(2), + Y = 10, + Scale = new Vector2(0.8f), + } + } }, - counterDrawFPS = new FramesPerSecondCounter - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Margin = new MarginPadding(2), - Y = 10, - Scale = new Vector2(0.8f), - } } }, }; @@ -159,6 +170,8 @@ namespace osu.Game.Graphics.UserInterface { base.Update(); + mainContent.Width = Math.Max(mainContent.Width, counters.DrawWidth); + // Handle the case where the window has become inactive or the user changed the // frame limiter (we want to show the FPS as it's changing, even if it isn't an outlier). bool aimRatesChanged = updateAimFPS(); From 4c4939d18d3a751246ee12f5945248dcf2486960 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 19:11:16 +0900 Subject: [PATCH 202/481] Fix draw FPS being inaccurate due to using `ElapsedFrameTime` Had a feeling this would be the case. Basically, we're calculating on the update thread and checking the last value of draw thread's `ElapsedFrameTime`. In the case that value is spiky, a completely incorrect fps can be displayed. I've left the spike display do use `ElapsedFrameTime`, as `FramesPerSecond` is too averaged to see spikes. --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 9d5aa525d7..2edfebe203 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -165,13 +164,14 @@ namespace osu.Game.Graphics.UserInterface // TODO: this is wrong (elapsed clock time, not actual run time). double newUpdateFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; - // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. - double newDrawFps = 1000 / Math.Max(0.001, gameHost.DrawThread.Clock.ElapsedFrameTime); + double newDrawFrameTime = gameHost.DrawThread.Clock.ElapsedFrameTime; + double newDrawFps = gameHost.DrawThread.Clock.FramesPerSecond; const double spike_time_ms = 20; bool hasUpdateSpike = counterUpdateFrameTime.Current.Value < spike_time_ms && newUpdateFrameTime > spike_time_ms; - bool hasDrawSpike = counterDrawFPS.Current.Value > (1000 / spike_time_ms) && newDrawFps <= (1000 / spike_time_ms); + // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. + bool hasDrawSpike = counterDrawFPS.Current.Value > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms; // If the frame time spikes up, make sure it shows immediately on the counter. if (hasUpdateSpike) @@ -180,7 +180,8 @@ namespace osu.Game.Graphics.UserInterface counterUpdateFrameTime.Current.Value = newUpdateFrameTime; if (hasDrawSpike) - counterDrawFPS.SetCountWithoutRolling(newDrawFps); + // show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show. + counterDrawFPS.SetCountWithoutRolling(1000 / newDrawFrameTime); else counterDrawFPS.Current.Value = newDrawFps; From 9c2f6103c524edf3e5cbfc118da7bbbcdf35fa4e Mon Sep 17 00:00:00 2001 From: andy840119 Date: Thu, 21 Jul 2022 19:30:04 +0800 Subject: [PATCH 203/481] Following the suggestion to mark the property as nullable. --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 5 +++-- osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index abe391ba4e..4824106c55 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets.Catch.Objects; @@ -35,9 +36,9 @@ namespace osu.Game.Rulesets.Catch.Mods public override float DefaultFlashlightSize => 350; - protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield); + protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield.AsNonNull()); - private CatchPlayfield playfield = null!; + private CatchPlayfield? playfield; public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index 60f1614d98..0ab6da0363 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; @@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public override string Description => @"Use the mouse to control the catcher."; - private DrawableRuleset drawableRuleset = null!; + private DrawableRuleset? drawableRuleset; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { @@ -27,6 +28,8 @@ namespace osu.Game.Rulesets.Catch.Mods public void ApplyToPlayer(Player player) { + Debug.Assert(drawableRuleset != null); + if (!drawableRuleset.HasReplayLoaded.Value) drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield)); } From 5db4d9437aee56366b248a8869fc1850d3ee0079 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 21:39:24 +0900 Subject: [PATCH 204/481] Add missing using statement --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 162d2d2a57..91e0429c85 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; From a4f071fe53e2d1d3e105ffdf0d20c4f65eec5db0 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Thu, 21 Jul 2022 08:26:48 -0500 Subject: [PATCH 205/481] Make zoom sensitivity relative to containers max zoom --- .../Screens/Edit/Compose/Components/Timeline/TimelineArea.cs | 4 +++- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index 97dc04b9fa..c25a4834d3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -20,6 +20,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Drawable userContent; + private const float zoom_button_sensitivity = 0.02f; + public TimelineArea(Drawable content = null) { RelativeSizeAxes = Axes.X; @@ -154,6 +156,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Timeline.TicksVisible.BindTo(ticksCheckbox.Current); } - private void changeZoom(float change) => Timeline.Zoom += change; + private void changeZoom(float change) => Timeline.Zoom += change * Timeline.MaxZoom * zoom_button_sensitivity; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 80cdef38e9..7a4d45301f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -104,6 +104,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline set => updateZoom(value); } + private const float zoom_scroll_sensitivity = 0.02f; + private void updateZoom(float? value = null) { float newZoom = Math.Clamp(value ?? Zoom, MinZoom, MaxZoom); @@ -127,7 +129,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (e.AltPressed) { // zoom when holding alt. - setZoomTarget(zoomTarget + e.ScrollDelta.Y, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); + setZoomTarget(zoomTarget + e.ScrollDelta.Y * MaxZoom * zoom_scroll_sensitivity, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); return true; } From 3fad481a96799fecc55bb1819a4a28ed2f12972a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 22:27:22 +0900 Subject: [PATCH 206/481] Avoid using `RollingCounter` in fps counter It wasn't made to be updated every frame, and it shows. Inaccurate for reasons I'm not really interested in investigating, because I don't want to incur the `Transorm` overhead in the first place for an fps counter. Was only used originally out of convenience. --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 57 ++++++++++++------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 91e0429c85..6982f94abc 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -22,8 +22,8 @@ namespace osu.Game.Graphics.UserInterface { public class FPSCounter : VisibilityContainer, IHasCustomTooltip { - private RollingCounter counterUpdateFrameTime = null!; - private RollingCounter counterDrawFPS = null!; + private OsuSpriteText counterUpdateFrameTime = null!; + private OsuSpriteText counterDrawFPS = null!; private Container mainContent = null!; @@ -35,6 +35,9 @@ namespace osu.Game.Graphics.UserInterface private readonly BindableBool showFpsDisplay = new BindableBool(true); + private double displayedFpsCount; + private double displayedFrameTime; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -77,20 +80,23 @@ namespace osu.Game.Graphics.UserInterface AutoSizeAxes = Axes.Both, Children = new Drawable[] { - counterUpdateFrameTime = new FrameTimeCounter + counterUpdateFrameTime = new OsuSpriteText { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Margin = new MarginPadding(1), + Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), + Spacing = new Vector2(-1), Y = -2, }, - counterDrawFPS = new FramesPerSecondCounter + counterDrawFPS = new OsuSpriteText { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Margin = new MarginPadding(2), + Font = OsuFont.Default.With(fixedWidth: true, size: 13, weight: FontWeight.SemiBold), + Spacing = new Vector2(-2), Y = 10, - Scale = new Vector2(0.8f), } } }, @@ -183,37 +189,48 @@ namespace osu.Game.Graphics.UserInterface const double spike_time_ms = 20; - bool hasUpdateSpike = counterUpdateFrameTime.Current.Value < spike_time_ms && newUpdateFrameTime > spike_time_ms; + bool hasUpdateSpike = displayedFrameTime < spike_time_ms && newUpdateFrameTime > spike_time_ms; // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. - bool hasDrawSpike = counterDrawFPS.Current.Value > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms; + bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms; - // If the frame time spikes up, make sure it shows immediately on the counter. - if (hasUpdateSpike) - counterUpdateFrameTime.SetCountWithoutRolling(newUpdateFrameTime); - else - counterUpdateFrameTime.Current.Value = newUpdateFrameTime; + // note that we use an elapsed time here of 1 intentionally. + // this weights all updates equally. if we passed in the elapsed time, longer frames would be weighted incorrectly lower. + displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, newUpdateFrameTime, hasUpdateSpike ? 0 : 200, 1); if (hasDrawSpike) // show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show. - counterDrawFPS.SetCountWithoutRolling(1000 / newDrawFrameTime); + displayedFpsCount = 1000 / newDrawFrameTime; else - counterDrawFPS.Current.Value = newDrawFps; + displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 200, Time.Elapsed); - counterDrawFPS.Colour = getColour(counterDrawFPS.DisplayedCount / aimDrawFPS); - - double displayedUpdateFPS = 1000 / counterUpdateFrameTime.DisplayedCount; - counterUpdateFrameTime.Colour = getColour(displayedUpdateFPS / aimUpdateFPS); + updateFpsDisplay(); + updateFrameTimeDisplay(); bool hasSignificantChanges = aimRatesChanged || hasDrawSpike || hasUpdateSpike - || counterDrawFPS.DisplayedCount < aimDrawFPS * 0.8 - || displayedUpdateFPS < aimUpdateFPS * 0.8; + || displayedFpsCount < aimDrawFPS * 0.8 + || 1000 / displayedFrameTime < aimUpdateFPS * 0.8; if (hasSignificantChanges) displayTemporarily(); } + private void updateFpsDisplay() + { + counterDrawFPS.Colour = getColour(displayedFpsCount / aimDrawFPS); + counterDrawFPS.Text = $"{displayedFpsCount:#,0}fps"; + } + + private void updateFrameTimeDisplay() + { + counterUpdateFrameTime.Text = displayedFrameTime < 5 + ? $"{displayedFrameTime:N1}ms" + : $"{displayedFrameTime:N0}ms"; + + counterUpdateFrameTime.Colour = getColour((1000 / displayedFrameTime) / aimUpdateFPS); + } + private bool updateAimFPS() { if (gameHost.UpdateThread.Clock.Throttling) From 3d2603e0eb149451919950b4d3c350642070f829 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 22:51:35 +0900 Subject: [PATCH 207/481] Remove unused classes --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 46 ------------------- 1 file changed, 46 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 6982f94abc..d9d8a03b29 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Framework.Localisation; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Framework.Utils; @@ -270,50 +269,5 @@ namespace osu.Game.Graphics.UserInterface public ITooltip GetCustomTooltip() => new FPSCounterTooltip(); public object TooltipContent => this; - - public class FramesPerSecondCounter : RollingCounter - { - protected override double RollingDuration => 1000; - - protected override OsuSpriteText CreateSpriteText() - { - return new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), - Spacing = new Vector2(-2), - }; - } - - protected override LocalisableString FormatCount(double count) - { - return $"{count:#,0}fps"; - } - } - - public class FrameTimeCounter : RollingCounter - { - protected override double RollingDuration => 1000; - - protected override OsuSpriteText CreateSpriteText() - { - return new OsuSpriteText - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Font = OsuFont.Default.With(fixedWidth: true, size: 16, weight: FontWeight.SemiBold), - Spacing = new Vector2(-1), - }; - } - - protected override LocalisableString FormatCount(double count) - { - if (count < 1) - return $"{count:N1}ms"; - - return $"{count:N0}ms"; - } - } } } From fc6445caeab481a09e5aa1793c64ba9b9b19fbf7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 22:55:45 +0900 Subject: [PATCH 208/481] Rate limit updates for good measure --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index d9d8a03b29..825bde9d2d 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -30,6 +30,10 @@ namespace osu.Game.Graphics.UserInterface private Container counters = null!; + private const double min_time_between_updates = 10; + + private const double spike_time_ms = 20; + private const float idle_background_alpha = 0.4f; private readonly BindableBool showFpsDisplay = new BindableBool(true); @@ -147,6 +151,8 @@ namespace osu.Game.Graphics.UserInterface private double aimDrawFPS; private double aimUpdateFPS; + private double lastUpdate; + private void displayTemporarily() { if (!isDisplayed) @@ -186,8 +192,6 @@ namespace osu.Game.Graphics.UserInterface double newDrawFrameTime = gameHost.DrawThread.Clock.ElapsedFrameTime; double newDrawFps = gameHost.DrawThread.Clock.FramesPerSecond; - const double spike_time_ms = 20; - bool hasUpdateSpike = displayedFrameTime < spike_time_ms && newUpdateFrameTime > spike_time_ms; // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms; @@ -202,8 +206,13 @@ namespace osu.Game.Graphics.UserInterface else displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 200, Time.Elapsed); - updateFpsDisplay(); - updateFrameTimeDisplay(); + if (Time.Current - lastUpdate > min_time_between_updates) + { + updateFpsDisplay(); + updateFrameTimeDisplay(); + + lastUpdate = Time.Current; + } bool hasSignificantChanges = aimRatesChanged || hasDrawSpike From c140601c2de65aeb6ba5bcd58a68246ad08e3dfb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 23:02:31 +0900 Subject: [PATCH 209/481] Cleanup pass on `FPSCounter` --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 54 +++++++++---------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 825bde9d2d..0db54c3947 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Framework.Threading; +using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Graphics.Sprites; @@ -41,6 +42,18 @@ namespace osu.Game.Graphics.UserInterface private double displayedFpsCount; private double displayedFrameTime; + private bool isDisplayed; + + private ScheduledDelegate? fadeOutDelegate; + + private double aimDrawFPS; + private double aimUpdateFPS; + + private double lastUpdate; + private ThrottledFrameClock drawClock = null!; + private ThrottledFrameClock updateClock = null!; + private ThrottledFrameClock inputClock = null!; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -50,7 +63,7 @@ namespace osu.Game.Graphics.UserInterface } [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, GameHost gameHost) { InternalChildren = new Drawable[] { @@ -108,6 +121,10 @@ namespace osu.Game.Graphics.UserInterface }; config.BindWith(OsuSetting.ShowFpsDisplay, showFpsDisplay); + + drawClock = gameHost.DrawThread.Clock; + updateClock = gameHost.UpdateThread.Clock; + inputClock = gameHost.InputThread.Clock; } protected override void LoadComplete() @@ -144,15 +161,6 @@ namespace osu.Game.Graphics.UserInterface base.OnHoverLost(e); } - private bool isDisplayed; - - private ScheduledDelegate? fadeOutDelegate; - - private double aimDrawFPS; - private double aimUpdateFPS; - - private double lastUpdate; - private void displayTemporarily() { if (!isDisplayed) @@ -174,9 +182,6 @@ namespace osu.Game.Graphics.UserInterface } } - [Resolved] - private GameHost gameHost { get; set; } = null!; - protected override void Update() { base.Update(); @@ -187,24 +192,19 @@ namespace osu.Game.Graphics.UserInterface // frame limiter (we want to show the FPS as it's changing, even if it isn't an outlier). bool aimRatesChanged = updateAimFPS(); - // TODO: this is wrong (elapsed clock time, not actual run time). - double newUpdateFrameTime = gameHost.UpdateThread.Clock.ElapsedFrameTime; - double newDrawFrameTime = gameHost.DrawThread.Clock.ElapsedFrameTime; - double newDrawFps = gameHost.DrawThread.Clock.FramesPerSecond; - - bool hasUpdateSpike = displayedFrameTime < spike_time_ms && newUpdateFrameTime > spike_time_ms; + bool hasUpdateSpike = displayedFrameTime < spike_time_ms && updateClock.ElapsedFrameTime > spike_time_ms; // use elapsed frame time rather then FramesPerSecond to better catch stutter frames. - bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && newDrawFrameTime > spike_time_ms; + bool hasDrawSpike = displayedFpsCount > (1000 / spike_time_ms) && drawClock.ElapsedFrameTime > spike_time_ms; // note that we use an elapsed time here of 1 intentionally. // this weights all updates equally. if we passed in the elapsed time, longer frames would be weighted incorrectly lower. - displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, newUpdateFrameTime, hasUpdateSpike ? 0 : 200, 1); + displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, updateClock.ElapsedFrameTime, hasUpdateSpike ? 0 : 200, 1); if (hasDrawSpike) // show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show. - displayedFpsCount = 1000 / newDrawFrameTime; + displayedFpsCount = 1000 / drawClock.ElapsedFrameTime; else - displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 200, Time.Elapsed); + displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, drawClock.FramesPerSecond, 200, Time.Elapsed); if (Time.Current - lastUpdate > min_time_between_updates) { @@ -241,10 +241,10 @@ namespace osu.Game.Graphics.UserInterface private bool updateAimFPS() { - if (gameHost.UpdateThread.Clock.Throttling) + if (updateClock.Throttling) { - double newAimDrawFPS = gameHost.DrawThread.Clock.MaximumUpdateHz; - double newAimUpdateFPS = gameHost.UpdateThread.Clock.MaximumUpdateHz; + double newAimDrawFPS = drawClock.MaximumUpdateHz; + double newAimUpdateFPS = updateClock.MaximumUpdateHz; if (aimDrawFPS != newAimDrawFPS || aimUpdateFPS != newAimUpdateFPS) { @@ -255,7 +255,7 @@ namespace osu.Game.Graphics.UserInterface } else { - double newAimFPS = gameHost.InputThread.Clock.MaximumUpdateHz; + double newAimFPS = inputClock.MaximumUpdateHz; if (aimDrawFPS != newAimFPS || aimUpdateFPS != newAimFPS) { From 726042d9ec2d8eef2a3549fe665d69ba8c51cc15 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 23:16:28 +0900 Subject: [PATCH 210/481] Use switch instead of `or` --- .../Skinning/TickFollowCircle.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs index de8a8150d0..ca1959a561 100644 --- a/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs @@ -25,13 +25,26 @@ namespace osu.Game.Rulesets.Osu.Skinning switch (state) { case ArmedState.Hit: - if (drawableObject is DrawableSliderTick or DrawableSliderRepeat) - OnSliderTick(); + switch (drawableObject) + { + case DrawableSliderTick: + case DrawableSliderRepeat: + OnSliderTick(); + break; + } + break; case ArmedState.Miss: - if (drawableObject is DrawableSliderTail or DrawableSliderTick or DrawableSliderRepeat) - OnSliderBreak(); + switch (drawableObject) + { + case DrawableSliderTail: + case DrawableSliderTick: + case DrawableSliderRepeat: + OnSliderBreak(); + break; + } + break; } } From 0eeafea50002d18f76a27ff390075ab859030893 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 Jul 2022 23:37:32 +0900 Subject: [PATCH 211/481] Increase responsiveness to change slightly --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index d9d8a03b29..07eb9fa3ca 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -194,13 +194,13 @@ namespace osu.Game.Graphics.UserInterface // note that we use an elapsed time here of 1 intentionally. // this weights all updates equally. if we passed in the elapsed time, longer frames would be weighted incorrectly lower. - displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, newUpdateFrameTime, hasUpdateSpike ? 0 : 200, 1); + displayedFrameTime = Interpolation.DampContinuously(displayedFrameTime, newUpdateFrameTime, hasUpdateSpike ? 0 : 100, 1); if (hasDrawSpike) // show spike time using raw elapsed value, to account for `FramesPerSecond` being so averaged spike frames don't show. displayedFpsCount = 1000 / newDrawFrameTime; else - displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 200, Time.Elapsed); + displayedFpsCount = Interpolation.DampContinuously(displayedFpsCount, newDrawFps, 100, Time.Elapsed); updateFpsDisplay(); updateFrameTimeDisplay(); From b3aa496ba783b3dc7c63303d88c940edf6dc9c99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 00:14:30 +0900 Subject: [PATCH 212/481] Add handling of realm disposed exceptions --- osu.Game/BackgroundBeatmapProcessor.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 9b04f487a8..6ecd8ca5c1 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -46,6 +46,14 @@ namespace osu.Game Logger.Log("Beginning background beatmap processing.."); checkForOutdatedStarRatings(); processBeatmapSetsWithMissingMetrics(); + }).ContinueWith(t => + { + if (t.Exception?.InnerException is ObjectDisposedException) + { + Logger.Log("Finished background aborted during shutdown"); + return; + } + Logger.Log("Finished background beatmap processing!"); }); } From d796b7d53c0bb135f667cd68dbd6633a442816c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jul 2022 21:56:23 +0200 Subject: [PATCH 213/481] Extract base mod select overlay panel presentation logic --- osu.Game/Overlays/Mods/ModPanel.cs | 237 ++--------------- .../Overlays/Mods/ModSelectOverlayPanel.cs | 250 ++++++++++++++++++ 2 files changed, 267 insertions(+), 220 deletions(-) create mode 100644 osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 02eb395bd9..5de6290248 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -1,144 +1,42 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; -using osu.Framework.Utils; -using osu.Game.Audio; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osuTK; -using osuTK.Input; namespace osu.Game.Overlays.Mods { - public class ModPanel : OsuClickableContainer + public class ModPanel : ModSelectOverlayPanel { public Mod Mod => modState.Mod; - public BindableBool Active => modState.Active; + public override BindableBool Active => modState.Active; public BindableBool Filtered => modState.Filtered; + protected override float IdleSwitchWidth => 54; + protected override float ExpandedSwitchWidth => 70; + private readonly ModState modState; - protected readonly Box Background; - protected readonly Container SwitchContainer; - protected readonly Container MainContentContainer; - protected readonly Box TextBackground; - protected readonly FillFlowContainer TextFlow; - - [Resolved] - protected OverlayColourProvider ColourProvider { get; private set; } = null!; - - protected const double TRANSITION_DURATION = 150; - - public const float CORNER_RADIUS = 7; - - protected const float HEIGHT = 42; - protected const float IDLE_SWITCH_WIDTH = 54; - protected const float EXPANDED_SWITCH_WIDTH = 70; - - private Colour4 activeColour; - - private readonly Bindable samplePlaybackDisabled = new BindableBool(); - private Sample? sampleOff; - private Sample? sampleOn; - public ModPanel(ModState modState) { this.modState = modState; - RelativeSizeAxes = Axes.X; - Height = 42; + Title = Mod.Name; + Description = Mod.Description; - // all below properties are applied to `Content` rather than the `ModPanel` in its entirety - // to allow external components to set these properties on the panel without affecting - // its "internal" appearance. - Content.Masking = true; - Content.CornerRadius = CORNER_RADIUS; - Content.BorderThickness = 2; - Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0); - - Children = new Drawable[] + SwitchContainer.Child = new ModSwitchSmall(Mod) { - Background = new Box - { - RelativeSizeAxes = Axes.Both - }, - SwitchContainer = new Container - { - RelativeSizeAxes = Axes.Y, - Child = new ModSwitchSmall(Mod) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Active = { BindTarget = Active }, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), - Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE) - } - }, - MainContentContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = CORNER_RADIUS, - Children = new Drawable[] - { - TextBackground = new Box - { - RelativeSizeAxes = Axes.Both - }, - TextFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Horizontal = 17.5f, - Vertical = 4 - }, - Direction = FillDirection.Vertical, - Children = new[] - { - new OsuSpriteText - { - Text = Mod.Name, - Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold), - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), - Margin = new MarginPadding - { - Left = -18 * ShearedOverlayContainer.SHEAR - } - }, - new OsuSpriteText - { - Text = Mod.Description, - Font = OsuFont.Default.With(size: 12), - RelativeSizeAxes = Axes.X, - Truncate = true, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0) - } - } - } - } - } - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Active = { BindTarget = Active }, + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Scale = new Vector2(HEIGHT / ModSwitchSmall.DEFAULT_SIZE) }; - - Action = Active.Toggle; } public ModPanel(Mod mod) @@ -146,122 +44,21 @@ namespace osu.Game.Overlays.Mods { } - [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, OsuColour colours, ISamplePlaybackDisabler? samplePlaybackDisabler) + [BackgroundDependencyLoader] + private void load(OsuColour colours) { - sampleOn = audio.Samples.Get(@"UI/check-on"); - sampleOff = audio.Samples.Get(@"UI/check-off"); - - activeColour = colours.ForModType(Mod.Type); - - if (samplePlaybackDisabler != null) - ((IBindable)samplePlaybackDisabled).BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); + AccentColour = colours.ForModType(Mod.Type); } - protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet); - protected override void LoadComplete() { base.LoadComplete(); - Active.BindValueChanged(_ => - { - playStateChangeSamples(); - UpdateState(); - }); + Filtered.BindValueChanged(_ => updateFilterState(), true); - - UpdateState(); - FinishTransforms(true); - } - - private void playStateChangeSamples() - { - if (samplePlaybackDisabled.Value) - return; - - if (Active.Value) - sampleOn?.Play(); - else - sampleOff?.Play(); - } - - protected override bool OnHover(HoverEvent e) - { - UpdateState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - UpdateState(); - base.OnHoverLost(e); - } - - private bool mouseDown; - - protected override bool OnMouseDown(MouseDownEvent e) - { - if (e.Button == MouseButton.Left) - mouseDown = true; - - UpdateState(); - return false; - } - - protected override void OnMouseUp(MouseUpEvent e) - { - mouseDown = false; - - UpdateState(); - base.OnMouseUp(e); - } - - protected virtual Colour4 BackgroundColour => Active.Value ? activeColour.Darken(0.3f) : ColourProvider.Background3; - protected virtual Colour4 ForegroundColour => Active.Value ? activeColour : ColourProvider.Background2; - protected virtual Colour4 TextColour => Active.Value ? ColourProvider.Background6 : Colour4.White; - - protected virtual void UpdateState() - { - float targetWidth = Active.Value ? EXPANDED_SWITCH_WIDTH : IDLE_SWITCH_WIDTH; - double transitionDuration = TRANSITION_DURATION; - - Colour4 backgroundColour = BackgroundColour; - Colour4 foregroundColour = ForegroundColour; - Colour4 textColour = TextColour; - - // Hover affects colour of button background - if (IsHovered) - { - backgroundColour = backgroundColour.Lighten(0.1f); - foregroundColour = foregroundColour.Lighten(0.1f); - } - - // Mouse down adds a halfway tween of the movement - if (mouseDown) - { - targetWidth = (float)Interpolation.Lerp(IDLE_SWITCH_WIDTH, EXPANDED_SWITCH_WIDTH, 0.5f); - transitionDuration *= 4; - } - - Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, foregroundColour), transitionDuration, Easing.OutQuint); - Background.FadeColour(backgroundColour, transitionDuration, Easing.OutQuint); - SwitchContainer.ResizeWidthTo(targetWidth, transitionDuration, Easing.OutQuint); - MainContentContainer.TransformTo(nameof(Padding), new MarginPadding - { - Left = targetWidth, - Right = CORNER_RADIUS - }, transitionDuration, Easing.OutQuint); - TextBackground.FadeColour(foregroundColour, transitionDuration, Easing.OutQuint); - TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint); } #region Filtering support - public void ApplyFilter(Func? filter) - { - Filtered.Value = filter != null && !filter.Invoke(Mod); - } - private void updateFilterState() { this.FadeTo(Filtered.Value ? 0 : 1); diff --git a/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs b/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs new file mode 100644 index 0000000000..d4969e0a81 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs @@ -0,0 +1,250 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Framework.Utils; +using osu.Game.Audio; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; + +namespace osu.Game.Overlays.Mods +{ + public abstract class ModSelectOverlayPanel : OsuClickableContainer, IHasAccentColour + { + public abstract BindableBool Active { get; } + + public Color4 AccentColour { get; set; } + + public LocalisableString Title + { + get => titleText.Text; + set => titleText.Text = value; + } + + public LocalisableString Description + { + get => descriptionText.Text; + set => descriptionText.Text = value; + } + + public const float CORNER_RADIUS = 7; + + protected const float HEIGHT = 42; + + protected virtual float IdleSwitchWidth => 14; + protected virtual float ExpandedSwitchWidth => 30; + protected virtual Colour4 BackgroundColour => Active.Value ? AccentColour.Darken(0.3f) : ColourProvider.Background3; + protected virtual Colour4 ForegroundColour => Active.Value ? AccentColour : ColourProvider.Background2; + protected virtual Colour4 TextColour => Active.Value ? ColourProvider.Background6 : Colour4.White; + + protected const double TRANSITION_DURATION = 150; + + protected readonly Box Background; + protected readonly Container SwitchContainer; + protected readonly Container MainContentContainer; + protected readonly Box TextBackground; + protected readonly FillFlowContainer TextFlow; + + [Resolved] + protected OverlayColourProvider ColourProvider { get; private set; } = null!; + + private readonly OsuSpriteText titleText; + private readonly OsuSpriteText descriptionText; + + private readonly Bindable samplePlaybackDisabled = new BindableBool(); + private Sample? sampleOff; + private Sample? sampleOn; + + protected ModSelectOverlayPanel() + { + RelativeSizeAxes = Axes.X; + Height = HEIGHT; + + // all below properties are applied to `Content` rather than the `ModPanel` in its entirety + // to allow external components to set these properties on the panel without affecting + // its "internal" appearance. + Content.Masking = true; + Content.CornerRadius = CORNER_RADIUS; + Content.BorderThickness = 2; + + Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0); + + Children = new Drawable[] + { + Background = new Box + { + RelativeSizeAxes = Axes.Both + }, + SwitchContainer = new Container + { + RelativeSizeAxes = Axes.Y, + }, + MainContentContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = CORNER_RADIUS, + Children = new Drawable[] + { + TextBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + TextFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Horizontal = 17.5f, + Vertical = 4 + }, + Direction = FillDirection.Vertical, + Children = new[] + { + titleText = new OsuSpriteText + { + Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold), + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Margin = new MarginPadding + { + Left = -18 * ShearedOverlayContainer.SHEAR + } + }, + descriptionText = new OsuSpriteText + { + Font = OsuFont.Default.With(size: 12), + RelativeSizeAxes = Axes.X, + Truncate = true, + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0) + } + } + } + } + } + } + }; + + Action = () => Active.Toggle(); + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio, ISamplePlaybackDisabler? samplePlaybackDisabler) + { + sampleOn = audio.Samples.Get(@"UI/check-on"); + sampleOff = audio.Samples.Get(@"UI/check-off"); + + if (samplePlaybackDisabler != null) + ((IBindable)samplePlaybackDisabled).BindTo(samplePlaybackDisabler.SamplePlaybackDisabled); + } + + protected sealed override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet); + + protected override void LoadComplete() + { + base.LoadComplete(); + Active.BindValueChanged(_ => + { + playStateChangeSamples(); + UpdateState(); + }); + + UpdateState(); + FinishTransforms(true); + } + + private void playStateChangeSamples() + { + if (samplePlaybackDisabled.Value) + return; + + if (Active.Value) + sampleOn?.Play(); + else + sampleOff?.Play(); + } + + protected override bool OnHover(HoverEvent e) + { + UpdateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + UpdateState(); + base.OnHoverLost(e); + } + + private bool mouseDown; + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.Button == MouseButton.Left) + mouseDown = true; + + UpdateState(); + return false; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + mouseDown = false; + + UpdateState(); + base.OnMouseUp(e); + } + + protected virtual void UpdateState() + { + float targetWidth = Active.Value ? ExpandedSwitchWidth : IdleSwitchWidth; + double transitionDuration = TRANSITION_DURATION; + + Colour4 backgroundColour = BackgroundColour; + Colour4 foregroundColour = ForegroundColour; + Colour4 textColour = TextColour; + + // Hover affects colour of button background + if (IsHovered) + { + backgroundColour = backgroundColour.Lighten(0.1f); + foregroundColour = foregroundColour.Lighten(0.1f); + } + + // Mouse down adds a halfway tween of the movement + if (mouseDown) + { + targetWidth = (float)Interpolation.Lerp(IdleSwitchWidth, ExpandedSwitchWidth, 0.5f); + transitionDuration *= 4; + } + + Content.TransformTo(nameof(BorderColour), ColourInfo.GradientVertical(backgroundColour, foregroundColour), transitionDuration, Easing.OutQuint); + Background.FadeColour(backgroundColour, transitionDuration, Easing.OutQuint); + SwitchContainer.ResizeWidthTo(targetWidth, transitionDuration, Easing.OutQuint); + MainContentContainer.TransformTo(nameof(Padding), new MarginPadding + { + Left = targetWidth, + Right = CORNER_RADIUS + }, transitionDuration, Easing.OutQuint); + TextBackground.FadeColour(foregroundColour, transitionDuration, Easing.OutQuint); + TextFlow.FadeColour(textColour, transitionDuration, Easing.OutQuint); + } + } +} From 6cd18fad99b65202a87c2a7e4399b1b86990a963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jul 2022 23:29:02 +0200 Subject: [PATCH 214/481] Fix code inspections after base panel class extraction --- osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs | 6 +++--- osu.Game/Overlays/Mods/ModColumn.cs | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs index c8d55c213a..c0a4cf2a25 100644 --- a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Masking = true, - CornerRadius = ModPanel.CORNER_RADIUS, + CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0), Children = new Drawable[] { @@ -69,7 +69,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Y, - Width = multiplier_value_area_width + ModPanel.CORNER_RADIUS + Width = multiplier_value_area_width + ModSelectOverlayPanel.CORNER_RADIUS }, new GridContainer { @@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Masking = true, - CornerRadius = ModPanel.CORNER_RADIUS, + CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, Children = new Drawable[] { contentBackground = new Box diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 3f788d10e3..63a51aaad0 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -105,20 +105,20 @@ namespace osu.Game.Overlays.Mods TopLevelContent = new Container { RelativeSizeAxes = Axes.Both, - CornerRadius = ModPanel.CORNER_RADIUS, + CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, Masking = true, Children = new Drawable[] { new Container { RelativeSizeAxes = Axes.X, - Height = header_height + ModPanel.CORNER_RADIUS, + Height = header_height + ModSelectOverlayPanel.CORNER_RADIUS, Children = new Drawable[] { headerBackground = new Box { RelativeSizeAxes = Axes.X, - Height = header_height + ModPanel.CORNER_RADIUS + Height = header_height + ModSelectOverlayPanel.CORNER_RADIUS }, headerText = new OsuTextFlowContainer(t => { @@ -135,7 +135,7 @@ namespace osu.Game.Overlays.Mods Padding = new MarginPadding { Horizontal = 17, - Bottom = ModPanel.CORNER_RADIUS + Bottom = ModSelectOverlayPanel.CORNER_RADIUS } } } @@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Mods { RelativeSizeAxes = Axes.Both, Masking = true, - CornerRadius = ModPanel.CORNER_RADIUS, + CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, BorderThickness = 3, Children = new Drawable[] { From de0a076eb6ce9835810a13977319413c4e41ac1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jul 2022 20:56:43 +0200 Subject: [PATCH 215/481] Add model class for mod presets --- osu.Game/Rulesets/Mods/ModPreset.cs | 35 +++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 osu.Game/Rulesets/Mods/ModPreset.cs diff --git a/osu.Game/Rulesets/Mods/ModPreset.cs b/osu.Game/Rulesets/Mods/ModPreset.cs new file mode 100644 index 0000000000..367acc8a91 --- /dev/null +++ b/osu.Game/Rulesets/Mods/ModPreset.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// A mod preset is a named collection of configured mods. + /// Presets are presented to the user in the mod select overlay for convenience. + /// + public class ModPreset + { + /// + /// The ruleset that the preset is valid for. + /// + public RulesetInfo RulesetInfo { get; set; } = null!; + + /// + /// The name of the mod preset. + /// + public string Name { get; set; } = string.Empty; + + /// + /// The description of the mod preset. + /// + public string Description { get; set; } = string.Empty; + + /// + /// The set of configured mods that are part of the preset. + /// + public ICollection Mods { get; set; } = Array.Empty(); + } +} From bdff7f1ef409bb48ca98a39d65c689c238281791 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jul 2022 22:01:03 +0200 Subject: [PATCH 216/481] Implement basic appearance of mod preset panels --- .../UserInterface/TestSceneModPresetPanel.cs | 74 +++++++++++++++++++ osu.Game/Overlays/Mods/ModPresetPanel.cs | 27 +++++++ .../Overlays/Mods/ModSelectOverlayPanel.cs | 2 + 3 files changed, 103 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs create mode 100644 osu.Game/Overlays/Mods/ModPresetPanel.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs new file mode 100644 index 0000000000..62e63d47bc --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneModPresetPanel : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + + [Test] + public void TestVariousModPresets() + { + AddStep("create content", () => Child = new FillFlowContainer + { + Width = 300, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(0, 5), + ChildrenEnumerable = createTestPresets().Select(preset => new ModPresetPanel(preset)) + }); + } + + private static IEnumerable createTestPresets() => new[] + { + new ModPreset + { + Name = "First preset", + Description = "Please ignore", + Mods = new Mod[] + { + new OsuModHardRock(), + new OsuModDoubleTime() + } + }, + new ModPreset + { + Name = "AR0", + Description = "For good readers", + Mods = new Mod[] + { + new OsuModDifficultyAdjust + { + ApproachRate = { Value = 0 } + } + } + }, + new ModPreset + { + Name = "This preset is going to have an extraordinarily long name", + Description = "This is done so that the capability to truncate overlong texts may be demonstrated", + Mods = new Mod[] + { + new OsuModFlashlight(), + new OsuModSpinIn() + } + } + }; + } +} diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs new file mode 100644 index 0000000000..7a55f99088 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModPresetPanel.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; + +namespace osu.Game.Overlays.Mods +{ + public class ModPresetPanel : ModSelectOverlayPanel + { + public override BindableBool Active { get; } = new BindableBool(); + + public ModPresetPanel(ModPreset preset) + { + Title = preset.Name; + Description = preset.Description; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Orange1; + } + } +} diff --git a/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs b/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs index d4969e0a81..a794884d7d 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs @@ -122,6 +122,8 @@ namespace osu.Game.Overlays.Mods titleText = new OsuSpriteText { Font = OsuFont.TorusAlternate.With(size: 18, weight: FontWeight.SemiBold), + RelativeSizeAxes = Axes.X, + Truncate = true, Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), Margin = new MarginPadding { From a3090003de8af21d617c373285bb98350c83933e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 21 Jul 2022 22:34:54 +0200 Subject: [PATCH 217/481] Add tooltip showing contents of mod preset --- osu.Game/Overlays/Mods/ModPresetPanel.cs | 10 +- osu.Game/Overlays/Mods/ModPresetTooltip.cs | 115 +++++++++++++++++++++ 2 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/Mods/ModPresetTooltip.cs diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs index 7a55f99088..39f092b91a 100644 --- a/osu.Game/Overlays/Mods/ModPresetPanel.cs +++ b/osu.Game/Overlays/Mods/ModPresetPanel.cs @@ -3,17 +3,22 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Graphics.Cursor; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; namespace osu.Game.Overlays.Mods { - public class ModPresetPanel : ModSelectOverlayPanel + public class ModPresetPanel : ModSelectOverlayPanel, IHasCustomTooltip { + public readonly ModPreset Preset; + public override BindableBool Active { get; } = new BindableBool(); public ModPresetPanel(ModPreset preset) { + Preset = preset; + Title = preset.Name; Description = preset.Description; } @@ -23,5 +28,8 @@ namespace osu.Game.Overlays.Mods { AccentColour = colours.Orange1; } + + public ModPreset TooltipContent => Preset; + public ITooltip GetCustomTooltip() => new ModPresetTooltip(ColourProvider); } } diff --git a/osu.Game/Overlays/Mods/ModPresetTooltip.cs b/osu.Game/Overlays/Mods/ModPresetTooltip.cs new file mode 100644 index 0000000000..68da649e81 --- /dev/null +++ b/osu.Game/Overlays/Mods/ModPresetTooltip.cs @@ -0,0 +1,115 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.UI; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + public class ModPresetTooltip : VisibilityContainer, ITooltip + { + protected override Container Content { get; } + + private const double transition_duration = 200; + + public ModPresetTooltip(OverlayColourProvider colourProvider) + { + Width = 250; + AutoSizeAxes = Axes.Y; + + Masking = true; + CornerRadius = 7; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6 + }, + Content = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(7), + Spacing = new Vector2(7) + } + }; + } + + private ModPreset? lastPreset; + + public void SetContent(ModPreset preset) + { + if (preset == lastPreset) + return; + + lastPreset = preset; + Content.ChildrenEnumerable = preset.Mods.Select(mod => new ModPresetRow(mod)); + } + + protected override void PopIn() => this.FadeIn(transition_duration, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(transition_duration, Easing.OutQuint); + + public void Move(Vector2 pos) => Position = pos; + + private class ModPresetRow : FillFlowContainer + { + public ModPresetRow(Mod mod) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + Spacing = new Vector2(4); + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(7), + Children = new Drawable[] + { + new ModSwitchTiny(mod) + { + Active = { Value = true }, + Scale = new Vector2(0.6f), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }, + new OsuSpriteText + { + Text = mod.Name, + Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold), + Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Margin = new MarginPadding { Bottom = 2 } + } + } + } + }; + + if (!string.IsNullOrEmpty(mod.SettingDescription)) + { + AddInternal(new OsuTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = 14 }, + Text = mod.SettingDescription + }); + } + } + } + } +} From d69dc457ba2adf3d0a6db8f89237292cebb62efd Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Thu, 21 Jul 2022 17:28:43 -0500 Subject: [PATCH 218/481] Extract zoom delta method --- .../Edit/Compose/Components/Timeline/TimelineArea.cs | 4 +--- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 6 ++++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index c25a4834d3..b47c09109b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -20,8 +20,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Drawable userContent; - private const float zoom_button_sensitivity = 0.02f; - public TimelineArea(Drawable content = null) { RelativeSizeAxes = Axes.X; @@ -156,6 +154,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Timeline.TicksVisible.BindTo(ticksCheckbox.Current); } - private void changeZoom(float change) => Timeline.Zoom += change * Timeline.MaxZoom * zoom_button_sensitivity; + private void changeZoom(float change) => Timeline.Zoom += Timeline.CalculateZoomChange(change); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 7a4d45301f..9bd7332105 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline set => updateZoom(value); } - private const float zoom_scroll_sensitivity = 0.02f; + private const float zoom_change_sensitivity = 0.02f; private void updateZoom(float? value = null) { @@ -129,7 +129,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (e.AltPressed) { // zoom when holding alt. - setZoomTarget(zoomTarget + e.ScrollDelta.Y * MaxZoom * zoom_scroll_sensitivity, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); + setZoomTarget(zoomTarget + CalculateZoomChange(e.ScrollDelta.Y), zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); return true; } @@ -167,6 +167,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { } + public float CalculateZoomChange(float rawChange) => rawChange * MaxZoom * zoom_change_sensitivity; + private class TransformZoom : Transform { /// From 997fe00cdcab42a58ef1ba7533acc1278fe42e37 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Thu, 21 Jul 2022 17:29:13 -0500 Subject: [PATCH 219/481] Fix zoom delta math --- .../Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 9bd7332105..4d95600450 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -167,7 +167,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { } - public float CalculateZoomChange(float rawChange) => rawChange * MaxZoom * zoom_change_sensitivity; + public float CalculateZoomChange(float rawChange) => rawChange * (MaxZoom - minZoom) * zoom_change_sensitivity; private class TransformZoom : Transform { From ed94d7fce84c5cc4eefcabc0585647d270d85129 Mon Sep 17 00:00:00 2001 From: LukynkaCZE <48604271+LukynkaCZE@users.noreply.github.com> Date: Fri, 22 Jul 2022 02:46:17 +0200 Subject: [PATCH 220/481] Fix requested changes --- osu.Game/Localisation/ToastStrings.cs | 20 +++++--------------- osu.Game/Screens/Edit/Editor.cs | 4 ++-- osu.Game/Skinning/Editor/SkinEditor.cs | 4 ++-- 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 4169a23798..9ceee807e6 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -35,24 +35,14 @@ namespace osu.Game.Localisation public static LocalisableString RestartTrack => new TranslatableString(getKey(@"restart_track"), @"Restart track"); /// - /// "Beatmap Editor" - /// r - public static LocalisableString BeatmapEditor => new TranslatableString(getKey(@"beatmap_editor"), @"Beatmap Editor"); + /// "Beatmap saved" + /// + public static LocalisableString BeatmapSaved => new TranslatableString(getKey(@"beatmap_saved"), @"Beatmap saved"); /// - /// "Beatmap Saved" + /// "Skin saved" /// - public static LocalisableString EditorSaveBeatmap => new TranslatableString(getKey(@"beatmap_editor_save"), @"Beatmap Saved"); - - /// - /// "Skin Editor" - /// - public static LocalisableString SkinEditor => new TranslatableString(getKey(@"skin_editor"), @"Skin Editor"); - - /// - /// "Skin Saved" - /// - public static LocalisableString EditorSaveSkin => new TranslatableString(getKey(@"skin_editor_save"), @"Skin Saved"); + public static LocalisableString SkinSaved => new TranslatableString(getKey(@"skin_saved"), @"Skin saved"); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index bf9785063b..1933076338 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -411,7 +411,7 @@ namespace osu.Game.Screens.Edit // no longer new after first user-triggered save. isNewBeatmap = false; updateLastSavedHash(); - onScreenDisplay?.Display(new BeatmapEditorToast(ToastStrings.EditorSaveBeatmap, editorBeatmap.BeatmapInfo.GetDisplayTitle())); + onScreenDisplay?.Display(new BeatmapEditorToast(ToastStrings.BeatmapSaved, editorBeatmap.BeatmapInfo.GetDisplayTitle())); return true; } @@ -945,7 +945,7 @@ namespace osu.Game.Screens.Edit private class BeatmapEditorToast : Toast { public BeatmapEditorToast(LocalisableString value, string beatmapDisplayName) - : base(ToastStrings.BeatmapEditor, value, beatmapDisplayName) { } + : base(InputSettingsStrings.EditorSection, value, beatmapDisplayName) { } } } } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 326574f2da..b02054072b 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -322,7 +322,7 @@ namespace osu.Game.Skinning.Editor currentSkin.Value.UpdateDrawableTarget(t); skins.Save(skins.CurrentSkin.Value); - onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.EditorSaveSkin, currentSkin.Value.SkinInfo.ToString())); + onScreenDisplay?.Display(new SkinEditorToast(ToastStrings.SkinSaved, currentSkin.Value.SkinInfo.ToString())); } protected override bool OnHover(HoverEvent e) => true; @@ -406,7 +406,7 @@ namespace osu.Game.Skinning.Editor private class SkinEditorToast : Toast { public SkinEditorToast(LocalisableString value, string skinDisplayName) - : base(ToastStrings.SkinEditor, value, skinDisplayName) { } + : base(SkinSettingsStrings.SkinLayoutEditor, value, skinDisplayName) { } } } } From c2c2c505a4f2ab578afaff235e3c042e1e42bf00 Mon Sep 17 00:00:00 2001 From: Alden Wu Date: Thu, 21 Jul 2022 19:46:46 -0700 Subject: [PATCH 221/481] Combine FollowCircle and TickFollowCircle classes --- .../Skinning/Default/DefaultFollowCircle.cs | 13 ++++ .../Skinning/FollowCircle.cs | 52 ++++++++++--- .../Skinning/Legacy/LegacyFollowCircle.cs | 6 +- .../Skinning/TickFollowCircle.cs | 73 ------------------- 4 files changed, 58 insertions(+), 86 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 51cfb2568b..3b087245e9 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -56,5 +56,18 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default this.ScaleTo(1, duration, Easing.OutQuint) .FadeOut(duration / 2, Easing.OutQuint); } + + protected override void OnSliderTick() + { + // TODO: Follow circle should bounce on each slider tick. + + // TEMP DUMMY ANIMS + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.1f) + .ScaleTo(DrawableSliderBall.FOLLOW_AREA, 175f); + } + + protected override void OnSliderBreak() + { + } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs index 06fa381cbb..9eb8e66c83 100644 --- a/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/FollowCircle.cs @@ -58,21 +58,45 @@ namespace osu.Game.Rulesets.Osu.Skinning private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) { - if (drawableObject is not DrawableSliderTail) - return; - Debug.Assert(ParentObject != null); - // Use ParentObject instead of drawableObject because slider tail hit state update time - // is ~36ms before the actual slider end (aka slider tail leniency) - using (BeginAbsoluteSequence(ParentObject.HitStateUpdateTime)) + switch (state) { - switch (state) - { - case ArmedState.Hit: - OnSliderEnd(); - break; - } + case ArmedState.Hit: + switch (drawableObject) + { + case DrawableSliderTail: + // Use ParentObject instead of drawableObject because slider tail's + // HitStateUpdateTime is ~36ms before the actual slider end (aka slider + // tail leniency) + using (BeginAbsoluteSequence(ParentObject.HitStateUpdateTime)) + OnSliderEnd(); + break; + + case DrawableSliderTick: + case DrawableSliderRepeat: + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + OnSliderTick(); + break; + } + + break; + + case ArmedState.Miss: + switch (drawableObject) + { + case DrawableSliderTail: + case DrawableSliderTick: + case DrawableSliderRepeat: + // Despite above comment, ok to use drawableObject.HitStateUpdateTime + // here, since on stable, the break anim plays right when the tail is + // missed, not when the slider ends + using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) + OnSliderBreak(); + break; + } + + break; } } @@ -92,5 +116,9 @@ namespace osu.Game.Rulesets.Osu.Skinning protected abstract void OnSliderRelease(); protected abstract void OnSliderEnd(); + + protected abstract void OnSliderTick(); + + protected abstract void OnSliderBreak(); } } diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index 9e1c2e7e9d..6d16596ed2 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -7,7 +7,7 @@ using osu.Framework.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Legacy { - public class LegacyFollowCircle : TickFollowCircle + public class LegacyFollowCircle : FollowCircle { public LegacyFollowCircle(Drawable animationContent) { @@ -32,6 +32,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy .FadeTo(0).FadeTo(1f, Math.Min(60f, remainingTime)); } + protected override void OnSliderRelease() + { + } + protected override void OnSliderEnd() { this.ScaleTo(1.6f, 200, Easing.Out) diff --git a/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs deleted file mode 100644 index ca1959a561..0000000000 --- a/osu.Game.Rulesets.Osu/Skinning/TickFollowCircle.cs +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables; - -namespace osu.Game.Rulesets.Osu.Skinning -{ - public abstract class TickFollowCircle : FollowCircle - { - protected override void LoadComplete() - { - base.LoadComplete(); - - if (ParentObject != null) - ParentObject.ApplyCustomUpdateState += updateStateTransforms; - } - - private void updateStateTransforms(DrawableHitObject drawableObject, ArmedState state) - { - // Fine to use drawableObject.HitStateUpdateTime even for DrawableSliderTail, since on - // stable, the break anim plays right when the tail is missed, not when the slider ends - using (BeginAbsoluteSequence(drawableObject.HitStateUpdateTime)) - { - switch (state) - { - case ArmedState.Hit: - switch (drawableObject) - { - case DrawableSliderTick: - case DrawableSliderRepeat: - OnSliderTick(); - break; - } - - break; - - case ArmedState.Miss: - switch (drawableObject) - { - case DrawableSliderTail: - case DrawableSliderTick: - case DrawableSliderRepeat: - OnSliderBreak(); - break; - } - - break; - } - } - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (ParentObject != null) - ParentObject.ApplyCustomUpdateState -= updateStateTransforms; - } - - /// - /// Sealed empty intentionally. Override instead, since - /// animations should only play on slider ticks. - /// - protected sealed override void OnSliderRelease() - { - } - - protected abstract void OnSliderTick(); - - protected abstract void OnSliderBreak(); - } -} From 92f59ce9a0da37e0c5e2a7d10d9d84ce95675161 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 13:10:18 +0900 Subject: [PATCH 222/481] Add the ability to save in the skin editor using system save hotkey --- osu.Game/Skinning/Editor/SkinEditor.cs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 649b63dda4..27c8d0711a 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -13,6 +13,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Testing; using osu.Game.Database; @@ -27,7 +29,7 @@ using osu.Game.Screens.Edit.Components.Menus; namespace osu.Game.Skinning.Editor { [Cached(typeof(SkinEditor))] - public class SkinEditor : VisibilityContainer, ICanAcceptFiles + public class SkinEditor : VisibilityContainer, ICanAcceptFiles, IKeyBindingHandler { public const double TRANSITION_DURATION = 500; @@ -199,6 +201,25 @@ namespace osu.Game.Skinning.Editor SelectedComponents.BindCollectionChanged((_, _) => Scheduler.AddOnce(populateSettings), true); } + public bool OnPressed(KeyBindingPressEvent e) + { + switch (e.Action) + { + case PlatformAction.Save: + if (e.Repeat) + return false; + + Save(); + return true; + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + public void UpdateTargetScreen(Drawable targetScreen) { this.targetScreen = targetScreen; From f713253d1b3bf4c959a58cbeebf51edf4742b18e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 14:00:29 +0900 Subject: [PATCH 223/481] Fix formatting inconsistencies in empty `ctor`s --- osu.Game/Screens/Edit/Editor.cs | 4 +++- osu.Game/Skinning/Editor/SkinEditor.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 1933076338..3e3940c5ba 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -945,7 +945,9 @@ namespace osu.Game.Screens.Edit private class BeatmapEditorToast : Toast { public BeatmapEditorToast(LocalisableString value, string beatmapDisplayName) - : base(InputSettingsStrings.EditorSection, value, beatmapDisplayName) { } + : base(InputSettingsStrings.EditorSection, value, beatmapDisplayName) + { + } } } } diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index b02054072b..741cad3e57 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -406,7 +406,9 @@ namespace osu.Game.Skinning.Editor private class SkinEditorToast : Toast { public SkinEditorToast(LocalisableString value, string skinDisplayName) - : base(SkinSettingsStrings.SkinLayoutEditor, value, skinDisplayName) { } + : base(SkinSettingsStrings.SkinLayoutEditor, value, skinDisplayName) + { + } } } } From 4cec9a085a4e4e9678785f21d63aed7fee86599a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 14:44:48 +0900 Subject: [PATCH 224/481] Combine both calls to use same pathway --- .../Compose/Components/Timeline/TimelineArea.cs | 6 ++---- .../Timeline/ZoomableScrollContainer.cs | 17 ++++++++++------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs index b47c09109b..c2415ce978 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineArea.cs @@ -118,7 +118,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Y, Height = 0.5f, Icon = FontAwesome.Solid.SearchPlus, - Action = () => changeZoom(1) + Action = () => Timeline.AdjustZoomRelatively(1) }, new TimelineButton { @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Y, Height = 0.5f, Icon = FontAwesome.Solid.SearchMinus, - Action = () => changeZoom(-1) + Action = () => Timeline.AdjustZoomRelatively(-1) }, } } @@ -153,7 +153,5 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Timeline.ControlPointsVisible.BindTo(controlPointsCheckbox.Current); Timeline.TicksVisible.BindTo(ticksCheckbox.Current); } - - private void changeZoom(float change) => Timeline.Zoom += Timeline.CalculateZoomChange(change); } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 4d95600450..96f6ef6d02 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -104,8 +104,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline set => updateZoom(value); } - private const float zoom_change_sensitivity = 0.02f; - private void updateZoom(float? value = null) { float newZoom = Math.Clamp(value ?? Zoom, MinZoom, MaxZoom); @@ -129,7 +127,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (e.AltPressed) { // zoom when holding alt. - setZoomTarget(zoomTarget + CalculateZoomChange(e.ScrollDelta.Y), zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); + AdjustZoomRelatively(e.ScrollDelta.Y, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); return true; } @@ -147,12 +145,19 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline zoomedContentWidthCache.Validate(); } + public void AdjustZoomRelatively(float change, float? focusPoint = null) + { + const float zoom_change_sensitivity = 0.02f; + + setZoomTarget(zoomTarget + change * (MaxZoom - minZoom) * zoom_change_sensitivity, focusPoint); + } + private float zoomTarget = 1; - private void setZoomTarget(float newZoom, float focusPoint) + private void setZoomTarget(float newZoom, float? focusPoint = null) { zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom); - transformZoomTo(zoomTarget, focusPoint, ZoomDuration, ZoomEasing); + transformZoomTo(zoomTarget, focusPoint ?? DrawWidth / 2, ZoomDuration, ZoomEasing); OnZoomChanged(); } @@ -167,8 +172,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { } - public float CalculateZoomChange(float rawChange) => rawChange * (MaxZoom - minZoom) * zoom_change_sensitivity; - private class TransformZoom : Transform { /// From 3b913bb9ad9fd63ec185ad96a5b51f888b72110e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 22 Jul 2022 09:15:48 +0300 Subject: [PATCH 225/481] Fix sorting mode not filling up to usable area in filter control --- osu.Game/Screens/Select/FilterControl.cs | 69 ++++++++++++++---------- 1 file changed, 40 insertions(+), 29 deletions(-) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index e43261f374..d39862b65f 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -115,42 +115,53 @@ namespace osu.Game.Screens.Select Origin = Anchor.BottomLeft, Anchor = Anchor.BottomLeft, }, - new FillFlowContainer + new GridContainer { Anchor = Anchor.BottomRight, Origin = Anchor.BottomRight, - Direction = FillDirection.Horizontal, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Spacing = new Vector2(OsuTabControl.HORIZONTAL_SPACING, 0), - Children = new Drawable[] + ColumnDimensions = new[] { - new OsuTabControlCheckbox + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, OsuTabControl.HORIZONTAL_SPACING), + new Dimension(), + new Dimension(GridSizeMode.Absolute, OsuTabControl.HORIZONTAL_SPACING), + new Dimension(GridSizeMode.AutoSize), + }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + Content = new[] + { + new[] { - Text = "Show converted", - Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - }, - sortTabs = new OsuTabControl - { - RelativeSizeAxes = Axes.X, - Width = 0.5f, - Height = 24, - AutoSort = true, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - AccentColour = colours.GreenLight, - Current = { BindTarget = sortMode } - }, - new OsuSpriteText - { - Text = SortStrings.Default, - Font = OsuFont.GetFont(size: 14), - Margin = new MarginPadding(5), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - }, + new OsuSpriteText + { + Text = SortStrings.Default, + Font = OsuFont.GetFont(size: 14), + Margin = new MarginPadding(5), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + Empty(), + sortTabs = new OsuTabControl + { + RelativeSizeAxes = Axes.X, + Height = 24, + AutoSort = true, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + AccentColour = colours.GreenLight, + Current = { BindTarget = sortMode } + }, + Empty(), + new OsuTabControlCheckbox + { + Text = "Show converted", + Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + } } }, } From 6eb42d08ce2ebbe216424a7179fbe65629d0eb09 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 15:21:25 +0900 Subject: [PATCH 226/481] Fix timeline zoom receiving feedback from bindable changes This causes the focal point zooming to not work (as the focal point is lost). There's no need to handle ongoing changes to `BeatmapInfo.TimelineZoom` because it is not a property which is changed at runtime. --- .../Edit/Compose/Components/Timeline/Timeline.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 37fc4b03b2..bbe011a2e0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -41,6 +41,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private EditorClock editorClock { get; set; } + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } + /// /// The timeline's scroll position in the last frame. /// @@ -68,8 +71,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// private float defaultTimelineZoom; - private readonly Bindable timelineZoomScale = new BindableDouble(1.0); - public Timeline(Drawable userContent) { this.userContent = userContent; @@ -93,7 +94,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private Bindable waveformOpacity; [BackgroundDependencyLoader] - private void load(IBindable beatmap, EditorBeatmap editorBeatmap, OsuColour colours, OsuConfigManager config) + private void load(IBindable beatmap, OsuColour colours, OsuConfigManager config) { CentreMarker centreMarker; @@ -154,12 +155,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } }, true); - timelineZoomScale.Value = editorBeatmap.BeatmapInfo.TimelineZoom; - timelineZoomScale.BindValueChanged(scale => - { - Zoom = (float)(defaultTimelineZoom * scale.NewValue); - editorBeatmap.BeatmapInfo.TimelineZoom = scale.NewValue; - }, true); + Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); } protected override void LoadComplete() @@ -221,7 +217,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override void OnZoomChanged() { base.OnZoomChanged(); - timelineZoomScale.Value = Zoom / defaultTimelineZoom; + editorBeatmap.BeatmapInfo.TimelineZoom = Zoom / defaultTimelineZoom; } protected override void UpdateAfterChildren() From e20458421a9ad07a409bafc3b22d4388ad0ee5a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 16:05:20 +0900 Subject: [PATCH 227/481] Update flaky timeline zoom test to output something useful --- osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs index 2cada1989e..6c5cca1874 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs @@ -5,7 +5,6 @@ using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Utils; namespace osu.Game.Tests.Visual.Editing { @@ -32,12 +31,12 @@ namespace osu.Game.Tests.Visual.Editing AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange); AddStep("scale zoom", () => TimelineArea.Timeline.Zoom = 200); - AddAssert("range halved", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange / 2, 1)); + AddStep("range halved", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange / 2).Within(1))); AddStep("descale zoom", () => TimelineArea.Timeline.Zoom = 50); - AddAssert("range doubled", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange * 2, 1)); + AddStep("range doubled", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange * 2).Within(1))); AddStep("restore zoom", () => TimelineArea.Timeline.Zoom = 100); - AddAssert("range restored", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange, 1)); + AddStep("range restored", () => Assert.That(TimelineArea.Timeline.VisibleRange, Is.EqualTo(initialVisibleRange).Within(1))); } [Test] From b884ed2a3d8b8fd50412134a3f162a84c1c29417 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 16:18:14 +0900 Subject: [PATCH 228/481] Make test actually test drum behaviours --- .../TestSceneDrumTouchInputArea.cs | 63 +++++++++---------- 1 file changed, 29 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs index 45555a55c1..979464db5d 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs @@ -1,55 +1,50 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Taiko.Objects; +using osu.Framework.Graphics; +using osu.Framework.Testing; using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests { [TestFixture] - public class TestSceneDrumTouchInputArea : DrawableTaikoRulesetTestScene + public class TestSceneDrumTouchInputArea : OsuTestScene { - protected const double NUM_HIT_OBJECTS = 10; - protected const double HIT_OBJECT_TIME_SPACING_MS = 1000; + [Cached] + private TaikoInputManager taikoInputManager = new TaikoInputManager(new TaikoRuleset().RulesetInfo); - [BackgroundDependencyLoader] - private void load() + private DrumTouchInputArea drumTouchInputArea = null!; + + [SetUpSteps] + public void SetUpSteps() { - var drumTouchInputArea = new DrumTouchInputArea(); - DrawableRuleset.KeyBindingInputManager.Add(drumTouchInputArea); - drumTouchInputArea.ShowTouchControls(); - } - - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) - { - List hitObjects = new List(); - - for (int i = 0; i < NUM_HIT_OBJECTS; i++) + AddStep("create drum", () => { - hitObjects.Add(new Hit + Children = new Drawable[] { - StartTime = Time.Current + i * HIT_OBJECT_TIME_SPACING_MS, - IsStrong = isOdd(i), - Type = isOdd(i / 2) ? HitType.Centre : HitType.Rim - }); - } - - var beatmap = new Beatmap - { - BeatmapInfo = { Ruleset = ruleset }, - HitObjects = hitObjects - }; - - return beatmap; + new InputDrum + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Height = 0.5f, + }, + drumTouchInputArea = new DrumTouchInputArea + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Height = 0.5f, + }, + }; + }); } - private bool isOdd(int number) + [Test] + public void TestDrum() { - return number % 2 == 0; + AddStep("show drum", () => drumTouchInputArea.Show()); } } } From 7015cf0b1b303607857c42a34ae9e36c0b434122 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 16:08:57 +0900 Subject: [PATCH 229/481] Move touch input drum to own file for now --- .../UI/DrumTouchInputArea.cs | 12 +- osu.Game.Rulesets.Taiko/UI/InputDrum.cs | 13 +- osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs | 187 ++++++++++++++++++ 3 files changed, 197 insertions(+), 15 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index a66c52df1f..679e66d33b 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -25,10 +26,10 @@ namespace osu.Game.Rulesets.Taiko.UI // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) private const float offscreen_percent = 0.35f; - private readonly InputDrum touchInputDrum; + private readonly TouchInputDrum touchInputDrum; private readonly Circle drumBackground; - private KeyBindingContainer keyBindingContainer; + private KeyBindingContainer keyBindingContainer = null!; // Which Taiko action was pressed by the last OnMouseDown event, so that the corresponding action can be released OnMouseUp even if the cursor position moved private TaikoAction mouseAction; @@ -62,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI FillMode = FillMode.Fit, Alpha = 0.9f, }, - touchInputDrum = new InputDrum + touchInputDrum = new TouchInputDrum { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -84,7 +85,10 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) { - keyBindingContainer = taikoInputManager?.KeyBindingContainer; + Debug.Assert(taikoInputManager?.KeyBindingContainer != null); + + keyBindingContainer = taikoInputManager.KeyBindingContainer; + drumBackground.Colour = colours.Gray0; } diff --git a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs index 97990395a2..054f98e18f 100644 --- a/osu.Game.Rulesets.Taiko/UI/InputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/InputDrum.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Screens.Ranking; using osu.Game.Skinning; using osuTK; @@ -24,7 +23,6 @@ namespace osu.Game.Rulesets.Taiko.UI /// internal class InputDrum : Container { - public float CentreSize = 0.7f; private const float middle_split = 0.025f; public InputDrum() @@ -64,7 +62,7 @@ namespace osu.Game.Rulesets.Taiko.UI Scale = new Vector2(0.9f), Children = new[] { - new TaikoHalfDrum(false, CentreSize) + new TaikoHalfDrum(false) { Name = "Left Half", Anchor = Anchor.Centre, @@ -75,7 +73,7 @@ namespace osu.Game.Rulesets.Taiko.UI RimAction = TaikoAction.LeftRim, CentreAction = TaikoAction.LeftCentre }, - new TaikoHalfDrum(true, CentreSize) + new TaikoHalfDrum(true) { Name = "Right Half", Anchor = Anchor.Centre, @@ -110,9 +108,6 @@ namespace osu.Game.Rulesets.Taiko.UI private readonly Sprite centre; private readonly Sprite centreHit; - [Resolved] - private DrumSampleTriggerSource sampleTriggerSource { get; set; } - public TaikoHalfDrum(bool flipped) { Masking = true; @@ -173,15 +168,11 @@ namespace osu.Game.Rulesets.Taiko.UI { target = centreHit; back = centre; - - sampleTriggerSource.Play(HitType.Centre); } else if (e.Action == RimAction) { target = rimHit; back = rim; - - sampleTriggerSource.Play(HitType.Rim); } if (target != null) diff --git a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs new file mode 100644 index 0000000000..85eec31a8a --- /dev/null +++ b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs @@ -0,0 +1,187 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.UI +{ + /// + /// A component of the playfield that captures input and displays input as a drum. + /// + internal class TouchInputDrum : Container + { + public float CentreSize = 0.7f; + private const float middle_split = 0.025f; + + public TouchInputDrum() + { + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Scale = new Vector2(0.9f), + Children = new Drawable[] + { + new TaikoHalfDrum(false, CentreSize) + { + Name = "Left Half", + Anchor = Anchor.Centre, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = -middle_split / 2, + RimAction = TaikoAction.LeftRim, + CentreAction = TaikoAction.LeftCentre + }, + new TaikoHalfDrum(true, CentreSize) + { + Name = "Right Half", + Anchor = Anchor.Centre, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.X, + X = middle_split / 2, + RimAction = TaikoAction.RightRim, + CentreAction = TaikoAction.RightCentre + } + } + }) + }; + } + + /// + /// A half-drum. Contains one centre and one rim hit. + /// + private class TaikoHalfDrum : Container, IKeyBindingHandler + { + /// + /// The key to be used for the rim of the half-drum. + /// + public TaikoAction RimAction; + + /// + /// The key to be used for the centre of the half-drum. + /// + public TaikoAction CentreAction; + + private readonly Sprite rim; + private readonly Sprite rimHit; + private readonly Sprite centre; + private readonly Sprite centreHit; + + public TaikoHalfDrum(bool flipped, float centreSize) + { + Masking = true; + + Children = new Drawable[] + { + rim = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both + }, + rimHit = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Alpha = 0, + Blending = BlendingParameters.Additive, + }, + centre = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(centreSize) + }, + centreHit = new Sprite + { + Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(centreSize), + Alpha = 0, + Blending = BlendingParameters.Additive + } + }; + } + + [BackgroundDependencyLoader] + private void load(TextureStore textures, OsuColour colours) + { + rim.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer"); + rimHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer-hit"); + centre.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner"); + centreHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner-hit"); + + rimHit.Colour = colours.Blue; + centreHit.Colour = colours.Pink; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + Drawable target = null; + Drawable back = null; + + if (e.Action == CentreAction) + { + target = centreHit; + back = centre; + } + else if (e.Action == RimAction) + { + target = rimHit; + back = rim; + } + + if (target != null) + { + const float scale_amount = 0.05f; + const float alpha_amount = 0.5f; + + const float down_time = 40; + const float up_time = 1000; + + back.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint) + .Then() + .ScaleTo(1, up_time, Easing.OutQuint); + + target.Animate( + t => t.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint), + t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, Easing.OutQuint) + ).Then( + t => t.ScaleTo(1, up_time, Easing.OutQuint), + t => t.FadeOut(up_time, Easing.OutQuint) + ); + } + + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + } + } + } +} From b604eb62620e811567239dcd291a6a3703dc072d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 16:18:22 +0900 Subject: [PATCH 230/481] Simplify implementation --- .../TestSceneDrumTouchInputArea.cs | 29 ++-- .../UI/DrawableTaikoRuleset.cs | 7 +- .../UI/DrumTouchInputArea.cs | 135 ++++++++---------- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs | 5 +- 5 files changed, 75 insertions(+), 103 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs index 979464db5d..7210419c0e 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneDrumTouchInputArea.cs @@ -2,7 +2,6 @@ // 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.Rulesets.Taiko.UI; @@ -13,9 +12,6 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestFixture] public class TestSceneDrumTouchInputArea : OsuTestScene { - [Cached] - private TaikoInputManager taikoInputManager = new TaikoInputManager(new TaikoRuleset().RulesetInfo); - private DrumTouchInputArea drumTouchInputArea = null!; [SetUpSteps] @@ -23,19 +19,22 @@ namespace osu.Game.Rulesets.Taiko.Tests { AddStep("create drum", () => { - Children = new Drawable[] + Child = new TaikoInputManager(new TaikoRuleset().RulesetInfo) { - new InputDrum + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Height = 0.5f, - }, - drumTouchInputArea = new DrumTouchInputArea - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Height = 0.5f, + new InputDrum + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Height = 0.2f, + }, + drumTouchInputArea = new DrumTouchInputArea + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + }, }, }; }); diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index a117435ada..2fa511b8ab 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -39,8 +39,6 @@ namespace osu.Game.Rulesets.Taiko.UI private SkinnableDrawable scroller; - private DrumTouchInputArea drumTouchInputArea; - public DrawableTaikoRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) : base(ruleset, beatmap, mods) { @@ -59,8 +57,7 @@ namespace osu.Game.Rulesets.Taiko.UI Depth = float.MaxValue }); - if ((drumTouchInputArea = CreateDrumTouchInputArea()) != null) - KeyBindingInputManager.Add(drumTouchInputArea); + KeyBindingInputManager.Add(new DrumTouchInputArea()); } protected override void UpdateAfterChildren() @@ -79,8 +76,6 @@ namespace osu.Game.Rulesets.Taiko.UI return ControlPoints[result]; } - public DrumTouchInputArea CreateDrumTouchInputArea() => new DrumTouchInputArea(); - public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer { LockPlayfieldAspect = { BindTarget = LockPlayfieldAspect } diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 679e66d33b..55eb533a47 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; @@ -13,152 +12,132 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; using osuTK; +using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Rulesets.Taiko.UI { /// - /// An overlay that captures and displays Taiko mouse and touch input. - /// The boundaries of this overlay defines the interactable area for touch input. - /// A secondary InputDrum is attached by this overlay, which defines the circular boundary which distinguishes "centre" from "rim" hits, and also displays input. + /// An overlay that captures and displays osu!taiko mouse and touch input. /// public class DrumTouchInputArea : Container { - // The percent of the drum that extends past the bottom of the screen (set to 0.0f to show the full drum) - private const float offscreen_percent = 0.35f; - - private readonly TouchInputDrum touchInputDrum; - private readonly Circle drumBackground; + private readonly Circle outerCircle; private KeyBindingContainer keyBindingContainer = null!; - // Which Taiko action was pressed by the last OnMouseDown event, so that the corresponding action can be released OnMouseUp even if the cursor position moved - private TaikoAction mouseAction; + private readonly Dictionary trackedTouches = new Dictionary(); + private readonly Dictionary trackedMouseButtons = new Dictionary(); - // A map of (Finger Index OnTouchDown -> Which Taiko action was pressed), so that the corresponding action can be released OnTouchUp is released even if the touch position moved - private readonly Dictionary touchActions = new Dictionary(Enum.GetNames(typeof(TouchSource)).Length); + private readonly Container mainContent; - private readonly Container visibleComponents; + private readonly Circle centreCircle; public DrumTouchInputArea() { - RelativeSizeAxes = Axes.Both; - RelativePositionAxes = Axes.Both; + RelativeSizeAxes = Axes.X; + Height = 300; + + Masking = true; + Children = new Drawable[] { - visibleComponents = new Container + mainContent = new Container { - Alpha = 0.0f, RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, + Height = 2, Children = new Drawable[] { - drumBackground = new Circle + outerCircle = new Circle { - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, FillMode = FillMode.Fit, - Alpha = 0.9f, - }, - touchInputDrum = new TouchInputDrum - { + RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, + centreCircle = new Circle + { + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.5f), + }, + new Box + { + FillMode = FillMode.Fit, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Color4.Black, + Width = 3, + }, } }, }; } - protected override void LoadComplete() - { - Padding = new MarginPadding - { - Top = TaikoPlayfield.DEFAULT_HEIGHT * 2f, // Visual elements should start right below the playfield - Bottom = -touchInputDrum.DrawHeight * offscreen_percent, // The drum should go past the bottom of the screen so that it can be wider - }; - } - [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) { - Debug.Assert(taikoInputManager?.KeyBindingContainer != null); + Debug.Assert(taikoInputManager.KeyBindingContainer != null); keyBindingContainer = taikoInputManager.KeyBindingContainer; - drumBackground.Colour = colours.Gray0; + outerCircle.Colour = colours.Gray0; } protected override bool OnMouseDown(MouseDownEvent e) { - ShowTouchControls(); - mouseAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); - keyBindingContainer?.TriggerPressed(mouseAction); + mainContent.Show(); + + TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); + + trackedMouseButtons.Add(e.Button, taikoAction); + keyBindingContainer.TriggerPressed(taikoAction); return true; } protected override void OnMouseUp(MouseUpEvent e) { - keyBindingContainer?.TriggerReleased(mouseAction); + keyBindingContainer.TriggerReleased(trackedMouseButtons[e.Button]); + trackedMouseButtons.Remove(e.Button); base.OnMouseUp(e); } protected override bool OnTouchDown(TouchDownEvent e) { - ShowTouchControls(); + mainContent.Show(); + TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); - touchActions.Add(e.Touch.Source, taikoAction); - keyBindingContainer?.TriggerPressed(touchActions[e.Touch.Source]); + + trackedTouches.Add(e.Touch.Source, taikoAction); + keyBindingContainer.TriggerPressed(taikoAction); return true; } protected override void OnTouchUp(TouchUpEvent e) { - keyBindingContainer?.TriggerReleased(touchActions[e.Touch.Source]); - touchActions.Remove(e.Touch.Source); + keyBindingContainer.TriggerReleased(trackedTouches[e.Touch.Source]); + trackedTouches.Remove(e.Touch.Source); base.OnTouchUp(e); } protected override bool OnKeyDown(KeyDownEvent e) { - HideTouchControls(); + mainContent.Hide(); return false; } - public void ShowTouchControls() - { - visibleComponents.Animate(components => components.FadeIn(500, Easing.OutQuint)); - } - - public void HideTouchControls() - { - visibleComponents.Animate(components => components.FadeOut(2000, Easing.OutQuint)); - } - private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { - bool centreHit = inputIsCenterHit(inputPosition); - bool leftSide = inputIsOnLeftSide(inputPosition); + bool centreHit = centreCircle.ScreenSpaceDrawQuad.Contains(inputPosition); + bool leftSide = ToLocalSpace(inputPosition).X < DrawWidth / 2; - return centreHit ? (leftSide ? TaikoAction.LeftCentre : TaikoAction.RightCentre) : (leftSide ? TaikoAction.LeftRim : TaikoAction.RightRim); - } + if (leftSide) + return centreHit ? TaikoAction.LeftCentre : TaikoAction.LeftRim; - private bool inputIsOnLeftSide(Vector2 inputPosition) - { - Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; - return inputPositionToDrumCentreDelta.X < 0f; - } - - private bool inputIsCenterHit(Vector2 inputPosition) - { - Vector2 inputPositionToDrumCentreDelta = touchInputDrum.ToLocalSpace(inputPosition) - touchInputDrum.OriginPosition; - - float inputDrumRadius = Math.Max(touchInputDrum.Width, touchInputDrum.DrawHeight) / 2f; - float centreRadius = (inputDrumRadius * touchInputDrum.CentreSize); - return inputPositionToDrumCentreDelta.Length <= centreRadius; + return centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim; } } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index a17fc0391a..ccca5587b7 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.UI [BackgroundDependencyLoader] private void load(OsuColour colours) { - inputDrum = new InputDrum() + inputDrum = new InputDrum { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, diff --git a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs index 85eec31a8a..21b83e5495 100644 --- a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Taiko.UI @@ -34,7 +33,7 @@ namespace osu.Game.Rulesets.Taiko.UI { Children = new Drawable[] { - new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container + new Container { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, @@ -64,7 +63,7 @@ namespace osu.Game.Rulesets.Taiko.UI CentreAction = TaikoAction.RightCentre } } - }) + } }; } From e1df50c8ffa2047f6eea539952d884b6237bf839 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Jul 2022 17:03:04 +0900 Subject: [PATCH 231/481] Fix timeline zoom focus point when using buttons --- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 96f6ef6d02..83fd1dea2b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -157,7 +157,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void setZoomTarget(float newZoom, float? focusPoint = null) { zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom); - transformZoomTo(zoomTarget, focusPoint ?? DrawWidth / 2, ZoomDuration, ZoomEasing); + focusPoint ??= zoomedContent.ToLocalSpace(ToScreenSpace(new Vector2(DrawWidth / 2, 0))).X; + + transformZoomTo(zoomTarget, focusPoint.Value, ZoomDuration, ZoomEasing); OnZoomChanged(); } From bd6ff40b43b91c8e79966cb0e751f9fc86515de8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 17:02:58 +0900 Subject: [PATCH 232/481] Combine touch and mouse handling into single path --- .../UI/DrawableTaikoRuleset.cs | 10 ++-- .../UI/DrumTouchInputArea.cs | 48 ++++++++++--------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 2fa511b8ab..58e703b8be 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -8,18 +8,18 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.UI; -using osu.Game.Rulesets.Taiko.Replays; using osu.Framework.Input; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Input.Handlers; using osu.Game.Replays; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.Replays; using osu.Game.Rulesets.Timing; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Scoring; using osu.Game.Skinning; diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 55eb533a47..253ced9a39 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -7,13 +7,11 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; using osuTK; using osuTK.Graphics; -using osuTK.Input; namespace osu.Game.Rulesets.Taiko.UI { @@ -26,8 +24,7 @@ namespace osu.Game.Rulesets.Taiko.UI private KeyBindingContainer keyBindingContainer = null!; - private readonly Dictionary trackedTouches = new Dictionary(); - private readonly Dictionary trackedMouseButtons = new Dictionary(); + private readonly Dictionary trackedActions = new Dictionary(); private readonly Container mainContent; @@ -87,46 +84,51 @@ namespace osu.Game.Rulesets.Taiko.UI outerCircle.Colour = colours.Gray0; } + protected override bool OnKeyDown(KeyDownEvent e) + { + // Hide whenever the keyboard is used. + mainContent.Hide(); + return false; + } + protected override bool OnMouseDown(MouseDownEvent e) { - mainContent.Show(); - - TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceMouseDownPosition); - - trackedMouseButtons.Add(e.Button, taikoAction); - keyBindingContainer.TriggerPressed(taikoAction); + handleDown(e.Button, e.ScreenSpaceMousePosition); return true; } protected override void OnMouseUp(MouseUpEvent e) { - keyBindingContainer.TriggerReleased(trackedMouseButtons[e.Button]); - trackedMouseButtons.Remove(e.Button); + handleUp(e.Button); base.OnMouseUp(e); } protected override bool OnTouchDown(TouchDownEvent e) { - mainContent.Show(); - - TaikoAction taikoAction = getTaikoActionFromInput(e.ScreenSpaceTouchDownPosition); - - trackedTouches.Add(e.Touch.Source, taikoAction); - keyBindingContainer.TriggerPressed(taikoAction); + handleDown(e.Touch.Source, e.ScreenSpaceTouchDownPosition); return true; } protected override void OnTouchUp(TouchUpEvent e) { - keyBindingContainer.TriggerReleased(trackedTouches[e.Touch.Source]); - trackedTouches.Remove(e.Touch.Source); + handleUp(e.Touch.Source); base.OnTouchUp(e); } - protected override bool OnKeyDown(KeyDownEvent e) + private void handleDown(object source, Vector2 position) { - mainContent.Hide(); - return false; + mainContent.Show(); + + TaikoAction taikoAction = getTaikoActionFromInput(position); + + trackedActions.Add(source, taikoAction); + keyBindingContainer.TriggerPressed(taikoAction); + } + + private void handleUp(object source) + { + keyBindingContainer.TriggerReleased(trackedActions[source]); + trackedActions.Remove(source); } private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) From e5ab6652fd5876324a72bfcf7d0de55199901591 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 22 Jul 2022 11:12:24 +0300 Subject: [PATCH 233/481] Fix one more case of referencing old mod select overlay in tests --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index d35887c443..29a5ef82fe 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -633,7 +633,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("invoke on back button", () => multiplayerComponents.OnBackButton()); - AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); + AddAssert("mod overlay is hidden", () => this.ChildrenOfType().Single().UserModsSelectOverlay.State.Value == Visibility.Hidden); AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden); From aeeedc40b446ae79030ce8acaf862b6b55e798d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 17:17:38 +0900 Subject: [PATCH 234/481] Add first pass design --- .../UI/DrumTouchInputArea.cs | 121 ++++++++++-------- 1 file changed, 70 insertions(+), 51 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 253ced9a39..d8ae4d9210 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -20,68 +20,74 @@ namespace osu.Game.Rulesets.Taiko.UI /// public class DrumTouchInputArea : Container { - private readonly Circle outerCircle; - private KeyBindingContainer keyBindingContainer = null!; private readonly Dictionary trackedActions = new Dictionary(); - private readonly Container mainContent; + private Container mainContent = null!; - private readonly Circle centreCircle; - - public DrumTouchInputArea() - { - RelativeSizeAxes = Axes.X; - Height = 300; - - Masking = true; - - Children = new Drawable[] - { - mainContent = new Container - { - RelativeSizeAxes = Axes.Both, - Height = 2, - Children = new Drawable[] - { - outerCircle = new Circle - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - centreCircle = new Circle - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.5f), - }, - new Box - { - FillMode = FillMode.Fit, - RelativeSizeAxes = Axes.Y, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = Color4.Black, - Width = 3, - }, - } - }, - }; - } + private Circle centreCircle = null!; + private Circle outerCircle = null!; [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) { Debug.Assert(taikoInputManager.KeyBindingContainer != null); - keyBindingContainer = taikoInputManager.KeyBindingContainer; - outerCircle.Colour = colours.Gray0; + // Container should handle input everywhere. + RelativeSizeAxes = Axes.Both; + + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + Height = 300, + Y = 20, + Masking = true, + FillMode = FillMode.Fit, + Children = new Drawable[] + { + mainContent = new Container + { + RelativeSizeAxes = Axes.Both, + Height = 2, + Children = new Drawable[] + { + outerCircle = new Circle + { + FillMode = FillMode.Fit, + Colour = colours.BlueDarker, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + centreCircle = new Circle + { + FillMode = FillMode.Fit, + Colour = colours.YellowDark, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.8f), + }, + new Box + { + Colour = colours.BlueDarker, + RelativeSizeAxes = Axes.Y, + Height = 0.9f, + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Width = 7, + }, + } + }, + } + }, + }; } protected override bool OnKeyDown(KeyDownEvent e) @@ -121,6 +127,19 @@ namespace osu.Game.Rulesets.Taiko.UI TaikoAction taikoAction = getTaikoActionFromInput(position); + switch (taikoAction) + { + case TaikoAction.LeftCentre: + case TaikoAction.RightCentre: + centreCircle.FlashColour(Color4.White, 2000, Easing.OutQuint); + break; + + case TaikoAction.LeftRim: + case TaikoAction.RightRim: + outerCircle.FlashColour(Color4.White, 2000, Easing.OutQuint); + break; + } + trackedActions.Add(source, taikoAction); keyBindingContainer.TriggerPressed(taikoAction); } @@ -133,7 +152,7 @@ namespace osu.Game.Rulesets.Taiko.UI private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { - bool centreHit = centreCircle.ScreenSpaceDrawQuad.Contains(inputPosition); + bool centreHit = centreCircle.Contains(inputPosition); bool leftSide = ToLocalSpace(inputPosition).X < DrawWidth / 2; if (leftSide) From 6b69ff19c87c834b670cf933b430e4e7aa248732 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 22 Jul 2022 17:24:46 +0900 Subject: [PATCH 235/481] Remove unused using --- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 29a5ef82fe..bf9b99e3e2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -24,7 +24,6 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; -using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mods; From 2d2d98ab6ee3c2b6a1ee6c904782f5844cac7afd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 17:48:07 +0900 Subject: [PATCH 236/481] Add final design pass --- .../UI/DrumTouchInputArea.cs | 151 +++++++++++++----- 1 file changed, 107 insertions(+), 44 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index d8ae4d9210..5ba2ea282e 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -18,16 +18,20 @@ namespace osu.Game.Rulesets.Taiko.UI /// /// An overlay that captures and displays osu!taiko mouse and touch input. /// - public class DrumTouchInputArea : Container + public class DrumTouchInputArea : VisibilityContainer { + // visibility state affects our child. we always want to handle input. + public override bool PropagatePositionalInputSubTree => true; + public override bool PropagateNonPositionalInputSubTree => true; + private KeyBindingContainer keyBindingContainer = null!; private readonly Dictionary trackedActions = new Dictionary(); private Container mainContent = null!; - private Circle centreCircle = null!; - private Circle outerCircle = null!; + private QuarterCircle leftCentre = null!; + private QuarterCircle rightCentre = null!; [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) @@ -38,6 +42,8 @@ namespace osu.Game.Rulesets.Taiko.UI // Container should handle input everywhere. RelativeSizeAxes = Axes.Both; + const float centre_region = 0.80f; + Children = new Drawable[] { new Container @@ -45,7 +51,7 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, - Height = 300, + Height = 350, Y = 20, Masking = true, FillMode = FillMode.Fit, @@ -54,35 +60,36 @@ namespace osu.Game.Rulesets.Taiko.UI mainContent = new Container { RelativeSizeAxes = Axes.Both, - Height = 2, Children = new Drawable[] { - outerCircle = new Circle + new QuarterCircle(TaikoAction.LeftRim, colours.YellowDark) { - FillMode = FillMode.Fit, - Colour = colours.BlueDarker, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - centreCircle = new Circle - { - FillMode = FillMode.Fit, - Colour = colours.YellowDark, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(0.8f), - }, - new Box - { - Colour = colours.BlueDarker, - RelativeSizeAxes = Axes.Y, - Height = 0.9f, Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Width = 7, + Origin = Anchor.BottomRight, + X = -2, }, + new QuarterCircle(TaikoAction.RightRim, colours.YellowDark) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomRight, + X = 2, + Rotation = 90, + }, + leftCentre = new QuarterCircle(TaikoAction.LeftCentre, colours.BlueDark) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomRight, + X = -2, + Scale = new Vector2(centre_region), + }, + rightCentre = new QuarterCircle(TaikoAction.RightCentre, colours.BlueDark) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomRight, + X = 2, + Scale = new Vector2(centre_region), + Rotation = 90, + } } }, } @@ -93,7 +100,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnKeyDown(KeyDownEvent e) { // Hide whenever the keyboard is used. - mainContent.Hide(); + Hide(); return false; } @@ -123,23 +130,10 @@ namespace osu.Game.Rulesets.Taiko.UI private void handleDown(object source, Vector2 position) { - mainContent.Show(); + Show(); TaikoAction taikoAction = getTaikoActionFromInput(position); - switch (taikoAction) - { - case TaikoAction.LeftCentre: - case TaikoAction.RightCentre: - centreCircle.FlashColour(Color4.White, 2000, Easing.OutQuint); - break; - - case TaikoAction.LeftRim: - case TaikoAction.RightRim: - outerCircle.FlashColour(Color4.White, 2000, Easing.OutQuint); - break; - } - trackedActions.Add(source, taikoAction); keyBindingContainer.TriggerPressed(taikoAction); } @@ -152,7 +146,7 @@ namespace osu.Game.Rulesets.Taiko.UI private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { - bool centreHit = centreCircle.Contains(inputPosition); + bool centreHit = leftCentre.Contains(inputPosition) || rightCentre.Contains(inputPosition); bool leftSide = ToLocalSpace(inputPosition).X < DrawWidth / 2; if (leftSide) @@ -160,5 +154,74 @@ namespace osu.Game.Rulesets.Taiko.UI return centreHit ? TaikoAction.RightCentre : TaikoAction.RightRim; } + + protected override void PopIn() + { + mainContent.FadeIn(500, Easing.OutQuint); + } + + protected override void PopOut() + { + mainContent.FadeOut(300); + } + + private class QuarterCircle : CompositeDrawable, IKeyBindingHandler + { + private readonly Circle overlay; + + private readonly TaikoAction handledAction; + + private readonly Circle circle; + + public override bool Contains(Vector2 screenSpacePos) => circle.Contains(screenSpacePos); + + public QuarterCircle(TaikoAction handledAction, Color4 colour) + { + this.handledAction = handledAction; + RelativeSizeAxes = Axes.Both; + + FillMode = FillMode.Fit; + + InternalChildren = new Drawable[] + { + new Container + { + Masking = true, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + circle = new Circle + { + RelativeSizeAxes = Axes.Both, + Colour = colour, + Alpha = 0.8f, + Scale = new Vector2(2), + }, + overlay = new Circle + { + Alpha = 0, + RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, + Colour = colour, + Scale = new Vector2(2), + } + } + }, + }; + } + + public bool OnPressed(KeyBindingPressEvent e) + { + if (e.Action == handledAction) + overlay.FadeTo(0.4f, 80, Easing.OutQuint); + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { + if (e.Action == handledAction) + overlay.FadeOut(1000, Easing.OutQuint); + } + } } } From 4279ac866c293291f6a0121684f414fa33d00108 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 17:58:52 +0900 Subject: [PATCH 237/481] Tidy up unnecessary changes and remove unused classes --- .../Skinning/Legacy/LegacyInputDrum.cs | 2 - osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs | 186 ------------------ 2 files changed, 188 deletions(-) delete mode 100644 osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs index a03d987efd..101f70b97a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyInputDrum.cs @@ -35,8 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { Child = content = new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, Size = new Vector2(180, 200), Children = new Drawable[] { diff --git a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs b/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs deleted file mode 100644 index 21b83e5495..0000000000 --- a/osu.Game.Rulesets.Taiko/UI/TouchInputDrum.cs +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.Textures; -using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; -using osu.Game.Graphics; -using osuTK; - -namespace osu.Game.Rulesets.Taiko.UI -{ - /// - /// A component of the playfield that captures input and displays input as a drum. - /// - internal class TouchInputDrum : Container - { - public float CentreSize = 0.7f; - private const float middle_split = 0.025f; - - public TouchInputDrum() - { - RelativeSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - FillMode = FillMode.Fit, - Scale = new Vector2(0.9f), - Children = new Drawable[] - { - new TaikoHalfDrum(false, CentreSize) - { - Name = "Left Half", - Anchor = Anchor.Centre, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = -middle_split / 2, - RimAction = TaikoAction.LeftRim, - CentreAction = TaikoAction.LeftCentre - }, - new TaikoHalfDrum(true, CentreSize) - { - Name = "Right Half", - Anchor = Anchor.Centre, - Origin = Anchor.CentreLeft, - RelativeSizeAxes = Axes.Both, - RelativePositionAxes = Axes.X, - X = middle_split / 2, - RimAction = TaikoAction.RightRim, - CentreAction = TaikoAction.RightCentre - } - } - } - }; - } - - /// - /// A half-drum. Contains one centre and one rim hit. - /// - private class TaikoHalfDrum : Container, IKeyBindingHandler - { - /// - /// The key to be used for the rim of the half-drum. - /// - public TaikoAction RimAction; - - /// - /// The key to be used for the centre of the half-drum. - /// - public TaikoAction CentreAction; - - private readonly Sprite rim; - private readonly Sprite rimHit; - private readonly Sprite centre; - private readonly Sprite centreHit; - - public TaikoHalfDrum(bool flipped, float centreSize) - { - Masking = true; - - Children = new Drawable[] - { - rim = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both - }, - rimHit = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Alpha = 0, - Blending = BlendingParameters.Additive, - }, - centre = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(centreSize) - }, - centreHit = new Sprite - { - Anchor = flipped ? Anchor.CentreLeft : Anchor.CentreRight, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(centreSize), - Alpha = 0, - Blending = BlendingParameters.Additive - } - }; - } - - [BackgroundDependencyLoader] - private void load(TextureStore textures, OsuColour colours) - { - rim.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer"); - rimHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-outer-hit"); - centre.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner"); - centreHit.Texture = textures.Get(@"Gameplay/taiko/taiko-drum-inner-hit"); - - rimHit.Colour = colours.Blue; - centreHit.Colour = colours.Pink; - } - - public bool OnPressed(KeyBindingPressEvent e) - { - Drawable target = null; - Drawable back = null; - - if (e.Action == CentreAction) - { - target = centreHit; - back = centre; - } - else if (e.Action == RimAction) - { - target = rimHit; - back = rim; - } - - if (target != null) - { - const float scale_amount = 0.05f; - const float alpha_amount = 0.5f; - - const float down_time = 40; - const float up_time = 1000; - - back.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint) - .Then() - .ScaleTo(1, up_time, Easing.OutQuint); - - target.Animate( - t => t.ScaleTo(target.Scale.X - scale_amount, down_time, Easing.OutQuint), - t => t.FadeTo(Math.Min(target.Alpha + alpha_amount, 1), down_time, Easing.OutQuint) - ).Then( - t => t.ScaleTo(1, up_time, Easing.OutQuint), - t => t.FadeOut(up_time, Easing.OutQuint) - ); - } - - return false; - } - - public void OnReleased(KeyBindingReleaseEvent e) - { - } - } - } -} From ec98693ccaa7c34468ac8784d450352442415274 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 18:07:10 +0900 Subject: [PATCH 238/481] Add back standard mouse bindings support and only handle mouse when inside the visible zone --- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 ++ osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 9a5f5791ab..223e268d7f 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -50,6 +50,8 @@ namespace osu.Game.Rulesets.Taiko public override IEnumerable GetDefaultKeyBindings(int variant = 0) => new[] { + new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre), + new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim), new KeyBinding(InputKey.D, TaikoAction.LeftRim), new KeyBinding(InputKey.F, TaikoAction.LeftCentre), new KeyBinding(InputKey.J, TaikoAction.RightCentre), diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 5ba2ea282e..8813cbbbc7 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Taiko.UI private QuarterCircle leftCentre = null!; private QuarterCircle rightCentre = null!; + private QuarterCircle leftRim = null!; + private QuarterCircle rightRim = null!; [BackgroundDependencyLoader] private void load(TaikoInputManager taikoInputManager, OsuColour colours) @@ -62,13 +64,13 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new QuarterCircle(TaikoAction.LeftRim, colours.YellowDark) + leftRim = new QuarterCircle(TaikoAction.LeftRim, colours.YellowDark) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, X = -2, }, - new QuarterCircle(TaikoAction.RightRim, colours.YellowDark) + rightRim = new QuarterCircle(TaikoAction.RightRim, colours.YellowDark) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, @@ -106,12 +108,18 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnMouseDown(MouseDownEvent e) { + if (validMouse(e)) + return false; + handleDown(e.Button, e.ScreenSpaceMousePosition); return true; } protected override void OnMouseUp(MouseUpEvent e) { + if (validMouse(e)) + return; + handleUp(e.Button); base.OnMouseUp(e); } @@ -144,6 +152,10 @@ namespace osu.Game.Rulesets.Taiko.UI trackedActions.Remove(source); } + private bool validMouse(MouseButtonEvent e) => + !leftRim.Contains(e.ScreenSpaceMouseDownPosition) + && !rightRim.Contains(e.ScreenSpaceMouseDownPosition); + private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { bool centreHit = leftCentre.Contains(inputPosition) || rightCentre.Contains(inputPosition); From 9e5e03af5d8e3c899a06bb7561702473a5d2c3fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 18:16:01 +0900 Subject: [PATCH 239/481] Adjust colours to match default skin for now --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index 8813cbbbc7..fb22921acf 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -64,27 +65,27 @@ namespace osu.Game.Rulesets.Taiko.UI RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - leftRim = new QuarterCircle(TaikoAction.LeftRim, colours.YellowDark) + leftRim = new QuarterCircle(TaikoAction.LeftRim, colours.Blue) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, X = -2, }, - rightRim = new QuarterCircle(TaikoAction.RightRim, colours.YellowDark) + rightRim = new QuarterCircle(TaikoAction.RightRim, colours.Blue) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, X = 2, Rotation = 90, }, - leftCentre = new QuarterCircle(TaikoAction.LeftCentre, colours.BlueDark) + leftCentre = new QuarterCircle(TaikoAction.LeftCentre, colours.Pink) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, X = -2, Scale = new Vector2(centre_region), }, - rightCentre = new QuarterCircle(TaikoAction.RightCentre, colours.BlueDark) + rightCentre = new QuarterCircle(TaikoAction.RightCentre, colours.Pink) { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomRight, @@ -205,7 +206,7 @@ namespace osu.Game.Rulesets.Taiko.UI circle = new Circle { RelativeSizeAxes = Axes.Both, - Colour = colour, + Colour = colour.Multiply(1.4f).Darken(2.8f), Alpha = 0.8f, Scale = new Vector2(2), }, @@ -225,7 +226,7 @@ namespace osu.Game.Rulesets.Taiko.UI public bool OnPressed(KeyBindingPressEvent e) { if (e.Action == handledAction) - overlay.FadeTo(0.4f, 80, Easing.OutQuint); + overlay.FadeTo(1f, 80, Easing.OutQuint); return false; } From ee5e27638ee829df1f4c99e3061368d0387a01b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 18:19:33 +0900 Subject: [PATCH 240/481] Fix method name not matching actual implementation --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index fb22921acf..bf91ba7d49 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override bool OnMouseDown(MouseDownEvent e) { - if (validMouse(e)) + if (!validMouse(e)) return false; handleDown(e.Button, e.ScreenSpaceMousePosition); @@ -118,7 +118,7 @@ namespace osu.Game.Rulesets.Taiko.UI protected override void OnMouseUp(MouseUpEvent e) { - if (validMouse(e)) + if (!validMouse(e)) return; handleUp(e.Button); @@ -154,8 +154,7 @@ namespace osu.Game.Rulesets.Taiko.UI } private bool validMouse(MouseButtonEvent e) => - !leftRim.Contains(e.ScreenSpaceMouseDownPosition) - && !rightRim.Contains(e.ScreenSpaceMouseDownPosition); + leftRim.Contains(e.ScreenSpaceMouseDownPosition) || rightRim.Contains(e.ScreenSpaceMouseDownPosition); private TaikoAction getTaikoActionFromInput(Vector2 inputPosition) { From 28586c704dbf6e5a2b1bc43e8f93c13e3b8f72e5 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Fri, 22 Jul 2022 05:43:05 -0500 Subject: [PATCH 241/481] Add showModdedValue parameter to StatisticRow --- .../Screens/Select/Details/AdvancedStats.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 693f182065..80e9989c47 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Select.Details } } - public AdvancedStats() + public AdvancedStats(bool updateWithModSelection = true) { Child = new FillFlowContainer { @@ -65,11 +65,11 @@ namespace osu.Game.Screens.Select.Details AutoSizeAxes = Axes.Y, Children = new[] { - FirstValue = new StatisticRow(), // circle size/key amount - HpDrain = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsDrain }, - Accuracy = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAccuracy }, - ApproachRate = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAr }, - starDifficulty = new StatisticRow(10, true) { Title = BeatmapsetsStrings.ShowStatsStars }, + FirstValue = new StatisticRow(updateWithModSelection), // circle size/key amount + HpDrain = new StatisticRow(updateWithModSelection) { Title = BeatmapsetsStrings.ShowStatsDrain }, + Accuracy = new StatisticRow(updateWithModSelection) { Title = BeatmapsetsStrings.ShowStatsAccuracy }, + ApproachRate = new StatisticRow(updateWithModSelection) { Title = BeatmapsetsStrings.ShowStatsAr }, + starDifficulty = new StatisticRow(updateWithModSelection, 10, true) { Title = BeatmapsetsStrings.ShowStatsStars }, }, }; } @@ -183,6 +183,7 @@ namespace osu.Game.Screens.Select.Details private readonly OsuSpriteText name, valueText; private readonly Bar bar; public readonly Bar ModBar; + private readonly bool showModdedValue; [Resolved] private OsuColour colours { get; set; } @@ -203,6 +204,9 @@ namespace osu.Game.Screens.Select.Details if (value == this.value) return; + if (!showModdedValue) + value.adjustedValue = null; + this.value = value; bar.Length = value.baseValue / maxValue; @@ -225,13 +229,14 @@ namespace osu.Game.Screens.Select.Details set => bar.AccentColour = value; } - public StatisticRow(float maxValue = 10, bool forceDecimalPlaces = false) + public StatisticRow(bool showModdedValue, float maxValue = 10, bool forceDecimalPlaces = false) { this.maxValue = maxValue; this.forceDecimalPlaces = forceDecimalPlaces; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Padding = new MarginPadding { Vertical = 2.5f }; + this.showModdedValue = showModdedValue; Children = new Drawable[] { From d9d35bb847a8f424c1379763019dbb0dc05c52a2 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Fri, 22 Jul 2022 05:43:56 -0500 Subject: [PATCH 242/481] Set BeatmapSetHeaderContent details to not show modded values --- osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs | 2 +- osu.Game/Overlays/BeatmapSet/Details.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index c9e97d5f2f..68f5f2be6e 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -222,7 +222,7 @@ namespace osu.Game.Overlays.BeatmapSet TextSize = 14, TextPadding = new MarginPadding { Horizontal = 35, Vertical = 10 } }, - Details = new Details(), + Details = new Details(false), }, }, } diff --git a/osu.Game/Overlays/BeatmapSet/Details.cs b/osu.Game/Overlays/BeatmapSet/Details.cs index 6db54db811..092646f0ca 100644 --- a/osu.Game/Overlays/BeatmapSet/Details.cs +++ b/osu.Game/Overlays/BeatmapSet/Details.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.BeatmapSet ratingBox.Alpha = BeatmapSet?.Status > 0 ? 1 : 0; } - public Details() + public Details(bool updateWithModSelection = true) { Width = BeatmapSetOverlay.RIGHT_WIDTH; AutoSizeAxes = Axes.Y; @@ -82,7 +82,7 @@ namespace osu.Game.Overlays.BeatmapSet }, new DetailBox { - Child = advanced = new AdvancedStats + Child = advanced = new AdvancedStats(updateWithModSelection) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, From 7baa1a7e85a366478772a972f8e16e30aa9037ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 Jul 2022 20:19:13 +0900 Subject: [PATCH 243/481] Attempt to fix crashing from weird input interactions --- osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index bf91ba7d49..a7d9bd18c5 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -143,6 +143,10 @@ namespace osu.Game.Rulesets.Taiko.UI TaikoAction taikoAction = getTaikoActionFromInput(position); + // Not too sure how this can happen, but let's avoid throwing. + if (trackedActions.ContainsKey(source)) + return; + trackedActions.Add(source, taikoAction); keyBindingContainer.TriggerPressed(taikoAction); } From 6ce8e74e6b75b8283921190344e25584b981c0df Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 22 Jul 2022 19:06:31 +0900 Subject: [PATCH 244/481] Add panel appearance sounds --- .../Expanded/ExpandedPanelTopContent.cs | 22 +++++++++++++-- osu.Game/Screens/Ranking/ResultsScreen.cs | 10 ++++++- osu.Game/Screens/Ranking/ScorePanel.cs | 27 ++++++++++++++++--- .../Ranking/Statistics/StatisticsPanel.cs | 27 ++++++++++++++++--- 4 files changed, 77 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelTopContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelTopContent.cs index d2b2a842b8..2708090855 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelTopContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelTopContent.cs @@ -4,6 +4,8 @@ #nullable disable using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; @@ -21,13 +23,19 @@ namespace osu.Game.Screens.Ranking.Expanded { private readonly APIUser user; + private Sample appearanceSample; + + private readonly bool playAppearanceSound; + /// /// Creates a new . /// /// The to display. - public ExpandedPanelTopContent(APIUser user) + /// Whether the appearance sample should play + public ExpandedPanelTopContent(APIUser user, bool playAppearanceSound = false) { this.user = user; + this.playAppearanceSound = playAppearanceSound; Anchor = Anchor.TopCentre; Origin = Anchor.Centre; @@ -35,8 +43,10 @@ namespace osu.Game.Screens.Ranking.Expanded } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { + appearanceSample = audio.Samples.Get(@"Results/score-panel-top-appear"); + InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -62,5 +72,13 @@ namespace osu.Game.Screens.Ranking.Expanded } }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + if (playAppearanceSound) + appearanceSample?.Play(); + } } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 2f8868c06d..c530febcae 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -7,6 +7,8 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -60,6 +62,8 @@ namespace osu.Game.Screens.Ranking private readonly bool allowRetry; private readonly bool allowWatchingReplay; + private Sample popInSample; + protected ResultsScreen(ScoreInfo score, bool allowRetry, bool allowWatchingReplay = true) { Score = score; @@ -70,10 +74,12 @@ namespace osu.Game.Screens.Ranking } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { FillFlowContainer buttons; + popInSample = audio.Samples.Get(@"UI/overlay-pop-in"); + InternalChild = new GridContainer { RelativeSizeAxes = Axes.Both, @@ -244,6 +250,8 @@ namespace osu.Game.Screens.Ranking }); bottomPanel.FadeTo(1, 250); + + popInSample?.Play(); } public override bool OnExiting(ScreenExitEvent e) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index babdd4b149..322124313f 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -6,13 +6,16 @@ using System; using osu.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Contracted; using osu.Game.Screens.Ranking.Expanded; @@ -107,6 +110,8 @@ namespace osu.Game.Screens.Ranking private Container middleLayerContentContainer; private Drawable middleLayerContent; + private DrawableSample samplePanelFocus; + public ScorePanel(ScoreInfo score, bool isNewLocalScore = false) { Score = score; @@ -116,7 +121,7 @@ namespace osu.Game.Screens.Ranking } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { // ScorePanel doesn't include the top extruding area in its own size. // Adding a manual offset here allows the expanded version to take on an "acceptable" vertical centre when at 100% UI scale. @@ -174,7 +179,8 @@ namespace osu.Game.Screens.Ranking }, middleLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both } } - } + }, + samplePanelFocus = new DrawableSample(audio.Samples.Get(@"Results/score-panel-focus")) } }; } @@ -202,12 +208,26 @@ namespace osu.Game.Screens.Ranking state = value; if (IsLoaded) + { updateState(); + if (value == PanelState.Expanded) + playAppearSample(); + } + StateChanged?.Invoke(value); } } + private void playAppearSample() + { + var channel = samplePanelFocus?.GetChannel(); + if (channel == null) return; + + channel.Frequency.Value = 0.99 + RNG.NextDouble(0.2); + channel.Play(); + } + private void updateState() { topLayerContent?.FadeOut(content_fade_duration).Expire(); @@ -221,7 +241,8 @@ namespace osu.Game.Screens.Ranking topLayerBackground.FadeColour(expanded_top_layer_colour, RESIZE_DURATION, Easing.OutQuint); middleLayerBackground.FadeColour(expanded_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint); - topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User) { Alpha = 0 }); + bool firstLoad = topLayerContent == null; + topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User, firstLoad) { Alpha = 0 }); middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair) { Alpha = 0 }); // only the first expanded display should happen with flair. diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index ab68dec92d..435162e057 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -8,6 +8,8 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -35,6 +37,10 @@ namespace osu.Game.Screens.Ranking.Statistics private readonly Container content; private readonly LoadingSpinner spinner; + private bool wasOpened; + private Sample popInSample; + private Sample popOutSample; + public StatisticsPanel() { InternalChild = new Container @@ -56,9 +62,12 @@ namespace osu.Game.Screens.Ranking.Statistics } [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { Score.BindValueChanged(populateStatistics, true); + + popInSample = audio.Samples.Get(@"Results/statistics-panel-pop-in"); + popOutSample = audio.Samples.Get(@"Results/statistics-panel-pop-out"); } private CancellationTokenSource loadCancellation; @@ -216,9 +225,21 @@ namespace osu.Game.Screens.Ranking.Statistics return true; } - protected override void PopIn() => this.FadeIn(150, Easing.OutQuint); + protected override void PopIn() + { + this.FadeIn(150, Easing.OutQuint); - protected override void PopOut() => this.FadeOut(150, Easing.OutQuint); + popInSample?.Play(); + wasOpened = true; + } + + protected override void PopOut() + { + this.FadeOut(150, Easing.OutQuint); + + if (wasOpened) + popOutSample?.Play(); + } protected override void Dispose(bool isDisposing) { From 89da21b6dee6856d690397f87f9c3fa4998e327f Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 22 Jul 2022 19:48:29 +0900 Subject: [PATCH 245/481] Add total score counter sfx --- .../Expanded/ExpandedPanelMiddleContent.cs | 2 +- .../Ranking/Expanded/TotalScoreCounter.cs | 62 ++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 5b8777d2a4..0f202e5e08 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -133,7 +133,7 @@ namespace osu.Game.Screens.Ranking.Expanded FillMode = FillMode.Fit, } }, - scoreCounter = new TotalScoreCounter + scoreCounter = new TotalScoreCounter(!withFlair) { Margin = new MarginPadding { Top = 0, Bottom = 5 }, Current = { Value = 0 }, diff --git a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs index 0b06dedc44..1e3443c2df 100644 --- a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs +++ b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs @@ -3,7 +3,11 @@ #nullable disable +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Audio; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -22,11 +26,32 @@ namespace osu.Game.Screens.Ranking.Expanded protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING; - public TotalScoreCounter() + private readonly bool playSound; + private bool isTicking; + private readonly Bindable tickPlaybackRate = new Bindable(); + private double lastSampleTime; + private DrawableSample sampleTick; + + public TotalScoreCounter(bool playSound = false) { // Todo: AutoSize X removed here due to https://github.com/ppy/osu-framework/issues/3369 AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; + this.playSound = playSound; + } + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + AddInternal(sampleTick = new DrawableSample(audio.Samples.Get(@"Results/score-tick-lesser"))); + lastSampleTime = Time.Current; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Current.BindValueChanged(startTickingPlayback); } protected override LocalisableString FormatCount(long count) => count.ToString("N0"); @@ -39,5 +64,40 @@ namespace osu.Game.Screens.Ranking.Expanded s.Font = OsuFont.Torus.With(size: 60, weight: FontWeight.Light, fixedWidth: true); s.Spacing = new Vector2(-5, 0); }); + + protected override void Update() + { + base.Update(); + + if (playSound && isTicking) playTickSample(); + } + + private void startTickingPlayback(ValueChangedEvent _) + { + const double tick_debounce_rate_start = 10f; + const double tick_debounce_rate_end = 100f; + const double tick_volume_start = 0.5f; + const double tick_volume_end = 1.0f; + double tickDuration = RollingDuration - AccuracyCircle.ACCURACY_TRANSFORM_DELAY - AccuracyCircle.RANK_CIRCLE_TRANSFORM_DELAY; + + this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_start); + this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_end, tickDuration, Easing.OutSine); + sampleTick.VolumeTo(tick_volume_start).Then().VolumeTo(tick_volume_end, tickDuration, Easing.OutSine); + + Scheduler.AddDelayed(stopTickingPlayback, tickDuration); + + isTicking = true; + } + + private void stopTickingPlayback() => isTicking = false; + + private void playTickSample() + { + if (Time.Current > lastSampleTime + tickPlaybackRate.Value) + { + sampleTick?.Play(); + lastSampleTime = Time.Current; + } + } } } From 62d4d4b05559334686737b59fb1667273e505a66 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Fri, 22 Jul 2022 19:52:42 +0900 Subject: [PATCH 246/481] Add dynamic panning fun to score panel sfx --- osu.Game/Screens/Ranking/ScorePanel.cs | 8 ++++---- osu.Game/Screens/Ranking/ScorePanelList.cs | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 322124313f..aa4b732bb6 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -96,9 +96,9 @@ namespace osu.Game.Screens.Ranking public readonly ScoreInfo Score; - private bool displayWithFlair; + public DrawableAudioMixer Mixer; - private Container content; + private bool displayWithFlair; private Container topLayerContainer; private Drawable topLayerBackground; @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Ranking // Adding a manual offset here allows the expanded version to take on an "acceptable" vertical centre when at 100% UI scale. const float vertical_fudge = 20; - InternalChild = content = new Container + InternalChild = Mixer = new DrawableAudioMixer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -265,7 +265,7 @@ namespace osu.Game.Screens.Ranking break; } - content.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint); + Mixer.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint); bool topLayerExpanded = topLayerContainer.Y < 0; diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 4f9e61a4a1..d312953a38 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -344,6 +344,23 @@ namespace osu.Game.Screens.Ranking private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() .OrderByDescending(GetLayoutPosition) .ThenBy(s => s.Panel.Score.OnlineID); + + protected override void Update() + { + foreach (ScorePanelTrackingContainer trackingContainer in FlowingChildren.OfType()) + { + var panel = trackingContainer.Panel; + + if (panel.State != PanelState.Expanded) continue; + + var scrollContainer = Parent.Parent; + float balance = scrollContainer.ToLocalSpace(panel.ToScreenSpace(panel.BoundingBox.Centre)).X / scrollContainer.RelativeToAbsoluteFactor.X; + + panel.Mixer.Balance.Value = Math.Clamp(-1 + balance * 2, -1, 1); + } + + base.Update(); + } } private class Scroll : OsuScrollContainer From 9c0d79389639becc7491a9b6c3af832cd90c8605 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 00:04:11 +0900 Subject: [PATCH 247/481] Update resources --- 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 c83b7872ac..7c4582adf5 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4fa4b804ab..ec0b392481 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index dc012ab2fa..73d219cb66 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + From e08e4fc4260b8e5b075eef360ee4054527f436ae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 00:04:14 +0900 Subject: [PATCH 248/481] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 7c4582adf5..97fc97153c 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ec0b392481..d95753179f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 73d219cb66..5455c94998 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 9f045209b93f4cead222a89cbc7ed041dbb3abc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 02:09:08 +0900 Subject: [PATCH 249/481] Simplify score panel balance adjustment --- osu.Game/Screens/Ranking/ScorePanel.cs | 15 ++++++++++++--- osu.Game/Screens/Ranking/ScorePanelList.cs | 17 ----------------- 2 files changed, 12 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index aa4b732bb6..9dcaafd9f9 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -96,7 +96,10 @@ namespace osu.Game.Screens.Ranking public readonly ScoreInfo Score; - public DrawableAudioMixer Mixer; + [Resolved] + private OsuGame game { get; set; } + + private DrawableAudioMixer mixer; private bool displayWithFlair; @@ -127,7 +130,7 @@ namespace osu.Game.Screens.Ranking // Adding a manual offset here allows the expanded version to take on an "acceptable" vertical centre when at 100% UI scale. const float vertical_fudge = 20; - InternalChild = Mixer = new DrawableAudioMixer + InternalChild = mixer = new DrawableAudioMixer { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -219,6 +222,12 @@ namespace osu.Game.Screens.Ranking } } + protected override void Update() + { + base.Update(); + mixer.Balance.Value = (ScreenSpaceDrawQuad.Centre.X / game.ScreenSpaceDrawQuad.Width) * 2 - 1; + } + private void playAppearSample() { var channel = samplePanelFocus?.GetChannel(); @@ -265,7 +274,7 @@ namespace osu.Game.Screens.Ranking break; } - Mixer.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint); + mixer.ResizeTo(Size, RESIZE_DURATION, Easing.OutQuint); bool topLayerExpanded = topLayerContainer.Y < 0; diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index d312953a38..4f9e61a4a1 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -344,23 +344,6 @@ namespace osu.Game.Screens.Ranking private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() .OrderByDescending(GetLayoutPosition) .ThenBy(s => s.Panel.Score.OnlineID); - - protected override void Update() - { - foreach (ScorePanelTrackingContainer trackingContainer in FlowingChildren.OfType()) - { - var panel = trackingContainer.Panel; - - if (panel.State != PanelState.Expanded) continue; - - var scrollContainer = Parent.Parent; - float balance = scrollContainer.ToLocalSpace(panel.ToScreenSpace(panel.BoundingBox.Centre)).X / scrollContainer.RelativeToAbsoluteFactor.X; - - panel.Mixer.Balance.Value = Math.Clamp(-1 + balance * 2, -1, 1); - } - - base.Update(); - } } private class Scroll : OsuScrollContainer From f3ceabc53f074d610a3fad562c0f2620ee86a0ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Jul 2022 19:14:39 +0200 Subject: [PATCH 250/481] Rename `ModSelect{Overlay -> }Panel` --- osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs | 6 +++--- osu.Game/Overlays/Mods/ModColumn.cs | 10 +++++----- osu.Game/Overlays/Mods/ModPanel.cs | 2 +- osu.Game/Overlays/Mods/ModPresetPanel.cs | 2 +- .../{ModSelectOverlayPanel.cs => ModSelectPanel.cs} | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) rename osu.Game/Overlays/Mods/{ModSelectOverlayPanel.cs => ModSelectPanel.cs} (98%) diff --git a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs index c0a4cf2a25..255d01466f 100644 --- a/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs +++ b/osu.Game/Overlays/Mods/DifficultyMultiplierDisplay.cs @@ -60,7 +60,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Masking = true, - CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, + CornerRadius = ModSelectPanel.CORNER_RADIUS, Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0), Children = new Drawable[] { @@ -69,7 +69,7 @@ namespace osu.Game.Overlays.Mods Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Y, - Width = multiplier_value_area_width + ModSelectOverlayPanel.CORNER_RADIUS + Width = multiplier_value_area_width + ModSelectPanel.CORNER_RADIUS }, new GridContainer { @@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.Y, AutoSizeAxes = Axes.X, Masking = true, - CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, + CornerRadius = ModSelectPanel.CORNER_RADIUS, Children = new Drawable[] { contentBackground = new Box diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index 63a51aaad0..beb4856477 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -105,20 +105,20 @@ namespace osu.Game.Overlays.Mods TopLevelContent = new Container { RelativeSizeAxes = Axes.Both, - CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, + CornerRadius = ModSelectPanel.CORNER_RADIUS, Masking = true, Children = new Drawable[] { new Container { RelativeSizeAxes = Axes.X, - Height = header_height + ModSelectOverlayPanel.CORNER_RADIUS, + Height = header_height + ModSelectPanel.CORNER_RADIUS, Children = new Drawable[] { headerBackground = new Box { RelativeSizeAxes = Axes.X, - Height = header_height + ModSelectOverlayPanel.CORNER_RADIUS + Height = header_height + ModSelectPanel.CORNER_RADIUS }, headerText = new OsuTextFlowContainer(t => { @@ -135,7 +135,7 @@ namespace osu.Game.Overlays.Mods Padding = new MarginPadding { Horizontal = 17, - Bottom = ModSelectOverlayPanel.CORNER_RADIUS + Bottom = ModSelectPanel.CORNER_RADIUS } } } @@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Mods { RelativeSizeAxes = Axes.Both, Masking = true, - CornerRadius = ModSelectOverlayPanel.CORNER_RADIUS, + CornerRadius = ModSelectPanel.CORNER_RADIUS, BorderThickness = 3, Children = new Drawable[] { diff --git a/osu.Game/Overlays/Mods/ModPanel.cs b/osu.Game/Overlays/Mods/ModPanel.cs index 5de6290248..6ef6ab0595 100644 --- a/osu.Game/Overlays/Mods/ModPanel.cs +++ b/osu.Game/Overlays/Mods/ModPanel.cs @@ -11,7 +11,7 @@ using osuTK; namespace osu.Game.Overlays.Mods { - public class ModPanel : ModSelectOverlayPanel + public class ModPanel : ModSelectPanel { public Mod Mod => modState.Mod; public override BindableBool Active => modState.Active; diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs index 39f092b91a..47e2f25538 100644 --- a/osu.Game/Overlays/Mods/ModPresetPanel.cs +++ b/osu.Game/Overlays/Mods/ModPresetPanel.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Overlays.Mods { - public class ModPresetPanel : ModSelectOverlayPanel, IHasCustomTooltip + public class ModPresetPanel : ModSelectPanel, IHasCustomTooltip { public readonly ModPreset Preset; diff --git a/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs b/osu.Game/Overlays/Mods/ModSelectPanel.cs similarity index 98% rename from osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs rename to osu.Game/Overlays/Mods/ModSelectPanel.cs index a794884d7d..abf327a388 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlayPanel.cs +++ b/osu.Game/Overlays/Mods/ModSelectPanel.cs @@ -24,7 +24,7 @@ using osuTK.Input; namespace osu.Game.Overlays.Mods { - public abstract class ModSelectOverlayPanel : OsuClickableContainer, IHasAccentColour + public abstract class ModSelectPanel : OsuClickableContainer, IHasAccentColour { public abstract BindableBool Active { get; } @@ -70,7 +70,7 @@ namespace osu.Game.Overlays.Mods private Sample? sampleOff; private Sample? sampleOn; - protected ModSelectOverlayPanel() + protected ModSelectPanel() { RelativeSizeAxes = Axes.X; Height = HEIGHT; From bb46ba66e03df7d0f102f3ff43ad1cb7a34b4be2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 02:25:37 +0900 Subject: [PATCH 251/481] Simplify `TotalScoreCounter` tick playback logic --- .../Ranking/Expanded/TotalScoreCounter.cs | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs index 1e3443c2df..33298063fc 100644 --- a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs +++ b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs @@ -26,32 +26,35 @@ namespace osu.Game.Screens.Ranking.Expanded protected override Easing RollingEasing => AccuracyCircle.ACCURACY_TRANSFORM_EASING; - private readonly bool playSound; - private bool isTicking; + private readonly bool playSamples; + private readonly Bindable tickPlaybackRate = new Bindable(); + private double lastSampleTime; + private DrawableSample sampleTick; - public TotalScoreCounter(bool playSound = false) + public TotalScoreCounter(bool playSamples = false) { // Todo: AutoSize X removed here due to https://github.com/ppy/osu-framework/issues/3369 AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; - this.playSound = playSound; + + this.playSamples = playSamples; } [BackgroundDependencyLoader] private void load(AudioManager audio) { AddInternal(sampleTick = new DrawableSample(audio.Samples.Get(@"Results/score-tick-lesser"))); - lastSampleTime = Time.Current; } protected override void LoadComplete() { base.LoadComplete(); - Current.BindValueChanged(startTickingPlayback); + if (playSamples) + Current.BindValueChanged(_ => startTicking()); } protected override LocalisableString FormatCount(long count) => count.ToString("N0"); @@ -65,39 +68,36 @@ namespace osu.Game.Screens.Ranking.Expanded s.Spacing = new Vector2(-5, 0); }); - protected override void Update() + public override long DisplayedCount { - base.Update(); + get => base.DisplayedCount; + set + { + if (base.DisplayedCount == value) + return; - if (playSound && isTicking) playTickSample(); + base.DisplayedCount = value; + + if (playSamples && Time.Current > lastSampleTime + tickPlaybackRate.Value) + { + sampleTick?.Play(); + lastSampleTime = Time.Current; + } + } } - private void startTickingPlayback(ValueChangedEvent _) + private void startTicking() { const double tick_debounce_rate_start = 10f; const double tick_debounce_rate_end = 100f; const double tick_volume_start = 0.5f; const double tick_volume_end = 1.0f; - double tickDuration = RollingDuration - AccuracyCircle.ACCURACY_TRANSFORM_DELAY - AccuracyCircle.RANK_CIRCLE_TRANSFORM_DELAY; + + double tickDuration = RollingDuration; this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_start); this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_end, tickDuration, Easing.OutSine); sampleTick.VolumeTo(tick_volume_start).Then().VolumeTo(tick_volume_end, tickDuration, Easing.OutSine); - - Scheduler.AddDelayed(stopTickingPlayback, tickDuration); - - isTicking = true; - } - - private void stopTickingPlayback() => isTicking = false; - - private void playTickSample() - { - if (Time.Current > lastSampleTime + tickPlaybackRate.Value) - { - sampleTick?.Play(); - lastSampleTime = Time.Current; - } } } } From 475679ca66da4f34051d14c52b0f01b9e5704920 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 02:27:47 +0900 Subject: [PATCH 252/481] Fix DI failure --- osu.Game/Screens/Ranking/ScorePanel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 9dcaafd9f9..0bcfa0da1f 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -97,7 +97,7 @@ namespace osu.Game.Screens.Ranking public readonly ScoreInfo Score; [Resolved] - private OsuGame game { get; set; } + private OsuGameBase game { get; set; } private DrawableAudioMixer mixer; From db632c0d6e5f40674d7e79136520b2ffd39145fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 02:38:00 +0900 Subject: [PATCH 253/481] Inline rolling duration --- osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs index 33298063fc..c7286a1838 100644 --- a/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs +++ b/osu.Game/Screens/Ranking/Expanded/TotalScoreCounter.cs @@ -93,11 +93,9 @@ namespace osu.Game.Screens.Ranking.Expanded const double tick_volume_start = 0.5f; const double tick_volume_end = 1.0f; - double tickDuration = RollingDuration; - this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_start); - this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_end, tickDuration, Easing.OutSine); - sampleTick.VolumeTo(tick_volume_start).Then().VolumeTo(tick_volume_end, tickDuration, Easing.OutSine); + this.TransformBindableTo(tickPlaybackRate, tick_debounce_rate_end, RollingDuration, Easing.OutSine); + sampleTick.VolumeTo(tick_volume_start).Then().VolumeTo(tick_volume_end, RollingDuration, Easing.OutSine); } } } From d451bc8fda5784c5574f6fe8e68f66f16d16ee49 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Fri, 22 Jul 2022 22:24:50 -0500 Subject: [PATCH 254/481] Revert commits This reverts commit 28586c704dbf6e5a2b1bc43e8f93c13e3b8f72e5. This reverts commit d9d35bb847a8f424c1379763019dbb0dc05c52a2. --- .../BeatmapSet/BeatmapSetHeaderContent.cs | 2 +- osu.Game/Overlays/BeatmapSet/Details.cs | 4 ++-- .../Screens/Select/Details/AdvancedStats.cs | 19 +++++++------------ 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 68f5f2be6e..c9e97d5f2f 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -222,7 +222,7 @@ namespace osu.Game.Overlays.BeatmapSet TextSize = 14, TextPadding = new MarginPadding { Horizontal = 35, Vertical = 10 } }, - Details = new Details(false), + Details = new Details(), }, }, } diff --git a/osu.Game/Overlays/BeatmapSet/Details.cs b/osu.Game/Overlays/BeatmapSet/Details.cs index 092646f0ca..6db54db811 100644 --- a/osu.Game/Overlays/BeatmapSet/Details.cs +++ b/osu.Game/Overlays/BeatmapSet/Details.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.BeatmapSet ratingBox.Alpha = BeatmapSet?.Status > 0 ? 1 : 0; } - public Details(bool updateWithModSelection = true) + public Details() { Width = BeatmapSetOverlay.RIGHT_WIDTH; AutoSizeAxes = Axes.Y; @@ -82,7 +82,7 @@ namespace osu.Game.Overlays.BeatmapSet }, new DetailBox { - Child = advanced = new AdvancedStats(updateWithModSelection) + Child = advanced = new AdvancedStats { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 80e9989c47..693f182065 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Select.Details } } - public AdvancedStats(bool updateWithModSelection = true) + public AdvancedStats() { Child = new FillFlowContainer { @@ -65,11 +65,11 @@ namespace osu.Game.Screens.Select.Details AutoSizeAxes = Axes.Y, Children = new[] { - FirstValue = new StatisticRow(updateWithModSelection), // circle size/key amount - HpDrain = new StatisticRow(updateWithModSelection) { Title = BeatmapsetsStrings.ShowStatsDrain }, - Accuracy = new StatisticRow(updateWithModSelection) { Title = BeatmapsetsStrings.ShowStatsAccuracy }, - ApproachRate = new StatisticRow(updateWithModSelection) { Title = BeatmapsetsStrings.ShowStatsAr }, - starDifficulty = new StatisticRow(updateWithModSelection, 10, true) { Title = BeatmapsetsStrings.ShowStatsStars }, + FirstValue = new StatisticRow(), // circle size/key amount + HpDrain = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsDrain }, + Accuracy = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAccuracy }, + ApproachRate = new StatisticRow { Title = BeatmapsetsStrings.ShowStatsAr }, + starDifficulty = new StatisticRow(10, true) { Title = BeatmapsetsStrings.ShowStatsStars }, }, }; } @@ -183,7 +183,6 @@ namespace osu.Game.Screens.Select.Details private readonly OsuSpriteText name, valueText; private readonly Bar bar; public readonly Bar ModBar; - private readonly bool showModdedValue; [Resolved] private OsuColour colours { get; set; } @@ -204,9 +203,6 @@ namespace osu.Game.Screens.Select.Details if (value == this.value) return; - if (!showModdedValue) - value.adjustedValue = null; - this.value = value; bar.Length = value.baseValue / maxValue; @@ -229,14 +225,13 @@ namespace osu.Game.Screens.Select.Details set => bar.AccentColour = value; } - public StatisticRow(bool showModdedValue, float maxValue = 10, bool forceDecimalPlaces = false) + public StatisticRow(float maxValue = 10, bool forceDecimalPlaces = false) { this.maxValue = maxValue; this.forceDecimalPlaces = forceDecimalPlaces; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; Padding = new MarginPadding { Vertical = 2.5f }; - this.showModdedValue = showModdedValue; Children = new Drawable[] { From 06462c13dd7023441ad9e2288673417c1e349c31 Mon Sep 17 00:00:00 2001 From: Adam Baker Date: Fri, 22 Jul 2022 23:15:24 -0500 Subject: [PATCH 255/481] Overwrite IBindable> cache in BeatmapSetOverlay Implement fix as suggested --- osu.Game/Overlays/BeatmapSetOverlay.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 823d0023cf..a50a604b92 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -3,7 +3,10 @@ #nullable disable +using System; +using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -12,6 +15,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Overlays.Comments; +using osu.Game.Rulesets.Mods; using osuTK; using osuTK.Graphics; @@ -25,6 +29,10 @@ namespace osu.Game.Overlays private readonly Bindable beatmapSet = new Bindable(); + [Cached] + [Cached(typeof(IBindable>))] + protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); + public BeatmapSetOverlay() : base(OverlayColourScheme.Blue) { From 661c79baf691c59bc0af3501dd3fe28d62a04028 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jul 2022 10:15:52 +0300 Subject: [PATCH 256/481] Add explanatory comment --- osu.Game/Overlays/BeatmapSetOverlay.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index a50a604b92..207dc91ca5 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -16,6 +16,7 @@ using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Overlays.Comments; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Select.Details; using osuTK; using osuTK.Graphics; @@ -29,6 +30,10 @@ namespace osu.Game.Overlays private readonly Bindable beatmapSet = new Bindable(); + /// + /// Isolates the beatmap set overlay from the game-wide selected mods bindable + /// to avoid affecting the beatmap details section (i.e. ). + /// [Cached] [Cached(typeof(IBindable>))] protected readonly Bindable> SelectedMods = new Bindable>(Array.Empty()); From c937c0548eb1782041d52445896a098b8d0d300e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jul 2022 10:01:06 +0300 Subject: [PATCH 257/481] Add test coverage --- .../Online/TestSceneBeatmapSetOverlay.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 416e8aebcc..bb4823fb1d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -16,6 +16,10 @@ using osu.Framework.Testing; using osu.Game.Beatmaps.Drawables; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Scores; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.Select.Details; using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Tests.Visual.Online @@ -34,6 +38,9 @@ namespace osu.Game.Tests.Visual.Online [Resolved] private IRulesetStore rulesets { get; set; } + [SetUp] + public void SetUp() => Schedule(() => SelectedMods.Value = Array.Empty()); + [Test] public void TestLoading() { @@ -205,6 +212,21 @@ namespace osu.Game.Tests.Visual.Online }); } + [Test] + public void TestSelectedModsDontAffectStatistics() + { + AddStep("show map", () => overlay.ShowBeatmapSet(getBeatmapSet())); + AddAssert("AR displayed as 0", () => overlay.ChildrenOfType().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value == (0, null)); + AddStep("set AR10 diff adjust", () => SelectedMods.Value = new[] + { + new OsuModDifficultyAdjust + { + ApproachRate = { Value = 10 } + } + }); + AddAssert("AR still displayed as 0", () => overlay.ChildrenOfType().Single(s => s.Title == BeatmapsetsStrings.ShowStatsAr).Value == (0, null)); + } + [Test] public void TestHide() { From 840ad8fad2e8abe166d20a86a5ca4c5d7df7269c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 23 Jul 2022 11:10:59 +0300 Subject: [PATCH 258/481] Fix background beatmap processor resetting star ratings in tests --- osu.Game/Tests/Visual/OsuGameTestScene.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index b9f6183869..69a945db34 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -171,6 +171,11 @@ namespace osu.Game.Tests.Visual API.Login("Rhythm Champion", "osu!"); Dependencies.Get().SetValue(Static.MutedAudioNotificationShownOnce, true); + + // set applied version to latest so that the BackgroundBeatmapProcessor doesn't consider + // beatmap star ratings as outdated and reset them throughout the test. + foreach (var ruleset in RulesetStore.AvailableRulesets) + ruleset.LastAppliedDifficultyVersion = ruleset.CreateInstance().CreateDifficultyCalculator(Beatmap.Default).Version; } protected override void Update() From f1791e79e3e6cb607867304a1b560937f5876ba2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 Jul 2022 19:21:12 +0900 Subject: [PATCH 259/481] Add error logging for background processing failures --- osu.Game/BackgroundBeatmapProcessor.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/BackgroundBeatmapProcessor.cs b/osu.Game/BackgroundBeatmapProcessor.cs index 6ecd8ca5c1..14fdb2e1ef 100644 --- a/osu.Game/BackgroundBeatmapProcessor.cs +++ b/osu.Game/BackgroundBeatmapProcessor.cs @@ -127,8 +127,15 @@ namespace osu.Game if (set != null) { - Logger.Log($"Background processing {set} ({++i} / {beatmapSetIds.Count})"); - beatmapUpdater.Process(set); + try + { + Logger.Log($"Background processing {set} ({++i} / {beatmapSetIds.Count})"); + beatmapUpdater.Process(set); + } + catch (Exception e) + { + Logger.Log($"Background processing failed on {set}: {e}"); + } } }); } From 16e655766eeb253f4a127bdc34d2dc3247092ba2 Mon Sep 17 00:00:00 2001 From: mk-56 Date: Sat, 23 Jul 2022 23:30:57 +0200 Subject: [PATCH 260/481] Addressed pertinent issues --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 185751cf3d..52ed843c4c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -26,13 +26,16 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel) }; - [SettingSource("Strength")] + private const int wiggle_duration = 100; // (ms) Higher = fewer wiggles + + [SettingSource("Strength", "Multiplier applied to the wiggling strength.")] public BindableDouble WiggleStrength { get; } = new BindableDouble(1) { MinValue = 0.1f, MaxValue = 2f, Precision = 0.1f }; + protected override void ApplyIncreasedVisibilityState(DrawableHitObject hitObject, ArmedState state) => drawableOnApplyCustomUpdateState(hitObject, state); protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) => drawableOnApplyCustomUpdateState(hitObject, state); @@ -50,18 +53,18 @@ namespace osu.Game.Rulesets.Osu.Mods Random objRand = new Random((int)osuObject.StartTime); // Wiggle all objects during TimePreempt - int amountWiggles = (int)osuObject.TimePreempt / 70; + int amountWiggles = (int)osuObject.TimePreempt / wiggle_duration; void wiggle() { float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI); float nextDist = (float)(objRand.NextDouble() * WiggleStrength.Value * 7); - drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), 70); + drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), wiggle_duration); } for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * 70)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime - osuObject.TimePreempt + i * wiggle_duration)) wiggle(); } @@ -69,11 +72,11 @@ namespace osu.Game.Rulesets.Osu.Mods if (!(osuObject is IHasDuration endTime)) return; - amountWiggles = (int)(endTime.Duration / 70); + amountWiggles = (int)(endTime.Duration / wiggle_duration); for (int i = 0; i < amountWiggles; i++) { - using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * 70)) + using (drawable.BeginAbsoluteSequence(osuObject.StartTime + i * wiggle_duration)) wiggle(); } } From 7c477e6f2298ffc2fbd8d77efb6badbfd348d068 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 24 Jul 2022 04:19:26 +0300 Subject: [PATCH 261/481] Fix beatmap overlay leaderboard not handling null PP scores properly --- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index c46c5cde43..08f750827b 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -6,7 +6,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions; @@ -178,10 +177,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } if (showPerformancePoints) - { - Debug.Assert(score.PP != null); - content.Add(new StatisticText(score.PP.Value, format: @"N0")); - } + content.Add(new StatisticText(score.PP, format: @"N0")); content.Add(new ScoreboardTime(score.Date, text_size) { @@ -222,19 +218,19 @@ namespace osu.Game.Overlays.BeatmapSet.Scores private class StatisticText : OsuSpriteText, IHasTooltip { - private readonly double count; + private readonly double? count; private readonly double? maxCount; private readonly bool showTooltip; public LocalisableString TooltipText => maxCount == null || !showTooltip ? string.Empty : $"{count}/{maxCount}"; - public StatisticText(double count, double? maxCount = null, string format = null, bool showTooltip = true) + public StatisticText(double? count, double? maxCount = null, string format = null, bool showTooltip = true) { this.count = count; this.maxCount = maxCount; this.showTooltip = showTooltip; - Text = count.ToLocalisableString(format); + Text = count?.ToLocalisableString(format) ?? default; Font = OsuFont.GetFont(size: text_size); } From 0c16ef3e2ed0215ac03c51a1fd7743273f695fbb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 24 Jul 2022 08:33:35 +0300 Subject: [PATCH 262/481] Add failing test case --- .../TestSceneLabelledSliderBar.cs | 33 +++++++++++++++---- 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.cs index 8ccfe2ee9c..e5f3aea2f7 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledSliderBar.cs @@ -8,8 +8,10 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; @@ -17,11 +19,26 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneLabelledSliderBar : OsuTestScene { - [TestCase(false)] - [TestCase(true)] - public void TestSliderBar(bool hasDescription) => createSliderBar(hasDescription); + [Test] + public void TestBasic() => createSliderBar(); - private void createSliderBar(bool hasDescription = false) + [Test] + public void TestDescription() + { + createSliderBar(); + AddStep("set description", () => this.ChildrenOfType>().ForEach(l => l.Description = "this text describes the component")); + } + + [Test] + public void TestSize() + { + createSliderBar(); + AddStep("set zero width", () => this.ChildrenOfType>().ForEach(l => l.ResizeWidthTo(0, 200, Easing.OutQuint))); + AddStep("set negative width", () => this.ChildrenOfType>().ForEach(l => l.ResizeWidthTo(-1, 200, Easing.OutQuint))); + AddStep("revert back", () => this.ChildrenOfType>().ForEach(l => l.ResizeWidthTo(1, 200, Easing.OutQuint))); + } + + private void createSliderBar() { AddStep("create component", () => { @@ -38,6 +55,8 @@ namespace osu.Game.Tests.Visual.UserInterface { new LabelledSliderBar { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Current = new BindableDouble(5) { MinValue = 0, @@ -45,7 +64,6 @@ namespace osu.Game.Tests.Visual.UserInterface Precision = 1, }, Label = "a sample component", - Description = hasDescription ? "this text describes the component" : string.Empty, }, }, }; @@ -54,10 +72,14 @@ namespace osu.Game.Tests.Visual.UserInterface { flow.Add(new OverlayColourContainer(colour) { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Child = new LabelledSliderBar { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, Current = new BindableDouble(5) { MinValue = 0, @@ -65,7 +87,6 @@ namespace osu.Game.Tests.Visual.UserInterface Precision = 1, }, Label = "a sample component", - Description = hasDescription ? "this text describes the component" : string.Empty, } }); } From 4332e6cae90980396482edbe6309259bc6096c42 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 24 Jul 2022 08:34:05 +0300 Subject: [PATCH 263/481] Fix `OsuSliderBar` throwing on negative draw width --- osu.Game/Graphics/UserInterface/OsuSliderBar.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index c48627bd21..2a8b41fd20 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -228,10 +228,8 @@ namespace osu.Game.Graphics.UserInterface protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); - LeftBox.Scale = new Vector2(Math.Clamp( - RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, DrawWidth), 1); - RightBox.Scale = new Vector2(Math.Clamp( - DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2, 0, DrawWidth), 1); + LeftBox.Scale = new Vector2(Math.Clamp(RangePadding + Nub.DrawPosition.X - Nub.DrawWidth / 2, 0, Math.Max(0, DrawWidth)), 1); + RightBox.Scale = new Vector2(Math.Clamp(DrawWidth - Nub.DrawPosition.X - RangePadding - Nub.DrawWidth / 2, 0, Math.Max(0, DrawWidth)), 1); } protected override void UpdateValue(float value) From b2f893411766567ad774de4e6327db1688d861cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Jul 2022 20:01:22 +0200 Subject: [PATCH 264/481] Extract base mod select column presentation logic --- osu.Game/Overlays/Mods/ModColumn.cs | 162 ++------------------ osu.Game/Overlays/Mods/ModSelectColumn.cs | 177 ++++++++++++++++++++++ 2 files changed, 187 insertions(+), 152 deletions(-) create mode 100644 osu.Game/Overlays/Mods/ModSelectColumn.cs diff --git a/osu.Game/Overlays/Mods/ModColumn.cs b/osu.Game/Overlays/Mods/ModColumn.cs index beb4856477..1c40c8c6e5 100644 --- a/osu.Game/Overlays/Mods/ModColumn.cs +++ b/osu.Game/Overlays/Mods/ModColumn.cs @@ -12,14 +12,10 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Configuration; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Localisation; using osu.Game.Overlays.Mods.Input; @@ -29,10 +25,8 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Mods { - public class ModColumn : CompositeDrawable + public class ModColumn : ModSelectColumn { - public readonly Container TopLevelContent; - public readonly ModType ModType; private IReadOnlyList availableMods = Array.Empty(); @@ -62,149 +56,29 @@ namespace osu.Game.Overlays.Mods } } - /// - /// Determines whether this column should accept user input. - /// - public Bindable Active = new BindableBool(true); - - protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value; - protected virtual ModPanel CreateModPanel(ModState mod) => new ModPanel(mod); private readonly bool allowIncompatibleSelection; - private readonly TextFlowContainer headerText; - private readonly Box headerBackground; - private readonly Container contentContainer; - private readonly Box contentBackground; - private readonly FillFlowContainer panelFlow; private readonly ToggleAllCheckbox? toggleAllCheckbox; - private Colour4 accentColour; - private Bindable hotkeyStyle = null!; private IModHotkeyHandler hotkeyHandler = null!; private Task? latestLoadTask; internal bool ItemsLoaded => latestLoadTask == null; - private const float header_height = 42; - public ModColumn(ModType modType, bool allowIncompatibleSelection) { ModType = modType; this.allowIncompatibleSelection = allowIncompatibleSelection; - Width = 320; - RelativeSizeAxes = Axes.Y; - Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0); - - Container controlContainer; - InternalChildren = new Drawable[] - { - TopLevelContent = new Container - { - RelativeSizeAxes = Axes.Both, - CornerRadius = ModSelectPanel.CORNER_RADIUS, - Masking = true, - Children = new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.X, - Height = header_height + ModSelectPanel.CORNER_RADIUS, - Children = new Drawable[] - { - headerBackground = new Box - { - RelativeSizeAxes = Axes.X, - Height = header_height + ModSelectPanel.CORNER_RADIUS - }, - headerText = new OsuTextFlowContainer(t => - { - t.Font = OsuFont.TorusAlternate.With(size: 17); - t.Shadow = false; - t.Colour = Colour4.Black; - }) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), - Padding = new MarginPadding - { - Horizontal = 17, - Bottom = ModSelectPanel.CORNER_RADIUS - } - } - } - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = header_height }, - Child = contentContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = ModSelectPanel.CORNER_RADIUS, - BorderThickness = 3, - Children = new Drawable[] - { - contentBackground = new Box - { - RelativeSizeAxes = Axes.Both - }, - new GridContainer - { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension() - }, - Content = new[] - { - new Drawable[] - { - controlContainer = new Container - { - RelativeSizeAxes = Axes.X, - Padding = new MarginPadding { Horizontal = 14 } - } - }, - new Drawable[] - { - new OsuScrollContainer(Direction.Vertical) - { - RelativeSizeAxes = Axes.Both, - ClampExtension = 100, - ScrollbarOverlapsContent = false, - Child = panelFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Spacing = new Vector2(0, 7), - Padding = new MarginPadding(7) - } - } - } - } - } - } - } - } - } - } - }; - - createHeaderText(); + HeaderText = ModType.Humanize(LetterCasing.Title); if (allowIncompatibleSelection) { - controlContainer.Height = 35; - controlContainer.Add(toggleAllCheckbox = new ToggleAllCheckbox(this) + ControlContainer.Height = 35; + ControlContainer.Add(toggleAllCheckbox = new ToggleAllCheckbox(this) { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -212,7 +86,7 @@ namespace osu.Game.Overlays.Mods RelativeSizeAxes = Axes.X, Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0) }); - panelFlow.Padding = new MarginPadding + ItemsFlow.Padding = new MarginPadding { Top = 0, Bottom = 7, @@ -221,33 +95,17 @@ namespace osu.Game.Overlays.Mods } } - private void createHeaderText() - { - IEnumerable headerTextWords = ModType.Humanize(LetterCasing.Title).Split(' '); - - if (headerTextWords.Count() > 1) - { - headerText.AddText($"{headerTextWords.First()} ", t => t.Font = t.Font.With(weight: FontWeight.SemiBold)); - headerTextWords = headerTextWords.Skip(1); - } - - headerText.AddText(string.Join(' ', headerTextWords)); - } - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, OsuColour colours, OsuConfigManager configManager) + private void load(OsuColour colours, OsuConfigManager configManager) { - headerBackground.Colour = accentColour = colours.ForModType(ModType); + AccentColour = colours.ForModType(ModType); if (toggleAllCheckbox != null) { - toggleAllCheckbox.AccentColour = accentColour; - toggleAllCheckbox.AccentHoverColour = accentColour.Lighten(0.3f); + toggleAllCheckbox.AccentColour = AccentColour; + toggleAllCheckbox.AccentHoverColour = AccentColour.Lighten(0.3f); } - contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Background4, colourProvider.Background3); - contentBackground.Colour = colourProvider.Background4; - hotkeyStyle = configManager.GetBindable(OsuSetting.ModSelectHotkeyStyle); } @@ -278,7 +136,7 @@ namespace osu.Game.Overlays.Mods latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded => { - panelFlow.ChildrenEnumerable = loaded; + ItemsFlow.ChildrenEnumerable = loaded; updateState(); }, (cancellationTokenSource = new CancellationTokenSource()).Token); loadTask.ContinueWith(_ => diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs new file mode 100644 index 0000000000..d211f9eb5e --- /dev/null +++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs @@ -0,0 +1,177 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Overlays.Mods +{ + public abstract class ModSelectColumn : CompositeDrawable, IHasAccentColour + { + public readonly Container TopLevelContent; + + public LocalisableString HeaderText + { + set => createHeaderText(value); + } + + public Color4 AccentColour + { + get => headerBackground.Colour; + set => headerBackground.Colour = value; + } + + /// + /// Determines whether this column should accept user input. + /// + public Bindable Active = new BindableBool(true); + + protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value; + + protected readonly Container ControlContainer; + protected readonly FillFlowContainer ItemsFlow; + + private readonly TextFlowContainer headerText; + private readonly Box headerBackground; + private readonly Container contentContainer; + private readonly Box contentBackground; + + private const float header_height = 42; + + protected ModSelectColumn() + { + Width = 320; + RelativeSizeAxes = Axes.Y; + Shear = new Vector2(ShearedOverlayContainer.SHEAR, 0); + + InternalChildren = new Drawable[] + { + TopLevelContent = new Container + { + RelativeSizeAxes = Axes.Both, + CornerRadius = ModSelectPanel.CORNER_RADIUS, + Masking = true, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = header_height + ModSelectPanel.CORNER_RADIUS, + Children = new Drawable[] + { + headerBackground = new Box + { + RelativeSizeAxes = Axes.X, + Height = header_height + ModSelectPanel.CORNER_RADIUS + }, + headerText = new OsuTextFlowContainer(t => + { + t.Font = OsuFont.TorusAlternate.With(size: 17); + t.Shadow = false; + t.Colour = Colour4.Black; + }) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0), + Padding = new MarginPadding + { + Horizontal = 17, + Bottom = ModSelectPanel.CORNER_RADIUS + } + } + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = header_height }, + Child = contentContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = ModSelectPanel.CORNER_RADIUS, + BorderThickness = 3, + Children = new Drawable[] + { + contentBackground = new Box + { + RelativeSizeAxes = Axes.Both + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[] + { + ControlContainer = new Container + { + RelativeSizeAxes = Axes.X, + Padding = new MarginPadding { Horizontal = 14 } + } + }, + new Drawable[] + { + new OsuScrollContainer(Direction.Vertical) + { + RelativeSizeAxes = Axes.Both, + ClampExtension = 100, + ScrollbarOverlapsContent = false, + Child = ItemsFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 7), + Padding = new MarginPadding(7) + } + } + } + } + } + } + } + } + } + } + }; + } + + private void createHeaderText(LocalisableString text) + { + headerText.Clear(); + + int wordIndex = 0; + + headerText.AddText(text, t => + { + if (wordIndex == 0) + t.Font = t.Font.With(weight: FontWeight.SemiBold); + wordIndex += 1; + }); + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + contentContainer.BorderColour = ColourInfo.GradientVertical(colourProvider.Background4, colourProvider.Background3); + contentBackground.Colour = colourProvider.Background4; + } + } +} From 6a67d76d7ca085c7f4dec1336fe7be5a66e2ebac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Jul 2022 21:05:02 +0200 Subject: [PATCH 265/481] Add basic mod preset column implementation --- .../UserInterface/TestSceneModPresetColumn.cs | 77 +++++++++++++++++++ .../Localisation/ModPresetColumnStrings.cs | 19 +++++ osu.Game/Overlays/Mods/ModPresetColumn.cs | 77 +++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs create mode 100644 osu.Game/Localisation/ModPresetColumnStrings.cs create mode 100644 osu.Game/Overlays/Mods/ModPresetColumn.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs new file mode 100644 index 0000000000..f6209e1b42 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs @@ -0,0 +1,77 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; +using osu.Game.Overlays.Mods; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu.Mods; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneModPresetColumn : OsuTestScene + { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + + [Test] + public void TestBasicAppearance() + { + ModPresetColumn modPresetColumn = null!; + + AddStep("create content", () => Child = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(30), + Child = modPresetColumn = new ModPresetColumn + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Presets = createTestPresets().ToArray() + } + }); + AddStep("change presets", () => modPresetColumn.Presets = createTestPresets().Skip(1).ToArray()); + } + + private static IEnumerable createTestPresets() => new[] + { + new ModPreset + { + Name = "First preset", + Description = "Please ignore", + Mods = new Mod[] + { + new OsuModHardRock(), + new OsuModDoubleTime() + } + }, + new ModPreset + { + Name = "AR0", + Description = "For good readers", + Mods = new Mod[] + { + new OsuModDifficultyAdjust + { + ApproachRate = { Value = 0 } + } + } + }, + new ModPreset + { + Name = "This preset is going to have an extraordinarily long name", + Description = "This is done so that the capability to truncate overlong texts may be demonstrated", + Mods = new Mod[] + { + new OsuModFlashlight(), + new OsuModSpinIn() + } + } + }; + } +} diff --git a/osu.Game/Localisation/ModPresetColumnStrings.cs b/osu.Game/Localisation/ModPresetColumnStrings.cs new file mode 100644 index 0000000000..b19a70a248 --- /dev/null +++ b/osu.Game/Localisation/ModPresetColumnStrings.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 ModPresetColumnStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.ModPresetColumn"; + + /// + /// "Personal Presets" + /// + public static LocalisableString PersonalPresets => new TranslatableString(getKey(@"personal_presets"), @"Personal Presets"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} \ No newline at end of file diff --git a/osu.Game/Overlays/Mods/ModPresetColumn.cs b/osu.Game/Overlays/Mods/ModPresetColumn.cs new file mode 100644 index 0000000000..b32015e6ea --- /dev/null +++ b/osu.Game/Overlays/Mods/ModPresetColumn.cs @@ -0,0 +1,77 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Game.Graphics; +using osu.Game.Localisation; +using osu.Game.Rulesets.Mods; +using osuTK; + +namespace osu.Game.Overlays.Mods +{ + public class ModPresetColumn : ModSelectColumn + { + private IReadOnlyList presets = Array.Empty(); + + /// + /// Sets the collection of available mod presets. + /// + public IReadOnlyList Presets + { + get => presets; + set + { + presets = value; + + if (IsLoaded) + asyncLoadPanels(); + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AccentColour = colours.Orange1; + HeaderText = ModPresetColumnStrings.PersonalPresets; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + asyncLoadPanels(); + } + + private CancellationTokenSource? cancellationTokenSource; + + private Task? latestLoadTask; + internal bool ItemsLoaded => latestLoadTask == null; + + private void asyncLoadPanels() + { + cancellationTokenSource?.Cancel(); + + var panels = presets.Select(preset => new ModPresetPanel(preset) + { + Shear = Vector2.Zero + }); + + Task? loadTask; + + latestLoadTask = loadTask = LoadComponentsAsync(panels, loaded => + { + ItemsFlow.ChildrenEnumerable = loaded; + }, (cancellationTokenSource = new CancellationTokenSource()).Token); + loadTask.ContinueWith(_ => + { + if (loadTask == latestLoadTask) + latestLoadTask = null; + }); + } + } +} From 8af9cfbe40c446f8ce1bfd0761f6e68ce9bead0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 24 Jul 2022 23:29:18 +0200 Subject: [PATCH 266/481] Add readonly modifier --- osu.Game/Overlays/Mods/ModSelectColumn.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSelectColumn.cs b/osu.Game/Overlays/Mods/ModSelectColumn.cs index d211f9eb5e..0224631577 100644 --- a/osu.Game/Overlays/Mods/ModSelectColumn.cs +++ b/osu.Game/Overlays/Mods/ModSelectColumn.cs @@ -33,7 +33,7 @@ namespace osu.Game.Overlays.Mods /// /// Determines whether this column should accept user input. /// - public Bindable Active = new BindableBool(true); + public readonly Bindable Active = new BindableBool(true); protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) => base.ReceivePositionalInputAtSubTree(screenSpacePos) && Active.Value; From 446485f804b4d7df948ea4e82ca23697d6ff2e80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 24 Jul 2022 23:30:52 +0200 Subject: [PATCH 267/481] Move localisation string to common location --- .../Localisation/ModPresetColumnStrings.cs | 19 ------------------- .../Localisation/ModSelectOverlayStrings.cs | 5 +++++ osu.Game/Overlays/Mods/ModPresetColumn.cs | 2 +- 3 files changed, 6 insertions(+), 20 deletions(-) delete mode 100644 osu.Game/Localisation/ModPresetColumnStrings.cs diff --git a/osu.Game/Localisation/ModPresetColumnStrings.cs b/osu.Game/Localisation/ModPresetColumnStrings.cs deleted file mode 100644 index b19a70a248..0000000000 --- a/osu.Game/Localisation/ModPresetColumnStrings.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Localisation; - -namespace osu.Game.Localisation -{ - public static class ModPresetColumnStrings - { - private const string prefix = @"osu.Game.Resources.Localisation.ModPresetColumn"; - - /// - /// "Personal Presets" - /// - public static LocalisableString PersonalPresets => new TranslatableString(getKey(@"personal_presets"), @"Personal Presets"); - - private static string getKey(string key) => $@"{prefix}:{key}"; - } -} \ No newline at end of file diff --git a/osu.Game/Localisation/ModSelectOverlayStrings.cs b/osu.Game/Localisation/ModSelectOverlayStrings.cs index e9af7147e3..3696b1f2cd 100644 --- a/osu.Game/Localisation/ModSelectOverlayStrings.cs +++ b/osu.Game/Localisation/ModSelectOverlayStrings.cs @@ -24,6 +24,11 @@ namespace osu.Game.Localisation /// public static LocalisableString ModCustomisation => new TranslatableString(getKey(@"mod_customisation"), @"Mod Customisation"); + /// + /// "Personal Presets" + /// + public static LocalisableString PersonalPresets => new TranslatableString(getKey(@"personal_presets"), @"Personal Presets"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } diff --git a/osu.Game/Overlays/Mods/ModPresetColumn.cs b/osu.Game/Overlays/Mods/ModPresetColumn.cs index b32015e6ea..1eea8383f8 100644 --- a/osu.Game/Overlays/Mods/ModPresetColumn.cs +++ b/osu.Game/Overlays/Mods/ModPresetColumn.cs @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.Mods private void load(OsuColour colours) { AccentColour = colours.Orange1; - HeaderText = ModPresetColumnStrings.PersonalPresets; + HeaderText = ModSelectOverlayStrings.PersonalPresets; } protected override void LoadComplete() From feef16b09b409b9446dd1b3dadea206b246691b7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 04:18:30 +0300 Subject: [PATCH 268/481] Add potentially failing test case --- .../Visual/Gameplay/TestSceneSpectatorHost.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs index c55e98c1a8..9ad8ac086c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorHost.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Spectator; @@ -43,6 +44,21 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("spectator client sent correct ruleset", () => spectatorClient.WatchedUserStates[dummy_user_id].RulesetID == Ruleset.Value.OnlineID); } + [Test] + public void TestRestart() + { + AddAssert("spectator client sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing); + + AddStep("exit player", () => Player.Exit()); + AddStep("reload player", LoadPlayer); + AddUntilStep("wait for player load", () => Player.IsLoaded && Player.Alpha == 1); + + AddAssert("spectator client sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing); + + AddWaitStep("wait", 5); + AddUntilStep("spectator client still sees playing state", () => spectatorClient.WatchedUserStates[dummy_user_id].State == SpectatedUserState.Playing); + } + public override void TearDownSteps() { base.TearDownSteps(); From f5a58876694efbb14e5c08be509abed2a6b62abe Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 04:21:52 +0300 Subject: [PATCH 269/481] Fix players potentially not displaying in spectator after restart --- osu.Game/Online/Spectator/SpectatorClient.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 68c8b57019..d12817d4d4 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -206,6 +206,11 @@ namespace osu.Game.Online.Spectator if (!IsPlaying) return; + // Disposal could be processed late, leading to EndPlaying potentially being called after a future BeginPlaying call. + // Account for this by ensuring the current score matches the score in the provided GameplayState. + if (currentScore != state.Score) + return; + if (pendingFrames.Count > 0) purgePendingFrames(); From e0266b0d81fabda2b946321e85fe09ca42c87613 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 04:34:42 +0300 Subject: [PATCH 270/481] Reword comment slightly --- osu.Game/Online/Spectator/SpectatorClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index d12817d4d4..b5e1c8a45f 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -206,8 +206,8 @@ namespace osu.Game.Online.Spectator if (!IsPlaying) return; - // Disposal could be processed late, leading to EndPlaying potentially being called after a future BeginPlaying call. - // Account for this by ensuring the current score matches the score in the provided GameplayState. + // Disposal can take some time, leading to EndPlaying potentially being called after a future play session. + // Account for this by ensuring the score of the current play matches the one in the provided state. if (currentScore != state.Score) return; From fa9daa68996e2ff9688458e36157689639dfdf0a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 05:21:27 +0300 Subject: [PATCH 271/481] Fix `TestSceneReplayRecorder` not using score provided by gameplay state --- .../Gameplay/TestSceneReplayRecorder.cs | 153 +++++++++--------- osu.Game/Screens/Play/GameplayState.cs | 1 + 2 files changed, 78 insertions(+), 76 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index 54b2e66f2f..b3401c916b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -3,10 +3,10 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -40,8 +40,7 @@ namespace osu.Game.Tests.Visual.Gameplay private TestReplayRecorder recorder; - [Cached] - private GameplayState gameplayState = TestGameplayState.Create(new OsuRuleset()); + private GameplayState gameplayState; [SetUpSteps] public void SetUpSteps() @@ -52,81 +51,15 @@ namespace osu.Game.Tests.Visual.Gameplay { replay = new Replay(); - Add(new GridContainer + gameplayState = TestGameplayState.Create(new OsuRuleset()); + gameplayState.Score.Replay = replay; + + Child = new DependencyProvidingContainer { RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) - { - Recorder = recorder = new TestReplayRecorder(new Score - { - Replay = replay, - ScoreInfo = - { - BeatmapInfo = gameplayState.Beatmap.BeatmapInfo, - Ruleset = new OsuRuleset().RulesetInfo, - } - }) - { - ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), - }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Brown, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Text = "Recording", - Scale = new Vector2(3), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new TestInputConsumer() - } - }, - } - }, - new Drawable[] - { - playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) - { - ReplayInputHandler = new TestFramedReplayInputHandler(replay) - { - GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), - }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Colour = Color4.DarkBlue, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Text = "Playback", - Scale = new Vector2(3), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new TestInputConsumer() - } - }, - } - } - } - }); + CachedDependencies = new (Type, object)[] { (typeof(GameplayState), gameplayState) }, + Child = createContent(), + }; }); } @@ -203,6 +136,74 @@ namespace osu.Game.Tests.Visual.Gameplay recorder = null; } + private Drawable createContent() => new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + Recorder = recorder = new TestReplayRecorder(gameplayState.Score) + { + ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Brown, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Recording", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestInputConsumer() + } + }, + } + }, + new Drawable[] + { + playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) + { + ReplayInputHandler = new TestFramedReplayInputHandler(replay) + { + GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), + }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.DarkBlue, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Text = "Playback", + Scale = new Vector2(3), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new TestInputConsumer() + } + }, + } + } + } + }; + public class TestFramedReplayInputHandler : FramedReplayInputHandler { public TestFramedReplayInputHandler(Replay replay) diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index 9fb62106f3..c2162d4df2 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -70,6 +70,7 @@ namespace osu.Game.Screens.Play { ScoreInfo = { + BeatmapInfo = beatmap.BeatmapInfo, Ruleset = ruleset.RulesetInfo } }; From f68c4e889017b5dc9e4fa01fdd253bf13cf4681f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 06:36:21 +0300 Subject: [PATCH 272/481] Fix code formatting --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 52ed843c4c..e22ba5c1db 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModTransform), typeof(OsuModMagnetised), typeof(OsuModRepel) }; - private const int wiggle_duration = 100; // (ms) Higher = fewer wiggles + private const int wiggle_duration = 100; // (ms) Higher = fewer wiggles [SettingSource("Strength", "Multiplier applied to the wiggling strength.")] public BindableDouble WiggleStrength { get; } = new BindableDouble(1) From 3d97b748131d2c581ce9e4bb1954e20c3a74cea7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 25 Jul 2022 13:03:47 +0900 Subject: [PATCH 273/481] Log beatmap difficulty retrieval failures during score calculation --- osu.Game/Scoring/ScoreManager.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 9aed8904e6..7cfc55580b 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -12,6 +12,7 @@ using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Beatmaps; @@ -172,6 +173,10 @@ namespace osu.Game.Scoring // We can compute the max combo locally after the async beatmap difficulty computation. var difficulty = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false); + + if (difficulty == null) + Logger.Log($"Couldn't get beatmap difficulty for beatmap {score.BeatmapInfo.OnlineID}"); + return difficulty?.MaxCombo; } From 0226b358eec2d3dd0fba037d178bb21011d1b8bf Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 25 Jul 2022 13:20:33 +0900 Subject: [PATCH 274/481] Disable timeline test for now --- osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs index 6c5cca1874..09d753ba41 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; namespace osu.Game.Tests.Visual.Editing { + [Ignore("Timeline initialisation is kinda broken.")] // Initial work to rectify this was done in https://github.com/ppy/osu/pull/19297, but needs more massaging to work. public class TestSceneTimelineZoom : TimelineTestScene { public override Drawable CreateTestComponent() => Empty(); From 54eb2b98a96b1d412cc790ba6a00516f01ca5dca Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 07:30:52 +0300 Subject: [PATCH 275/481] Display exclamation triangle on scores with unprocessed PP --- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 08f750827b..e9cb02406e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -23,6 +23,7 @@ using osuTK.Graphics; using osu.Framework.Localisation; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapSet.Scores @@ -177,7 +178,12 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } if (showPerformancePoints) - content.Add(new StatisticText(score.PP, format: @"N0")); + { + if (score.PP != null) + content.Add(new StatisticText(score.PP, format: @"N0")); + else + content.Add(new ProcessingPPIcon()); + } content.Add(new ScoreboardTime(score.Date, text_size) { @@ -241,5 +247,18 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Colour = colours.GreenLight; } } + + private class ProcessingPPIcon : SpriteIcon, IHasTooltip + { + public LocalisableString TooltipText => ScoresStrings.StatusProcessing; + + public ProcessingPPIcon() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Size = new Vector2(text_size); + Icon = FontAwesome.Solid.ExclamationTriangle; + } + } } } From 6c95c49da32924ac0321a46c733454a11c3746ee Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 07:31:52 +0300 Subject: [PATCH 276/481] Mark test score with null PP for visual testing --- osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 864b2b6878..19acc8d7c0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -210,7 +210,7 @@ namespace osu.Game.Tests.Visual.Online new APIMod { Acronym = new OsuModHidden().Acronym }, }, Rank = ScoreRank.B, - PP = 180, + PP = null, MaxCombo = 1234, TotalScore = 12345678, Accuracy = 0.9854, From 91d1c9686c0d970d131dc743a7d6922c55ff0495 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 09:07:51 +0300 Subject: [PATCH 277/481] Separate unprocessed PP placeholder to own class --- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 17 ++---------- ...UnprocessedPerformancePointsPlaceholder.cs | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 osu.Game/Scoring/Drawables/UnprocessedPerformancePointsPlaceholder.cs diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index e9cb02406e..5463c7a50f 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -23,8 +23,8 @@ using osuTK.Graphics; using osu.Framework.Localisation; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; +using osu.Game.Scoring.Drawables; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -182,7 +182,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (score.PP != null) content.Add(new StatisticText(score.PP, format: @"N0")); else - content.Add(new ProcessingPPIcon()); + content.Add(new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(text_size) }); } content.Add(new ScoreboardTime(score.Date, text_size) @@ -247,18 +247,5 @@ namespace osu.Game.Overlays.BeatmapSet.Scores Colour = colours.GreenLight; } } - - private class ProcessingPPIcon : SpriteIcon, IHasTooltip - { - public LocalisableString TooltipText => ScoresStrings.StatusProcessing; - - public ProcessingPPIcon() - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - Size = new Vector2(text_size); - Icon = FontAwesome.Solid.ExclamationTriangle; - } - } } } diff --git a/osu.Game/Scoring/Drawables/UnprocessedPerformancePointsPlaceholder.cs b/osu.Game/Scoring/Drawables/UnprocessedPerformancePointsPlaceholder.cs new file mode 100644 index 0000000000..6087ca9eb9 --- /dev/null +++ b/osu.Game/Scoring/Drawables/UnprocessedPerformancePointsPlaceholder.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable +using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Scoring.Drawables +{ + /// + /// A placeholder used in PP columns for scores with unprocessed PP value. + /// + public class UnprocessedPerformancePointsPlaceholder : SpriteIcon, IHasTooltip + { + public LocalisableString TooltipText => ScoresStrings.StatusProcessing; + + public UnprocessedPerformancePointsPlaceholder() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Icon = FontAwesome.Solid.ExclamationTriangle; + } + } +} From f54cee027065eb30ff0c00e1957e075f0be59e45 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 09:10:35 +0300 Subject: [PATCH 278/481] Display placeholder for leaderboard top scores --- .../Scores/TopScoreStatisticsSection.cs | 57 +++++++++++++------ 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 3b5ab811ae..653bfd6d2c 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -12,14 +12,17 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; using osu.Game.Scoring; +using osu.Game.Scoring.Drawables; using osuTK; namespace osu.Game.Overlays.BeatmapSet.Scores @@ -121,7 +124,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores maxComboColumn.Text = value.MaxCombo.ToLocalisableString(@"0\x"); ppColumn.Alpha = value.BeatmapInfo.Status.GrantsPerformancePoints() ? 1 : 0; - ppColumn.Text = value.PP?.ToLocalisableString(@"N0") ?? default; + + if (value.PP is double pp) + ppColumn.Text = pp.ToLocalisableString(@"N0"); + else + ppColumn.Drawable = new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(smallFont.Size) }; statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn); modsColumn.Mods = value.Mods; @@ -197,30 +204,48 @@ namespace osu.Game.Overlays.BeatmapSet.Scores } } - private class TextColumn : InfoColumn + private class TextColumn : InfoColumn, IHasCurrentValue { - private readonly SpriteText text; - - public TextColumn(LocalisableString title, FontUsage font, float? minWidth = null) - : this(title, new OsuSpriteText { Font = font }, minWidth) - { - } - - private TextColumn(LocalisableString title, SpriteText text, float? minWidth = null) - : base(title, text, minWidth) - { - this.text = text; - } + private readonly OsuTextFlowContainer text; public LocalisableString Text { set => text.Text = value; } + public Drawable Drawable + { + set + { + text.Clear(); + text.AddArbitraryDrawable(value); + } + } + + private Bindable current; + public Bindable Current { - get => text.Current; - set => text.Current = value; + get => current; + set + { + text.Clear(); + text.AddText(value.Value, t => t.Current = current = value); + } + } + + public TextColumn(LocalisableString title, FontUsage font, float? minWidth = null) + : this(title, new OsuTextFlowContainer(t => t.Font = font) + { + AutoSizeAxes = Axes.Both + }, minWidth) + { + } + + private TextColumn(LocalisableString title, OsuTextFlowContainer text, float? minWidth = null) + : base(title, text, minWidth) + { + this.text = text; } } From bbbc0a863ff9b5f33b6720423569f58b0a6fc85d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 15:21:24 +0900 Subject: [PATCH 279/481] Add test coverage of `WorkingBeatmap` retrieval from `BeatmapManager` --- .../Beatmaps/WorkingBeatmapManagerTest.cs | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs diff --git a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs new file mode 100644 index 0000000000..0348e47d4a --- /dev/null +++ b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs @@ -0,0 +1,100 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Extensions; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Rulesets; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Beatmaps +{ + [HeadlessTest] + public class WorkingBeatmapManagerTest : OsuTestScene + { + private BeatmapManager beatmaps = null!; + + private BeatmapSetInfo importedSet = null!; + + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio, RulesetStore rulesets) + { + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("import beatmap", () => + { + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); + importedSet = beatmaps.GetAllUsableBeatmapSets().First(); + }); + } + + [Test] + public void TestGetWorkingBeatmap() => AddStep("run test", () => + { + Assert.That(beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()), Is.Not.Null); + }); + + [Test] + public void TestCachedRetrievalNoFiles() => AddStep("run test", () => + { + var beatmap = importedSet.Beatmaps.First(); + + Assert.That(beatmap.BeatmapSet?.Files, Is.Empty); + + var first = beatmaps.GetWorkingBeatmap(beatmap); + var second = beatmaps.GetWorkingBeatmap(beatmap); + + Assert.That(first, Is.SameAs(second)); + Assert.That(first.BeatmapInfo.BeatmapSet?.Files, Has.Count.GreaterThan(0)); + }); + + [Test] + public void TestCachedRetrievalWithFiles() => AddStep("run test", () => + { + var beatmap = Realm.Run(r => r.Find(importedSet.Beatmaps.First().ID).Detach()); + + Assert.That(beatmap.BeatmapSet?.Files, Has.Count.GreaterThan(0)); + + var first = beatmaps.GetWorkingBeatmap(beatmap); + var second = beatmaps.GetWorkingBeatmap(beatmap); + + Assert.That(first, Is.SameAs(second)); + Assert.That(first.BeatmapInfo.BeatmapSet?.Files, Has.Count.GreaterThan(0)); + }); + + [Test] + public void TestForcedRefetchRetrievalNoFiles() => AddStep("run test", () => + { + var beatmap = importedSet.Beatmaps.First(); + + Assert.That(beatmap.BeatmapSet?.Files, Is.Empty); + + var first = beatmaps.GetWorkingBeatmap(beatmap); + var second = beatmaps.GetWorkingBeatmap(beatmap, true); + Assert.That(first, Is.Not.SameAs(second)); + }); + + [Test] + public void TestForcedRefetchRetrievalWithFiles() => AddStep("run test", () => + { + var beatmap = Realm.Run(r => r.Find(importedSet.Beatmaps.First().ID).Detach()); + + Assert.That(beatmap.BeatmapSet?.Files, Has.Count.GreaterThan(0)); + + var first = beatmaps.GetWorkingBeatmap(beatmap); + var second = beatmaps.GetWorkingBeatmap(beatmap, true); + Assert.That(first, Is.Not.SameAs(second)); + }); + } +} From 2ec90e37bb9e005dea4f99691758793a549ecd94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 14:59:11 +0900 Subject: [PATCH 280/481] Fix calls to `GetWorkingBeatmap` invalidating cache too often With recent changes, the pathway between refetching (on request) and refetching (on requirement due to unpopulated files) was combined. Unfortunately this pathway also added a forced invalidation, which should not have been applied to the second case. Closes https://github.com/ppy/osu/issues/19365. --- osu.Game/Beatmaps/BeatmapManager.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 30456afd2f..7717c9cc87 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -439,12 +439,15 @@ namespace osu.Game.Beatmaps { if (beatmapInfo != null) { - // Detached sets don't come with files. - // If we seem to be missing files, now is a good time to re-fetch. - if (refetch || beatmapInfo.IsManaged || beatmapInfo.BeatmapSet?.Files.Count == 0) - { + if (refetch) workingBeatmapCache.Invalidate(beatmapInfo); + // Detached beatmapsets don't come with files as an optimisation (see `RealmObjectExtensions.beatmap_set_mapper`). + // If we seem to be missing files, now is a good time to re-fetch. + bool missingFiles = beatmapInfo.BeatmapSet?.Files.Count == 0; + + if (refetch || beatmapInfo.IsManaged || missingFiles) + { Guid id = beatmapInfo.ID; beatmapInfo = Realm.Run(r => r.Find(id)?.Detach()) ?? beatmapInfo; } From e402e919ab4b2049e56338bdae7bff5a41348d82 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 09:28:54 +0300 Subject: [PATCH 281/481] Display placeholder for user profile scores --- .../Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs | 4 ++++ .../Profile/Sections/Ranks/DrawableProfileWeightedScore.cs | 5 ++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 5d8f8c8326..b8446c153d 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -19,6 +19,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; using osu.Game.Rulesets; using osu.Game.Rulesets.UI; +using osu.Game.Scoring.Drawables; using osu.Game.Utils; using osuTK; @@ -246,6 +247,9 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks }; } + if (Score.Beatmap?.Status.GrantsPerformancePoints() == true) + return new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(16), Colour = colourProvider.Highlight1 }; + return new OsuSpriteText { Font = OsuFont.GetFont(weight: FontWeight.Bold), diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs index 94d95dc27e..8c46f10ba2 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs @@ -42,12 +42,11 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks CreateDrawableAccuracy(), new Container { - AutoSizeAxes = Axes.Y, - Width = 50, + Size = new Vector2(50, 14), Child = new OsuSpriteText { Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), - Text = $"{Score.PP * weight:0}pp", + Text = Score.PP.HasValue ? $"{Score.PP * weight:0}pp" : string.Empty, }, } } From 6bdd1f43a294082ac7a586a05447c095b91b72ed Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 09:28:59 +0300 Subject: [PATCH 282/481] Add visual test coverage --- .../Visual/Online/TestSceneScoresContainer.cs | 15 ++++++++++++++- .../Online/TestSceneUserProfileScores.cs | 19 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 19acc8d7c0..cfa9f77634 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -141,6 +141,19 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("best score not displayed", () => scoresContainer.ChildrenOfType().Count() == 1); } + [Test] + public void TestUnprocessedPP() + { + AddStep("Load scores with unprocessed PP", () => + { + var allScores = createScores(); + allScores.Scores[0].PP = null; + allScores.UserScore = createUserBest(); + allScores.UserScore.Score.PP = null; + scoresContainer.Scores = allScores; + }); + } + private int onlineID = 1; private APIScoresCollection createScores() @@ -210,7 +223,7 @@ namespace osu.Game.Tests.Visual.Online new APIMod { Acronym = new OsuModHidden().Acronym }, }, Rank = ScoreRank.B, - PP = null, + PP = 180, MaxCombo = 1234, TotalScore = 12345678, Accuracy = 0.9854, diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs index 0eb6ec3c04..4bbb72c862 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs @@ -7,6 +7,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; @@ -99,6 +100,23 @@ namespace osu.Game.Tests.Visual.Online Accuracy = 0.55879 }; + var unprocessedPPScore = new SoloScoreInfo + { + Rank = ScoreRank.B, + Beatmap = new APIBeatmap + { + BeatmapSet = new APIBeatmapSet + { + Title = "C18H27NO3(extend)", + Artist = "Team Grimoire", + }, + DifficultyName = "[4K] Cataclysmic Hypernova", + Status = BeatmapOnlineStatus.Ranked, + }, + EndedAt = DateTimeOffset.Now, + Accuracy = 0.55879 + }; + Add(new FillFlowContainer { Anchor = Anchor.Centre, @@ -112,6 +130,7 @@ namespace osu.Game.Tests.Visual.Online new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(firstScore)), new ColourProvidedContainer(OverlayColourScheme.Green, new DrawableProfileScore(secondScore)), new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(noPPScore)), + new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileScore(unprocessedPPScore)), new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(firstScore, 0.97)), new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(secondScore, 0.85)), new ColourProvidedContainer(OverlayColourScheme.Pink, new DrawableProfileWeightedScore(thirdScore, 0.66)), From 6bf2645b1ae4639bda41bcc825741d1c5a3ae34a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 15:44:54 +0900 Subject: [PATCH 283/481] Fix `StarRatingDisplay` not handling negative numbers as "pending" --- .../Visual/UserInterface/TestSceneStarRatingDisplay.cs | 2 +- osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneStarRatingDisplay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneStarRatingDisplay.cs index 1f65b6ec7f..72929a4555 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneStarRatingDisplay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneStarRatingDisplay.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.UserInterface AutoSizeAxes = Axes.Both, Spacing = new Vector2(2f), Direction = FillDirection.Horizontal, - ChildrenEnumerable = Enumerable.Range(0, 15).Select(i => new FillFlowContainer + ChildrenEnumerable = Enumerable.Range(-1, 15).Select(i => new FillFlowContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs b/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs index 44bccd69d0..9585f1bdb5 100644 --- a/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs +++ b/osu.Game/Beatmaps/Drawables/StarRatingDisplay.cs @@ -151,7 +151,7 @@ namespace osu.Game.Beatmaps.Drawables displayedStars.BindValueChanged(s => { - starsText.Text = s.NewValue.ToLocalisableString("0.00"); + starsText.Text = s.NewValue < 0 ? "-" : s.NewValue.ToLocalisableString("0.00"); background.Colour = colours.ForStarDifficulty(s.NewValue); From 4d90e6bbac73e242fb61eeae70b4c6818f4f14c7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 10:03:06 +0300 Subject: [PATCH 284/481] Flip method to read better --- .../Sections/Ranks/DrawableProfileScore.cs | 60 +++++++++---------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index b8446c153d..fda2db7acc 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -219,42 +219,42 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private Drawable createDrawablePerformance() { - if (Score.PP.HasValue) + if (!Score.PP.HasValue) { - return new FillFlowContainer + if (Score.Beatmap?.Status.GrantsPerformancePoints() == true) + return new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(16), Colour = colourProvider.Highlight1 }; + + return new OsuSpriteText { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Children = new[] - { - new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(weight: FontWeight.Bold), - Text = $"{Score.PP:0}", - Colour = colourProvider.Highlight1 - }, - new OsuSpriteText - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Text = "pp", - Colour = colourProvider.Light3 - } - } + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Text = "-", + Colour = colourProvider.Highlight1 }; } - if (Score.Beatmap?.Status.GrantsPerformancePoints() == true) - return new UnprocessedPerformancePointsPlaceholder { Size = new Vector2(16), Colour = colourProvider.Highlight1 }; - - return new OsuSpriteText + return new FillFlowContainer { - Font = OsuFont.GetFont(weight: FontWeight.Bold), - Text = "-", - Colour = colourProvider.Highlight1 + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new[] + { + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.GetFont(weight: FontWeight.Bold), + Text = $"{Score.PP:0}", + Colour = colourProvider.Highlight1 + }, + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Text = "pp", + Colour = colourProvider.Light3 + } + } }; } From 2dd99ef1fdc835fd14297371a159d94af8f7c9d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 16:26:47 +0900 Subject: [PATCH 285/481] Refactor `FPSCounter` to not use scheduled tasks While on the surface this looks harmless (ignoring allocations), `Scheduler` doesn't clear cancelled tasks until they reach their execution time. This can cause an increase in time spent processing the scheduler itself. I don't think a per-frame updating component should use scheduled tasks in this way in the first place, so I've just rewritten the logic to avoid that overhead altogether. --- osu.Game/Graphics/UserInterface/FPSCounter.cs | 55 +++++++++---------- 1 file changed, 26 insertions(+), 29 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/FPSCounter.cs b/osu.Game/Graphics/UserInterface/FPSCounter.cs index 6fa53b32d8..539ac7ed1f 100644 --- a/osu.Game/Graphics/UserInterface/FPSCounter.cs +++ b/osu.Game/Graphics/UserInterface/FPSCounter.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Platform; -using osu.Framework.Threading; using osu.Framework.Timing; using osu.Framework.Utils; using osu.Game.Configuration; @@ -44,8 +43,6 @@ namespace osu.Game.Graphics.UserInterface private bool isDisplayed; - private ScheduledDelegate? fadeOutDelegate; - private double aimDrawFPS; private double aimUpdateFPS; @@ -54,6 +51,11 @@ namespace osu.Game.Graphics.UserInterface private ThrottledFrameClock updateClock = null!; private ThrottledFrameClock inputClock = null!; + /// + /// The last time value where the display was required (due to a significant change or hovering). + /// + private double lastDisplayRequiredTime; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -131,13 +133,13 @@ namespace osu.Game.Graphics.UserInterface { base.LoadComplete(); - displayTemporarily(); + requestDisplay(); showFpsDisplay.BindValueChanged(showFps => { State.Value = showFps.NewValue ? Visibility.Visible : Visibility.Hidden; if (showFps.NewValue) - displayTemporarily(); + requestDisplay(); }, true); State.BindValueChanged(state => showFpsDisplay.Value = state.NewValue == Visibility.Visible); @@ -150,38 +152,17 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { background.FadeTo(1, 200); - displayTemporarily(); + requestDisplay(); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { background.FadeTo(idle_background_alpha, 200); - displayTemporarily(); + requestDisplay(); base.OnHoverLost(e); } - private void displayTemporarily() - { - if (!isDisplayed) - { - mainContent.FadeTo(1, 300, Easing.OutQuint); - isDisplayed = true; - } - - fadeOutDelegate?.Cancel(); - fadeOutDelegate = null; - - if (!IsHovered) - { - fadeOutDelegate = Scheduler.AddDelayed(() => - { - mainContent.FadeTo(0, 300, Easing.OutQuint); - isDisplayed = false; - }, 2000); - } - } - protected override void Update() { base.Update(); @@ -221,7 +202,23 @@ namespace osu.Game.Graphics.UserInterface || 1000 / displayedFrameTime < aimUpdateFPS * 0.8; if (hasSignificantChanges) - displayTemporarily(); + requestDisplay(); + else if (isDisplayed && Time.Current - lastDisplayRequiredTime > 2000) + { + mainContent.FadeTo(0, 300, Easing.OutQuint); + isDisplayed = false; + } + } + + private void requestDisplay() + { + lastDisplayRequiredTime = Time.Current; + + if (!isDisplayed) + { + mainContent.FadeTo(1, 300, Easing.OutQuint); + isDisplayed = true; + } } private void updateFpsDisplay() From fcf767e28bc4d68d97a0137358016261a7255cee Mon Sep 17 00:00:00 2001 From: Andrew Hong Date: Mon, 25 Jul 2022 04:07:33 -0400 Subject: [PATCH 286/481] Add contextmenu to beatmap external link --- .../UserInterface/ExternalLinkButton.cs | 17 +- .../BeatmapSet/BeatmapSetHeaderContent.cs | 184 +++++++++--------- 2 files changed, 110 insertions(+), 91 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index ae286f5092..4c54e45a50 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -3,10 +3,12 @@ #nullable disable +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; @@ -16,7 +18,7 @@ using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface { - public class ExternalLinkButton : CompositeDrawable, IHasTooltip + public class ExternalLinkButton : CompositeDrawable, IHasTooltip, IHasContextMenu { public string Link { get; set; } @@ -41,6 +43,19 @@ namespace osu.Game.Graphics.UserInterface new HoverClickSounds() }; } + + public MenuItem[] ContextMenuItems + { + get + { + List items = new List + { + new OsuMenuItem("Copy URL", MenuItemType.Standard, () => host.GetClipboard()?.SetText(Link)) + }; + + return items.ToArray(); + } + } [BackgroundDependencyLoader] private void load(OsuColour colours) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index c9e97d5f2f..85b98a92e2 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; +using osu.Game.Graphics.Cursor; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; @@ -90,113 +91,116 @@ namespace osu.Game.Overlays.BeatmapSet }, }, }, - new Container + new OsuContextMenuContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding + RelativeSizeAxes = Axes.Both, + Child = new Container { - Vertical = BeatmapSetOverlay.Y_PADDING, - Left = BeatmapSetOverlay.X_PADDING, - Right = BeatmapSetOverlay.X_PADDING + BeatmapSetOverlay.RIGHT_WIDTH, - }, - Children = new Drawable[] - { - fadeContent = new FillFlowContainer + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] + Vertical = BeatmapSetOverlay.Y_PADDING, + Left = BeatmapSetOverlay.X_PADDING, + Right = BeatmapSetOverlay.X_PADDING + BeatmapSetOverlay.RIGHT_WIDTH, + }, + Children = new Drawable[] + { + fadeContent = new FillFlowContainer { - new Container + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Child = Picker = new BeatmapPicker(), - }, - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Top = 15 }, - Children = new Drawable[] + new Container { - title = new OsuSpriteText + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = Picker = new BeatmapPicker(), + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Top = 15 }, + Children = new Drawable[] { - Font = OsuFont.GetFont(size: 30, weight: FontWeight.SemiBold, italics: true) - }, - externalLink = new ExternalLinkButton - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Left = 5, Bottom = 4 }, // To better lineup with the font - }, - explicitContent = new ExplicitContentBeatmapBadge - { - Alpha = 0f, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Left = 10, Bottom = 4 }, - }, - spotlight = new SpotlightBeatmapBadge - { - Alpha = 0f, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Left = 10, Bottom = 4 }, + title = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 30, weight: FontWeight.SemiBold, italics: true) + }, + externalLink = new ExternalLinkButton { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Left = 5, Bottom = 4 }, // To better lineup with the font + }, + explicitContent = new ExplicitContentBeatmapBadge + { + Alpha = 0f, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Left = 10, Bottom = 4 }, + }, + spotlight = new SpotlightBeatmapBadge + { + Alpha = 0f, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Left = 10, Bottom = 4 }, + } } - } - }, - new FillFlowContainer - { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Bottom = 20 }, - Children = new Drawable[] + }, + new FillFlowContainer { - artist = new OsuSpriteText + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Bottom = 20 }, + Children = new Drawable[] { - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true), - }, - featuredArtist = new FeaturedArtistBeatmapBadge - { - Alpha = 0f, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Left = 10 } + artist = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true), + }, + featuredArtist = new FeaturedArtistBeatmapBadge + { + Alpha = 0f, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Margin = new MarginPadding { Left = 10 } + } } - } - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Child = author = new AuthorInfo(), - }, - beatmapAvailability = new BeatmapAvailability(), - new Container - { - RelativeSizeAxes = Axes.X, - Height = buttons_height, - Margin = new MarginPadding { Top = 10 }, - Children = new Drawable[] + }, + new Container { - favouriteButton = new FavouriteButton + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = author = new AuthorInfo(), + }, + beatmapAvailability = new BeatmapAvailability(), + new Container + { + RelativeSizeAxes = Axes.X, + Height = buttons_height, + Margin = new MarginPadding { Top = 10 }, + Children = new Drawable[] { - BeatmapSet = { BindTarget = BeatmapSet } - }, - downloadButtonsContainer = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = buttons_height + buttons_spacing }, - Spacing = new Vector2(buttons_spacing), + favouriteButton = new FavouriteButton + { + BeatmapSet = { BindTarget = BeatmapSet } + }, + downloadButtonsContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = buttons_height + buttons_spacing }, + Spacing = new Vector2(buttons_spacing), + }, }, }, }, }, - }, - } + } + }, }, loading = new LoadingSpinner { From f1534da683623e65f74019d62983d28bd2391a32 Mon Sep 17 00:00:00 2001 From: Andrew Hong Date: Mon, 25 Jul 2022 04:13:05 -0400 Subject: [PATCH 287/481] Formatting issues --- osu.Game/Graphics/UserInterface/ExternalLinkButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 4c54e45a50..e813e2d8e8 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -43,7 +43,7 @@ namespace osu.Game.Graphics.UserInterface new HoverClickSounds() }; } - + public MenuItem[] ContextMenuItems { get From 93175eaf6efda8c782557c8624b4cc23f2075c66 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 11:39:23 +0300 Subject: [PATCH 288/481] Re-enable timeline zoom test and remove flaky attribute --- .../Visual/Editing/TestSceneTimelineZoom.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs index 09d753ba41..11ac102814 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs @@ -8,22 +8,11 @@ using osu.Framework.Graphics; namespace osu.Game.Tests.Visual.Editing { - [Ignore("Timeline initialisation is kinda broken.")] // Initial work to rectify this was done in https://github.com/ppy/osu/pull/19297, but needs more massaging to work. public class TestSceneTimelineZoom : TimelineTestScene { public override Drawable CreateTestComponent() => Empty(); [Test] - [FlakyTest] - /* - * Fail rate around 0.3% - * - * TearDown : osu.Framework.Testing.Drawables.Steps.AssertButton+TracedException : range halved - * --TearDown - * at osu.Framework.Threading.ScheduledDelegate.RunTaskInternal() - * at osu.Framework.Threading.Scheduler.Update() - * at osu.Framework.Graphics.Drawable.UpdateSubTree() - */ public void TestVisibleRangeUpdatesOnZoomChange() { double initialVisibleRange = 0; From 123930306bc0146f52273f66e26f5c2c5e48f6e2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 11:54:10 +0300 Subject: [PATCH 289/481] Refactor `ZoomableScrollContainer` to allow setting up zoom range and initial zoom after load --- .../TestSceneZoomableScrollContainer.cs | 17 +--- .../Timeline/ZoomableScrollContainer.cs | 99 ++++++++++--------- 2 files changed, 56 insertions(+), 60 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs index 9dc403814b..ce418f33f0 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Editing RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(30) }, - scrollContainer = new ZoomableScrollContainer + scrollContainer = new ZoomableScrollContainer(1, 60, 1) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -80,21 +80,6 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth); } - [Test] - public void TestZoomRangeUpdate() - { - AddStep("set zoom to 2", () => scrollContainer.Zoom = 2); - AddStep("set min zoom to 5", () => scrollContainer.MinZoom = 5); - AddAssert("zoom = 5", () => scrollContainer.Zoom == 5); - - AddStep("set max zoom to 10", () => scrollContainer.MaxZoom = 10); - AddAssert("zoom = 5", () => scrollContainer.Zoom == 5); - - AddStep("set min zoom to 20", () => scrollContainer.MinZoom = 20); - AddStep("set max zoom to 40", () => scrollContainer.MaxZoom = 40); - AddAssert("zoom = 20", () => scrollContainer.Zoom == 20); - } - [Test] public void TestZoom0() { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 83fd1dea2b..fb2297e88c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -32,19 +32,27 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Container zoomedContent; protected override Container Content => zoomedContent; - private float currentZoom = 1; /// - /// The current zoom level of . - /// It may differ from during transitions. + /// The current zoom level of . + /// It may differ from during transitions. /// - public float CurrentZoom => currentZoom; + public float CurrentZoom { get; private set; } = 1; + + private bool isZoomSetUp; [Resolved(canBeNull: true)] private IFrameBasedClock editorClock { get; set; } private readonly LayoutValue zoomedContentWidthCache = new LayoutValue(Invalidation.DrawSize); + private float minZoom; + private float maxZoom; + + /// + /// Creates a with no zoom range. + /// Functionality will be disabled until zoom is set up via . + /// public ZoomableScrollContainer() : base(Direction.Horizontal) { @@ -53,46 +61,36 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline AddLayout(zoomedContentWidthCache); } - private float minZoom = 1; - /// - /// The minimum zoom level allowed. + /// Creates a with a defined zoom range. /// - public float MinZoom + public ZoomableScrollContainer(float minimum, float maximum, float initial) + : this() { - get => minZoom; - set - { - if (value < 1) - throw new ArgumentException($"{nameof(MinZoom)} must be >= 1.", nameof(value)); - - minZoom = value; - - // ensure zoom range is in valid state before updating zoom. - if (MinZoom < MaxZoom) - updateZoom(); - } + SetupZoom(initial, minimum, maximum); } - private float maxZoom = 60; - /// - /// The maximum zoom level allowed. + /// Sets up the minimum and maximum range of this zoomable scroll container, along with the initial zoom value. /// - public float MaxZoom + /// The initial zoom value, applied immediately. + /// The minimum zoom value. + /// The maximum zoom value. + public void SetupZoom(float initial, float minimum, float maximum) { - get => maxZoom; - set - { - if (value < 1) - throw new ArgumentException($"{nameof(MaxZoom)} must be >= 1.", nameof(value)); + if (minimum < 1) + throw new ArgumentException($"{nameof(minimum)} ({minimum}) must be >= 1.", nameof(maximum)); - maxZoom = value; + if (maximum < 1) + throw new ArgumentException($"{nameof(maximum)} ({maximum}) must be >= 1.", nameof(maximum)); - // ensure zoom range is in valid state before updating zoom. - if (MaxZoom > MinZoom) - updateZoom(); - } + if (minimum > maximum) + throw new ArgumentException($"{nameof(minimum)} ({minimum}) must be less than {nameof(maximum)} ({maximum})"); + + minZoom = minimum; + maxZoom = maximum; + CurrentZoom = zoomTarget = initial; + isZoomSetUp = true; } /// @@ -104,14 +102,17 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline set => updateZoom(value); } - private void updateZoom(float? value = null) + private void updateZoom(float value) { - float newZoom = Math.Clamp(value ?? Zoom, MinZoom, MaxZoom); + if (!isZoomSetUp) + return; + + float newZoom = Math.Clamp(value, minZoom, maxZoom); if (IsLoaded) setZoomTarget(newZoom, ToSpaceOfOtherDrawable(new Vector2(DrawWidth / 2, 0), zoomedContent).X); else - currentZoom = zoomTarget = newZoom; + CurrentZoom = zoomTarget = newZoom; } protected override void Update() @@ -141,22 +142,32 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateZoomedContentWidth() { - zoomedContent.Width = DrawWidth * currentZoom; + zoomedContent.Width = DrawWidth * CurrentZoom; zoomedContentWidthCache.Validate(); } public void AdjustZoomRelatively(float change, float? focusPoint = null) { + if (!isZoomSetUp) + return; + const float zoom_change_sensitivity = 0.02f; - setZoomTarget(zoomTarget + change * (MaxZoom - minZoom) * zoom_change_sensitivity, focusPoint); + setZoomTarget(zoomTarget + change * (maxZoom - minZoom) * zoom_change_sensitivity, focusPoint); + } + + protected void SetZoomImmediately(float value, float min, float max) + { + maxZoom = max; + minZoom = min; + CurrentZoom = zoomTarget = value; } private float zoomTarget = 1; private void setZoomTarget(float newZoom, float? focusPoint = null) { - zoomTarget = Math.Clamp(newZoom, MinZoom, MaxZoom); + zoomTarget = Math.Clamp(newZoom, minZoom, maxZoom); focusPoint ??= zoomedContent.ToLocalSpace(ToScreenSpace(new Vector2(DrawWidth / 2, 0))).X; transformZoomTo(zoomTarget, focusPoint.Value, ZoomDuration, ZoomEasing); @@ -192,7 +203,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly float scrollOffset; /// - /// Transforms to a new value. + /// Transforms to a new value. /// /// The focus point in absolute coordinates local to the content. /// The size of the content. @@ -204,7 +215,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline this.scrollOffset = scrollOffset; } - public override string TargetMember => nameof(currentZoom); + public override string TargetMember => nameof(CurrentZoom); private float valueAt(double time) { @@ -222,7 +233,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline float expectedWidth = d.DrawWidth * newZoom; float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset; - d.currentZoom = newZoom; + d.CurrentZoom = newZoom; d.updateZoomedContentWidth(); // Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area. @@ -231,7 +242,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline d.ScrollTo(targetOffset, false); } - protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.currentZoom; + protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.CurrentZoom; } } } From 07c6b4486491848b358f1a2e8c8de56523cc62e3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 11:54:47 +0300 Subject: [PATCH 290/481] Fix `Timeline` attempting to setup zoom with unloaded track --- .../Compose/Components/Timeline/Timeline.cs | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index bbe011a2e0..54f2d13707 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -146,13 +146,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveform.Waveform = b.NewValue.Waveform; track = b.NewValue.Track; - // todo: i don't think this is safe, the track may not be loaded yet. - if (track.Length > 0) - { - MaxZoom = getZoomLevelForVisibleMilliseconds(500); - MinZoom = getZoomLevelForVisibleMilliseconds(10000); - defaultTimelineZoom = getZoomLevelForVisibleMilliseconds(6000); - } + setupTimelineZoom(); }, true); Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); @@ -205,6 +199,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline scrollToTrackTime(); } + private void setupTimelineZoom() + { + if (!track.IsLoaded) + { + Scheduler.AddOnce(setupTimelineZoom); + return; + } + + defaultTimelineZoom = getZoomLevelForVisibleMilliseconds(6000); + + float initialZoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); + SetupZoom(initialZoom, getZoomLevelForVisibleMilliseconds(10000), getZoomLevelForVisibleMilliseconds(500)); + } + protected override bool OnScroll(ScrollEvent e) { // if this is not a precision scroll event, let the editor handle the seek itself (for snapping support) From bc2b629ee7c89ec71dfbac7d11bd36adfdc4bb8a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 11:43:34 +0300 Subject: [PATCH 291/481] Let tests wait until track load before testing zoom --- osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs index 11ac102814..630d048867 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs @@ -17,6 +17,8 @@ namespace osu.Game.Tests.Visual.Editing { double initialVisibleRange = 0; + AddUntilStep("wait for load", () => MusicController.TrackLoaded); + AddStep("reset zoom", () => TimelineArea.Timeline.Zoom = 100); AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange); @@ -34,6 +36,8 @@ namespace osu.Game.Tests.Visual.Editing { double initialVisibleRange = 0; + AddUntilStep("wait for load", () => MusicController.TrackLoaded); + AddStep("reset timeline size", () => TimelineArea.Timeline.Width = 1); AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange); From 48bcf57066f1b5f67e0849a3cac9a9c8e4feb8bc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 12:06:54 +0300 Subject: [PATCH 292/481] Mark `SetupZoom` and parameterless `ZoomableScrollContainer` ctor as protected --- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index fb2297e88c..725f94e7db 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// Creates a with no zoom range. /// Functionality will be disabled until zoom is set up via . /// - public ZoomableScrollContainer() + protected ZoomableScrollContainer() : base(Direction.Horizontal) { base.Content.Add(zoomedContent = new Container { RelativeSizeAxes = Axes.Y }); @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// The initial zoom value, applied immediately. /// The minimum zoom value. /// The maximum zoom value. - public void SetupZoom(float initial, float minimum, float maximum) + protected void SetupZoom(float initial, float minimum, float maximum) { if (minimum < 1) throw new ArgumentException($"{nameof(minimum)} ({minimum}) must be >= 1.", nameof(maximum)); From d04df19c7e2ba81107cac71df5435d5cd016f504 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 13:03:06 +0300 Subject: [PATCH 293/481] Remove `APIScore` and replace its final usage --- .../Gameplay/TestSceneReplayDownloadButton.cs | 28 ++- .../Online/API/Requests/Responses/APIScore.cs | 162 ------------------ osu.Game/Online/Solo/SubmittableScore.cs | 2 - 3 files changed, 10 insertions(+), 182 deletions(-) delete mode 100644 osu.Game/Online/API/Requests/Responses/APIScore.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index c259d5f0a8..1384d4ef6e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -7,7 +7,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online; -using osu.Game.Online.API.Requests.Responses; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -15,7 +14,6 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; @@ -30,9 +28,6 @@ namespace osu.Game.Tests.Visual.Gameplay { private const long online_score_id = 2553163309; - [Resolved] - private RulesetStore rulesets { get; set; } - private TestReplayDownloadButton downloadButton; [Resolved] @@ -211,21 +206,18 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value); } - private ScoreInfo getScoreInfo(bool replayAvailable, bool hasOnlineId = true) + private ScoreInfo getScoreInfo(bool replayAvailable, bool hasOnlineId = true) => new ScoreInfo { - return new APIScore + OnlineID = hasOnlineId ? online_score_id : 0, + Ruleset = new OsuRuleset().RulesetInfo, + BeatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(), + Hash = replayAvailable ? "hash" : string.Empty, + User = new APIUser { - OnlineID = hasOnlineId ? online_score_id : 0, - RulesetID = 0, - Beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo).Beatmaps.First(), - HasReplay = replayAvailable, - User = new APIUser - { - Id = 39828, - Username = @"WubWoofWolf", - } - }.CreateScoreInfo(rulesets, beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First()); - } + Id = 39828, + Username = @"WubWoofWolf", + } + }; private class TestReplayDownloadButton : ReplayDownloadButton { diff --git a/osu.Game/Online/API/Requests/Responses/APIScore.cs b/osu.Game/Online/API/Requests/Responses/APIScore.cs deleted file mode 100644 index f236607761..0000000000 --- a/osu.Game/Online/API/Requests/Responses/APIScore.cs +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using osu.Game.Beatmaps; -using osu.Game.Database; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; -using osu.Game.Scoring; -using osu.Game.Scoring.Legacy; -using osu.Game.Users; - -namespace osu.Game.Online.API.Requests.Responses -{ - public class APIScore : IScoreInfo - { - [JsonProperty(@"score")] - public long TotalScore { get; set; } - - [JsonProperty(@"max_combo")] - public int MaxCombo { get; set; } - - [JsonProperty(@"user")] - public APIUser User { get; set; } - - [JsonProperty(@"id")] - public long OnlineID { get; set; } - - [JsonProperty(@"replay")] - public bool HasReplay { get; set; } - - [JsonProperty(@"created_at")] - public DateTimeOffset Date { get; set; } - - [JsonProperty(@"beatmap")] - [CanBeNull] - public APIBeatmap Beatmap { get; set; } - - [JsonProperty("accuracy")] - public double Accuracy { get; set; } - - [JsonProperty(@"pp")] - public double? PP { get; set; } - - [JsonProperty(@"beatmapset")] - [CanBeNull] - public APIBeatmapSet BeatmapSet - { - set - { - // in the deserialisation case we need to ferry this data across. - // the order of properties returned by the API guarantees that the beatmap is populated by this point. - if (!(Beatmap is APIBeatmap apiBeatmap)) - throw new InvalidOperationException("Beatmap set metadata arrived before beatmap metadata in response"); - - apiBeatmap.BeatmapSet = value; - } - } - - [JsonProperty("statistics")] - public Dictionary Statistics { get; set; } - - [JsonProperty(@"mode_int")] - public int RulesetID { get; set; } - - [JsonProperty(@"mods")] - private string[] mods { set => Mods = value.Select(acronym => new APIMod { Acronym = acronym }); } - - [NotNull] - public IEnumerable Mods { get; set; } = Array.Empty(); - - [JsonProperty("rank")] - [JsonConverter(typeof(StringEnumConverter))] - public ScoreRank Rank { get; set; } - - /// - /// Create a from an API score instance. - /// - /// A ruleset store, used to populate a ruleset instance in the returned score. - /// An optional beatmap, copied into the returned score (for cases where the API does not populate the beatmap). - /// - public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) - { - var ruleset = rulesets.GetRuleset(RulesetID) ?? throw new InvalidOperationException($"Ruleset with ID of {RulesetID} not found locally"); - - var rulesetInstance = ruleset.CreateInstance(); - - var modInstances = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray(); - - // all API scores provided by this class are considered to be legacy. - modInstances = modInstances.Append(rulesetInstance.CreateMod()).ToArray(); - - var scoreInfo = new ScoreInfo - { - TotalScore = TotalScore, - MaxCombo = MaxCombo, - BeatmapInfo = beatmap ?? new BeatmapInfo(), - User = User, - Accuracy = Accuracy, - OnlineID = OnlineID, - Date = Date, - PP = PP, - Hash = HasReplay ? "online" : string.Empty, // todo: temporary? - Rank = Rank, - Ruleset = ruleset, - Mods = modInstances, - }; - - if (Statistics != null) - { - foreach (var kvp in Statistics) - { - switch (kvp.Key) - { - case @"count_geki": - scoreInfo.SetCountGeki(kvp.Value); - break; - - case @"count_300": - scoreInfo.SetCount300(kvp.Value); - break; - - case @"count_katu": - scoreInfo.SetCountKatu(kvp.Value); - break; - - case @"count_100": - scoreInfo.SetCount100(kvp.Value); - break; - - case @"count_50": - scoreInfo.SetCount50(kvp.Value); - break; - - case @"count_miss": - scoreInfo.SetCountMiss(kvp.Value); - break; - } - } - } - - return scoreInfo; - } - - public IRulesetInfo Ruleset => new RulesetInfo { OnlineID = RulesetID }; - IEnumerable IHasNamedFiles.Files => throw new NotImplementedException(); - - #region Implementation of IScoreInfo - - IBeatmapInfo IScoreInfo.Beatmap => Beatmap; - IUser IScoreInfo.User => User; - - #endregion - } -} diff --git a/osu.Game/Online/Solo/SubmittableScore.cs b/osu.Game/Online/Solo/SubmittableScore.cs index 31f0cb1dfb..73782060b3 100644 --- a/osu.Game/Online/Solo/SubmittableScore.cs +++ b/osu.Game/Online/Solo/SubmittableScore.cs @@ -9,7 +9,6 @@ using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -17,7 +16,6 @@ namespace osu.Game.Online.Solo { /// /// A class specifically for sending scores to the API during score submission. - /// This is used instead of due to marginally different serialisation naming requirements. /// [Serializable] public class SubmittableScore From 1b6ebcfd87772a104773ead922fa0817891609f6 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 13:43:19 +0300 Subject: [PATCH 294/481] Remove `SubmittableScore` and replace with `SoloScoreInfo` extension method --- .../Online/TestAPIModJsonSerialization.cs | 13 ++-- ... => TestSoloScoreInfoJsonSerialization.cs} | 8 +-- .../API/Requests/Responses/SoloScoreInfo.cs | 17 +++++ osu.Game/Online/Rooms/SubmitScoreRequest.cs | 6 +- osu.Game/Online/Solo/SubmittableScore.cs | 70 ------------------- 5 files changed, 30 insertions(+), 84 deletions(-) rename osu.Game.Tests/Online/{TestSubmittableScoreJsonSerialization.cs => TestSoloScoreInfoJsonSerialization.cs} (79%) delete mode 100644 osu.Game/Online/Solo/SubmittableScore.cs diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index 185f85513b..67dbcf0ccf 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -12,7 +12,6 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; @@ -110,30 +109,30 @@ namespace osu.Game.Tests.Online } [Test] - public void TestDeserialiseSubmittableScoreWithEmptyMods() + public void TestDeserialiseSoloScoreWithEmptyMods() { - var score = new SubmittableScore(new ScoreInfo + var score = SoloScoreInfo.ForSubmission(new ScoreInfo { User = new APIUser(), Ruleset = new OsuRuleset().RulesetInfo, }); - var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); + var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); Assert.That(deserialised?.Mods.Length, Is.Zero); } [Test] - public void TestDeserialiseSubmittableScoreWithCustomModSetting() + public void TestDeserialiseSoloScoreWithCustomModSetting() { - var score = new SubmittableScore(new ScoreInfo + var score = SoloScoreInfo.ForSubmission(new ScoreInfo { Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } }, User = new APIUser(), Ruleset = new OsuRuleset().RulesetInfo, }); - var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); + var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); Assert.That((deserialised?.Mods[0])?.Settings["speed_change"], Is.EqualTo(2)); } diff --git a/osu.Game.Tests/Online/TestSubmittableScoreJsonSerialization.cs b/osu.Game.Tests/Online/TestSoloScoreInfoJsonSerialization.cs similarity index 79% rename from osu.Game.Tests/Online/TestSubmittableScoreJsonSerialization.cs rename to osu.Game.Tests/Online/TestSoloScoreInfoJsonSerialization.cs index f0f6727393..8ff0b67b5b 100644 --- a/osu.Game.Tests/Online/TestSubmittableScoreJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestSoloScoreInfoJsonSerialization.cs @@ -6,7 +6,7 @@ using Newtonsoft.Json; using NUnit.Framework; using osu.Game.IO.Serialization; -using osu.Game.Online.Solo; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Online @@ -15,12 +15,12 @@ namespace osu.Game.Tests.Online /// Basic testing to ensure our attribute-based naming is correctly working. /// [TestFixture] - public class TestSubmittableScoreJsonSerialization + public class TestSoloScoreInfoJsonSerialization { [Test] public void TestScoreSerialisationViaExtensionMethod() { - var score = new SubmittableScore(TestResources.CreateTestScoreInfo()); + var score = SoloScoreInfo.ForSubmission(TestResources.CreateTestScoreInfo()); string serialised = score.Serialize(); @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Online [Test] public void TestScoreSerialisationWithoutSettings() { - var score = new SubmittableScore(TestResources.CreateTestScoreInfo()); + var score = SoloScoreInfo.ForSubmission(TestResources.CreateTestScoreInfo()); string serialised = JsonConvert.SerializeObject(score); diff --git a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs index bfc8b4102a..6558578023 100644 --- a/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/SoloScoreInfo.cs @@ -151,6 +151,23 @@ namespace osu.Game.Online.API.Requests.Responses PP = PP, }; + /// + /// Creates a from a local score for score submission. + /// + /// The local score. + public static SoloScoreInfo ForSubmission(ScoreInfo score) => new SoloScoreInfo + { + Rank = score.Rank, + TotalScore = (int)score.TotalScore, + Accuracy = score.Accuracy, + PP = score.PP, + MaxCombo = score.MaxCombo, + RulesetID = score.RulesetID, + Passed = score.Passed, + Mods = score.APIMods, + Statistics = score.Statistics, + }; + public long OnlineID => ID ?? -1; } } diff --git a/osu.Game/Online/Rooms/SubmitScoreRequest.cs b/osu.Game/Online/Rooms/SubmitScoreRequest.cs index d681938da5..48a7780a03 100644 --- a/osu.Game/Online/Rooms/SubmitScoreRequest.cs +++ b/osu.Game/Online/Rooms/SubmitScoreRequest.cs @@ -7,20 +7,20 @@ using System.Net.Http; using Newtonsoft.Json; using osu.Framework.IO.Network; using osu.Game.Online.API; -using osu.Game.Online.Solo; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Scoring; namespace osu.Game.Online.Rooms { public abstract class SubmitScoreRequest : APIRequest { - public readonly SubmittableScore Score; + public readonly SoloScoreInfo Score; protected readonly long ScoreId; protected SubmitScoreRequest(ScoreInfo scoreInfo, long scoreId) { - Score = new SubmittableScore(scoreInfo); + Score = SoloScoreInfo.ForSubmission(scoreInfo); ScoreId = scoreId; } diff --git a/osu.Game/Online/Solo/SubmittableScore.cs b/osu.Game/Online/Solo/SubmittableScore.cs deleted file mode 100644 index 73782060b3..0000000000 --- a/osu.Game/Online/Solo/SubmittableScore.cs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using System.Collections.Generic; -using JetBrains.Annotations; -using Newtonsoft.Json; -using Newtonsoft.Json.Converters; -using osu.Game.Online.API; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; - -namespace osu.Game.Online.Solo -{ - /// - /// A class specifically for sending scores to the API during score submission. - /// - [Serializable] - public class SubmittableScore - { - [JsonProperty("rank")] - [JsonConverter(typeof(StringEnumConverter))] - public ScoreRank Rank { get; set; } - - [JsonProperty("total_score")] - public long TotalScore { get; set; } - - [JsonProperty("accuracy")] - public double Accuracy { get; set; } - - [JsonProperty(@"pp")] - public double? PP { get; set; } - - [JsonProperty("max_combo")] - public int MaxCombo { get; set; } - - [JsonProperty("ruleset_id")] - public int RulesetID { get; set; } - - [JsonProperty("passed")] - public bool Passed { get; set; } - - // Used for API serialisation/deserialisation. - [JsonProperty("mods")] - public APIMod[] Mods { get; set; } - - [JsonProperty("statistics")] - public Dictionary Statistics { get; set; } - - [UsedImplicitly] - public SubmittableScore() - { - } - - public SubmittableScore(ScoreInfo score) - { - Rank = score.Rank; - TotalScore = score.TotalScore; - Accuracy = score.Accuracy; - PP = score.PP; - MaxCombo = score.MaxCombo; - RulesetID = score.RulesetID; - Passed = score.Passed; - Mods = score.APIMods; - Statistics = score.Statistics; - } - } -} From ec477a3ebfd74b2cdfa0444c23644d710c422df8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 17:18:28 +0900 Subject: [PATCH 295/481] Add basic coverage of current behaviour of beatmap reimport --- .../Database/BeatmapImporterTests.cs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 9ee88c0670..076be8339a 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -670,6 +670,61 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestImportThenReimportWithNewDifficulty() + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var store = new RealmRulesetStore(realm, storage); + + string? pathOriginal = TestResources.GetTestBeatmapForImport(); + + string pathMissingOneBeatmap = pathOriginal.Replace(".osz", "_missing_difficulty.osz"); + + string extractedFolder = $"{pathOriginal}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + using (var zip = ZipArchive.Open(pathOriginal)) + zip.WriteToDirectory(extractedFolder); + + // remove one difficulty before first import + new FileInfo(Directory.GetFiles(extractedFolder, "*.osu").First()).Delete(); + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(extractedFolder); + zip.SaveTo(pathMissingOneBeatmap, new ZipWriterOptions(CompressionType.Deflate)); + } + + var firstImport = await importer.Import(new ImportTask(pathMissingOneBeatmap)); + Assert.That(firstImport, Is.Not.Null); + + Assert.That(realm.Realm.All().Where(s => !s.DeletePending), Has.Count.EqualTo(1)); + Assert.That(realm.Realm.All().First(s => !s.DeletePending).Beatmaps, Has.Count.EqualTo(11)); + + // Second import matches first but contains one extra .osu file. + var secondImport = await importer.Import(new ImportTask(pathOriginal)); + Assert.That(secondImport, Is.Not.Null); + + Assert.That(realm.Realm.All(), Has.Count.EqualTo(23)); + Assert.That(realm.Realm.All(), Has.Count.EqualTo(2)); + + Assert.That(realm.Realm.All().Where(s => !s.DeletePending), Has.Count.EqualTo(1)); + Assert.That(realm.Realm.All().First(s => !s.DeletePending).Beatmaps, Has.Count.EqualTo(12)); + + // check the newly "imported" beatmap is not the original. + Assert.That(firstImport?.ID, Is.Not.EqualTo(secondImport?.ID)); + } + finally + { + Directory.Delete(extractedFolder, true); + } + }); + } + [Test] public void TestImportThenReimportAfterMissingFiles() { From 6a3e8e31de7587b2d6859c7bbb9964531424cfaf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 18:51:19 +0900 Subject: [PATCH 296/481] Centralise calls to reset online info of a `BeatmapInfo` --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 2 +- .../Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 2 +- osu.Game/Beatmaps/BeatmapImporter.cs | 4 ++-- osu.Game/Beatmaps/BeatmapInfo.cs | 11 +++++++++++ osu.Game/Beatmaps/BeatmapManager.cs | 3 +-- osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs | 4 ++-- 6 files changed, 18 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 076be8339a..66384e8cd1 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -797,7 +797,7 @@ namespace osu.Game.Tests.Database await realm.Realm.WriteAsync(() => { foreach (var b in imported.Beatmaps) - b.OnlineID = -1; + b.ResetOnlineInfo(); }); deleteBeatmapSet(imported, realm.Realm); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 96efca6b65..d1bdfb1dfa 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -239,7 +239,7 @@ namespace osu.Game.Tests.Visual.Gameplay createPlayerTest(false, r => { var beatmap = createTestBeatmap(r); - beatmap.BeatmapInfo.OnlineID = -1; + beatmap.BeatmapInfo.ResetOnlineInfo(); return beatmap; }); diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 3e4d01a9a3..2fdaeb9eed 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -87,7 +87,7 @@ namespace osu.Game.Beatmaps existingSetWithSameOnlineID.OnlineID = -1; foreach (var b in existingSetWithSameOnlineID.Beatmaps) - b.OnlineID = -1; + b.ResetOnlineInfo(); LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineID ({beatmapSet.OnlineID}). It will be disassociated and marked for deletion."); } @@ -133,7 +133,7 @@ namespace osu.Game.Beatmaps } } - void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineID = -1); + void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.ResetOnlineInfo()); } protected override bool CanSkipImport(BeatmapSetInfo existing, BeatmapSetInfo import) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 466448d771..66c1995c8b 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -109,6 +109,17 @@ namespace osu.Game.Beatmaps [JsonIgnore] public bool Hidden { get; set; } + /// + /// Reset any fetched online linking information (and history). + /// + public void ResetOnlineInfo() + { + OnlineID = -1; + LastOnlineUpdate = null; + OnlineMD5Hash = string.Empty; + Status = BeatmapOnlineStatus.None; + } + #region Properties we may not want persisted (but also maybe no harm?) public double AudioLeadIn { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 7717c9cc87..62edd6e8da 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -164,8 +164,7 @@ namespace osu.Game.Beatmaps // clear the hash, as that's what is used to match .osu files with their corresponding realm beatmaps. newBeatmapInfo.Hash = string.Empty; // clear online properties. - newBeatmapInfo.OnlineID = -1; - newBeatmapInfo.Status = BeatmapOnlineStatus.None; + newBeatmapInfo.ResetOnlineInfo(); return addDifficultyToSet(targetBeatmapSet, newBeatmap, referenceWorkingBeatmap.Skin); } diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs index d3be240d4c..6a3383cc92 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs @@ -88,7 +88,7 @@ namespace osu.Game.Beatmaps if (req.CompletionState == APIRequestCompletionState.Failed) { logForModel(set, $"Online retrieval failed for {beatmapInfo}"); - beatmapInfo.OnlineID = -1; + beatmapInfo.ResetOnlineInfo(); return; } @@ -118,7 +118,7 @@ namespace osu.Game.Beatmaps catch (Exception e) { logForModel(set, $"Online retrieval failed for {beatmapInfo} ({e.Message})"); - beatmapInfo.OnlineID = -1; + beatmapInfo.ResetOnlineInfo(); } } From b7f6413bcee7c4381fbbe30f9725bf144248eda8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 18:52:53 +0900 Subject: [PATCH 297/481] Fix old version of beatmap potentially not being deleted during update flow This can happen if the online IDs are not present in the `.osu` files. Previously this was only working due to the early logic in the import process (that relies on matching all online IDs perfectly). --- osu.Game/Beatmaps/BeatmapModelDownloader.cs | 20 +++++++++++++++++++ osu.Game/Database/ModelDownloader.cs | 8 ++++++-- .../Select/Carousel/UpdateBeatmapSetButton.cs | 2 +- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapModelDownloader.cs b/osu.Game/Beatmaps/BeatmapModelDownloader.cs index 4295def5c3..8c037b6382 100644 --- a/osu.Game/Beatmaps/BeatmapModelDownloader.cs +++ b/osu.Game/Beatmaps/BeatmapModelDownloader.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Logging; using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -19,5 +20,24 @@ namespace osu.Game.Beatmaps : base(beatmapImporter, api) { } + + public bool Update(BeatmapSetInfo model) + { + return Download(model, false, onSuccess); + + void onSuccess(Live imported) + { + imported.PerformWrite(updated => + { + Logger.Log($"Beatmap \"{updated}\"update completed successfully", LoggingTarget.Database); + + var original = updated.Realm.Find(model.ID); + + // Generally the import process will do this for us if the OnlineIDs match, + // but that isn't a guarantee (ie. if the .osu file doesn't have OnlineIDs populated). + original.DeletePending = true; + }); + } + } } } diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index 02bcb342e4..8fa1c12d88 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -42,7 +42,9 @@ namespace osu.Game.Database /// The request object. protected abstract ArchiveDownloadRequest CreateDownloadRequest(T model, bool minimiseDownloadSize); - public bool Download(T model, bool minimiseDownloadSize = false) + public bool Download(T model, bool minimiseDownloadSize = false) => Download(model, minimiseDownloadSize, null); + + protected bool Download(T model, bool minimiseDownloadSize, Action>? onSuccess) { if (!canDownload(model)) return false; @@ -67,7 +69,9 @@ namespace osu.Game.Database var imported = await importer.Import(notification, new ImportTask(filename)).ConfigureAwait(false); // for now a failed import will be marked as a failed download for simplicity. - if (!imported.Any()) + if (imported.Any()) + onSuccess?.Invoke(imported.Single()); + else DownloadFailed?.Invoke(request); CurrentDownloads.Remove(request); diff --git a/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs index b80eb40018..3746c1975c 100644 --- a/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs +++ b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Select.Carousel Action = () => { - beatmapDownloader.Download(beatmapSetInfo); + beatmapDownloader.Update(beatmapSetInfo); attachExistingDownload(); }; } From 912218e1231a18ebf7fc17e749f60963c90a4bf8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 18:54:33 +0900 Subject: [PATCH 298/481] Ensure scores are transferred after beatmap update if difficulty hash didn't change --- osu.Game/Beatmaps/BeatmapModelDownloader.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapModelDownloader.cs b/osu.Game/Beatmaps/BeatmapModelDownloader.cs index 8c037b6382..0110ce0c50 100644 --- a/osu.Game/Beatmaps/BeatmapModelDownloader.cs +++ b/osu.Game/Beatmaps/BeatmapModelDownloader.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Logging; using osu.Game.Database; using osu.Game.Online.API; @@ -36,6 +37,23 @@ namespace osu.Game.Beatmaps // Generally the import process will do this for us if the OnlineIDs match, // but that isn't a guarantee (ie. if the .osu file doesn't have OnlineIDs populated). original.DeletePending = true; + + foreach (var beatmap in original.Beatmaps.ToArray()) + { + var updatedBeatmap = updated.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.Hash); + + if (updatedBeatmap != null) + { + // If the updated beatmap matches an existing one, transfer any user data across.. + if (beatmap.Scores.Any()) + { + Logger.Log($"Transferring {beatmap.Scores.Count()} scores for unchanged difficulty \"{beatmap}\"", LoggingTarget.Database); + + foreach (var score in beatmap.Scores) + score.BeatmapInfo = updatedBeatmap; + } + } + } }); } } From e5ad07454ca37dbe6bb57b03d3dfcb48da1504cc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 18:54:55 +0900 Subject: [PATCH 299/481] Ensure previous version prior to update loses online info after marked pending delete --- osu.Game/Beatmaps/BeatmapModelDownloader.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapModelDownloader.cs b/osu.Game/Beatmaps/BeatmapModelDownloader.cs index 0110ce0c50..7c48abbe66 100644 --- a/osu.Game/Beatmaps/BeatmapModelDownloader.cs +++ b/osu.Game/Beatmaps/BeatmapModelDownloader.cs @@ -52,6 +52,19 @@ namespace osu.Game.Beatmaps foreach (var score in beatmap.Scores) score.BeatmapInfo = updatedBeatmap; } + + // ..then nuke the old beatmap completely. + // this is done instead of a soft deletion to avoid a user potentially creating weird + // interactions, like restoring the outdated beatmap then updating a second time + // (causing user data to be wiped). + original.Beatmaps.Remove(beatmap); + } + else + { + // If the beatmap differs in the original, leave it in a soft-deleted state but reset online info. + // This caters to the case where a user has made modifications they potentially want to restore, + // but after restoring we want to ensure it can't be used to trigger an update of the beatmap. + beatmap.ResetOnlineInfo(); } } }); From 2363a3fb7b1d5b6a8cafbce199fde6e96636e942 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 18:55:08 +0900 Subject: [PATCH 300/481] Persist `DateAdded` over beatmap updates --- osu.Game/Beatmaps/BeatmapModelDownloader.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapModelDownloader.cs b/osu.Game/Beatmaps/BeatmapModelDownloader.cs index 7c48abbe66..a566aee49e 100644 --- a/osu.Game/Beatmaps/BeatmapModelDownloader.cs +++ b/osu.Game/Beatmaps/BeatmapModelDownloader.cs @@ -38,6 +38,9 @@ namespace osu.Game.Beatmaps // but that isn't a guarantee (ie. if the .osu file doesn't have OnlineIDs populated). original.DeletePending = true; + // Transfer local values which should be persisted across a beatmap update. + updated.DateAdded = original.DateAdded; + foreach (var beatmap in original.Beatmaps.ToArray()) { var updatedBeatmap = updated.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.Hash); From 2e14d8730c397f86cf5ad75cc6b7bd24ef73ffd7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 19:31:46 +0900 Subject: [PATCH 301/481] Move implementation of updating a beatmap to `BeatmapImporter` --- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 2 +- osu.Game/Beatmaps/BeatmapImporter.cs | 56 ++++++++++++++++ osu.Game/Beatmaps/BeatmapManager.cs | 3 + osu.Game/Beatmaps/BeatmapModelDownloader.cs | 64 ++++--------------- .../Drawables/BundledBeatmapDownloader.cs | 3 +- osu.Game/Database/ModelDownloader.cs | 20 ++++-- 6 files changed, 89 insertions(+), 59 deletions(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 536322805b..0a980efbae 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -237,7 +237,7 @@ namespace osu.Game.Tests.Online internal class TestBeatmapModelDownloader : BeatmapModelDownloader { - public TestBeatmapModelDownloader(IModelImporter importer, IAPIProvider apiProvider) + public TestBeatmapModelDownloader(BeatmapManager importer, IAPIProvider apiProvider) : base(importer, apiProvider) { } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 2fdaeb9eed..3cfeb0df67 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -3,9 +3,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Logging; @@ -16,6 +18,7 @@ using osu.Game.Database; using osu.Game.Extensions; using osu.Game.IO; using osu.Game.IO.Archives; +using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using Realms; @@ -38,6 +41,59 @@ namespace osu.Game.Beatmaps { } + public async Task>> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original) + { + var imported = await Import(notification, importTask); + + Debug.Assert(imported.Count() == 1); + + imported.First().PerformWrite(updated => + { + Logger.Log($"Beatmap \"{updated}\"update completed successfully", LoggingTarget.Database); + + original = updated.Realm.Find(original.ID); + + // Generally the import process will do this for us if the OnlineIDs match, + // but that isn't a guarantee (ie. if the .osu file doesn't have OnlineIDs populated). + original.DeletePending = true; + + // Transfer local values which should be persisted across a beatmap update. + updated.DateAdded = original.DateAdded; + + foreach (var beatmap in original.Beatmaps.ToArray()) + { + var updatedBeatmap = updated.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.Hash); + + if (updatedBeatmap != null) + { + // If the updated beatmap matches an existing one, transfer any user data across.. + if (beatmap.Scores.Any()) + { + Logger.Log($"Transferring {beatmap.Scores.Count()} scores for unchanged difficulty \"{beatmap}\"", LoggingTarget.Database); + + foreach (var score in beatmap.Scores) + score.BeatmapInfo = updatedBeatmap; + } + + // ..then nuke the old beatmap completely. + // this is done instead of a soft deletion to avoid a user potentially creating weird + // interactions, like restoring the outdated beatmap then updating a second time + // (causing user data to be wiped). + original.Beatmaps.Remove(beatmap); + } + else + { + // If the beatmap differs in the original, leave it in a soft-deleted state but reset online info. + // This caters to the case where a user has made modifications they potentially want to restore, + // but after restoring we want to ensure it can't be used to trigger an update of the beatmap. + beatmap.ResetOnlineInfo(); + } + } + }); + + return imported; + } + protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == ".osz"; protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 62edd6e8da..1b31d29c2b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -408,6 +408,9 @@ namespace osu.Game.Beatmaps Realm.Run(r => Undelete(r.All().Where(s => s.DeletePending).ToList())); } + public Task>> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original) => + beatmapImporter.ImportAsUpdate(notification, importTask, original); + #region Implementation of ICanAcceptFiles public Task Import(params string[] paths) => beatmapImporter.Import(paths); diff --git a/osu.Game/Beatmaps/BeatmapModelDownloader.cs b/osu.Game/Beatmaps/BeatmapModelDownloader.cs index a566aee49e..e70168f1b3 100644 --- a/osu.Game/Beatmaps/BeatmapModelDownloader.cs +++ b/osu.Game/Beatmaps/BeatmapModelDownloader.cs @@ -1,77 +1,39 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; -using osu.Framework.Logging; +using System.Collections.Generic; +using System.Threading.Tasks; using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Overlays.Notifications; namespace osu.Game.Beatmaps { public class BeatmapModelDownloader : ModelDownloader { + private readonly BeatmapManager beatmapManager; + protected override ArchiveDownloadRequest CreateDownloadRequest(IBeatmapSetInfo set, bool minimiseDownloadSize) => new DownloadBeatmapSetRequest(set, minimiseDownloadSize); public override ArchiveDownloadRequest? GetExistingDownload(IBeatmapSetInfo model) => CurrentDownloads.Find(r => r.Model.OnlineID == model.OnlineID); - public BeatmapModelDownloader(IModelImporter beatmapImporter, IAPIProvider api) + public BeatmapModelDownloader(BeatmapManager beatmapImporter, IAPIProvider api) : base(beatmapImporter, api) { + beatmapManager = beatmapImporter; } - public bool Update(BeatmapSetInfo model) + protected override Task>> Import(ProgressNotification notification, string filename, BeatmapSetInfo? originalModel) { - return Download(model, false, onSuccess); + if (originalModel != null) + return beatmapManager.ImportAsUpdate(notification, new ImportTask(filename), originalModel); - void onSuccess(Live imported) - { - imported.PerformWrite(updated => - { - Logger.Log($"Beatmap \"{updated}\"update completed successfully", LoggingTarget.Database); - - var original = updated.Realm.Find(model.ID); - - // Generally the import process will do this for us if the OnlineIDs match, - // but that isn't a guarantee (ie. if the .osu file doesn't have OnlineIDs populated). - original.DeletePending = true; - - // Transfer local values which should be persisted across a beatmap update. - updated.DateAdded = original.DateAdded; - - foreach (var beatmap in original.Beatmaps.ToArray()) - { - var updatedBeatmap = updated.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.Hash); - - if (updatedBeatmap != null) - { - // If the updated beatmap matches an existing one, transfer any user data across.. - if (beatmap.Scores.Any()) - { - Logger.Log($"Transferring {beatmap.Scores.Count()} scores for unchanged difficulty \"{beatmap}\"", LoggingTarget.Database); - - foreach (var score in beatmap.Scores) - score.BeatmapInfo = updatedBeatmap; - } - - // ..then nuke the old beatmap completely. - // this is done instead of a soft deletion to avoid a user potentially creating weird - // interactions, like restoring the outdated beatmap then updating a second time - // (causing user data to be wiped). - original.Beatmaps.Remove(beatmap); - } - else - { - // If the beatmap differs in the original, leave it in a soft-deleted state but reset online info. - // This caters to the case where a user has made modifications they potentially want to restore, - // but after restoring we want to ensure it can't be used to trigger an update of the beatmap. - beatmap.ResetOnlineInfo(); - } - } - }); - } + return base.Import(notification, filename, null); } + + public bool Update(BeatmapSetInfo model) => Download(model, false, model); } } diff --git a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs index 80af4108c7..bcbe7084d5 100644 --- a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs +++ b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs @@ -11,7 +11,6 @@ using System.Text.RegularExpressions; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Database; using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -109,7 +108,7 @@ namespace osu.Game.Beatmaps.Drawables private class BundledBeatmapModelDownloader : BeatmapModelDownloader { - public BundledBeatmapModelDownloader(IModelImporter beatmapImporter, IAPIProvider api) + public BundledBeatmapModelDownloader(BeatmapManager beatmapImporter, IAPIProvider api) : base(beatmapImporter, api) { } diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index 8fa1c12d88..5fd124cafc 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -44,7 +44,7 @@ namespace osu.Game.Database public bool Download(T model, bool minimiseDownloadSize = false) => Download(model, minimiseDownloadSize, null); - protected bool Download(T model, bool minimiseDownloadSize, Action>? onSuccess) + protected bool Download(T model, bool minimiseDownloadSize, TModel? originalModel) { if (!canDownload(model)) return false; @@ -66,12 +66,10 @@ namespace osu.Game.Database Task.Factory.StartNew(async () => { // This gets scheduled back to the update thread, but we want the import to run in the background. - var imported = await importer.Import(notification, new ImportTask(filename)).ConfigureAwait(false); + var imported = await Import(notification, filename, originalModel).ConfigureAwait(false); // for now a failed import will be marked as a failed download for simplicity. - if (imported.Any()) - onSuccess?.Invoke(imported.Single()); - else + if (!imported.Any()) DownloadFailed?.Invoke(request); CurrentDownloads.Remove(request); @@ -107,6 +105,18 @@ namespace osu.Game.Database } } + /// + /// Run the post-download import for the model. + /// + /// The notification to update. + /// The path of the temporary downloaded file. + /// An optional model for update scenarios, to be used as a reference. + /// The imported model. + protected virtual Task>> Import(ProgressNotification notification, string filename, TModel? originalModel) + { + return importer.Import(notification, new ImportTask(filename)); + } + public abstract ArchiveDownloadRequest? GetExistingDownload(T model); private bool canDownload(T model) => GetExistingDownload(model) == null && api != null; From 92dd1bcddb6dc3cfd669e721fc0f459a6b6d1e21 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 19:50:58 +0900 Subject: [PATCH 302/481] Add test coverage of actual update flow --- .../Database/BeatmapImporterTests.cs | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 66384e8cd1..784613ce3e 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -725,6 +725,60 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestImportThenReimportWithNewDifficultyAsUpdate() + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var store = new RealmRulesetStore(realm, storage); + + string? pathOriginal = TestResources.GetTestBeatmapForImport(); + + string pathMissingOneBeatmap = pathOriginal.Replace(".osz", "_missing_difficulty.osz"); + + string extractedFolder = $"{pathOriginal}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + using (var zip = ZipArchive.Open(pathOriginal)) + zip.WriteToDirectory(extractedFolder); + + // remove one difficulty before first import + new FileInfo(Directory.GetFiles(extractedFolder, "*.osu").First()).Delete(); + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(extractedFolder); + zip.SaveTo(pathMissingOneBeatmap, new ZipWriterOptions(CompressionType.Deflate)); + } + + var firstImport = await importer.Import(new ImportTask(pathMissingOneBeatmap)); + Assert.That(firstImport, Is.Not.Null); + Debug.Assert(firstImport != null); + + Assert.That(realm.Realm.All().Where(s => !s.DeletePending), Has.Count.EqualTo(1)); + Assert.That(realm.Realm.All().First(s => !s.DeletePending).Beatmaps, Has.Count.EqualTo(11)); + + // Second import matches first but contains one extra .osu file. + var secondImport = (await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginal), firstImport.Value)).FirstOrDefault(); + Assert.That(secondImport, Is.Not.Null); + + Assert.That(realm.Realm.All(), Has.Count.EqualTo(12)); + Assert.That(realm.Realm.All(), Has.Count.EqualTo(12)); + Assert.That(realm.Realm.All(), Has.Count.EqualTo(1)); + + // check the newly "imported" beatmap is not the original. + Assert.That(firstImport?.ID, Is.Not.EqualTo(secondImport?.ID)); + } + finally + { + Directory.Delete(extractedFolder, true); + } + }); + } + [Test] public void TestImportThenReimportAfterMissingFiles() { From 8a0c8f5fd874cbca96918c1f396ff31b2fb27b6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 19:51:10 +0900 Subject: [PATCH 303/481] Fix some realm pieces not being cleaned up --- osu.Game/Beatmaps/BeatmapImporter.cs | 13 +++++++++++-- osu.Game/Database/RealmAccess.cs | 1 - 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 3cfeb0df67..104a36c788 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -49,9 +49,11 @@ namespace osu.Game.Beatmaps imported.First().PerformWrite(updated => { - Logger.Log($"Beatmap \"{updated}\"update completed successfully", LoggingTarget.Database); + var realm = updated.Realm; - original = updated.Realm.Find(original.ID); + Logger.Log($"Beatmap \"{updated}\" update completed successfully", LoggingTarget.Database); + + original = realm.Find(original.ID); // Generally the import process will do this for us if the OnlineIDs match, // but that isn't a guarantee (ie. if the .osu file doesn't have OnlineIDs populated). @@ -80,6 +82,9 @@ namespace osu.Game.Beatmaps // interactions, like restoring the outdated beatmap then updating a second time // (causing user data to be wiped). original.Beatmaps.Remove(beatmap); + + realm.Remove(beatmap.Metadata); + realm.Remove(beatmap); } else { @@ -89,6 +94,10 @@ namespace osu.Game.Beatmaps beatmap.ResetOnlineInfo(); } } + + // If the original has no beatmaps left, delete the set as well. + if (!original.Beatmaps.Any()) + realm.Remove(original); }); return imported; diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 826341a5b9..a93fdea35b 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -278,7 +278,6 @@ namespace osu.Game.Database realm.Remove(score); realm.Remove(beatmap.Metadata); - realm.Remove(beatmap); } From 9c411c2250175f7cf4ecb34983bb356fd9deae93 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 20:06:36 +0900 Subject: [PATCH 304/481] Fix test nullability assertions --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 784613ce3e..3b1f82375c 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -764,13 +764,14 @@ namespace osu.Game.Tests.Database // Second import matches first but contains one extra .osu file. var secondImport = (await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginal), firstImport.Value)).FirstOrDefault(); Assert.That(secondImport, Is.Not.Null); + Debug.Assert(secondImport != null); Assert.That(realm.Realm.All(), Has.Count.EqualTo(12)); Assert.That(realm.Realm.All(), Has.Count.EqualTo(12)); Assert.That(realm.Realm.All(), Has.Count.EqualTo(1)); // check the newly "imported" beatmap is not the original. - Assert.That(firstImport?.ID, Is.Not.EqualTo(secondImport?.ID)); + Assert.That(firstImport.ID, Is.Not.EqualTo(secondImport.ID)); } finally { From e5355f314d4b5a851c0646f2da7d2916c2972106 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 25 Jul 2022 15:19:32 +0300 Subject: [PATCH 305/481] Use longer hash string --- osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 1384d4ef6e..9d70d1ef33 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -211,7 +211,7 @@ namespace osu.Game.Tests.Visual.Gameplay OnlineID = hasOnlineId ? online_score_id : 0, Ruleset = new OsuRuleset().RulesetInfo, BeatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(), - Hash = replayAvailable ? "hash" : string.Empty, + Hash = replayAvailable ? "online" : string.Empty, User = new APIUser { Id = 39828, From d41ac36a69c60c41b24ce20ece66a1a67ee2304e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 Jul 2022 23:59:25 +0900 Subject: [PATCH 306/481] Fix scenario where import is expected to be empty --- osu.Game/Beatmaps/BeatmapImporter.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 104a36c788..2a6121b541 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -45,6 +45,9 @@ namespace osu.Game.Beatmaps { var imported = await Import(notification, importTask); + if (!imported.Any()) + return imported; + Debug.Assert(imported.Count() == 1); imported.First().PerformWrite(updated => From 46c4e784777b031a71e3c60374904d5fb15d1931 Mon Sep 17 00:00:00 2001 From: Andrew Hong Date: Mon, 25 Jul 2022 15:40:20 -0400 Subject: [PATCH 307/481] Add notification and another menuitem --- .../UserInterface/ExternalLinkButton.cs | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index e813e2d8e8..c5e1e51e07 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -13,6 +13,8 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Platform; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osuTK; using osuTK.Graphics; @@ -22,6 +24,9 @@ namespace osu.Game.Graphics.UserInterface { public string Link { get; set; } + [Resolved] + private INotificationOverlay notificationOverlay { get; set; } + private Color4 hoverColour; [Resolved] @@ -44,14 +49,26 @@ namespace osu.Game.Graphics.UserInterface }; } + private void copyUrl() + { + host.GetClipboard()?.SetText(Link); + notificationOverlay.Post(new SimpleNotification + { + Text = "Copied URL!" + }); + } + public MenuItem[] ContextMenuItems { get { - List items = new List + List items = new List(); + + if (Link != null) { - new OsuMenuItem("Copy URL", MenuItemType.Standard, () => host.GetClipboard()?.SetText(Link)) - }; + items.Add(new OsuMenuItem("Open", MenuItemType.Standard, () => host.OpenUrlExternally(Link))); + items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, () => copyUrl())); + } return items.ToArray(); } From a8e315abf033fb82a148c507b8be1d12613d91bf Mon Sep 17 00:00:00 2001 From: Andrew Hong Date: Mon, 25 Jul 2022 17:16:33 -0400 Subject: [PATCH 308/481] Refactor --- osu.Game/Graphics/UserInterface/ExternalLinkButton.cs | 2 +- osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index c5e1e51e07..166546bb8a 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -8,8 +8,8 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Platform; diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 85b98a92e2..c02c42786b 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -130,7 +130,8 @@ namespace osu.Game.Overlays.BeatmapSet { Font = OsuFont.GetFont(size: 30, weight: FontWeight.SemiBold, italics: true) }, - externalLink = new ExternalLinkButton { + externalLink = new ExternalLinkButton + { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Margin = new MarginPadding { Left = 5, Bottom = 4 }, // To better lineup with the font From ee0c67e114826da6d497bec8b78f6ddd5efd5630 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 14:07:33 +0900 Subject: [PATCH 309/481] Add ability to make cursor show even during touch input I completely disagree with this from a UX perspective, but it's come up so often that I figure we should just let users bone themselves. --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ osu.Game/Graphics/Cursor/MenuCursorContainer.cs | 15 +++++++++++++-- osu.Game/Localisation/SkinSettingsStrings.cs | 5 +++++ .../Settings/Sections/Gameplay/InputSettings.cs | 5 +++++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index e816fd50f3..fb585e9cbd 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -91,6 +91,7 @@ namespace osu.Game.Configuration // Input SetDefault(OsuSetting.MenuCursorSize, 1.0f, 0.5f, 2f, 0.01f); SetDefault(OsuSetting.GameplayCursorSize, 1.0f, 0.1f, 2f, 0.01f); + SetDefault(OsuSetting.GameplayCursorDuringTouch, false); SetDefault(OsuSetting.AutoCursorSize, false); SetDefault(OsuSetting.MouseDisableButtons, false); @@ -292,6 +293,7 @@ namespace osu.Game.Configuration MenuCursorSize, GameplayCursorSize, AutoCursorSize, + GameplayCursorDuringTouch, DimLevel, BlurLevel, LightenDuringBreaks, diff --git a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs index 746f1b5d5e..e03dc4df6f 100644 --- a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs +++ b/osu.Game/Graphics/Cursor/MenuCursorContainer.cs @@ -3,16 +3,20 @@ #nullable disable +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Input; using osu.Framework.Input.StateChanges; +using osu.Game.Configuration; namespace osu.Game.Graphics.Cursor { /// - /// A container which provides a which can be overridden by hovered s. + /// A container which provides a . + /// It also handles cases where a more localised cursor is provided by another component (via ). /// public class MenuCursorContainer : Container, IProvideCursor { @@ -36,12 +40,19 @@ namespace osu.Game.Graphics.Cursor }); } + private Bindable showDuringTouch; + private InputManager inputManager; + [Resolved] + private OsuConfigManager config { get; set; } + protected override void LoadComplete() { base.LoadComplete(); inputManager = GetContainingInputManager(); + + showDuringTouch = config.GetBindable(OsuSetting.GameplayCursorDuringTouch); } private IProvideCursor currentTarget; @@ -51,7 +62,7 @@ namespace osu.Game.Graphics.Cursor base.Update(); var lastMouseSource = inputManager.CurrentState.Mouse.LastSource; - bool hasValidInput = lastMouseSource != null && !(lastMouseSource is ISourcedFromTouch); + bool hasValidInput = lastMouseSource != null && (showDuringTouch.Value || lastMouseSource is not ISourcedFromTouch); if (!hasValidInput || !CanShowCursor) { diff --git a/osu.Game/Localisation/SkinSettingsStrings.cs b/osu.Game/Localisation/SkinSettingsStrings.cs index 81035c5a5e..4b6b0ce1d6 100644 --- a/osu.Game/Localisation/SkinSettingsStrings.cs +++ b/osu.Game/Localisation/SkinSettingsStrings.cs @@ -34,6 +34,11 @@ namespace osu.Game.Localisation /// public static LocalisableString AutoCursorSize => new TranslatableString(getKey(@"auto_cursor_size"), @"Adjust gameplay cursor size based on current beatmap"); + /// + /// "Show gameplay cursor during touch input" + /// + public static LocalisableString GameplayCursorDuringTouch => new TranslatableString(getKey(@"gameplay_cursor_during_touch"), @"Show gameplay cursor during touch input"); + /// /// "Beatmap skins" /// diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs index b5315d5268..ac59a6c0ed 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/InputSettings.cs @@ -32,6 +32,11 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = SkinSettingsStrings.AutoCursorSize, Current = config.GetBindable(OsuSetting.AutoCursorSize) }, + new SettingsCheckbox + { + LabelText = SkinSettingsStrings.GameplayCursorDuringTouch, + Current = config.GetBindable(OsuSetting.GameplayCursorDuringTouch) + }, }; if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) From ef10145d6f9f77117e6e148cf82f76f6b89ffdb4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 14:11:52 +0900 Subject: [PATCH 310/481] Rename `MenuCursorContainer` and clean up code --- .../Visual/Gameplay/TestScenePause.cs | 2 +- .../Visual/UserInterface/TestSceneCursors.cs | 62 +++++++++--------- osu.Game.Tournament/TournamentGameBase.cs | 4 +- ...sorContainer.cs => GlobalCursorDisplay.cs} | 64 +++++++++---------- osu.Game/Graphics/Cursor/IProvideCursor.cs | 4 +- osu.Game/OsuGame.cs | 4 +- osu.Game/OsuGameBase.cs | 6 +- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- osu.Game/Screens/Utility/LatencyArea.cs | 8 +-- .../Visual/OsuManualInputManagerTestScene.cs | 6 +- 10 files changed, 80 insertions(+), 82 deletions(-) rename osu.Game/Graphics/Cursor/{MenuCursorContainer.cs => GlobalCursorDisplay.cs} (52%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 5bd0a29308..71cc1f7b23 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Gameplay public TestScenePause() { - base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }); + base.Content.Add(content = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both }); } [SetUpSteps] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs index 0fa7c5303c..bcbe146456 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCursors.cs @@ -21,12 +21,12 @@ namespace osu.Game.Tests.Visual.UserInterface [TestFixture] public class TestSceneCursors : OsuManualInputManagerTestScene { - private readonly MenuCursorContainer menuCursorContainer; + private readonly GlobalCursorDisplay globalCursorDisplay; private readonly CustomCursorBox[] cursorBoxes = new CustomCursorBox[6]; public TestSceneCursors() { - Child = menuCursorContainer = new MenuCursorContainer + Child = globalCursorDisplay = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both, Children = new[] @@ -96,11 +96,11 @@ namespace osu.Game.Tests.Visual.UserInterface private void testUserCursor() { AddStep("Move to green area", () => InputManager.MoveMouseTo(cursorBoxes[0])); - AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].Cursor)); - AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].Cursor)); + AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].MenuCursor)); + AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].MenuCursor)); AddStep("Move out", moveOut); - AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor)); - AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor)); + AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].MenuCursor)); + AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor)); } /// @@ -111,13 +111,13 @@ namespace osu.Game.Tests.Visual.UserInterface private void testLocalCursor() { AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3])); - AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); - AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor)); - AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor)); - AddAssert("Check global cursor at mouse", () => checkAtMouse(menuCursorContainer.Cursor)); + AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor)); + AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].MenuCursor)); + AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor)); + AddAssert("Check global cursor at mouse", () => checkAtMouse(globalCursorDisplay.MenuCursor)); AddStep("Move out", moveOut); - AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); - AddAssert("Check global cursor visible", () => checkVisible(menuCursorContainer.Cursor)); + AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor)); + AddAssert("Check global cursor visible", () => checkVisible(globalCursorDisplay.MenuCursor)); } /// @@ -128,12 +128,12 @@ namespace osu.Game.Tests.Visual.UserInterface private void testUserCursorOverride() { AddStep("Move to blue-green boundary", () => InputManager.MoveMouseTo(cursorBoxes[1].ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); - AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor)); - AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor)); - AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor)); + AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].MenuCursor)); + AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].MenuCursor)); + AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].MenuCursor)); AddStep("Move out", moveOut); - AddAssert("Check blue cursor not visible", () => !checkVisible(cursorBoxes[1].Cursor)); - AddAssert("Check green cursor not visible", () => !checkVisible(cursorBoxes[0].Cursor)); + AddAssert("Check blue cursor not visible", () => !checkVisible(cursorBoxes[1].MenuCursor)); + AddAssert("Check green cursor not visible", () => !checkVisible(cursorBoxes[0].MenuCursor)); } /// @@ -143,13 +143,13 @@ namespace osu.Game.Tests.Visual.UserInterface private void testMultipleLocalCursors() { AddStep("Move to yellow-purple boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.BottomRight - new Vector2(10))); - AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); - AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor)); - AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); - AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].Cursor)); + AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor)); + AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].MenuCursor)); + AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor)); + AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].MenuCursor)); AddStep("Move out", moveOut); - AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor)); - AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); + AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].MenuCursor)); + AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor)); } /// @@ -159,13 +159,13 @@ namespace osu.Game.Tests.Visual.UserInterface private void testUserOverrideWithLocal() { AddStep("Move to yellow-blue boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.TopRight - new Vector2(10))); - AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor)); - AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor)); - AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); - AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].Cursor)); + AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].MenuCursor)); + AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].MenuCursor)); + AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor)); + AddAssert("Check yellow cursor at mouse", () => checkAtMouse(cursorBoxes[5].MenuCursor)); AddStep("Move out", moveOut); - AddAssert("Check blue cursor invisible", () => !checkVisible(cursorBoxes[1].Cursor)); - AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor)); + AddAssert("Check blue cursor invisible", () => !checkVisible(cursorBoxes[1].MenuCursor)); + AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].MenuCursor)); } /// @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Visual.UserInterface { public bool SmoothTransition; - public CursorContainer Cursor { get; } + public CursorContainer MenuCursor { get; } public bool ProvidingUserCursor { get; } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || (SmoothTransition && !ProvidingUserCursor); @@ -218,7 +218,7 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Text = providesUserCursor ? "User cursor" : "Local cursor" }, - Cursor = new TestCursorContainer + MenuCursor = new TestCursorContainer { State = { Value = providesUserCursor ? Visibility.Hidden : Visibility.Visible }, } diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 063b62bf08..1861e39c60 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -70,10 +70,10 @@ namespace osu.Game.Tournament protected override void LoadComplete() { - MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display + GlobalCursorDisplay.MenuCursor.AlwaysPresent = true; // required for tooltip display // we don't want to show the menu cursor as it would appear on stream output. - MenuCursorContainer.Cursor.Alpha = 0; + GlobalCursorDisplay.MenuCursor.Alpha = 0; base.LoadComplete(); diff --git a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs b/osu.Game/Graphics/Cursor/GlobalCursorDisplay.cs similarity index 52% rename from osu.Game/Graphics/Cursor/MenuCursorContainer.cs rename to osu.Game/Graphics/Cursor/GlobalCursorDisplay.cs index e03dc4df6f..6613e18cbe 100644 --- a/osu.Game/Graphics/Cursor/MenuCursorContainer.cs +++ b/osu.Game/Graphics/Cursor/GlobalCursorDisplay.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,48 +13,48 @@ using osu.Game.Configuration; namespace osu.Game.Graphics.Cursor { /// - /// A container which provides a . - /// It also handles cases where a more localised cursor is provided by another component (via ). + /// A container which provides the main . + /// Also handles cases where a more localised cursor is provided by another component (via ). /// - public class MenuCursorContainer : Container, IProvideCursor + public class GlobalCursorDisplay : Container, IProvideCursor { - protected override Container Content => content; - private readonly Container content; - /// - /// Whether any cursors can be displayed. + /// Control whether any cursor should be displayed. /// - internal bool CanShowCursor = true; + internal bool ShowCursor = true; + + public CursorContainer MenuCursor { get; } - public CursorContainer Cursor { get; } public bool ProvidingUserCursor => true; - public MenuCursorContainer() + protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; + + private Bindable showDuringTouch = null!; + + private InputManager inputManager = null!; + + private IProvideCursor? currentOverrideProvider; + + [Resolved] + private OsuConfigManager config { get; set; } = null!; + + public GlobalCursorDisplay() { AddRangeInternal(new Drawable[] { - Cursor = new MenuCursor { State = { Value = Visibility.Hidden } }, - content = new Container { RelativeSizeAxes = Axes.Both } + MenuCursor = new MenuCursor { State = { Value = Visibility.Hidden } }, + Content = new Container { RelativeSizeAxes = Axes.Both } }); } - private Bindable showDuringTouch; - - private InputManager inputManager; - - [Resolved] - private OsuConfigManager config { get; set; } - protected override void LoadComplete() { base.LoadComplete(); - inputManager = GetContainingInputManager(); + inputManager = GetContainingInputManager(); showDuringTouch = config.GetBindable(OsuSetting.GameplayCursorDuringTouch); } - private IProvideCursor currentTarget; - protected override void Update() { base.Update(); @@ -64,31 +62,31 @@ namespace osu.Game.Graphics.Cursor var lastMouseSource = inputManager.CurrentState.Mouse.LastSource; bool hasValidInput = lastMouseSource != null && (showDuringTouch.Value || lastMouseSource is not ISourcedFromTouch); - if (!hasValidInput || !CanShowCursor) + if (!hasValidInput || !ShowCursor) { - currentTarget?.Cursor?.Hide(); - currentTarget = null; + currentOverrideProvider?.MenuCursor?.Hide(); + currentOverrideProvider = null; return; } - IProvideCursor newTarget = this; + IProvideCursor newOverrideProvider = this; foreach (var d in inputManager.HoveredDrawables) { if (d is IProvideCursor p && p.ProvidingUserCursor) { - newTarget = p; + newOverrideProvider = p; break; } } - if (currentTarget == newTarget) + if (currentOverrideProvider == newOverrideProvider) return; - currentTarget?.Cursor?.Hide(); - newTarget.Cursor?.Show(); + currentOverrideProvider?.MenuCursor?.Hide(); + newOverrideProvider.MenuCursor?.Show(); - currentTarget = newTarget; + currentOverrideProvider = newOverrideProvider; } } } diff --git a/osu.Game/Graphics/Cursor/IProvideCursor.cs b/osu.Game/Graphics/Cursor/IProvideCursor.cs index 9f01e5da6d..f7f7b75bc8 100644 --- a/osu.Game/Graphics/Cursor/IProvideCursor.cs +++ b/osu.Game/Graphics/Cursor/IProvideCursor.cs @@ -17,10 +17,10 @@ namespace osu.Game.Graphics.Cursor /// The cursor provided by this . /// May be null if no cursor should be visible. /// - CursorContainer Cursor { get; } + CursorContainer MenuCursor { get; } /// - /// Whether should be displayed as the singular user cursor. This will temporarily hide any other user cursor. + /// Whether should be displayed as the singular user cursor. This will temporarily hide any other user cursor. /// This value is checked every frame and may be used to control whether multiple cursors are displayed (e.g. watching replays). /// bool ProvidingUserCursor { get; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4f08511783..1ee53e2848 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -716,7 +716,7 @@ namespace osu.Game // The next time this is updated is in UpdateAfterChildren, which occurs too late and results // in the cursor being shown for a few frames during the intro. // This prevents the cursor from showing until we have a screen with CursorVisible = true - MenuCursorContainer.CanShowCursor = menuScreen?.CursorVisible ?? false; + GlobalCursorDisplay.ShowCursor = menuScreen?.CursorVisible ?? false; // todo: all archive managers should be able to be looped here. SkinManager.PostNotification = n => Notifications.Post(n); @@ -1231,7 +1231,7 @@ namespace osu.Game ScreenOffsetContainer.X = horizontalOffset; overlayContent.X = horizontalOffset * 1.2f; - MenuCursorContainer.CanShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false; + GlobalCursorDisplay.ShowCursor = (ScreenStack.CurrentScreen as IOsuScreen)?.CursorVisible ?? false; } private void screenChanged(IScreen current, IScreen newScreen) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3b81b5c8cd..7b6cda17a2 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -138,7 +138,7 @@ namespace osu.Game protected RealmKeyBindingStore KeyBindingStore { get; private set; } - protected MenuCursorContainer MenuCursorContainer { get; private set; } + protected GlobalCursorDisplay GlobalCursorDisplay { get; private set; } protected MusicController MusicController { get; private set; } @@ -340,10 +340,10 @@ namespace osu.Game RelativeSizeAxes = Axes.Both, Child = CreateScalingContainer().WithChildren(new Drawable[] { - (MenuCursorContainer = new MenuCursorContainer + (GlobalCursorDisplay = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both - }).WithChild(content = new OsuTooltipContainer(MenuCursorContainer.Cursor) + }).WithChild(content = new OsuTooltipContainer(GlobalCursorDisplay.MenuCursor) { RelativeSizeAxes = Axes.Both }), diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 95cb02c477..f7f62d2af0 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -380,7 +380,7 @@ namespace osu.Game.Rulesets.UI // only show the cursor when within the playfield, by default. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Playfield.ReceivePositionalInputAt(screenSpacePos); - CursorContainer IProvideCursor.Cursor => Playfield.Cursor; + CursorContainer IProvideCursor.MenuCursor => Playfield.Cursor; public override GameplayCursorContainer Cursor => Playfield.Cursor; diff --git a/osu.Game/Screens/Utility/LatencyArea.cs b/osu.Game/Screens/Utility/LatencyArea.cs index b7d45ba642..c8e0bf93a2 100644 --- a/osu.Game/Screens/Utility/LatencyArea.cs +++ b/osu.Game/Screens/Utility/LatencyArea.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Utility public readonly Bindable VisualMode = new Bindable(); - public CursorContainer? Cursor { get; private set; } + public CursorContainer? MenuCursor { get; private set; } public bool ProvidingUserCursor => IsActiveArea.Value; @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Utility { RelativeSizeAxes = Axes.Both, }, - Cursor = new LatencyCursorContainer + MenuCursor = new LatencyCursorContainer { RelativeSizeAxes = Axes.Both, }, @@ -105,7 +105,7 @@ namespace osu.Game.Screens.Utility { RelativeSizeAxes = Axes.Both, }, - Cursor = new LatencyCursorContainer + MenuCursor = new LatencyCursorContainer { RelativeSizeAxes = Axes.Both, }, @@ -119,7 +119,7 @@ namespace osu.Game.Screens.Utility { RelativeSizeAxes = Axes.Both, }, - Cursor = new LatencyCursorContainer + MenuCursor = new LatencyCursorContainer { RelativeSizeAxes = Axes.Both, }, diff --git a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs index eccd2efa96..9082ca9c58 100644 --- a/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs +++ b/osu.Game/Tests/Visual/OsuManualInputManagerTestScene.cs @@ -38,11 +38,11 @@ namespace osu.Game.Tests.Visual protected OsuManualInputManagerTestScene() { - MenuCursorContainer cursorContainer; + GlobalCursorDisplay cursorDisplay; - CompositeDrawable mainContent = cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }; + CompositeDrawable mainContent = cursorDisplay = new GlobalCursorDisplay { RelativeSizeAxes = Axes.Both }; - cursorContainer.Child = content = new OsuTooltipContainer(cursorContainer.Cursor) + cursorDisplay.Child = content = new OsuTooltipContainer(cursorDisplay.MenuCursor) { RelativeSizeAxes = Axes.Both }; From 1b2158ff048da3e37c13f908eac533e4cefe2aca Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 26 Jul 2022 14:15:59 +0900 Subject: [PATCH 311/481] Remove unused method --- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 725f94e7db..7d51284f46 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -156,13 +156,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline setZoomTarget(zoomTarget + change * (maxZoom - minZoom) * zoom_change_sensitivity, focusPoint); } - protected void SetZoomImmediately(float value, float min, float max) - { - maxZoom = max; - minZoom = min; - CurrentZoom = zoomTarget = value; - } - private float zoomTarget = 1; private void setZoomTarget(float newZoom, float? focusPoint = null) From 7d8a78ef015b10dcb77edf9b4c14c890b22fee88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 14:53:20 +0900 Subject: [PATCH 312/481] Move tests to own class --- .../Database/BeatmapImporterTests.cs | 55 ------------- .../Database/BeatmapImporterUpdateTests.cs | 81 +++++++++++++++++++ 2 files changed, 81 insertions(+), 55 deletions(-) create mode 100644 osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 3b1f82375c..66384e8cd1 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -725,61 +725,6 @@ namespace osu.Game.Tests.Database }); } - [Test] - public void TestImportThenReimportWithNewDifficultyAsUpdate() - { - RunTestWithRealmAsync(async (realm, storage) => - { - var importer = new BeatmapImporter(storage, realm); - using var store = new RealmRulesetStore(realm, storage); - - string? pathOriginal = TestResources.GetTestBeatmapForImport(); - - string pathMissingOneBeatmap = pathOriginal.Replace(".osz", "_missing_difficulty.osz"); - - string extractedFolder = $"{pathOriginal}_extracted"; - Directory.CreateDirectory(extractedFolder); - - try - { - using (var zip = ZipArchive.Open(pathOriginal)) - zip.WriteToDirectory(extractedFolder); - - // remove one difficulty before first import - new FileInfo(Directory.GetFiles(extractedFolder, "*.osu").First()).Delete(); - - using (var zip = ZipArchive.Create()) - { - zip.AddAllFromDirectory(extractedFolder); - zip.SaveTo(pathMissingOneBeatmap, new ZipWriterOptions(CompressionType.Deflate)); - } - - var firstImport = await importer.Import(new ImportTask(pathMissingOneBeatmap)); - Assert.That(firstImport, Is.Not.Null); - Debug.Assert(firstImport != null); - - Assert.That(realm.Realm.All().Where(s => !s.DeletePending), Has.Count.EqualTo(1)); - Assert.That(realm.Realm.All().First(s => !s.DeletePending).Beatmaps, Has.Count.EqualTo(11)); - - // Second import matches first but contains one extra .osu file. - var secondImport = (await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginal), firstImport.Value)).FirstOrDefault(); - Assert.That(secondImport, Is.Not.Null); - Debug.Assert(secondImport != null); - - Assert.That(realm.Realm.All(), Has.Count.EqualTo(12)); - Assert.That(realm.Realm.All(), Has.Count.EqualTo(12)); - Assert.That(realm.Realm.All(), Has.Count.EqualTo(1)); - - // check the newly "imported" beatmap is not the original. - Assert.That(firstImport.ID, Is.Not.EqualTo(secondImport.ID)); - } - finally - { - Directory.Delete(extractedFolder, true); - } - }); - } - [Test] public void TestImportThenReimportAfterMissingFiles() { diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs new file mode 100644 index 0000000000..713a73d64e --- /dev/null +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -0,0 +1,81 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics; +using System.IO; +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Overlays.Notifications; +using osu.Game.Rulesets; +using osu.Game.Tests.Resources; +using SharpCompress.Archives; +using SharpCompress.Archives.Zip; +using SharpCompress.Common; +using SharpCompress.Writers.Zip; + +namespace osu.Game.Tests.Database +{ + /// + /// Tests the flow where a beatmap is already loaded and an update is applied. + /// + [TestFixture] + public class BeatmapImporterUpdateTests : RealmTest + { + [Test] + public void TestImportThenUpdateWithNewDifficulty() + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var store = new RealmRulesetStore(realm, storage); + + string? pathOriginal = TestResources.GetTestBeatmapForImport(); + + string pathMissingOneBeatmap = pathOriginal.Replace(".osz", "_missing_difficulty.osz"); + + string extractedFolder = $"{pathOriginal}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + using (var zip = ZipArchive.Open(pathOriginal)) + zip.WriteToDirectory(extractedFolder); + + // remove one difficulty before first import + new FileInfo(Directory.GetFiles(extractedFolder, "*.osu").First()).Delete(); + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(extractedFolder); + zip.SaveTo(pathMissingOneBeatmap, new ZipWriterOptions(CompressionType.Deflate)); + } + + var firstImport = await importer.Import(new ImportTask(pathMissingOneBeatmap)); + Assert.That(firstImport, Is.Not.Null); + Debug.Assert(firstImport != null); + + Assert.That(realm.Realm.All().Where(s => !s.DeletePending), Has.Count.EqualTo(1)); + Assert.That(realm.Realm.All().First(s => !s.DeletePending).Beatmaps, Has.Count.EqualTo(11)); + + // Second import matches first but contains one extra .osu file. + var secondImport = (await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginal), firstImport.Value)).FirstOrDefault(); + Assert.That(secondImport, Is.Not.Null); + Debug.Assert(secondImport != null); + + Assert.That(realm.Realm.All(), Has.Count.EqualTo(12)); + Assert.That(realm.Realm.All(), Has.Count.EqualTo(12)); + Assert.That(realm.Realm.All(), Has.Count.EqualTo(1)); + + // check the newly "imported" beatmap is not the original. + Assert.That(firstImport.ID, Is.Not.EqualTo(secondImport.ID)); + } + finally + { + Directory.Delete(extractedFolder, true); + } + }); + } + } +} From 59d3cc52c445694f2874ff91d4f8bcb9538b6430 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 14:56:41 +0900 Subject: [PATCH 313/481] Avoid leaving left-over files after test run completes --- .../Database/BeatmapImporterTests.cs | 1 - osu.Game.Tests/Database/RealmLiveTests.cs | 30 ++++++++----------- osu.Game.Tests/Database/RealmTest.cs | 23 +++++--------- .../NonVisual/DataLoadTest.cs | 4 +-- 4 files changed, 23 insertions(+), 35 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 9ee88c0670..2e49aa7bdc 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -142,7 +142,6 @@ namespace osu.Game.Tests.Database { Task.Run(async () => { - // ReSharper disable once AccessToDisposedClosure var beatmapSet = await importer.Import(new ImportTask(TestResources.GetTestBeatmapStream(), "renatus.osz")); Assert.NotNull(beatmapSet); diff --git a/osu.Game.Tests/Database/RealmLiveTests.cs b/osu.Game.Tests/Database/RealmLiveTests.cs index 3615cebe6a..d853e75db0 100644 --- a/osu.Game.Tests/Database/RealmLiveTests.cs +++ b/osu.Game.Tests/Database/RealmLiveTests.cs @@ -32,31 +32,29 @@ namespace osu.Game.Tests.Database [Test] public void TestAccessAfterStorageMigrate() { - RunTestWithRealm((realm, storage) => + using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target")) { - var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); - - Live? liveBeatmap = null; - - realm.Run(r => + RunTestWithRealm((realm, storage) => { - r.Write(_ => r.Add(beatmap)); + var beatmap = new BeatmapInfo(CreateRuleset(), new BeatmapDifficulty(), new BeatmapMetadata()); - liveBeatmap = beatmap.ToLive(realm); - }); + Live? liveBeatmap = null; + + realm.Run(r => + { + r.Write(_ => r.Add(beatmap)); + + liveBeatmap = beatmap.ToLive(realm); + }); - using (var migratedStorage = new TemporaryNativeStorage("realm-test-migration-target")) - { migratedStorage.DeleteDirectory(string.Empty); using (realm.BlockAllOperations("testing")) - { storage.Migrate(migratedStorage); - } Assert.IsFalse(liveBeatmap?.PerformRead(l => l.Hidden)); - } - }); + }); + } } [Test] @@ -341,14 +339,12 @@ namespace osu.Game.Tests.Database liveBeatmap.PerformRead(resolved => { // retrieval causes an implicit refresh. even changes that aren't related to the retrieval are fired at this point. - // ReSharper disable once AccessToDisposedClosure Assert.AreEqual(2, outerRealm.All().Count()); Assert.AreEqual(1, changesTriggered); // can access properties without a crash. Assert.IsFalse(resolved.Hidden); - // ReSharper disable once AccessToDisposedClosure outerRealm.Write(r => { // can use with the main context. diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs index d6b3c1ff44..1b1878942b 100644 --- a/osu.Game.Tests/Database/RealmTest.cs +++ b/osu.Game.Tests/Database/RealmTest.cs @@ -4,11 +4,11 @@ using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; +using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO; @@ -20,22 +20,15 @@ namespace osu.Game.Tests.Database [TestFixture] public abstract class RealmTest { - private static readonly TemporaryNativeStorage storage; - - static RealmTest() - { - storage = new TemporaryNativeStorage("realm-test"); - storage.DeleteDirectory(string.Empty); - } - - protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "") + protected void RunTestWithRealm([InstantHandle] Action testAction, [CallerMemberName] string caller = "") { using (HeadlessGameHost host = new CleanRunHeadlessGameHost(callingMethodName: caller)) { host.Run(new RealmTestGame(() => { - // ReSharper disable once AccessToDisposedClosure - var testStorage = new OsuStorage(host, storage.GetStorageForDirectory(caller)); + var defaultStorage = host.Storage; + + var testStorage = new OsuStorage(host, defaultStorage); using (var realm = new RealmAccess(testStorage, OsuGameBase.CLIENT_DATABASE_FILENAME)) { @@ -58,7 +51,7 @@ namespace osu.Game.Tests.Database { host.Run(new RealmTestGame(async () => { - var testStorage = storage.GetStorageForDirectory(caller); + var testStorage = host.Storage; using (var realm = new RealmAccess(testStorage, OsuGameBase.CLIENT_DATABASE_FILENAME)) { @@ -116,7 +109,7 @@ namespace osu.Game.Tests.Database private class RealmTestGame : Framework.Game { - public RealmTestGame(Func work) + public RealmTestGame([InstantHandle] Func work) { // ReSharper disable once AsyncVoidLambda Scheduler.Add(async () => @@ -126,7 +119,7 @@ namespace osu.Game.Tests.Database }); } - public RealmTestGame(Action work) + public RealmTestGame([InstantHandle] Action work) { Scheduler.Add(() => { diff --git a/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs b/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs index ad776622be..df77b31191 100644 --- a/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs +++ b/osu.Game.Tournament.Tests/NonVisual/DataLoadTest.cs @@ -6,6 +6,7 @@ using System; using System.IO; using System.Threading.Tasks; +using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -27,7 +28,6 @@ namespace osu.Game.Tournament.Tests.NonVisual { var osu = new TestTournament(runOnLoadComplete: () => { - // ReSharper disable once AccessToDisposedClosure var storage = host.Storage.GetStorageForDirectory(Path.Combine("tournaments", "default")); using (var stream = storage.CreateFileSafely("bracket.json")) @@ -85,7 +85,7 @@ namespace osu.Game.Tournament.Tests.NonVisual public new Task BracketLoadTask => base.BracketLoadTask; - public TestTournament(bool resetRuleset = false, Action runOnLoadComplete = null) + public TestTournament(bool resetRuleset = false, [InstantHandle] Action runOnLoadComplete = null) { this.resetRuleset = resetRuleset; this.runOnLoadComplete = runOnLoadComplete; From ac553d22ea076a161743a0fefdc67099b37b0d4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 15:05:01 +0900 Subject: [PATCH 314/481] Fix left over resource file which isn't actually imported --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 2e49aa7bdc..af285f82b1 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -310,6 +310,7 @@ namespace osu.Game.Tests.Database } finally { + File.Delete(temp); Directory.Delete(extractedFolder, true); } }); From 99bdf417175c8fa856fca0e9dba78babbf7005c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 15:12:22 +0900 Subject: [PATCH 315/481] Avoid potential realm fetch after disposal in `StatisticsPanel` As seen at https://github.com/ppy/osu/runs/7513799859?check_suite_focus=true. --- .../Screens/Ranking/Statistics/StatisticsPanel.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 435162e057..79d7b99e51 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -11,6 +11,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; @@ -90,18 +91,16 @@ namespace osu.Game.Screens.Ranking.Statistics spinner.Show(); var localCancellationSource = loadCancellation = new CancellationTokenSource(); - IBeatmap playableBeatmap = null; + + var workingBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo); // Todo: The placement of this is temporary. Eventually we'll both generate the playable beatmap _and_ run through it in a background task to generate the hit events. - Task.Run(() => - { - playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods); - }, loadCancellation.Token).ContinueWith(_ => Schedule(() => + Task.Run(() => workingBeatmap.GetPlayableBeatmap(newScore.Ruleset, newScore.Mods), loadCancellation.Token).ContinueWith(task => Schedule(() => { bool hitEventsAvailable = newScore.HitEvents.Count != 0; Container container; - var statisticRows = newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap); + var statisticRows = newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, task.GetResultSafely()); if (!hitEventsAvailable && statisticRows.SelectMany(r => r.Columns).All(c => c.RequiresHitEvents)) { From d7ef4170be894807edb5880d282d7f01844a5dfb Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 26 Jul 2022 09:36:25 +0300 Subject: [PATCH 316/481] Maintain sort stability by using carousel item ID as fallback --- osu.Game/Screens/Select/Carousel/CarouselGroup.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index e430ff3d3a..07c3c24018 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -24,6 +24,7 @@ namespace osu.Game.Screens.Select.Carousel private ulong currentChildID; private Comparer? criteriaComparer; + private Comparer? itemIDComparer; private FilterCriteria? lastCriteria; @@ -90,7 +91,8 @@ namespace osu.Game.Screens.Select.Carousel // IEnumerable.OrderBy() is used instead of List.Sort() to ensure sorting stability criteriaComparer = Comparer.Create((x, y) => x.CompareTo(criteria, y)); - items = items.OrderBy(c => c, criteriaComparer).ToList(); + itemIDComparer = Comparer.Create((x, y) => x.ChildID.CompareTo(y.ChildID)); + items = items.OrderBy(c => c, criteriaComparer).ThenBy(c => c, itemIDComparer).ToList(); lastCriteria = criteria; } From 693ac8750c214d43a28eb44fd76f12829d1c6177 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 26 Jul 2022 09:39:30 +0300 Subject: [PATCH 317/481] Remove remaining uses of "child" terminology in non-drawable components --- osu.Game/Screens/Select/Carousel/CarouselGroup.cs | 12 ++++++------ .../Select/Carousel/CarouselGroupEagerSelect.cs | 14 +++++++------- osu.Game/Screens/Select/Carousel/CarouselItem.cs | 4 ++-- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 07c3c24018..7fcf53e68c 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -7,7 +7,7 @@ using System.Linq; namespace osu.Game.Screens.Select.Carousel { /// - /// A group which ensures only one child is selected. + /// A group which ensures only one item is selected. /// public class CarouselGroup : CarouselItem { @@ -18,10 +18,10 @@ namespace osu.Game.Screens.Select.Carousel private List items = new List(); /// - /// Used to assign a monotonically increasing ID to children as they are added. This member is - /// incremented whenever a child is added. + /// Used to assign a monotonically increasing ID to items as they are added. This member is + /// incremented whenever an item is added. /// - private ulong currentChildID; + private ulong currentItemID; private Comparer? criteriaComparer; private Comparer? itemIDComparer; @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Select.Carousel public virtual void AddItem(CarouselItem i) { i.State.ValueChanged += state => ChildItemStateChanged(i, state.NewValue); - i.ChildID = ++currentChildID; + i.ItemID = ++currentItemID; if (lastCriteria != null) { @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Select.Carousel // IEnumerable.OrderBy() is used instead of List.Sort() to ensure sorting stability criteriaComparer = Comparer.Create((x, y) => x.CompareTo(criteria, y)); - itemIDComparer = Comparer.Create((x, y) => x.ChildID.CompareTo(y.ChildID)); + itemIDComparer = Comparer.Create((x, y) => x.ItemID.CompareTo(y.ItemID)); items = items.OrderBy(c => c, criteriaComparer).ThenBy(c => c, itemIDComparer).ToList(); lastCriteria = criteria; diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs index 613b3db5d5..61109829f3 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroupEagerSelect.cs @@ -10,7 +10,7 @@ using System.Linq; namespace osu.Game.Screens.Select.Carousel { /// - /// A group which ensures at least one child is selected (if the group itself is selected). + /// A group which ensures at least one item is selected (if the group itself is selected). /// public class CarouselGroupEagerSelect : CarouselGroup { @@ -35,16 +35,16 @@ namespace osu.Game.Screens.Select.Carousel /// /// To avoid overhead during filter operations, we don't attempt any selections until after all - /// children have been filtered. This bool will be true during the base + /// items have been filtered. This bool will be true during the base /// operation. /// - private bool filteringChildren; + private bool filteringItems; public override void Filter(FilterCriteria criteria) { - filteringChildren = true; + filteringItems = true; base.Filter(criteria); - filteringChildren = false; + filteringItems = false; attemptSelection(); } @@ -97,12 +97,12 @@ namespace osu.Game.Screens.Select.Carousel private void attemptSelection() { - if (filteringChildren) return; + if (filteringItems) return; // we only perform eager selection if we are a currently selected group. if (State.Value != CarouselItemState.Selected) return; - // we only perform eager selection if none of our children are in a selected state already. + // we only perform eager selection if none of our items are in a selected state already. if (Items.Any(i => i.State.Value == CarouselItemState.Selected)) return; PerformSelection(); diff --git a/osu.Game/Screens/Select/Carousel/CarouselItem.cs b/osu.Game/Screens/Select/Carousel/CarouselItem.cs index a901f72dad..cbf079eb4b 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselItem.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselItem.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Select.Carousel /// /// Used as a default sort method for s of differing types. /// - internal ulong ChildID; + internal ulong ItemID; /// /// Create a fresh drawable version of this item. @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select.Carousel { } - public virtual int CompareTo(FilterCriteria criteria, CarouselItem other) => ChildID.CompareTo(other.ChildID); + public virtual int CompareTo(FilterCriteria criteria, CarouselItem other) => ItemID.CompareTo(other.ItemID); public int CompareTo(CarouselItem other) => CarouselYPosition.CompareTo(other.CarouselYPosition); } From 8370ca976557116af528330389f7fe18583d0896 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 15:46:29 +0900 Subject: [PATCH 318/481] Add `ImportAsUpdate` method to `IModelImporter` to avoid otehr changes --- .../Database/BeatmapImporterUpdateTests.cs | 2 +- ...ceneOnlinePlayBeatmapAvailabilityTracker.cs | 2 +- osu.Game/Beatmaps/BeatmapImporter.cs | 10 ++++++---- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Beatmaps/BeatmapModelDownloader.cs | 18 +----------------- osu.Game/Database/IModelImporter.cs | 10 ++++++++++ osu.Game/Database/ModelDownloader.cs | 16 +++------------- osu.Game/Database/RealmArchiveModelImporter.cs | 2 ++ osu.Game/Scoring/ScoreManager.cs | 2 ++ .../Select/Carousel/UpdateBeatmapSetButton.cs | 2 +- osu.Game/Skinning/SkinManager.cs | 2 ++ 11 files changed, 30 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index 713a73d64e..cf4245ae83 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Database Assert.That(realm.Realm.All().First(s => !s.DeletePending).Beatmaps, Has.Count.EqualTo(11)); // Second import matches first but contains one extra .osu file. - var secondImport = (await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginal), firstImport.Value)).FirstOrDefault(); + var secondImport = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginal), firstImport.Value); Assert.That(secondImport, Is.Not.Null); Debug.Assert(secondImport != null); diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 0a980efbae..536322805b 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -237,7 +237,7 @@ namespace osu.Game.Tests.Online internal class TestBeatmapModelDownloader : BeatmapModelDownloader { - public TestBeatmapModelDownloader(BeatmapManager importer, IAPIProvider apiProvider) + public TestBeatmapModelDownloader(IModelImporter importer, IAPIProvider apiProvider) : base(importer, apiProvider) { } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 2a6121b541..1e90e14e68 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -41,16 +41,18 @@ namespace osu.Game.Beatmaps { } - public async Task>> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original) + public override async Task?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original) { var imported = await Import(notification, importTask); if (!imported.Any()) - return imported; + return null; Debug.Assert(imported.Count() == 1); - imported.First().PerformWrite(updated => + var first = imported.First(); + + first.PerformWrite(updated => { var realm = updated.Realm; @@ -103,7 +105,7 @@ namespace osu.Game.Beatmaps realm.Remove(original); }); - return imported; + return first; } protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == ".osz"; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 1b31d29c2b..debe4c6829 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -408,7 +408,7 @@ namespace osu.Game.Beatmaps Realm.Run(r => Undelete(r.All().Where(s => s.DeletePending).ToList())); } - public Task>> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original) => + public Task?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original) => beatmapImporter.ImportAsUpdate(notification, importTask, original); #region Implementation of ICanAcceptFiles diff --git a/osu.Game/Beatmaps/BeatmapModelDownloader.cs b/osu.Game/Beatmaps/BeatmapModelDownloader.cs index e70168f1b3..4295def5c3 100644 --- a/osu.Game/Beatmaps/BeatmapModelDownloader.cs +++ b/osu.Game/Beatmaps/BeatmapModelDownloader.cs @@ -1,39 +1,23 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; -using System.Threading.Tasks; using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.API.Requests; -using osu.Game.Overlays.Notifications; namespace osu.Game.Beatmaps { public class BeatmapModelDownloader : ModelDownloader { - private readonly BeatmapManager beatmapManager; - protected override ArchiveDownloadRequest CreateDownloadRequest(IBeatmapSetInfo set, bool minimiseDownloadSize) => new DownloadBeatmapSetRequest(set, minimiseDownloadSize); public override ArchiveDownloadRequest? GetExistingDownload(IBeatmapSetInfo model) => CurrentDownloads.Find(r => r.Model.OnlineID == model.OnlineID); - public BeatmapModelDownloader(BeatmapManager beatmapImporter, IAPIProvider api) + public BeatmapModelDownloader(IModelImporter beatmapImporter, IAPIProvider api) : base(beatmapImporter, api) { - beatmapManager = beatmapImporter; } - - protected override Task>> Import(ProgressNotification notification, string filename, BeatmapSetInfo? originalModel) - { - if (originalModel != null) - return beatmapManager.ImportAsUpdate(notification, new ImportTask(filename), originalModel); - - return base.Import(notification, filename, null); - } - - public bool Update(BeatmapSetInfo model) => Download(model, false, model); } } diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index ebb8be39ef..4085f122d0 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.cs @@ -23,6 +23,16 @@ namespace osu.Game.Database /// The imported models. Task>> Import(ProgressNotification notification, params ImportTask[] tasks); + /// + /// Process a single import as an update for an existing model. + /// This will still run a full import, but perform any post-processing required to make it feel like an update to the user. + /// + /// The notification to update. + /// The import task. + /// The original model which is being updated. + /// The imported model. + Task?> ImportAsUpdate(ProgressNotification notification, ImportTask task, TModel original); + /// /// A user displayable name for the model type associated with this manager. /// diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index 5fd124cafc..7ce0fa46b7 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -44,6 +44,8 @@ namespace osu.Game.Database public bool Download(T model, bool minimiseDownloadSize = false) => Download(model, minimiseDownloadSize, null); + public void DownloadAsUpdate(TModel originalModel) => Download(originalModel, false, originalModel); + protected bool Download(T model, bool minimiseDownloadSize, TModel? originalModel) { if (!canDownload(model)) return false; @@ -66,7 +68,7 @@ namespace osu.Game.Database Task.Factory.StartNew(async () => { // This gets scheduled back to the update thread, but we want the import to run in the background. - var imported = await Import(notification, filename, originalModel).ConfigureAwait(false); + var imported = await importer.Import(notification, new ImportTask(filename)).ConfigureAwait(false); // for now a failed import will be marked as a failed download for simplicity. if (!imported.Any()) @@ -105,18 +107,6 @@ namespace osu.Game.Database } } - /// - /// Run the post-download import for the model. - /// - /// The notification to update. - /// The path of the temporary downloaded file. - /// An optional model for update scenarios, to be used as a reference. - /// The imported model. - protected virtual Task>> Import(ProgressNotification notification, string filename, TModel? originalModel) - { - return importer.Import(notification, new ImportTask(filename)); - } - public abstract ArchiveDownloadRequest? GetExistingDownload(T model); private bool canDownload(T model) => GetExistingDownload(model) == null && api != null; diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index aa7fac07a8..a0cf98b978 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -174,6 +174,8 @@ namespace osu.Game.Database return imported; } + public virtual Task?> ImportAsUpdate(ProgressNotification notification, ImportTask task, TModel original) => throw new NotImplementedException(); + /// /// Import one from the filesystem and delete the file on success. /// Note that this bypasses the UI flow and should only be used for special cases or testing. diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index 7cfc55580b..7367a1ef77 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -268,6 +268,8 @@ namespace osu.Game.Scoring public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) => scoreImporter.Import(notification, tasks); + public Task> ImportAsUpdate(ProgressNotification notification, ImportTask task, ScoreInfo original) => scoreImporter.ImportAsUpdate(notification, task, original); + public Live Import(ScoreInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default) => scoreImporter.ImportModel(item, archive, batchImport, cancellationToken); diff --git a/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs index 3746c1975c..73e7d23df0 100644 --- a/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs +++ b/osu.Game/Screens/Select/Carousel/UpdateBeatmapSetButton.cs @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Select.Carousel Action = () => { - beatmapDownloader.Update(beatmapSetInfo); + beatmapDownloader.DownloadAsUpdate(beatmapSetInfo); attachExistingDownload(); }; } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index dc0197e613..dd35f83434 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -272,6 +272,8 @@ namespace osu.Game.Skinning public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) => skinImporter.Import(notification, tasks); + public Task> ImportAsUpdate(ProgressNotification notification, ImportTask task, SkinInfo original) => skinImporter.ImportAsUpdate(notification, task, original); + public Task> Import(ImportTask task, bool batchImport = false, CancellationToken cancellationToken = default) => skinImporter.Import(task, batchImport, cancellationToken); #endregion From 9939866f7d3e65796fa61c39880c36a38b3258aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 15:54:10 +0900 Subject: [PATCH 319/481] Revert one more missed change --- osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs index bcbe7084d5..80af4108c7 100644 --- a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs +++ b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs @@ -11,6 +11,7 @@ using System.Text.RegularExpressions; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Database; using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -108,7 +109,7 @@ namespace osu.Game.Beatmaps.Drawables private class BundledBeatmapModelDownloader : BeatmapModelDownloader { - public BundledBeatmapModelDownloader(BeatmapManager beatmapImporter, IAPIProvider api) + public BundledBeatmapModelDownloader(IModelImporter beatmapImporter, IAPIProvider api) : base(beatmapImporter, api) { } From a4f6f2b9eb8ad8a5e0f0ca35d6c641a3fc411360 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 26 Jul 2022 09:55:37 +0300 Subject: [PATCH 320/481] Make item ID comparer static --- osu.Game/Screens/Select/Carousel/CarouselGroup.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 7fcf53e68c..8d141b6f72 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -24,7 +24,8 @@ namespace osu.Game.Screens.Select.Carousel private ulong currentItemID; private Comparer? criteriaComparer; - private Comparer? itemIDComparer; + + private static readonly Comparer item_id_comparer = Comparer.Create((x, y) => x.ItemID.CompareTo(y.ItemID)); private FilterCriteria? lastCriteria; @@ -91,8 +92,7 @@ namespace osu.Game.Screens.Select.Carousel // IEnumerable.OrderBy() is used instead of List.Sort() to ensure sorting stability criteriaComparer = Comparer.Create((x, y) => x.CompareTo(criteria, y)); - itemIDComparer = Comparer.Create((x, y) => x.ItemID.CompareTo(y.ItemID)); - items = items.OrderBy(c => c, criteriaComparer).ThenBy(c => c, itemIDComparer).ToList(); + items = items.OrderBy(c => c, criteriaComparer).ThenBy(c => c, item_id_comparer).ToList(); lastCriteria = criteria; } From 91ffa7007f85f56cbe96f1c23111624a16e13172 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 26 Jul 2022 10:24:16 +0300 Subject: [PATCH 321/481] Improve existing test coverage to cover order changes from other sort modes --- .../Visual/SongSelect/TestSceneBeatmapCarousel.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index e574ee30fb..59932f8781 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -522,15 +522,15 @@ namespace osu.Game.Tests.Visual.SongSelect { var sets = new List(); - for (int i = 0; i < 20; i++) + for (int i = 0; i < 10; i++) { var set = TestResources.CreateTestBeatmapSetInfo(); // only need to set the first as they are a shared reference. var beatmap = set.Beatmaps.First(); - beatmap.Metadata.Artist = "same artist"; - beatmap.Metadata.Title = "same title"; + beatmap.Metadata.Artist = $"artist {i / 2}"; + beatmap.Metadata.Title = $"title {9 - i}"; sets.Add(set); } @@ -540,10 +540,13 @@ namespace osu.Game.Tests.Visual.SongSelect loadBeatmaps(sets); AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); - AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == index + idOffset).All(b => b)); + AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b)); AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false)); - AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == index + idOffset).All(b => b)); + AddAssert("Items are in reverse order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + sets.Count - index - 1).All(b => b)); + + AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); + AddAssert("Items reset to original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b)); } [Test] From 846291d20387ecf0e23669edb100ec288a3675b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 16:30:44 +0900 Subject: [PATCH 322/481] Refactor new tests to not suck as much as the old importer tests --- .../Database/BeatmapImporterUpdateTests.cs | 107 +++++++++++------- 1 file changed, 69 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index cf4245ae83..38c5172227 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -1,15 +1,19 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Diagnostics; using System.IO; using System.Linq; +using System.Linq.Expressions; using NUnit.Framework; +using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; using osu.Game.Tests.Resources; +using Realms; using SharpCompress.Archives; using SharpCompress.Archives.Zip; using SharpCompress.Common; @@ -24,58 +28,85 @@ namespace osu.Game.Tests.Database public class BeatmapImporterUpdateTests : RealmTest { [Test] - public void TestImportThenUpdateWithNewDifficulty() + public void TestNewDifficultyAdded() { RunTestWithRealmAsync(async (realm, storage) => { var importer = new BeatmapImporter(storage, realm); - using var store = new RealmRulesetStore(realm, storage); + using var rulesets = new RealmRulesetStore(realm, storage); - string? pathOriginal = TestResources.GetTestBeatmapForImport(); - - string pathMissingOneBeatmap = pathOriginal.Replace(".osz", "_missing_difficulty.osz"); - - string extractedFolder = $"{pathOriginal}_extracted"; - Directory.CreateDirectory(extractedFolder); - - try + using var __ = getBeatmapArchive(out string pathOriginal); + using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory => { - using (var zip = ZipArchive.Open(pathOriginal)) - zip.WriteToDirectory(extractedFolder); - // remove one difficulty before first import - new FileInfo(Directory.GetFiles(extractedFolder, "*.osu").First()).Delete(); + directory.GetFiles("*.osu").First().Delete(); + }); - using (var zip = ZipArchive.Create()) - { - zip.AddAllFromDirectory(extractedFolder); - zip.SaveTo(pathMissingOneBeatmap, new ZipWriterOptions(CompressionType.Deflate)); - } + var importBeforeUpdate = await importer.Import(new ImportTask(pathMissingOneBeatmap)); - var firstImport = await importer.Import(new ImportTask(pathMissingOneBeatmap)); - Assert.That(firstImport, Is.Not.Null); - Debug.Assert(firstImport != null); + Assert.That(importBeforeUpdate, Is.Not.Null); + Debug.Assert(importBeforeUpdate != null); - Assert.That(realm.Realm.All().Where(s => !s.DeletePending), Has.Count.EqualTo(1)); - Assert.That(realm.Realm.All().First(s => !s.DeletePending).Beatmaps, Has.Count.EqualTo(11)); + checkCount(realm, 1, s => !s.DeletePending); + Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(11)); - // Second import matches first but contains one extra .osu file. - var secondImport = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginal), firstImport.Value); - Assert.That(secondImport, Is.Not.Null); - Debug.Assert(secondImport != null); + // Second import matches first but contains one extra .osu file. + var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginal), importBeforeUpdate.Value); - Assert.That(realm.Realm.All(), Has.Count.EqualTo(12)); - Assert.That(realm.Realm.All(), Has.Count.EqualTo(12)); - Assert.That(realm.Realm.All(), Has.Count.EqualTo(1)); + Assert.That(importAfterUpdate, Is.Not.Null); + Debug.Assert(importAfterUpdate != null); - // check the newly "imported" beatmap is not the original. - Assert.That(firstImport.ID, Is.Not.EqualTo(secondImport.ID)); - } - finally - { - Directory.Delete(extractedFolder, true); - } + checkCount(realm, 12); + checkCount(realm, 12); + checkCount(realm, 1); + + // check the newly "imported" beatmap is not the original. + Assert.That(importBeforeUpdate.ID, Is.Not.EqualTo(importAfterUpdate.ID)); }); } + + private static void checkCount(RealmAccess realm, int expected, Expression>? condition = null) where T : RealmObject + { + var query = realm.Realm.All(); + + if (condition != null) + query = query.Where(condition); + + Assert.That(query, Has.Count.EqualTo(expected)); + } + + private static IDisposable getBeatmapArchiveWithModifications(out string path, Action applyModifications) + { + var cleanup = getBeatmapArchive(out path); + + string extractedFolder = $"{path}_extracted"; + Directory.CreateDirectory(extractedFolder); + + using (var zip = ZipArchive.Open(path)) + zip.WriteToDirectory(extractedFolder); + + applyModifications(new DirectoryInfo(extractedFolder)); + + File.Delete(path); + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(extractedFolder); + zip.SaveTo(path, new ZipWriterOptions(CompressionType.Deflate)); + } + + Directory.Delete(extractedFolder, true); + + return cleanup; + } + + private static IDisposable getBeatmapArchive(out string path, bool quick = true) + { + string beatmapPath = TestResources.GetTestBeatmapForImport(quick); + + path = beatmapPath; + + return new InvokeOnDisposal(() => File.Delete(beatmapPath)); + } } } From 4c22b55ce345a7e0b05aef98f165e1969fd42332 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 17:00:28 +0900 Subject: [PATCH 323/481] Fix incorrect handling if an update is processed with no changes --- osu.Game/Beatmaps/BeatmapImporter.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 1e90e14e68..ef0e76234a 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -52,6 +52,10 @@ namespace osu.Game.Beatmaps var first = imported.First(); + // If there were no changes, ensure we don't accidentally nuke ourselves. + if (first.ID == original.ID) + return first; + first.PerformWrite(updated => { var realm = updated.Realm; From aaf6ec05bbef6d30b9be0e5e8f9775e882c5c3c5 Mon Sep 17 00:00:00 2001 From: Andrew Hong Date: Tue, 26 Jul 2022 04:19:54 -0400 Subject: [PATCH 324/481] Remove notification upon copy --- .../Graphics/UserInterface/ExternalLinkButton.cs | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 166546bb8a..acabeca66e 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -13,8 +13,6 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Platform; -using osu.Game.Overlays; -using osu.Game.Overlays.Notifications; using osuTK; using osuTK.Graphics; @@ -24,9 +22,6 @@ namespace osu.Game.Graphics.UserInterface { public string Link { get; set; } - [Resolved] - private INotificationOverlay notificationOverlay { get; set; } - private Color4 hoverColour; [Resolved] @@ -49,15 +44,6 @@ namespace osu.Game.Graphics.UserInterface }; } - private void copyUrl() - { - host.GetClipboard()?.SetText(Link); - notificationOverlay.Post(new SimpleNotification - { - Text = "Copied URL!" - }); - } - public MenuItem[] ContextMenuItems { get @@ -67,7 +53,7 @@ namespace osu.Game.Graphics.UserInterface if (Link != null) { items.Add(new OsuMenuItem("Open", MenuItemType.Standard, () => host.OpenUrlExternally(Link))); - items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, () => copyUrl())); + items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, () => host.GetClipboard()?.SetText(Link))); } return items.ToArray(); From 1221cb1a42a68265c0f80151514b6952442e9fdf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 17:22:52 +0900 Subject: [PATCH 325/481] Add comprehensive test coverage of update scenarios --- .../Database/BeatmapImporterUpdateTests.cs | 289 +++++++++++++++++- 1 file changed, 286 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index 38c5172227..515bed5c35 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -10,8 +10,10 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Beatmaps; using osu.Game.Database; +using osu.Game.Models; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; +using osu.Game.Scoring; using osu.Game.Tests.Resources; using Realms; using SharpCompress.Archives; @@ -27,6 +29,8 @@ namespace osu.Game.Tests.Database [TestFixture] public class BeatmapImporterUpdateTests : RealmTest { + private const int count_beatmaps = 12; + [Test] public void TestNewDifficultyAdded() { @@ -48,7 +52,7 @@ namespace osu.Game.Tests.Database Debug.Assert(importBeforeUpdate != null); checkCount(realm, 1, s => !s.DeletePending); - Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(11)); + Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps - 1)); // Second import matches first but contains one extra .osu file. var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginal), importBeforeUpdate.Value); @@ -56,12 +60,291 @@ namespace osu.Game.Tests.Database Assert.That(importAfterUpdate, Is.Not.Null); Debug.Assert(importAfterUpdate != null); - checkCount(realm, 12); - checkCount(realm, 12); + checkCount(realm, count_beatmaps); + checkCount(realm, count_beatmaps); checkCount(realm, 1); // check the newly "imported" beatmap is not the original. Assert.That(importBeforeUpdate.ID, Is.Not.EqualTo(importAfterUpdate.ID)); + + // Previous beatmap set has no beatmaps so will be completely purged on the spot. + Assert.That(importBeforeUpdate.Value.IsValid, Is.False); + }); + } + + [Test] + public void TestExistingDifficultyModified() + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var rulesets = new RealmRulesetStore(realm, storage); + + using var __ = getBeatmapArchive(out string pathOriginal); + using var _ = getBeatmapArchiveWithModifications(out string pathModified, directory => + { + // Modify one .osu file with different content. + var firstOsuFile = directory.GetFiles("*.osu").First(); + + string existingContent = File.ReadAllText(firstOsuFile.FullName); + + File.WriteAllText(firstOsuFile.FullName, existingContent + "\n# I am new content"); + }); + + var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal)); + + Assert.That(importBeforeUpdate, Is.Not.Null); + Debug.Assert(importBeforeUpdate != null); + + checkCount(realm, 1, s => !s.DeletePending); + Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps)); + + // Second import matches first but contains one extra .osu file. + var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathModified), importBeforeUpdate.Value); + + Assert.That(importAfterUpdate, Is.Not.Null); + Debug.Assert(importAfterUpdate != null); + + // should only contain the modified beatmap (others purged). + Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(1)); + Assert.That(importAfterUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps)); + + checkCount(realm, count_beatmaps + 1); + checkCount(realm, count_beatmaps + 1); + + checkCount(realm, 1, s => !s.DeletePending); + checkCount(realm, 1, s => s.DeletePending); + }); + } + + [Test] + public void TestExistingDifficultyRemoved() + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var rulesets = new RealmRulesetStore(realm, storage); + + using var __ = getBeatmapArchive(out string pathOriginal); + using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory => + { + // remove one difficulty before first import + directory.GetFiles("*.osu").First().Delete(); + }); + + var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal)); + + Assert.That(importBeforeUpdate, Is.Not.Null); + Debug.Assert(importBeforeUpdate != null); + + Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps)); + Assert.That(importBeforeUpdate.Value.Beatmaps.First().OnlineID, Is.GreaterThan(-1)); + + // Second import matches first but contains one extra .osu file. + var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathMissingOneBeatmap), importBeforeUpdate.Value); + + Assert.That(importAfterUpdate, Is.Not.Null); + Debug.Assert(importAfterUpdate != null); + + checkCount(realm, count_beatmaps); + checkCount(realm, count_beatmaps); + checkCount(realm, 2); + + // previous set should contain the removed beatmap still. + Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(1)); + Assert.That(importBeforeUpdate.Value.Beatmaps.First().OnlineID, Is.EqualTo(-1)); + + // Previous beatmap set has no beatmaps so will be completely purged on the spot. + Assert.That(importAfterUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps - 1)); + }); + } + + [Test] + public void TestUpdatedImportContainsNothing() + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var rulesets = new RealmRulesetStore(realm, storage); + + using var __ = getBeatmapArchive(out string pathOriginal); + using var _ = getBeatmapArchiveWithModifications(out string pathEmpty, directory => + { + foreach (var file in directory.GetFiles()) + file.Delete(); + }); + + var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal)); + + Assert.That(importBeforeUpdate, Is.Not.Null); + Debug.Assert(importBeforeUpdate != null); + + var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathEmpty), importBeforeUpdate.Value); + Assert.That(importAfterUpdate, Is.Null); + + checkCount(realm, 1); + checkCount(realm, count_beatmaps); + checkCount(realm, count_beatmaps); + + Assert.That(importBeforeUpdate.Value.IsValid, Is.True); + }); + } + + [Test] + public void TestNoChanges() + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var rulesets = new RealmRulesetStore(realm, storage); + + using var __ = getBeatmapArchive(out string pathOriginal); + using var _ = getBeatmapArchive(out string pathOriginalSecond); + + var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal)); + + Assert.That(importBeforeUpdate, Is.Not.Null); + Debug.Assert(importBeforeUpdate != null); + + var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginalSecond), importBeforeUpdate.Value); + + Assert.That(importAfterUpdate, Is.Not.Null); + Debug.Assert(importAfterUpdate != null); + + checkCount(realm, 1); + checkCount(realm, count_beatmaps); + checkCount(realm, count_beatmaps); + + Assert.That(importBeforeUpdate.Value.Beatmaps.First().OnlineID, Is.GreaterThan(-1)); + Assert.That(importBeforeUpdate.ID, Is.EqualTo(importAfterUpdate.ID)); + }); + } + + [Test] + public void TestScoreTransferredOnUnchanged() + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var rulesets = new RealmRulesetStore(realm, storage); + + using var __ = getBeatmapArchive(out string pathOriginal); + using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory => + { + // arbitrary beatmap removal + directory.GetFiles("*.osu").First().Delete(); + }); + + var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal)); + + Assert.That(importBeforeUpdate, Is.Not.Null); + Debug.Assert(importBeforeUpdate != null); + + string scoreTargetBeatmapHash = string.Empty; + + importBeforeUpdate.PerformWrite(s => + { + var beatmapInfo = s.Beatmaps.Last(); + scoreTargetBeatmapHash = beatmapInfo.Hash; + s.Realm.Add(new ScoreInfo(beatmapInfo, s.Realm.All().First(), new RealmUser())); + }); + + checkCount(realm, 1); + + var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathMissingOneBeatmap), importBeforeUpdate.Value); + + Assert.That(importAfterUpdate, Is.Not.Null); + Debug.Assert(importAfterUpdate != null); + + checkCount(realm, count_beatmaps); + checkCount(realm, count_beatmaps); + checkCount(realm, 2); + + // score is transferred across to the new set + checkCount(realm, 1); + Assert.That(importAfterUpdate.Value.Beatmaps.First(b => b.Hash == scoreTargetBeatmapHash).Scores, Has.Count.EqualTo(1)); + }); + } + + [Test] + public void TestScoreLostOnModification() + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var rulesets = new RealmRulesetStore(realm, storage); + + using var __ = getBeatmapArchive(out string pathOriginal); + + var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal)); + + Assert.That(importBeforeUpdate, Is.Not.Null); + Debug.Assert(importBeforeUpdate != null); + + string? scoreTargetFilename = string.Empty; + + importBeforeUpdate.PerformWrite(s => + { + var beatmapInfo = s.Beatmaps.Last(); + scoreTargetFilename = beatmapInfo.File?.Filename; + s.Realm.Add(new ScoreInfo(beatmapInfo, s.Realm.All().First(), new RealmUser())); + }); + + checkCount(realm, 1); + + using var _ = getBeatmapArchiveWithModifications(out string pathModified, directory => + { + // Modify one .osu file with different content. + var firstOsuFile = directory.GetFiles(scoreTargetFilename).First(); + + string existingContent = File.ReadAllText(firstOsuFile.FullName); + + File.WriteAllText(firstOsuFile.FullName, existingContent + "\n# I am new content"); + }); + + var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathModified), importBeforeUpdate.Value); + + Assert.That(importAfterUpdate, Is.Not.Null); + Debug.Assert(importAfterUpdate != null); + + checkCount(realm, count_beatmaps + 1); + checkCount(realm, count_beatmaps + 1); + checkCount(realm, 2); + + // score is not transferred due to modifications. + checkCount(realm, 1); + Assert.That(importBeforeUpdate.Value.Beatmaps.AsEnumerable().First(b => b.File?.Filename == scoreTargetFilename).Scores, Has.Count.EqualTo(1)); + Assert.That(importAfterUpdate.Value.Beatmaps.AsEnumerable().First(b => b.File?.Filename == scoreTargetFilename).Scores, Has.Count.EqualTo(0)); + }); + } + + [Test] + public void TestMetadataTransferred() + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var rulesets = new RealmRulesetStore(realm, storage); + + using var __ = getBeatmapArchive(out string pathOriginal); + using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory => + { + // arbitrary beatmap removal + directory.GetFiles("*.osu").First().Delete(); + }); + + var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal)); + + Assert.That(importBeforeUpdate, Is.Not.Null); + Debug.Assert(importBeforeUpdate != null); + + var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathMissingOneBeatmap), importBeforeUpdate.Value); + + Assert.That(importAfterUpdate, Is.Not.Null); + Debug.Assert(importAfterUpdate != null); + + Assert.That(importBeforeUpdate.ID, Is.Not.EqualTo(importAfterUpdate.ID)); + Assert.That(importBeforeUpdate.Value.DateAdded, Is.EqualTo(importAfterUpdate.Value.DateAdded)); }); } From 003aec86ae8b2304804d129c3c90cf2776aa627e Mon Sep 17 00:00:00 2001 From: Andrew Hong Date: Tue, 26 Jul 2022 04:27:22 -0400 Subject: [PATCH 326/481] Rearrange sizeaxes --- osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index c02c42786b..9e14122ae4 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -93,7 +93,8 @@ namespace osu.Game.Overlays.BeatmapSet }, new OsuContextMenuContainer { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Child = new Container { RelativeSizeAxes = Axes.X, From ee694c12576369bd0b04cf58a4b4607e22812375 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 17:27:23 +0900 Subject: [PATCH 327/481] Add test coverage of no online ID scenario --- .../Database/BeatmapImporterUpdateTests.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index 515bed5c35..a82386fd51 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -72,6 +72,58 @@ namespace osu.Game.Tests.Database }); } + /// + /// Regression test covering https://github.com/ppy/osu/issues/19369 (import potentially duplicating if original has no ). + /// + [Test] + public void TestNewDifficultyAddedNoOnlineID() + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var rulesets = new RealmRulesetStore(realm, storage); + + using var __ = getBeatmapArchive(out string pathOriginal); + using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory => + { + // remove one difficulty before first import + directory.GetFiles("*.osu").First().Delete(); + }); + + var importBeforeUpdate = await importer.Import(new ImportTask(pathMissingOneBeatmap)); + + Assert.That(importBeforeUpdate, Is.Not.Null); + Debug.Assert(importBeforeUpdate != null); + + // This test is the same as TestNewDifficultyAdded except for this block. + importBeforeUpdate.PerformWrite(s => + { + s.OnlineID = -1; + foreach (var beatmap in s.Beatmaps) + beatmap.ResetOnlineInfo(); + }); + + checkCount(realm, 1, s => !s.DeletePending); + Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps - 1)); + + // Second import matches first but contains one extra .osu file. + var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginal), importBeforeUpdate.Value); + + Assert.That(importAfterUpdate, Is.Not.Null); + Debug.Assert(importAfterUpdate != null); + + checkCount(realm, count_beatmaps); + checkCount(realm, count_beatmaps); + checkCount(realm, 1); + + // check the newly "imported" beatmap is not the original. + Assert.That(importBeforeUpdate.ID, Is.Not.EqualTo(importAfterUpdate.ID)); + + // Previous beatmap set has no beatmaps so will be completely purged on the spot. + Assert.That(importBeforeUpdate.Value.IsValid, Is.False); + }); + } + [Test] public void TestExistingDifficultyModified() { From 1539fa704bb1b8c1b64a297862007ba615d6bc78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 17:46:23 +0900 Subject: [PATCH 328/481] Always allow selecting the top-most button using the select binding --- osu.Game/Overlays/DialogOverlay.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 493cd66258..ba8083e535 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -121,7 +121,11 @@ namespace osu.Game.Overlays switch (e.Action) { case GlobalAction.Select: - CurrentDialog?.Buttons.OfType().FirstOrDefault()?.TriggerClick(); + var clickableButton = + CurrentDialog?.Buttons.OfType().FirstOrDefault() ?? + CurrentDialog?.Buttons.First(); + + clickableButton?.TriggerClick(); return true; } From 91732719005dde9e7aa6e031f8a76067fc4c873a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 17:58:09 +0900 Subject: [PATCH 329/481] Fix new update pathway not actually being used --- osu.Game/Database/ModelDownloader.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index 7ce0fa46b7..edb8563c65 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -67,11 +67,15 @@ namespace osu.Game.Database { Task.Factory.StartNew(async () => { - // This gets scheduled back to the update thread, but we want the import to run in the background. - var imported = await importer.Import(notification, new ImportTask(filename)).ConfigureAwait(false); + bool importSuccessful; + + if (originalModel != null) + importSuccessful = (await importer.ImportAsUpdate(notification, new ImportTask(filename), originalModel)) != null; + else + importSuccessful = (await importer.Import(notification, new ImportTask(filename))).Any(); // for now a failed import will be marked as a failed download for simplicity. - if (!imported.Any()) + if (!importSuccessful) DownloadFailed?.Invoke(request); CurrentDownloads.Remove(request); From e782590b3cc3f58a4a126041dc40a702edea6c66 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 Jul 2022 18:20:43 +0900 Subject: [PATCH 330/481] Don't show audio playback issue notification if debugger is attached I've hit this countless times recently when debugging during the startup procedure. --- osu.Game/Screens/Menu/IntroScreen.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index c1621ce78f..a2ecd7eacb 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Diagnostics; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -195,10 +196,14 @@ namespace osu.Game.Screens.Menu PrepareMenuLoad(); LoadMenu(); - notifications.Post(new SimpleErrorNotification + + if (!Debugger.IsAttached) { - Text = "osu! doesn't seem to be able to play audio correctly.\n\nPlease try changing your audio device to a working setting." - }); + notifications.Post(new SimpleErrorNotification + { + Text = "osu! doesn't seem to be able to play audio correctly.\n\nPlease try changing your audio device to a working setting." + }); + } }, 5000); } From e28584da89b2694ebbaff3e75c91a7747594207e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 23:44:14 +0800 Subject: [PATCH 331/481] Remove nullable disable annotation in the Osu ruleset. --- osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs | 2 -- osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModMuted.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs | 4 +--- osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs | 2 -- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 -- 37 files changed, 1 insertion(+), 75 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs index affc0bae6a..4a3b187e83 100644 --- a/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs +++ b/osu.Game.Rulesets.Osu/Mods/IHidesApproachCircles.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Osu.Mods { /// diff --git a/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs index a108f5fd14..1458abfe05 100644 --- a/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs +++ b/osu.Game.Rulesets.Osu/Mods/IRequiresApproachCircles.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Osu.Mods { /// diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs index e25845f5ab..e6889403a3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModApproachDifferent.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.Bindables; using osu.Framework.Graphics; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index a3f6448457..2d579e511e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs index c4de77b8a3..7c1f6be9ed 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutoplay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs index 71bdd98457..9e71f657ce 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBarrelRoll.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index 17a9a81de8..e3aa8f93d0 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs index d5096619b9..769694baf4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModCinema.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs index 00009f4c3d..e021992f86 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModClassic.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModClassic.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.Bindables; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs index c4cc0b4f48..371dfe6a1a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDaycore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Osu.Mods diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs index e95e61312e..ee6a7815e2 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDeflate.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index be159523b7..3a6b232f9f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.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.Game.Beatmaps; using osu.Game.Configuration; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs index b86efe84ee..700a3f44bc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDoubleTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Osu.Mods diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs index 90b22e8d9c..06b5b6cfb8 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModEasy.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Osu.Mods diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index b72e6b4dcb..25b900752c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.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.Bindables; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs index 34840de983..182d6eeb4b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModGrow.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs index 54c5c56ca6..4769e7660b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHalfTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Osu.Mods diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs index 1f25655c8c..5430929143 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.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.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs index 253eaf473b..97f201b2cc 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHidden.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Diagnostics; using System.Linq; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index f9a74d2a3a..97a573f1b4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.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.Bindables; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs index 1d822a2d4c..3faca0b01f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMirror.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.Bindables; using osu.Game.Configuration; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMuted.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMuted.cs index 1d4650a379..5e3ee37b61 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMuted.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMuted.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs index b1fe066a1e..b7838ebaa7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNightcore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Objects; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs index c20fcf0b1b..9f707a5aa6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoFail.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.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs index fe415cb967..8e377ea632 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.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.Bindables; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs index 44942e9e37..59984f9a7b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModObjectScaleTween.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.Bindables; using osu.Framework.Graphics; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs index bde7718da5..33581405a6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModPerfect.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.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 2030156f2e..4c70b8f22c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Diagnostics; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs index 16e7780af0..95e7d13ee7 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpinIn.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs index 61028a1ee8..d9ab749ad3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSpunOut.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs index 565ff415be..0b34ab28a3 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModStrictTracking.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using System.Threading; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs index 4eb7659152..429fe30fc5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModSuddenDeath.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.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index f03bcffdc8..43169dac68 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs index f8c1e1639d..7276cc753c 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTouchDevice.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Osu.Mods diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs index 6e5dd45a7a..d862d36670 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTraceable.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.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -59,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Mods } } - private void applyCirclePieceState(DrawableOsuHitObject hitObject, IDrawable hitCircle = null) + private void applyCirclePieceState(DrawableOsuHitObject hitObject, IDrawable? hitCircle = null) { var h = hitObject.HitObject; using (hitObject.BeginAbsoluteSequence(h.StartTime - h.TimePreempt)) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs index 84906f6eed..4354ecbe9a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTransform.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 8acd4fc422..6bfbe25471 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; From deb39bd33030194a3d35a3167441333af07d9805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 23:49:38 +0800 Subject: [PATCH 332/481] Mark the property as nullable or non-nullable. --- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 4 ++-- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index e3aa8f93d0..39234147a9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -53,9 +53,9 @@ namespace osu.Game.Rulesets.Osu.Mods /// /// Black background boxes behind blind panel textures. /// - private Box blackBoxLeft, blackBoxRight; + private Box blackBoxLeft = null!, blackBoxRight = null!; - private Drawable panelLeft, panelRight, bgPanelLeft, bgPanelRight; + private Drawable panelLeft = null!, panelRight = null!, bgPanelLeft = null!, bgPanelRight = null!; private readonly Beatmap beatmap; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 4c70b8f22c..c9c3fdfc89 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Mods private OsuInputManager osuInputManager; - private ReplayState state; + private ReplayState? state; private double lastStateChangeTime; private bool hasReplay; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 43169dac68..29f5b8f512 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -403,7 +403,7 @@ namespace osu.Game.Rulesets.Osu.Mods /// The list of hit objects in a beatmap, ordered by StartTime /// The point in time to get samples for /// Hit samples - private IList getSamplesAtTime(IEnumerable hitObjects, double time) + private IList? getSamplesAtTime(IEnumerable hitObjects, double time) { // Get a hit object that // either has StartTime equal to the target time From 9134525111130704a23ab0788c549449b6e1763b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 23:47:57 +0800 Subject: [PATCH 333/481] Mark the property as nullable and add some assert check. --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 12 +++++++--- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 5 ++++- .../Mods/OsuModFlashlight.cs | 5 +++-- .../Mods/OsuModMagnetised.cs | 5 ++++- osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs | 5 +++-- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 6 ++++- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 22 ++++++++++++++----- 7 files changed, 45 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 2d579e511e..03cc1a9305 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.StateChanges; using osu.Game.Graphics; @@ -28,16 +30,20 @@ namespace osu.Game.Rulesets.Osu.Mods public bool RestartOnFail => false; - private OsuInputManager inputManager; + private OsuInputManager? inputManager; - private IFrameStableClock gameplayClock; + private IFrameStableClock? gameplayClock; - private List replayFrames; + private List? replayFrames; private int currentFrame; public void Update(Playfield playfield) { + Debug.Assert(inputManager != null); + Debug.Assert(gameplayClock != null); + Debug.Assert(replayFrames != null); + if (currentFrame == replayFrames.Count - 1) return; double time = gameplayClock.CurrentTime; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index 39234147a9..35054a50db 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -31,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight) }; - private DrawableOsuBlinds blinds; + private DrawableOsuBlinds? blinds; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { @@ -40,6 +41,8 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToHealthProcessor(HealthProcessor healthProcessor) { + Debug.Assert(blinds != null); + healthProcessor.Health.ValueChanged += health => { blinds.AnimateClosedness((float)health.NewValue); }; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 25b900752c..510e95b13f 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; @@ -51,14 +52,14 @@ namespace osu.Game.Rulesets.Osu.Mods public override float DefaultFlashlightSize => 180; - private OsuFlashlight flashlight; + private OsuFlashlight? flashlight; protected override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(this); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { if (drawable is DrawableSlider s) - s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange; + s.Tracking.ValueChanged += flashlight.AsNonNull().OnSliderTrackingChange; } private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index 97a573f1b4..107eac6430 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; @@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel) }; - private IFrameStableClock gameplayClock; + private IFrameStableClock? gameplayClock; [SettingSource("Attraction strength", "How strong the pull is.", 0)] public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f) @@ -74,6 +75,8 @@ namespace osu.Game.Rulesets.Osu.Mods { double dampLength = Interpolation.Lerp(3000, 40, AttractionStrength.Value); + Debug.Assert(gameplayClock != null); + float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs index 8e377ea632..0197b7cb1b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -19,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Description => "Where's the cursor?"; - private PeriodTracker spinnerPeriods; + private PeriodTracker? spinnerPeriods; [SettingSource( "Hidden at combo", @@ -41,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - bool shouldAlwaysShowCursor = IsBreakTime.Value || spinnerPeriods.IsInAny(playfield.Clock.CurrentTime); + bool shouldAlwaysShowCursor = IsBreakTime.Value || spinnerPeriods.AsNonNull().IsInAny(playfield.Clock.CurrentTime); float targetAlpha = shouldAlwaysShowCursor ? 1 : ComboBasedAlpha; playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index c9c3fdfc89..ac45ce2ade 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Mods private bool isDownState; private bool wasLeft; - private OsuInputManager osuInputManager; + private OsuInputManager? osuInputManager; private ReplayState? state; private double lastStateChangeTime; @@ -44,6 +44,8 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToPlayer(Player player) { + Debug.Assert(osuInputManager != null); + if (osuInputManager.ReplayInputHandler != null) { hasReplay = true; @@ -132,6 +134,8 @@ namespace osu.Game.Rulesets.Osu.Mods wasLeft = !wasLeft; } + Debug.Assert(osuInputManager != null); + state?.Apply(osuInputManager.CurrentState, osuInputManager); } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 29f5b8f512..36f21ba291 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; @@ -94,11 +96,11 @@ namespace osu.Game.Rulesets.Osu.Mods #region Private Fields - private ControlPointInfo controlPointInfo; + private ControlPointInfo? controlPointInfo; - private List originalHitObjects; + private List? originalHitObjects; - private Random rng; + private Random? rng; #endregion @@ -158,7 +160,7 @@ namespace osu.Game.Rulesets.Osu.Mods circle.ApproachCircle.Hide(); } - using (circle.BeginAbsoluteSequence(startTime - controlPointInfo.TimingPointAt(startTime).BeatLength - undim_duration)) + using (circle.BeginAbsoluteSequence(startTime - controlPointInfo.AsNonNull().TimingPointAt(startTime).BeatLength - undim_duration)) circle.FadeColour(Color4.White, undim_duration); } @@ -200,6 +202,8 @@ namespace osu.Game.Rulesets.Osu.Mods private IEnumerable generateBeats(IBeatmap beatmap) { + Debug.Assert(originalHitObjects != null); + double startTime = originalHitObjects.First().StartTime; double endTime = originalHitObjects.Last().GetEndTime(); @@ -228,6 +232,8 @@ namespace osu.Game.Rulesets.Osu.Mods private void addHitSamples(IEnumerable hitObjects) { + Debug.Assert(originalHitObjects != null); + foreach (var obj in hitObjects) { var samples = getSamplesAtTime(originalHitObjects, obj.StartTime); @@ -240,6 +246,8 @@ namespace osu.Game.Rulesets.Osu.Mods private void fixComboInfo(List hitObjects) { + Debug.Assert(originalHitObjects != null); + // Copy combo indices from an original object at the same time or from the closest preceding object // (Objects lying between two combos are assumed to belong to the preceding combo) hitObjects.ForEach(newObj => @@ -276,7 +284,7 @@ namespace osu.Game.Rulesets.Osu.Mods { if (hitObjects.Count == 0) return; - float nextSingle(float max = 1f) => (float)(rng.NextDouble() * max); + float nextSingle(float max = 1f) => (float)(rng.AsNonNull().NextDouble() * max); const float two_pi = MathF.PI * 2; @@ -357,6 +365,8 @@ namespace osu.Game.Rulesets.Osu.Mods /// The time to be checked.= private bool isInsideBreakPeriod(IEnumerable breaks, double time) { + Debug.Assert(originalHitObjects != null); + return breaks.Any(breakPeriod => { var firstObjAfterBreak = originalHitObjects.First(obj => almostBigger(obj.StartTime, breakPeriod.EndTime)); @@ -372,6 +382,8 @@ namespace osu.Game.Rulesets.Osu.Mods int i = 0; double currentTime = timingPoint.Time; + Debug.Assert(controlPointInfo != null); + while (!definitelyBigger(currentTime, mapEndTime) && ReferenceEquals(controlPointInfo.TimingPointAt(currentTime), timingPoint)) { beats.Add(Math.Floor(currentTime)); From 8fa576557398ce1e51ee262430fdab07ab7bd15e Mon Sep 17 00:00:00 2001 From: andy840119 Date: Wed, 20 Jul 2022 20:34:55 +0800 Subject: [PATCH 334/481] Remove nullable disable annotation in the Osu test case. --- osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs | 2 -- .../Mods/TestSceneOsuModAlternate.cs | 2 -- .../Mods/TestSceneOsuModAutoplay.cs | 7 +++---- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 2 -- .../Mods/TestSceneOsuModDoubleTime.cs | 2 -- .../Mods/TestSceneOsuModHidden.cs | 4 +--- .../Mods/TestSceneOsuModMagnetised.cs | 2 -- .../Mods/TestSceneOsuModMuted.cs | 4 +--- .../Mods/TestSceneOsuModNoScope.cs | 2 -- .../Mods/TestSceneOsuModPerfect.cs | 2 -- .../Mods/TestSceneOsuModSpunOut.cs | 12 +++++------- 11 files changed, 10 insertions(+), 31 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs b/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs index 4f005a0c70..d3cb3bcf59 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests.Mods diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs index 3d59e4fb51..5e46498aca 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAlternate.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using NUnit.Framework; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs index 378b71ccf7..3563995234 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModAutoplay.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.Linq; using NUnit.Framework; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; @@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods private void runSpmTest(Mod mod) { - SpinnerSpmCalculator spmCalculator = null; + SpinnerSpmCalculator? spmCalculator = null; CreateModTest(new ModTestData { @@ -61,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods return spmCalculator != null; }); - AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCalculator.Result.Value, 477, 5)); + AddUntilStep("SPM is correct", () => Precision.AlmostEquals(spmCalculator.AsNonNull().Result.Value, 477, 5)); } } } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 80dc83d7dc..9d06ff5801 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using NUnit.Framework; diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs index e1bed5153b..335ef31019 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Framework.Utils; using osu.Game.Rulesets.Osu.Mods; diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs index 5ed25baca3..e692f8ecbc 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -162,7 +160,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods private class TestOsuModHidden : OsuModHidden { - public new HitObject FirstObject => base.FirstObject; + public new HitObject? FirstObject => base.FirstObject; } } } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs index 1f1db04c24..9b49e60363 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMagnetised.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Game.Rulesets.Osu.Mods; diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs index 99c9036ac0..68669d1a53 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModMuted.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Testing; @@ -33,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods [Test] public void TestModCopy() { - OsuModMuted muted = null; + OsuModMuted muted = null!; AddStep("create inversed mod", () => muted = new OsuModMuted { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs index 47e7ad320c..44404ca245 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModNoScope.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using NUnit.Framework; diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs index b7669624ff..985baa8cf5 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs index 4f6d6376bf..e121e6103d 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModSpunOut.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; @@ -30,8 +28,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods [Test] public void TestSpinnerAutoCompleted() { - DrawableSpinner spinner = null; - JudgementResult lastResult = null; + DrawableSpinner? spinner = null; + JudgementResult? lastResult = null; CreateModTest(new ModTestData { @@ -63,11 +61,11 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods [TestCase(null)] [TestCase(typeof(OsuModDoubleTime))] [TestCase(typeof(OsuModHalfTime))] - public void TestSpinRateUnaffectedByMods(Type additionalModType) + public void TestSpinRateUnaffectedByMods(Type? additionalModType) { var mods = new List { new OsuModSpunOut() }; if (additionalModType != null) - mods.Add((Mod)Activator.CreateInstance(additionalModType)); + mods.Add((Mod)Activator.CreateInstance(additionalModType)!); CreateModTest(new ModTestData { @@ -96,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods [Test] public void TestSpinnerGetsNoBonusScore() { - DrawableSpinner spinner = null; + DrawableSpinner? spinner = null; List results = new List(); CreateModTest(new ModTestData From 0fe64d1e803202028ca9d20c4006641bad961675 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Wed, 27 Jul 2022 01:05:50 +0800 Subject: [PATCH 335/481] Remove unused namespace. --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 03cc1a9305..83c1deb3b9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.StateChanges; using osu.Game.Graphics; From 6b0f3674c320f345caaff0068f6c00bfebdafb22 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Wed, 27 Jul 2022 08:51:55 +0800 Subject: [PATCH 336/481] implement `LegacySongProgress` --- osu.Game/Skinning/LegacySongProgress.cs | 101 ++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 osu.Game/Skinning/LegacySongProgress.cs diff --git a/osu.Game/Skinning/LegacySongProgress.cs b/osu.Game/Skinning/LegacySongProgress.cs new file mode 100644 index 0000000000..57280fff07 --- /dev/null +++ b/osu.Game/Skinning/LegacySongProgress.cs @@ -0,0 +1,101 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Play; +using osuTK; + +namespace osu.Game.Skinning +{ + public class LegacySongProgress : CompositeDrawable, ISkinnableDrawable + { + public bool UsesFixedAnchor { get; set; } + + [Resolved] + private GameplayClock gameplayClock { get; set; } + + [Resolved(canBeNull: true)] + private DrawableRuleset drawableRuleset { get; set; } + + [Resolved(canBeNull: true)] + private IBindable beatmap { get; set; } + + private double lastHitTime; + private double firstHitTime; + private double firstEventTime; + private CircularProgress pie; + + [BackgroundDependencyLoader] + private void load() + { + Size = new Vector2(35); + + InternalChildren = new Drawable[] + { + pie = new CircularProgress + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.5f, + }, + new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderColour = Colour4.White, + BorderThickness = 2, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0, + } + }, + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = Colour4.White, + Size = new Vector2(3), + } + }; + + firstEventTime = beatmap?.Value.Storyboard.EarliestEventTime ?? 0; + firstHitTime = drawableRuleset.Objects.First().StartTime; + lastHitTime = drawableRuleset.Objects.Last().GetEndTime() + 1; + } + + protected override void Update() + { + base.Update(); + + double gameplayTime = gameplayClock?.CurrentTime ?? Time.Current; + + if (gameplayTime < firstHitTime) + { + pie.Scale = new Vector2(-1, 1); + pie.Anchor = Anchor.TopRight; + pie.Colour = Colour4.LimeGreen; + pie.Current.Value = 1 - Math.Clamp((gameplayTime - firstEventTime) / (firstHitTime - firstEventTime), 0, 1); + } + else + { + pie.Scale = new Vector2(1); + pie.Anchor = Anchor.TopLeft; + pie.Colour = Colour4.White; + pie.Current.Value = Math.Clamp((gameplayTime - firstHitTime) / (lastHitTime - firstHitTime), 0, 1); + } + } + } +} From a2320aeb278281c51f17f51386988e4c69574ba1 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Wed, 27 Jul 2022 08:52:27 +0800 Subject: [PATCH 337/481] replace `SongProgress` with `LegacySongProgress` --- osu.Game/Skinning/LegacySkin.cs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 34219722a1..c3872f4fa0 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -16,12 +16,13 @@ using osu.Framework.IO.Stores; using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.IO; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; -using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; +using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning @@ -344,7 +345,15 @@ namespace osu.Game.Skinning accuracy.Y = container.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y; } - var songProgress = container.OfType().FirstOrDefault(); + var songProgress = container.OfType().FirstOrDefault(); + + if (songProgress != null && accuracy != null) + { + songProgress.Anchor = Anchor.TopRight; + songProgress.Origin = Anchor.CentreRight; + songProgress.X = -accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).X - 10; + songProgress.Y = container.ToLocalSpace(accuracy.ScreenSpaceDrawQuad.TopLeft).Y + (accuracy.ScreenSpaceDeltaToParentSpace(accuracy.ScreenSpaceDrawQuad.Size).Y / 2); + } var hitError = container.OfType().FirstOrDefault(); @@ -354,12 +363,6 @@ namespace osu.Game.Skinning hitError.Origin = Anchor.CentreLeft; hitError.Rotation = -90; } - - if (songProgress != null) - { - if (hitError != null) hitError.Y -= SongProgress.MAX_HEIGHT; - if (combo != null) combo.Y -= SongProgress.MAX_HEIGHT; - } }) { Children = new Drawable[] @@ -368,7 +371,7 @@ namespace osu.Game.Skinning new LegacyScoreCounter(), new LegacyAccuracyCounter(), new LegacyHealthDisplay(), - new SongProgress(), + new LegacySongProgress(), new BarHitErrorMeter(), } }; From 842ab3c5c1c4a08ae0518223e16537590405559b Mon Sep 17 00:00:00 2001 From: Nitrous Date: Wed, 27 Jul 2022 09:41:58 +0800 Subject: [PATCH 338/481] remove unused using --- osu.Game/Skinning/LegacySkin.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index c3872f4fa0..66258fb4a2 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -22,7 +22,6 @@ using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; -using osuTK; using osuTK.Graphics; namespace osu.Game.Skinning From b803ec543f3423afa408c340d88bb63fe7cab43e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 13:50:03 +0900 Subject: [PATCH 339/481] Remove unused `combo` DI retrieval --- osu.Game/Skinning/LegacySkin.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 66258fb4a2..15d4965a1d 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -337,7 +337,6 @@ namespace osu.Game.Skinning { var score = container.OfType().FirstOrDefault(); var accuracy = container.OfType().FirstOrDefault(); - var combo = container.OfType().FirstOrDefault(); if (score != null && accuracy != null) { From 62ca3aada6bb969de3185065df1001834aa2034c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 13:53:25 +0900 Subject: [PATCH 340/481] Transfer TODO comment across to copy-pasted implmentation --- osu.Game/Skinning/LegacySongProgress.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/LegacySongProgress.cs b/osu.Game/Skinning/LegacySongProgress.cs index 57280fff07..4d8838e031 100644 --- a/osu.Game/Skinning/LegacySongProgress.cs +++ b/osu.Game/Skinning/LegacySongProgress.cs @@ -73,6 +73,7 @@ namespace osu.Game.Skinning firstEventTime = beatmap?.Value.Storyboard.EarliestEventTime ?? 0; firstHitTime = drawableRuleset.Objects.First().StartTime; + //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). lastHitTime = drawableRuleset.Objects.Last().GetEndTime() + 1; } From d8e605d8aaa8e8119bdd3397fcaa7d3685942824 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 13:58:01 +0900 Subject: [PATCH 341/481] Fix broken tests due to badly reimplemented copy-pasted code --- osu.Game/Skinning/LegacySongProgress.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacySongProgress.cs b/osu.Game/Skinning/LegacySongProgress.cs index 4d8838e031..2fd4180139 100644 --- a/osu.Game/Skinning/LegacySongProgress.cs +++ b/osu.Game/Skinning/LegacySongProgress.cs @@ -72,9 +72,13 @@ namespace osu.Game.Skinning }; firstEventTime = beatmap?.Value.Storyboard.EarliestEventTime ?? 0; - firstHitTime = drawableRuleset.Objects.First().StartTime; - //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). - lastHitTime = drawableRuleset.Objects.Last().GetEndTime() + 1; + + if (drawableRuleset != null) + { + firstHitTime = drawableRuleset.Objects.First().StartTime; + //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). + lastHitTime = drawableRuleset.Objects.Last().GetEndTime() + 1; + } } protected override void Update() From 6b73f7c7ec5b411c9f30ec7b9c8d94d2a2d12e34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 15:04:09 +0900 Subject: [PATCH 342/481] Split out legacy import path from realm manager --- .../Collections/IO/ImportCollectionsTest.cs | 3 +- osu.Game.Tests/ImportTest.cs | 2 +- .../TestSceneManageCollectionsDialog.cs | 2 +- .../SongSelect/TestSceneFilterControl.cs | 2 +- osu.Game/Beatmaps/RealmBeatmapCollection.cs | 40 +++ osu.Game/Collections/BeatmapCollection.cs | 17 - osu.Game/Collections/CollectionManager.cs | 331 ++---------------- osu.Game/Database/LegacyCollectionImporter.cs | 167 +++++++++ osu.Game/Database/LegacyImportManager.cs | 4 +- osu.Game/OsuGame.cs | 2 +- 10 files changed, 237 insertions(+), 333 deletions(-) create mode 100644 osu.Game/Beatmaps/RealmBeatmapCollection.cs create mode 100644 osu.Game/Database/LegacyCollectionImporter.cs diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index 9a8f29647d..685586ff02 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -11,6 +11,7 @@ using NUnit.Framework; using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Testing; +using osu.Game.Database; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Collections.IO @@ -187,7 +188,7 @@ namespace osu.Game.Tests.Collections.IO { // intentionally spin this up on a separate task to avoid disposal deadlocks. // see https://github.com/EventStore/EventStore/issues/1179 - await Task.Factory.StartNew(() => osu.CollectionManager.Import(stream).WaitSafely(), TaskCreationOptions.LongRunning); + await Task.Factory.StartNew(() => new LegacyCollectionImporter(osu.CollectionManager).Import(stream).WaitSafely(), TaskCreationOptions.LongRunning); } } } diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs index 32b6dc649c..1f18f92158 100644 --- a/osu.Game.Tests/ImportTest.cs +++ b/osu.Game.Tests/ImportTest.cs @@ -63,7 +63,7 @@ namespace osu.Game.Tests if (withBeatmap) BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely(); - AddInternal(CollectionManager = new CollectionManager(Storage)); + AddInternal(CollectionManager = new CollectionManager()); } } } diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 3f30fa367c..789139f483 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Collections base.Content.AddRange(new Drawable[] { - manager = new CollectionManager(LocalStorage), + manager = new CollectionManager(), Content, dialogOverlay = new DialogOverlay(), }); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 6807180640..01bf312ddd 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.SongSelect base.Content.AddRange(new Drawable[] { - collectionManager = new CollectionManager(LocalStorage), + collectionManager = new CollectionManager(), Content }); diff --git a/osu.Game/Beatmaps/RealmBeatmapCollection.cs b/osu.Game/Beatmaps/RealmBeatmapCollection.cs new file mode 100644 index 0000000000..d3261fc39e --- /dev/null +++ b/osu.Game/Beatmaps/RealmBeatmapCollection.cs @@ -0,0 +1,40 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using JetBrains.Annotations; +using osu.Game.Database; +using Realms; + +namespace osu.Game.Beatmaps +{ + public class RealmBeatmapCollection : RealmObject, IHasGuidPrimaryKey + { + [PrimaryKey] + public Guid ID { get; } + + public string Name { get; set; } = string.Empty; + + public List BeatmapMD5Hashes { get; set; } = null!; + + /// + /// The date when this collection was last modified. + /// + public DateTimeOffset LastModified { get; set; } + + public RealmBeatmapCollection(string? name, List? beatmapMD5Hashes) + { + ID = Guid.NewGuid(); + Name = name ?? string.Empty; + BeatmapMD5Hashes = beatmapMD5Hashes ?? new List(); + + LastModified = DateTimeOffset.UtcNow; + } + + [UsedImplicitly] + private RealmBeatmapCollection() + { + } + } +} diff --git a/osu.Game/Collections/BeatmapCollection.cs b/osu.Game/Collections/BeatmapCollection.cs index 742d757bec..abfd0e6dd0 100644 --- a/osu.Game/Collections/BeatmapCollection.cs +++ b/osu.Game/Collections/BeatmapCollection.cs @@ -14,11 +14,6 @@ namespace osu.Game.Collections /// public class BeatmapCollection { - /// - /// Invoked whenever any change occurs on this . - /// - public event Action Changed; - /// /// The collection's name. /// @@ -33,17 +28,5 @@ namespace osu.Game.Collections /// The date when this collection was last modified. /// public DateTimeOffset LastModifyDate { get; private set; } = DateTimeOffset.UtcNow; - - public BeatmapCollection() - { - BeatmapHashes.CollectionChanged += (_, _) => onChange(); - Name.ValueChanged += _ => onChange(); - } - - private void onChange() - { - LastModifyDate = DateTimeOffset.Now; - Changed?.Invoke(); - } } } diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs index 796b3c426c..0d4ee5c722 100644 --- a/osu.Game/Collections/CollectionManager.cs +++ b/osu.Game/Collections/CollectionManager.cs @@ -4,346 +4,59 @@ #nullable disable using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Logging; -using osu.Framework.Platform; +using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.IO; -using osu.Game.IO.Legacy; using osu.Game.Overlays.Notifications; +using Realms; namespace osu.Game.Collections { /// /// Handles user-defined collections of beatmaps. /// - /// - /// This is currently reading and writing from the osu-stable file format. This is a temporary arrangement until we refactor the - /// database backing the game. Going forward writing should be done in a similar way to other model stores. - /// public class CollectionManager : Component, IPostNotifications { - /// - /// Database version in stable-compatible YYYYMMDD format. - /// - private const int database_version = 30000000; - - private const string database_name = "collection.db"; - private const string database_backup_name = "collection.db.bak"; - public readonly BindableList Collections = new BindableList(); - private readonly Storage storage; - - public CollectionManager(Storage storage) - { - this.storage = storage; - } - - [Resolved(canBeNull: true)] - private DatabaseContextFactory efContextFactory { get; set; } = null!; + [Resolved] + private RealmAccess realm { get; set; } [BackgroundDependencyLoader] private void load() { - efContextFactory?.WaitForMigrationCompletion(); - - Collections.CollectionChanged += collectionsChanged; - - if (storage.Exists(database_backup_name)) - { - // If a backup file exists, it means the previous write operation didn't run to completion. - // Always prefer the backup file in such a case as it's the most recent copy that is guaranteed to not be malformed. - // - // The database is saved 100ms after any change, and again when the game is closed, so there shouldn't be a large diff between the two files in the worst case. - if (storage.Exists(database_name)) - storage.Delete(database_name); - File.Copy(storage.GetFullPath(database_backup_name), storage.GetFullPath(database_name)); - } - - if (storage.Exists(database_name)) - { - List beatmapCollections; - - using (var stream = storage.GetStream(database_name)) - beatmapCollections = readCollections(stream); - - // intentionally fire-and-forget async. - importCollections(beatmapCollections); - } } - private void collectionsChanged(object sender, NotifyCollectionChangedEventArgs e) => Schedule(() => + protected override void LoadComplete() { - switch (e.Action) + base.LoadComplete(); + + realm.RegisterForNotifications(r => r.All(), collectionsChanged); + } + + private void collectionsChanged(IRealmCollection sender, ChangeSet changes, Exception error) + { + // TODO: hook up with realm changes. + + if (changes == null) { - case NotifyCollectionChangedAction.Add: - foreach (var c in e.NewItems.Cast()) - c.Changed += backgroundSave; - break; - - case NotifyCollectionChangedAction.Remove: - foreach (var c in e.OldItems.Cast()) - c.Changed -= backgroundSave; - break; - - case NotifyCollectionChangedAction.Replace: - foreach (var c in e.OldItems.Cast()) - c.Changed -= backgroundSave; - - foreach (var c in e.NewItems.Cast()) - c.Changed += backgroundSave; - break; + foreach (var collection in sender) + Collections.Add(new BeatmapCollection + { + Name = { Value = collection.Name }, + BeatmapHashes = { Value = collection.BeatmapMD5Hashes }, + }); } - - backgroundSave(); - }); + } public Action PostNotification { protected get; set; } - public Task GetAvailableCount(StableStorage stableStorage) - { - if (!stableStorage.Exists(database_name)) - return Task.FromResult(0); - - return Task.Run(() => - { - using (var stream = stableStorage.GetStream(database_name)) - return readCollections(stream).Count; - }); - } - - /// - /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. - /// - public Task ImportFromStableAsync(StableStorage stableStorage) - { - if (!stableStorage.Exists(database_name)) - { - // This handles situations like when the user does not have a collections.db file - Logger.Log($"No {database_name} available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); - return Task.CompletedTask; - } - - return Task.Run(async () => - { - using (var stream = stableStorage.GetStream(database_name)) - await Import(stream).ConfigureAwait(false); - }); - } - - public async Task Import(Stream stream) - { - var notification = new ProgressNotification - { - State = ProgressNotificationState.Active, - Text = "Collections import is initialising..." - }; - - PostNotification?.Invoke(notification); - - var collections = readCollections(stream, notification); - await importCollections(collections).ConfigureAwait(false); - - notification.CompletionText = $"Imported {collections.Count} collections"; - notification.State = ProgressNotificationState.Completed; - } - - private Task importCollections(List newCollections) - { - var tcs = new TaskCompletionSource(); - - Schedule(() => - { - try - { - foreach (var newCol in newCollections) - { - var existing = Collections.FirstOrDefault(c => c.Name.Value == newCol.Name.Value); - if (existing == null) - Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } }); - - foreach (string newBeatmap in newCol.BeatmapHashes) - { - if (!existing.BeatmapHashes.Contains(newBeatmap)) - existing.BeatmapHashes.Add(newBeatmap); - } - } - - tcs.SetResult(true); - } - catch (Exception e) - { - Logger.Error(e, "Failed to import collection."); - tcs.SetException(e); - } - }); - - return tcs.Task; - } - - private List readCollections(Stream stream, ProgressNotification notification = null) - { - if (notification != null) - { - notification.Text = "Reading collections..."; - notification.Progress = 0; - } - - var result = new List(); - - try - { - using (var sr = new SerializationReader(stream)) - { - sr.ReadInt32(); // Version - - int collectionCount = sr.ReadInt32(); - result.Capacity = collectionCount; - - for (int i = 0; i < collectionCount; i++) - { - if (notification?.CancellationToken.IsCancellationRequested == true) - return result; - - var collection = new BeatmapCollection { Name = { Value = sr.ReadString() } }; - int mapCount = sr.ReadInt32(); - - for (int j = 0; j < mapCount; j++) - { - if (notification?.CancellationToken.IsCancellationRequested == true) - return result; - - string checksum = sr.ReadString(); - - collection.BeatmapHashes.Add(checksum); - } - - if (notification != null) - { - notification.Text = $"Imported {i + 1} of {collectionCount} collections"; - notification.Progress = (float)(i + 1) / collectionCount; - } - - result.Add(collection); - } - } - } - catch (Exception e) - { - Logger.Error(e, "Failed to read collection database."); - } - - return result; - } - public void DeleteAll() { Collections.Clear(); PostNotification?.Invoke(new ProgressCompletionNotification { Text = "Deleted all collections!" }); } - - private readonly object saveLock = new object(); - private int lastSave; - private int saveFailures; - - /// - /// Perform a save with debounce. - /// - private void backgroundSave() - { - int current = Interlocked.Increment(ref lastSave); - Task.Delay(100).ContinueWith(_ => - { - if (current != lastSave) - return; - - if (!save()) - backgroundSave(); - }); - } - - private bool save() - { - lock (saveLock) - { - Interlocked.Increment(ref lastSave); - - // This is NOT thread-safe!! - try - { - string tempPath = Path.GetTempFileName(); - - using (var ms = new MemoryStream()) - { - using (var sw = new SerializationWriter(ms, true)) - { - sw.Write(database_version); - - var collectionsCopy = Collections.ToArray(); - sw.Write(collectionsCopy.Length); - - foreach (var c in collectionsCopy) - { - sw.Write(c.Name.Value); - - string[] beatmapsCopy = c.BeatmapHashes.ToArray(); - - sw.Write(beatmapsCopy.Length); - - foreach (string b in beatmapsCopy) - sw.Write(b); - } - } - - using (var fs = File.OpenWrite(tempPath)) - ms.WriteTo(fs); - - string databasePath = storage.GetFullPath(database_name); - string databaseBackupPath = storage.GetFullPath(database_backup_name); - - // Back up the existing database, clearing any existing backup. - if (File.Exists(databaseBackupPath)) - File.Delete(databaseBackupPath); - if (File.Exists(databasePath)) - File.Move(databasePath, databaseBackupPath); - - // Move the new database in-place of the existing one. - File.Move(tempPath, databasePath); - - // If everything succeeded up to this point, remove the backup file. - if (File.Exists(databaseBackupPath)) - File.Delete(databaseBackupPath); - } - - if (saveFailures < 10) - saveFailures = 0; - return true; - } - catch (Exception e) - { - // Since this code is not thread-safe, we may run into random exceptions (such as collection enumeration or out of range indexing). - // Failures are thus only alerted if they exceed a threshold (once) to indicate "actual" errors having occurred. - if (++saveFailures == 10) - Logger.Error(e, "Failed to save collection database!"); - } - - return false; - } - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - save(); - } } } diff --git a/osu.Game/Database/LegacyCollectionImporter.cs b/osu.Game/Database/LegacyCollectionImporter.cs new file mode 100644 index 0000000000..8168419e80 --- /dev/null +++ b/osu.Game/Database/LegacyCollectionImporter.cs @@ -0,0 +1,167 @@ +// 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.Logging; +using osu.Game.Collections; +using osu.Game.IO; +using osu.Game.IO.Legacy; +using osu.Game.Overlays.Notifications; + +namespace osu.Game.Database +{ + public class LegacyCollectionImporter + { + private readonly CollectionManager collections; + + public LegacyCollectionImporter(CollectionManager collections) + { + this.collections = collections; + } + + public Action PostNotification { protected get; set; } + + private const string database_name = "collection.db"; + + public Task GetAvailableCount(StableStorage stableStorage) + { + if (!stableStorage.Exists(database_name)) + return Task.FromResult(0); + + return Task.Run(() => + { + using (var stream = stableStorage.GetStream(database_name)) + return readCollections(stream).Count; + }); + } + + /// + /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. + /// + public Task ImportFromStableAsync(StableStorage stableStorage) + { + if (!stableStorage.Exists(database_name)) + { + // This handles situations like when the user does not have a collections.db file + Logger.Log($"No {database_name} available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); + return Task.CompletedTask; + } + + return Task.Run(async () => + { + using (var stream = stableStorage.GetStream(database_name)) + await Import(stream).ConfigureAwait(false); + }); + } + + public async Task Import(Stream stream) + { + var notification = new ProgressNotification + { + State = ProgressNotificationState.Active, + Text = "Collections import is initialising..." + }; + + PostNotification?.Invoke(notification); + + var importedCollections = readCollections(stream, notification); + await importCollections(importedCollections).ConfigureAwait(false); + + notification.CompletionText = $"Imported {importedCollections.Count} collections"; + notification.State = ProgressNotificationState.Completed; + } + + private Task importCollections(List newCollections) + { + var tcs = new TaskCompletionSource(); + + // Schedule(() => + // { + try + { + foreach (var newCol in newCollections) + { + var existing = collections.Collections.FirstOrDefault(c => c.Name.Value == newCol.Name.Value); + if (existing == null) + collections.Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } }); + + foreach (string newBeatmap in newCol.BeatmapHashes) + { + if (!existing.BeatmapHashes.Contains(newBeatmap)) + existing.BeatmapHashes.Add(newBeatmap); + } + } + + tcs.SetResult(true); + } + catch (Exception e) + { + Logger.Error(e, "Failed to import collection."); + tcs.SetException(e); + } + // }); + + return tcs.Task; + } + + private List readCollections(Stream stream, ProgressNotification notification = null) + { + if (notification != null) + { + notification.Text = "Reading collections..."; + notification.Progress = 0; + } + + var result = new List(); + + try + { + using (var sr = new SerializationReader(stream)) + { + sr.ReadInt32(); // Version + + int collectionCount = sr.ReadInt32(); + result.Capacity = collectionCount; + + for (int i = 0; i < collectionCount; i++) + { + if (notification?.CancellationToken.IsCancellationRequested == true) + return result; + + var collection = new BeatmapCollection { Name = { Value = sr.ReadString() } }; + int mapCount = sr.ReadInt32(); + + for (int j = 0; j < mapCount; j++) + { + if (notification?.CancellationToken.IsCancellationRequested == true) + return result; + + string checksum = sr.ReadString(); + + collection.BeatmapHashes.Add(checksum); + } + + if (notification != null) + { + notification.Text = $"Imported {i + 1} of {collectionCount} collections"; + notification.Progress = (float)(i + 1) / collectionCount; + } + + result.Add(collection); + } + } + } + catch (Exception e) + { + Logger.Error(e, "Failed to read collection database."); + } + + return result; + } + } +} diff --git a/osu.Game/Database/LegacyImportManager.cs b/osu.Game/Database/LegacyImportManager.cs index f40e0d33c2..05bd5ceb54 100644 --- a/osu.Game/Database/LegacyImportManager.cs +++ b/osu.Game/Database/LegacyImportManager.cs @@ -72,7 +72,7 @@ namespace osu.Game.Database return await new LegacySkinImporter(skins).GetAvailableCount(stableStorage); case StableContent.Collections: - return await collections.GetAvailableCount(stableStorage); + return await new LegacyCollectionImporter(collections).GetAvailableCount(stableStorage); case StableContent.Scores: return await new LegacyScoreImporter(scores).GetAvailableCount(stableStorage); @@ -109,7 +109,7 @@ namespace osu.Game.Database importTasks.Add(new LegacySkinImporter(skins).ImportFromStableAsync(stableStorage)); if (content.HasFlagFast(StableContent.Collections)) - importTasks.Add(beatmapImportTask.ContinueWith(_ => collections.ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); + importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyCollectionImporter(collections).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); if (content.HasFlagFast(StableContent.Scores)) importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyScoreImporter(scores).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1ee53e2848..8d8864a46a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -858,7 +858,7 @@ namespace osu.Game d.Origin = Anchor.TopRight; }), rightFloatingOverlayContent.Add, true); - loadComponentSingleFile(new CollectionManager(Storage) + loadComponentSingleFile(new CollectionManager { PostNotification = n => Notifications.Post(n), }, Add, true); From 2d4655f61e011d71f504202cadc6601bd6ac580f Mon Sep 17 00:00:00 2001 From: Andrew Hong Date: Wed, 27 Jul 2022 02:25:40 -0400 Subject: [PATCH 343/481] Add Toast Notification to Copy URL --- .../UserInterface/ExternalLinkButton.cs | 25 ++++++++++++++++++- osu.Game/Localisation/ToastStrings.cs | 5 ++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index acabeca66e..810cd6ef58 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -12,7 +12,10 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Game.Localisation; using osu.Framework.Platform; +using osu.Game.Overlays; +using osu.Game.Overlays.OSD; using osuTK; using osuTK.Graphics; @@ -27,6 +30,9 @@ namespace osu.Game.Graphics.UserInterface [Resolved] private GameHost host { get; set; } + [Resolved(canBeNull: true)] + private OnScreenDisplay onScreenDisplay { get; set; } + private readonly SpriteIcon linkIcon; public ExternalLinkButton(string link = null) @@ -44,6 +50,23 @@ namespace osu.Game.Graphics.UserInterface }; } + private class CopyUrlToast : Toast + { + public CopyUrlToast(LocalisableString value) + : base(UserInterfaceStrings.GeneralHeader, value, "") + { + } + } + + private void copyUrl() + { + if (Link != null) + { + host.GetClipboard()?.SetText(Link); + onScreenDisplay?.Display(new CopyUrlToast(ToastStrings.CopiedUrl)); + } + } + public MenuItem[] ContextMenuItems { get @@ -53,7 +76,7 @@ namespace osu.Game.Graphics.UserInterface if (Link != null) { items.Add(new OsuMenuItem("Open", MenuItemType.Standard, () => host.OpenUrlExternally(Link))); - items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, () => host.GetClipboard()?.SetText(Link))); + items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, () => copyUrl())); } return items.ToArray(); diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index 9ceee807e6..d6771fcd96 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -44,6 +44,11 @@ namespace osu.Game.Localisation /// public static LocalisableString SkinSaved => new TranslatableString(getKey(@"skin_saved"), @"Skin saved"); + /// + /// "Copied URL" + /// + public static LocalisableString CopiedUrl => new TranslatableString(getKey(@"copied_url"), @"Copied URL"); + private static string getKey(string key) => $@"{prefix}:{key}"; } } From a12676c25de8486987374068b3636d4c4fe5a882 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Wed, 27 Jul 2022 14:35:18 +0800 Subject: [PATCH 344/481] scale down graph from bleeding through border --- osu.Game/Skinning/LegacySongProgress.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacySongProgress.cs b/osu.Game/Skinning/LegacySongProgress.cs index 2fd4180139..a141a5f91e 100644 --- a/osu.Game/Skinning/LegacySongProgress.cs +++ b/osu.Game/Skinning/LegacySongProgress.cs @@ -44,10 +44,16 @@ namespace osu.Game.Skinning InternalChildren = new Drawable[] { - pie = new CircularProgress + new Container { + Size = new Vector2(0.95f), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Alpha = 0.5f, + Child = pie = new CircularProgress + { + RelativeSizeAxes = Axes.Both, + }, }, new CircularContainer { @@ -91,14 +97,14 @@ namespace osu.Game.Skinning { pie.Scale = new Vector2(-1, 1); pie.Anchor = Anchor.TopRight; - pie.Colour = Colour4.LimeGreen; + pie.Colour = new Colour4(199, 255, 47, 153); pie.Current.Value = 1 - Math.Clamp((gameplayTime - firstEventTime) / (firstHitTime - firstEventTime), 0, 1); } else { pie.Scale = new Vector2(1); pie.Anchor = Anchor.TopLeft; - pie.Colour = Colour4.White; + pie.Colour = new Colour4(255, 255, 255, 153); pie.Current.Value = Math.Clamp((gameplayTime - firstHitTime) / (lastHitTime - firstHitTime), 0, 1); } } From 89644a652e09be0fa46f446921f1e92a094e4a9c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 27 Jul 2022 10:13:40 +0300 Subject: [PATCH 345/481] Separate combined fields --- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index 35054a50db..808e7720a4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -58,7 +58,10 @@ namespace osu.Game.Rulesets.Osu.Mods /// private Box blackBoxLeft = null!, blackBoxRight = null!; - private Drawable panelLeft = null!, panelRight = null!, bgPanelLeft = null!, bgPanelRight = null!; + private Drawable panelLeft = null!; + private Drawable panelRight = null!; + private Drawable bgPanelLeft = null!; + private Drawable bgPanelRight = null!; private readonly Beatmap beatmap; From 37e642b0bd41f30265e239b737faafecabc6bac9 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Wed, 27 Jul 2022 15:19:21 +0800 Subject: [PATCH 346/481] make `SongProgress` abstract - move unrelated logic to `DefaultSongProgress` - make `LegacySongProgress` inherit `SongProgress` --- .../Visual/Gameplay/TestSceneSongProgress.cs | 4 +- .../DefaultSongProgress.cs} | 67 ++++----------- osu.Game/Screens/Play/HUD/SongProgress.cs | 85 +++++++++++++++++++ .../Screens/Play/{ => HUD}/SongProgressBar.cs | 2 +- .../Play/{ => HUD}/SongProgressGraph.cs | 2 +- .../Play/{ => HUD}/SongProgressInfo.cs | 2 +- osu.Game/Skinning/DefaultSkin.cs | 2 +- osu.Game/Skinning/LegacySongProgress.cs | 53 ++++-------- 8 files changed, 126 insertions(+), 91 deletions(-) rename osu.Game/Screens/Play/{SongProgress.cs => HUD/DefaultSongProgress.cs} (82%) create mode 100644 osu.Game/Screens/Play/HUD/SongProgress.cs rename osu.Game/Screens/Play/{ => HUD}/SongProgressBar.cs (99%) rename osu.Game/Screens/Play/{ => HUD}/SongProgressGraph.cs (97%) rename osu.Game/Screens/Play/{ => HUD}/SongProgressInfo.cs (98%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index 07efb25b46..6f2853b095 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public class TestSceneSongProgress : OsuTestScene { - private SongProgress progress; + private DefaultSongProgress progress; private TestSongProgressGraph graph; private readonly Container progressContainer; @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Gameplay progress = null; } - progressContainer.Add(progress = new SongProgress + progressContainer.Add(progress = new DefaultSongProgress { RelativeSizeAxes = Axes.X, Anchor = Anchor.BottomLeft, diff --git a/osu.Game/Screens/Play/SongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs similarity index 82% rename from osu.Game/Screens/Play/SongProgress.cs rename to osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index d1510d10c2..7c2d8a9de2 100644 --- a/osu.Game/Screens/Play/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -5,12 +5,9 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Timing; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Objects; @@ -18,9 +15,9 @@ using osu.Game.Rulesets.UI; using osu.Game.Skinning; using osuTK; -namespace osu.Game.Screens.Play +namespace osu.Game.Screens.Play.HUD { - public class SongProgress : OverlayContainer, ISkinnableDrawable + public class DefaultSongProgress : SongProgress { public const float MAX_HEIGHT = info_height + bottom_bar_height + graph_height + handle_height; @@ -52,41 +49,13 @@ namespace osu.Game.Screens.Play protected override bool BlockScrollInput => false; - private double firstHitTime => objects.First().StartTime; - - //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). - private double lastHitTime => objects.Last().GetEndTime() + 1; - - private IEnumerable objects; - - public IEnumerable Objects - { - set - { - graph.Objects = objects = value; - - info.StartTime = firstHitTime; - info.EndTime = lastHitTime; - - bar.StartTime = firstHitTime; - bar.EndTime = lastHitTime; - } - } - [Resolved(canBeNull: true)] private Player player { get; set; } - [Resolved] - private GameplayClock gameplayClock { get; set; } - [Resolved(canBeNull: true)] private DrawableRuleset drawableRuleset { get; set; } - private IClock referenceClock; - - public bool UsesFixedAnchor { get; set; } - - public SongProgress() + public DefaultSongProgress() { RelativeSizeAxes = Axes.X; Anchor = Anchor.BottomRight; @@ -127,9 +96,6 @@ namespace osu.Game.Screens.Play { if (player?.Configuration.AllowUserInteraction == true) ((IBindable)AllowSeeking).BindTo(drawableRuleset.HasReplayLoaded); - - referenceClock = drawableRuleset.FrameStableClock; - Objects = drawableRuleset.Objects; } graph.FillColour = bar.FillColour = colours.BlueLighter; @@ -203,21 +169,24 @@ namespace osu.Game.Screens.Play this.FadeOut(100); } + protected override void UpdateObjects(IEnumerable objects) + { + graph.Objects = objects; + info.StartTime = FirstHitTime; + info.EndTime = LastHitTime; + bar.StartTime = FirstHitTime; + bar.EndTime = LastHitTime; + } + + protected override void UpdateProgress(double progress, double time, bool isIntro) + { + bar.CurrentTime = time; + graph.Progress = (int)(graph.ColumnCount * progress); + } + protected override void Update() { base.Update(); - - if (objects == null) - return; - - double gameplayTime = gameplayClock?.CurrentTime ?? Time.Current; - double frameStableTime = referenceClock?.CurrentTime ?? gameplayTime; - - double progress = Math.Min(1, (frameStableTime - firstHitTime) / (lastHitTime - firstHitTime)); - - bar.CurrentTime = gameplayTime; - graph.Progress = (int)(graph.ColumnCount * progress); - Height = bottom_bar_height + graph_height + handle_size.Y + info_height - graph.Y; } diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs new file mode 100644 index 0000000000..c245a47554 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -0,0 +1,85 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Containers; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.UI; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Play.HUD +{ + public abstract class SongProgress : OverlayContainer, ISkinnableDrawable + { + public bool UsesFixedAnchor { get; set; } + + [Resolved] + private GameplayClock gameplayClock { get; set; } + + [Resolved(canBeNull: true)] + private DrawableRuleset drawableRuleset { get; set; } + + [Resolved(canBeNull: true)] + private IBindable beatmap { get; set; } + + private IClock referenceClock; + private IEnumerable objects; + + public IEnumerable Objects + { + set => UpdateObjects(objects = value); + } + + protected double FirstHitTime => objects.FirstOrDefault()?.StartTime ?? 0; + + //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). + protected double LastHitTime => objects.LastOrDefault()?.GetEndTime() ?? 0; + + protected double FirstEventTime { get; private set; } + + protected abstract void UpdateProgress(double progress, double time, bool isIntro); + protected abstract void UpdateObjects(IEnumerable objects); + + [BackgroundDependencyLoader] + private void load() + { + if (drawableRuleset != null) + { + Objects = drawableRuleset.Objects; + referenceClock = drawableRuleset.FrameStableClock; + } + + if (beatmap != null) + { + FirstEventTime = beatmap.Value.Storyboard.EarliestEventTime ?? 0; + } + } + + protected override void Update() + { + base.Update(); + + if (objects == null) + return; + + double gameplayTime = gameplayClock?.CurrentTime ?? Time.Current; + double frameStableTime = referenceClock?.CurrentTime ?? gameplayTime; + + if (frameStableTime < FirstHitTime) + { + UpdateProgress((frameStableTime - FirstEventTime) / (FirstHitTime - FirstEventTime), gameplayTime, true); + } + else + { + UpdateProgress((frameStableTime - FirstHitTime) / (LastHitTime - FirstHitTime), gameplayTime, false); + } + } + } +} diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/HUD/SongProgressBar.cs similarity index 99% rename from osu.Game/Screens/Play/SongProgressBar.cs rename to osu.Game/Screens/Play/HUD/SongProgressBar.cs index 67923f4b6a..db4e200724 100644 --- a/osu.Game/Screens/Play/SongProgressBar.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressBar.cs @@ -13,7 +13,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Utils; using osu.Framework.Threading; -namespace osu.Game.Screens.Play +namespace osu.Game.Screens.Play.HUD { public class SongProgressBar : SliderBar { diff --git a/osu.Game/Screens/Play/SongProgressGraph.cs b/osu.Game/Screens/Play/HUD/SongProgressGraph.cs similarity index 97% rename from osu.Game/Screens/Play/SongProgressGraph.cs rename to osu.Game/Screens/Play/HUD/SongProgressGraph.cs index c742df67ce..f234b45922 100644 --- a/osu.Game/Screens/Play/SongProgressGraph.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressGraph.cs @@ -8,7 +8,7 @@ using System.Collections.Generic; using System.Diagnostics; using osu.Game.Rulesets.Objects; -namespace osu.Game.Screens.Play +namespace osu.Game.Screens.Play.HUD { public class SongProgressGraph : SquareGraph { diff --git a/osu.Game/Screens/Play/SongProgressInfo.cs b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs similarity index 98% rename from osu.Game/Screens/Play/SongProgressInfo.cs rename to osu.Game/Screens/Play/HUD/SongProgressInfo.cs index 40759c3a3b..8f10e84509 100644 --- a/osu.Game/Screens/Play/SongProgressInfo.cs +++ b/osu.Game/Screens/Play/HUD/SongProgressInfo.cs @@ -10,7 +10,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using System; -namespace osu.Game.Screens.Play +namespace osu.Game.Screens.Play.HUD { public class SongProgressInfo : Container { diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 5267861e3e..0848f42360 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -147,7 +147,7 @@ namespace osu.Game.Skinning new DefaultScoreCounter(), new DefaultAccuracyCounter(), new DefaultHealthDisplay(), - new SongProgress(), + new DefaultSongProgress(), new BarHitErrorMeter(), new BarHitErrorMeter(), new PerformancePointsCounter() diff --git a/osu.Game/Skinning/LegacySongProgress.cs b/osu.Game/Skinning/LegacySongProgress.cs index a141a5f91e..5f27d73761 100644 --- a/osu.Game/Skinning/LegacySongProgress.cs +++ b/osu.Game/Skinning/LegacySongProgress.cs @@ -3,38 +3,20 @@ #nullable disable -using System; -using System.Linq; +using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.UI; -using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; using osuTK; namespace osu.Game.Skinning { - public class LegacySongProgress : CompositeDrawable, ISkinnableDrawable + public class LegacySongProgress : SongProgress { - public bool UsesFixedAnchor { get; set; } - - [Resolved] - private GameplayClock gameplayClock { get; set; } - - [Resolved(canBeNull: true)] - private DrawableRuleset drawableRuleset { get; set; } - - [Resolved(canBeNull: true)] - private IBindable beatmap { get; set; } - - private double lastHitTime; - private double firstHitTime; - private double firstEventTime; private CircularProgress pie; [BackgroundDependencyLoader] @@ -76,36 +58,35 @@ namespace osu.Game.Skinning Size = new Vector2(3), } }; - - firstEventTime = beatmap?.Value.Storyboard.EarliestEventTime ?? 0; - - if (drawableRuleset != null) - { - firstHitTime = drawableRuleset.Objects.First().StartTime; - //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). - lastHitTime = drawableRuleset.Objects.Last().GetEndTime() + 1; - } } - protected override void Update() + protected override void PopIn() { - base.Update(); + } - double gameplayTime = gameplayClock?.CurrentTime ?? Time.Current; + protected override void PopOut() + { + } - if (gameplayTime < firstHitTime) + protected override void UpdateObjects(IEnumerable objects) + { + } + + protected override void UpdateProgress(double progress, double time, bool isIntro) + { + if (isIntro) { pie.Scale = new Vector2(-1, 1); pie.Anchor = Anchor.TopRight; pie.Colour = new Colour4(199, 255, 47, 153); - pie.Current.Value = 1 - Math.Clamp((gameplayTime - firstEventTime) / (firstHitTime - firstEventTime), 0, 1); + pie.Current.Value = 1 - progress; } else { pie.Scale = new Vector2(1); pie.Anchor = Anchor.TopLeft; pie.Colour = new Colour4(255, 255, 255, 153); - pie.Current.Value = Math.Clamp((gameplayTime - firstHitTime) / (lastHitTime - firstHitTime), 0, 1); + pie.Current.Value = progress; } } } From 494486ad0926dca9fa17bb733e3ccd65f18b4ebf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 16:27:10 +0900 Subject: [PATCH 347/481] Fix potential test failure if scores are added to the beatmap which is subsequently removed --- osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index a82386fd51..fdc9f2569d 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -279,12 +279,16 @@ namespace osu.Game.Tests.Database { var importer = new BeatmapImporter(storage, realm); using var rulesets = new RealmRulesetStore(realm, storage); + string removedFilename = null!; using var __ = getBeatmapArchive(out string pathOriginal); using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory => { // arbitrary beatmap removal - directory.GetFiles("*.osu").First().Delete(); + var fileToRemove = directory.GetFiles("*.osu").First(); + + removedFilename = fileToRemove.Name; + fileToRemove.Delete(); }); var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal)); @@ -296,7 +300,9 @@ namespace osu.Game.Tests.Database importBeforeUpdate.PerformWrite(s => { - var beatmapInfo = s.Beatmaps.Last(); + // make sure not to add scores to the same beatmap that is removed in the update. + var beatmapInfo = s.Beatmaps.First(b => b.File?.Filename != removedFilename); + scoreTargetBeatmapHash = beatmapInfo.Hash; s.Realm.Add(new ScoreInfo(beatmapInfo, s.Realm.All().First(), new RealmUser())); }); From 1e013bd4e90ce2b22830fff88ca5ab283f0a77bd Mon Sep 17 00:00:00 2001 From: Nitrous Date: Wed, 27 Jul 2022 15:57:23 +0800 Subject: [PATCH 348/481] move song progress graph to its own test scene --- .../Gameplay/TestSceneSongProgressGraph.cs | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestSceneSongProgressGraph.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgressGraph.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgressGraph.cs new file mode 100644 index 0000000000..2fa3c0c7ec --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgressGraph.cs @@ -0,0 +1,73 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Play.HUD; + +namespace osu.Game.Tests.Visual.Gameplay +{ + [TestFixture] + public class TestSceneSongProgressGraph : OsuTestScene + { + private TestSongProgressGraph graph; + + [SetUpSteps] + public void SetupSteps() + { + AddStep("add new big graph", () => + { + if (graph != null) + { + graph.Expire(); + graph = null; + } + + Add(graph = new TestSongProgressGraph + { + RelativeSizeAxes = Axes.X, + Height = 200, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }); + }); + } + + [Test] + public void TestGraphRecreation() + { + AddAssert("ensure not created", () => graph.CreationCount == 0); + AddStep("display values", displayRandomValues); + AddUntilStep("wait for creation count", () => graph.CreationCount == 1); + AddRepeatStep("new values", displayRandomValues, 5); + AddWaitStep("wait some", 5); + AddAssert("ensure recreation debounced", () => graph.CreationCount == 2); + } + + private void displayRandomValues() + { + var objects = new List(); + for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000) + objects.Add(new HitObject { StartTime = i }); + + graph.Objects = objects; + } + + private class TestSongProgressGraph : SongProgressGraph + { + public int CreationCount { get; private set; } + + protected override void RecreateGraph() + { + base.RecreateGraph(); + CreationCount++; + } + } + } +} From 6af6f03e293b22b763dc6b55c833fff2414fc36a Mon Sep 17 00:00:00 2001 From: Nitrous Date: Wed, 27 Jul 2022 15:57:47 +0800 Subject: [PATCH 349/481] refactor song progress test scene --- .../Visual/Gameplay/TestSceneSongProgress.cs | 115 +++++------------- 1 file changed, 30 insertions(+), 85 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index 6f2853b095..f32a7e7cab 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -7,23 +7,20 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; -using osu.Framework.Utils; using osu.Framework.Timing; -using osu.Game.Graphics; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestSceneSongProgress : OsuTestScene + public class TestSceneSongProgress : SkinnableHUDComponentTestScene { private DefaultSongProgress progress; - private TestSongProgressGraph graph; - private readonly Container progressContainer; + private readonly List progresses = new List(); private readonly StopwatchClock clock; private readonly FramedClock framedClock; @@ -35,77 +32,18 @@ namespace osu.Game.Tests.Visual.Gameplay { clock = new StopwatchClock(); gameplayClock = new GameplayClock(framedClock = new FramedClock(clock)); - - Add(progressContainer = new Container - { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - Height = 100, - Y = -100, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(1), - } - }); } [SetUpSteps] public void SetupSteps() { - AddStep("add new song progress", () => - { - if (progress != null) - { - progress.Expire(); - progress = null; - } - - progressContainer.Add(progress = new DefaultSongProgress - { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - }); - }); - - AddStep("add new big graph", () => - { - if (graph != null) - { - graph.Expire(); - graph = null; - } - - Add(graph = new TestSongProgressGraph - { - RelativeSizeAxes = Axes.X, - Height = 200, - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - }); - }); - AddStep("reset clock", clock.Reset); } - [Test] - public void TestGraphRecreation() - { - AddAssert("ensure not created", () => graph.CreationCount == 0); - AddStep("display values", displayRandomValues); - AddUntilStep("wait for creation count", () => graph.CreationCount == 1); - AddRepeatStep("new values", displayRandomValues, 5); - AddWaitStep("wait some", 5); - AddAssert("ensure recreation debounced", () => graph.CreationCount == 2); - } - [Test] public void TestDisplay() { AddStep("display max values", displayMaxValues); - AddUntilStep("wait for graph", () => graph.CreationCount == 1); AddStep("start", clock.Start); AddStep("allow seeking", () => progress.AllowSeeking.Value = true); AddStep("hide graph", () => progress.ShowGraph.Value = false); @@ -115,15 +53,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("stop", clock.Stop); } - private void displayRandomValues() - { - var objects = new List(); - for (double i = 0; i < 5000; i += RNG.NextDouble() * 10 + i / 1000) - objects.Add(new HitObject { StartTime = i }); - - replaceObjects(objects); - } - private void displayMaxValues() { var objects = new List(); @@ -135,10 +64,12 @@ namespace osu.Game.Tests.Visual.Gameplay private void replaceObjects(List objects) { - progress.Objects = objects; - graph.Objects = objects; - progress.RequestSeek = pos => clock.Seek(pos); + + foreach (var progress in progresses) + { + progress.Objects = objects; + } } protected override void Update() @@ -147,15 +78,29 @@ namespace osu.Game.Tests.Visual.Gameplay framedClock.ProcessFrame(); } - private class TestSongProgressGraph : SongProgressGraph + protected override Drawable CreateDefaultImplementation() { - public int CreationCount { get; private set; } - - protected override void RecreateGraph() + progress = new DefaultSongProgress { - base.RecreateGraph(); - CreationCount++; - } + RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }; + + progresses.Add(progress); + return progress; + } + + protected override Drawable CreateLegacyImplementation() + { + var progress = new LegacySongProgress + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + + progresses.Add(progress); + return progress; } } } From a222278710568f240b75b69dc4600941ecb395f4 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Wed, 27 Jul 2022 16:01:35 +0800 Subject: [PATCH 350/481] remove unused using --- osu.Game/Skinning/DefaultSkin.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 0848f42360..7d217dd956 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -16,7 +16,6 @@ using osu.Game.Audio; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; using osu.Game.IO; -using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD.HitErrorMeters; using osuTK; From 9c543fef481b0afda90d60288a111dd10a544067 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 15:59:36 +0900 Subject: [PATCH 351/481] Remove `CollectionManager` --- .../Collections/IO/ImportCollectionsTest.cs | 83 ++++++++---- osu.Game.Tests/ImportTest.cs | 6 +- .../TestSceneManageCollectionsDialog.cs | 120 +++++++++++------- .../SongSelect/TestSceneFilterControl.cs | 52 ++++---- osu.Game/Beatmaps/RealmBeatmapCollection.cs | 4 +- .../Collections/CollectionFilterDropdown.cs | 6 +- osu.Game/Collections/CollectionManager.cs | 62 --------- .../Collections/CollectionToggleMenuItem.cs | 10 +- .../Collections/DeleteCollectionDialog.cs | 5 +- .../Collections/DrawableCollectionList.cs | 17 ++- .../Collections/DrawableCollectionListItem.cs | 47 ++++--- .../Collections/ManageCollectionsDialog.cs | 5 - osu.Game/Database/LegacyCollectionImporter.cs | 54 ++++---- osu.Game/Database/LegacyImportManager.cs | 11 +- osu.Game/OsuGame.cs | 5 - .../Maintenance/CollectionsSettings.cs | 23 +++- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 10 +- .../Carousel/DrawableCarouselBeatmap.cs | 18 ++- .../Carousel/DrawableCarouselBeatmapSet.cs | 30 ++--- 19 files changed, 276 insertions(+), 292 deletions(-) delete mode 100644 osu.Game/Collections/CollectionManager.cs diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index 685586ff02..32503cdb12 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -5,12 +5,14 @@ using System; using System.IO; +using System.Linq; using System.Text; using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Tests.Resources; @@ -30,7 +32,11 @@ namespace osu.Game.Tests.Collections.IO await importCollectionsFromStream(osu, new MemoryStream()); - Assert.That(osu.CollectionManager.Collections.Count, Is.Zero); + osu.Realm.Run(realm => + { + var collections = realm.All().ToList(); + Assert.That(collections.Count, Is.Zero); + }); } finally { @@ -50,18 +56,22 @@ namespace osu.Game.Tests.Collections.IO await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db")); - Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2)); + osu.Realm.Run(realm => + { + var collections = realm.All().ToList(); + Assert.That(collections.Count, Is.EqualTo(2)); - // Even with no beatmaps imported, collections are tracking the hashes and will continue to. - // In the future this whole mechanism will be replaced with having the collections in realm, - // but until that happens it makes rough sense that we want to track not-yet-imported beatmaps - // and have them associate with collections if/when they become available. + // Even with no beatmaps imported, collections are tracking the hashes and will continue to. + // In the future this whole mechanism will be replaced with having the collections in realm, + // but until that happens it makes rough sense that we want to track not-yet-imported beatmaps + // and have them associate with collections if/when they become available. - Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First")); - Assert.That(osu.CollectionManager.Collections[0].BeatmapHashes.Count, Is.EqualTo(1)); + Assert.That(collections[0].Name, Is.EqualTo("First")); + Assert.That(collections[0].BeatmapMD5Hashes.Count, Is.EqualTo(1)); - Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Second")); - Assert.That(osu.CollectionManager.Collections[1].BeatmapHashes.Count, Is.EqualTo(12)); + Assert.That(collections[1].Name, Is.EqualTo("Second")); + Assert.That(collections[1].BeatmapMD5Hashes.Count, Is.EqualTo(12)); + }); } finally { @@ -81,13 +91,18 @@ namespace osu.Game.Tests.Collections.IO await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db")); - Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2)); + osu.Realm.Run(realm => + { + var collections = realm.All().ToList(); - Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First")); - Assert.That(osu.CollectionManager.Collections[0].BeatmapHashes.Count, Is.EqualTo(1)); + Assert.That(collections.Count, Is.EqualTo(2)); - Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Second")); - Assert.That(osu.CollectionManager.Collections[1].BeatmapHashes.Count, Is.EqualTo(12)); + Assert.That(collections[0].Name, Is.EqualTo("First")); + Assert.That(collections[0].BeatmapMD5Hashes.Count, Is.EqualTo(1)); + + Assert.That(collections[1].Name, Is.EqualTo("Second")); + Assert.That(collections[1].BeatmapMD5Hashes.Count, Is.EqualTo(12)); + }); } finally { @@ -124,7 +139,11 @@ namespace osu.Game.Tests.Collections.IO } Assert.That(exceptionThrown, Is.False); - Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(0)); + osu.Realm.Run(realm => + { + var collections = realm.All().ToList(); + Assert.That(collections.Count, Is.EqualTo(0)); + }); } finally { @@ -149,12 +168,18 @@ namespace osu.Game.Tests.Collections.IO await importCollectionsFromStream(osu, TestResources.OpenResource("Collections/collections.db")); - // Move first beatmap from second collection into the first. - osu.CollectionManager.Collections[0].BeatmapHashes.Add(osu.CollectionManager.Collections[1].BeatmapHashes[0]); - osu.CollectionManager.Collections[1].BeatmapHashes.RemoveAt(0); + // ReSharper disable once MethodHasAsyncOverload + osu.Realm.Write(realm => + { + var collections = realm.All().ToList(); - // Rename the second collecction. - osu.CollectionManager.Collections[1].Name.Value = "Another"; + // Move first beatmap from second collection into the first. + collections[0].BeatmapMD5Hashes.Add(collections[1].BeatmapMD5Hashes[0]); + collections[1].BeatmapMD5Hashes.RemoveAt(0); + + // Rename the second collecction. + collections[1].Name = "Another"; + }); } finally { @@ -169,13 +194,17 @@ namespace osu.Game.Tests.Collections.IO { var osu = LoadOsuIntoHost(host, true); - Assert.That(osu.CollectionManager.Collections.Count, Is.EqualTo(2)); + osu.Realm.Run(realm => + { + var collections = realm.All().ToList(); + Assert.That(collections.Count, Is.EqualTo(2)); - Assert.That(osu.CollectionManager.Collections[0].Name.Value, Is.EqualTo("First")); - Assert.That(osu.CollectionManager.Collections[0].BeatmapHashes.Count, Is.EqualTo(2)); + Assert.That(collections[0].Name, Is.EqualTo("First")); + Assert.That(collections[0].BeatmapMD5Hashes.Count, Is.EqualTo(2)); - Assert.That(osu.CollectionManager.Collections[1].Name.Value, Is.EqualTo("Another")); - Assert.That(osu.CollectionManager.Collections[1].BeatmapHashes.Count, Is.EqualTo(11)); + Assert.That(collections[1].Name, Is.EqualTo("Another")); + Assert.That(collections[1].BeatmapMD5Hashes.Count, Is.EqualTo(11)); + }); } finally { @@ -188,7 +217,7 @@ namespace osu.Game.Tests.Collections.IO { // intentionally spin this up on a separate task to avoid disposal deadlocks. // see https://github.com/EventStore/EventStore/issues/1179 - await Task.Factory.StartNew(() => new LegacyCollectionImporter(osu.CollectionManager).Import(stream).WaitSafely(), TaskCreationOptions.LongRunning); + await Task.Factory.StartNew(() => new LegacyCollectionImporter(osu.Realm).Import(stream).WaitSafely(), TaskCreationOptions.LongRunning); } } } diff --git a/osu.Game.Tests/ImportTest.cs b/osu.Game.Tests/ImportTest.cs index 1f18f92158..23ca31ee42 100644 --- a/osu.Game.Tests/ImportTest.cs +++ b/osu.Game.Tests/ImportTest.cs @@ -10,7 +10,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Platform; -using osu.Game.Collections; +using osu.Game.Database; using osu.Game.Tests.Resources; namespace osu.Game.Tests @@ -47,7 +47,7 @@ namespace osu.Game.Tests public class TestOsuGameBase : OsuGameBase { - public CollectionManager CollectionManager { get; private set; } + public RealmAccess Realm => Dependencies.Get(); private readonly bool withBeatmap; @@ -62,8 +62,6 @@ namespace osu.Game.Tests // Beatmap must be imported before the collection manager is loaded. if (withBeatmap) BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely(); - - AddInternal(CollectionManager = new CollectionManager()); } } } diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 789139f483..21d2b0328b 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -27,13 +25,10 @@ namespace osu.Game.Tests.Visual.Collections { protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; - private DialogOverlay dialogOverlay; - private CollectionManager manager; - - private RulesetStore rulesets; - private BeatmapManager beatmapManager; - - private ManageCollectionsDialog dialog; + private DialogOverlay dialogOverlay = null!; + private RulesetStore rulesets = null!; + private BeatmapManager beatmapManager = null!; + private ManageCollectionsDialog dialog = null!; [BackgroundDependencyLoader] private void load(GameHost host) @@ -46,19 +41,17 @@ namespace osu.Game.Tests.Visual.Collections base.Content.AddRange(new Drawable[] { - manager = new CollectionManager(), Content, dialogOverlay = new DialogOverlay(), }); - Dependencies.Cache(manager); Dependencies.CacheAs(dialogOverlay); } [SetUp] public void SetUp() => Schedule(() => { - manager.Collections.Clear(); + Realm.Write(r => r.RemoveAll()); Child = dialog = new ManageCollectionsDialog(); }); @@ -78,17 +71,17 @@ namespace osu.Game.Tests.Visual.Collections [Test] public void TestLastItemIsPlaceholder() { - AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType().Last().Model)); + AddAssert("last item is placeholder", () => !dialog.ChildrenOfType().Last().Model.IsManaged); } [Test] public void TestAddCollectionExternal() { - AddStep("add collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "First collection" } })); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "First collection")))); assertCollectionCount(1); assertCollectionName(0, "First collection"); - AddStep("add another collection", () => manager.Collections.Add(new BeatmapCollection { Name = { Value = "Second collection" } })); + AddStep("add another collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "Second collection")))); assertCollectionCount(2); assertCollectionName(1, "Second collection"); } @@ -108,7 +101,7 @@ namespace osu.Game.Tests.Visual.Collections [Test] public void TestAddCollectionViaPlaceholder() { - DrawableCollectionListItem placeholderItem = null; + DrawableCollectionListItem placeholderItem = null!; AddStep("focus placeholder", () => { @@ -117,23 +110,31 @@ namespace osu.Game.Tests.Visual.Collections }); // Done directly via the collection since InputManager methods cannot add text to textbox... - AddStep("change collection name", () => placeholderItem.Model.Name.Value = "a"); + AddStep("change collection name", () => placeholderItem.Model.Name = "a"); assertCollectionCount(1); - AddAssert("collection now exists", () => manager.Collections.Contains(placeholderItem.Model)); + AddAssert("collection now exists", () => placeholderItem.Model.IsManaged); - AddAssert("last item is placeholder", () => !manager.Collections.Contains(dialog.ChildrenOfType().Last().Model)); + AddAssert("last item is placeholder", () => !dialog.ChildrenOfType().Last().Model.IsManaged); } [Test] public void TestRemoveCollectionExternal() { - AddStep("add two collections", () => manager.Collections.AddRange(new[] - { - new BeatmapCollection { Name = { Value = "1" } }, - new BeatmapCollection { Name = { Value = "2" } }, - })); + RealmBeatmapCollection first = null!; - AddStep("remove first collection", () => manager.Collections.RemoveAt(0)); + AddStep("add two collections", () => + { + Realm.Write(r => + { + r.Add(new[] + { + first = new RealmBeatmapCollection(name: "1"), + new RealmBeatmapCollection(name: "2"), + }); + }); + }); + + AddStep("change first collection name", () => Realm.Write(r => r.Remove(first))); assertCollectionCount(1); assertCollectionName(0, "2"); } @@ -151,21 +152,27 @@ namespace osu.Game.Tests.Visual.Collections Width = 0.4f, }); }); - AddStep("add two collections with same name", () => manager.Collections.AddRange(new[] + AddStep("add two collections with same name", () => Realm.Write(r => r.Add(new[] { - new BeatmapCollection { Name = { Value = "1" } }, - new BeatmapCollection { Name = { Value = "1" }, BeatmapHashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } }, - })); + new RealmBeatmapCollection(name: "1"), + new RealmBeatmapCollection(name: "1") + { + BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } + }, + }))); } [Test] public void TestRemoveCollectionViaButton() { - AddStep("add two collections", () => manager.Collections.AddRange(new[] + AddStep("add two collections", () => Realm.Write(r => r.Add(new[] { - new BeatmapCollection { Name = { Value = "1" } }, - new BeatmapCollection { Name = { Value = "2" }, BeatmapHashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } }, - })); + new RealmBeatmapCollection(name: "1"), + new RealmBeatmapCollection(name: "2") + { + BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } + }, + }))); assertCollectionCount(2); @@ -198,10 +205,13 @@ namespace osu.Game.Tests.Visual.Collections [Test] public void TestCollectionNotRemovedWhenDialogCancelled() { - AddStep("add two collections", () => manager.Collections.AddRange(new[] + AddStep("add collection", () => Realm.Write(r => r.Add(new[] { - new BeatmapCollection { Name = { Value = "1" }, BeatmapHashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } }, - })); + new RealmBeatmapCollection(name: "1") + { + BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } + }, + }))); assertCollectionCount(1); @@ -224,13 +234,21 @@ namespace osu.Game.Tests.Visual.Collections [Test] public void TestCollectionRenamedExternal() { - AddStep("add two collections", () => manager.Collections.AddRange(new[] - { - new BeatmapCollection { Name = { Value = "1" } }, - new BeatmapCollection { Name = { Value = "2" } }, - })); + RealmBeatmapCollection first = null!; - AddStep("change first collection name", () => manager.Collections[0].Name.Value = "First"); + AddStep("add two collections", () => + { + Realm.Write(r => + { + r.Add(new[] + { + first = new RealmBeatmapCollection(name: "1"), + new RealmBeatmapCollection(name: "2"), + }); + }); + }); + + AddStep("change first collection name", () => Realm.Write(_ => first.Name = "First")); assertCollectionName(0, "First"); } @@ -238,16 +256,24 @@ namespace osu.Game.Tests.Visual.Collections [Test] public void TestCollectionRenamedOnTextChange() { - AddStep("add two collections", () => manager.Collections.AddRange(new[] + RealmBeatmapCollection first = null!; + + AddStep("add two collections", () => { - new BeatmapCollection { Name = { Value = "1" } }, - new BeatmapCollection { Name = { Value = "2" } }, - })); + Realm.Write(r => + { + r.Add(new[] + { + first = new RealmBeatmapCollection(name: "1"), + new RealmBeatmapCollection(name: "2"), + }); + }); + }); assertCollectionCount(2); AddStep("change first collection name", () => dialog.ChildrenOfType().First().Text = "First"); - AddAssert("collection has new name", () => manager.Collections[0].Name.Value == "First"); + AddUntilStep("collection has new name", () => first.Name == "First"); } private void assertCollectionCount(int count) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 01bf312ddd..e4d69334a3 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -28,12 +26,9 @@ namespace osu.Game.Tests.Visual.SongSelect { protected override Container Content { get; } = new Container { RelativeSizeAxes = Axes.Both }; - private CollectionManager collectionManager; - - private RulesetStore rulesets; - private BeatmapManager beatmapManager; - - private FilterControl control; + private RulesetStore rulesets = null!; + private BeatmapManager beatmapManager = null!; + private FilterControl control = null!; [BackgroundDependencyLoader] private void load(GameHost host) @@ -46,17 +41,14 @@ namespace osu.Game.Tests.Visual.SongSelect base.Content.AddRange(new Drawable[] { - collectionManager = new CollectionManager(), Content }); - - Dependencies.Cache(collectionManager); } [SetUp] public void SetUp() => Schedule(() => { - collectionManager.Collections.Clear(); + Realm.Write(r => r.RemoveAll()); Child = control = new FilterControl { @@ -77,8 +69,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestCollectionAddedToDropdown() { - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "2" } })); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "2")))); assertCollectionDropdownContains("1"); assertCollectionDropdownContains("2"); } @@ -86,9 +78,11 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestCollectionRemovedFromDropdown() { - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "2" } })); - AddStep("remove collection", () => collectionManager.Collections.RemoveAt(0)); + var first = new RealmBeatmapCollection(name: "1"); + + AddStep("add collection", () => Realm.Write(r => r.Add(first))); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "2")))); + AddStep("remove collection", () => Realm.Write(r => r.Remove(first))); assertCollectionDropdownContains("1", false); assertCollectionDropdownContains("2"); @@ -97,7 +91,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestCollectionRenamed() { - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); AddStep("select collection", () => { var dropdown = control.ChildrenOfType().Single(); @@ -106,7 +100,7 @@ namespace osu.Game.Tests.Visual.SongSelect addExpandHeaderStep(); - AddStep("change name", () => collectionManager.Collections[0].Name.Value = "First"); + AddStep("change name", () => Realm.Write(_ => getFirstCollection().Name = "First")); assertCollectionDropdownContains("First"); assertCollectionHeaderDisplays("First"); @@ -124,7 +118,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestCollectionFilterHasAddButton() { addExpandHeaderStep(); - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); AddStep("hover collection", () => InputManager.MoveMouseTo(getAddOrRemoveButton(1))); AddAssert("collection has add button", () => getAddOrRemoveButton(1).IsPresent); } @@ -134,7 +128,7 @@ namespace osu.Game.Tests.Visual.SongSelect { addExpandHeaderStep(); - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddAssert("button enabled", () => getAddOrRemoveButton(1).Enabled.Value); @@ -150,13 +144,13 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); - AddStep("add beatmap to collection", () => collectionManager.Collections[0].BeatmapHashes.Add(Beatmap.Value.BeatmapInfo.MD5Hash)); + AddStep("add beatmap to collection", () => getFirstCollection().BeatmapMD5Hashes.Add(Beatmap.Value.BeatmapInfo.MD5Hash)); AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); - AddStep("remove beatmap from collection", () => collectionManager.Collections[0].BeatmapHashes.Clear()); + AddStep("remove beatmap from collection", () => getFirstCollection().BeatmapMD5Hashes.Clear()); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); } @@ -167,15 +161,15 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); addClickAddOrRemoveButtonStep(1); - AddAssert("collection contains beatmap", () => collectionManager.Collections[0].BeatmapHashes.Contains(Beatmap.Value.BeatmapInfo.MD5Hash)); + AddAssert("collection contains beatmap", () => getFirstCollection().BeatmapMD5Hashes.Contains(Beatmap.Value.BeatmapInfo.MD5Hash)); AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); addClickAddOrRemoveButtonStep(1); - AddAssert("collection does not contain beatmap", () => !collectionManager.Collections[0].BeatmapHashes.Contains(Beatmap.Value.BeatmapInfo.MD5Hash)); + AddAssert("collection does not contain beatmap", () => !getFirstCollection().BeatmapMD5Hashes.Contains(Beatmap.Value.BeatmapInfo.MD5Hash)); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); } @@ -184,7 +178,7 @@ namespace osu.Game.Tests.Visual.SongSelect { addExpandHeaderStep(); - AddStep("add collection", () => collectionManager.Collections.Add(new BeatmapCollection { Name = { Value = "1" } })); + AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); AddStep("select collection", () => { InputManager.MoveMouseTo(getCollectionDropdownItems().ElementAt(1)); @@ -202,6 +196,8 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("collection filter still selected", () => control.CreateCriteria().Collection?.Name.Value == "1"); } + private RealmBeatmapCollection getFirstCollection() => Realm.Run(r => r.All().First()); + private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) => AddAssert($"collection dropdown header displays '{collectionName}'", () => shouldDisplay == (control.ChildrenOfType().Single().ChildrenOfType().First().Text == collectionName)); diff --git a/osu.Game/Beatmaps/RealmBeatmapCollection.cs b/osu.Game/Beatmaps/RealmBeatmapCollection.cs index d3261fc39e..22ba9d5789 100644 --- a/osu.Game/Beatmaps/RealmBeatmapCollection.cs +++ b/osu.Game/Beatmaps/RealmBeatmapCollection.cs @@ -16,14 +16,14 @@ namespace osu.Game.Beatmaps public string Name { get; set; } = string.Empty; - public List BeatmapMD5Hashes { get; set; } = null!; + public IList BeatmapMD5Hashes { get; } = null!; /// /// The date when this collection was last modified. /// public DateTimeOffset LastModified { get; set; } - public RealmBeatmapCollection(string? name, List? beatmapMD5Hashes) + public RealmBeatmapCollection(string? name = null, IList? beatmapMD5Hashes = null) { ID = Guid.NewGuid(); Name = name ?? string.Empty; diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index d099eb6e1b..ed2c0c7cfb 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -46,9 +46,6 @@ namespace osu.Game.Collections [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } - [Resolved(CanBeNull = true)] - private CollectionManager collectionManager { get; set; } - public CollectionFilterDropdown() { ItemSource = filters; @@ -59,8 +56,7 @@ namespace osu.Game.Collections { base.LoadComplete(); - if (collectionManager != null) - collections.BindTo(collectionManager.Collections); + // TODO: bind to realm data // Dropdown has logic which triggers a change on the bindable with every change to the contained items. // This is not desirable here, as it leads to multiple filter operations running even though nothing has changed. diff --git a/osu.Game/Collections/CollectionManager.cs b/osu.Game/Collections/CollectionManager.cs deleted file mode 100644 index 0d4ee5c722..0000000000 --- a/osu.Game/Collections/CollectionManager.cs +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using System; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Database; -using osu.Game.Overlays.Notifications; -using Realms; - -namespace osu.Game.Collections -{ - /// - /// Handles user-defined collections of beatmaps. - /// - public class CollectionManager : Component, IPostNotifications - { - public readonly BindableList Collections = new BindableList(); - - [Resolved] - private RealmAccess realm { get; set; } - - [BackgroundDependencyLoader] - private void load() - { - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - realm.RegisterForNotifications(r => r.All(), collectionsChanged); - } - - private void collectionsChanged(IRealmCollection sender, ChangeSet changes, Exception error) - { - // TODO: hook up with realm changes. - - if (changes == null) - { - foreach (var collection in sender) - Collections.Add(new BeatmapCollection - { - Name = { Value = collection.Name }, - BeatmapHashes = { Value = collection.BeatmapMD5Hashes }, - }); - } - } - - public Action PostNotification { protected get; set; } - - public void DeleteAll() - { - Collections.Clear(); - PostNotification?.Invoke(new ProgressCompletionNotification { Text = "Deleted all collections!" }); - } - } -} diff --git a/osu.Game/Collections/CollectionToggleMenuItem.cs b/osu.Game/Collections/CollectionToggleMenuItem.cs index f2b10305b8..632249913d 100644 --- a/osu.Game/Collections/CollectionToggleMenuItem.cs +++ b/osu.Game/Collections/CollectionToggleMenuItem.cs @@ -8,16 +8,16 @@ namespace osu.Game.Collections { public class CollectionToggleMenuItem : ToggleMenuItem { - public CollectionToggleMenuItem(BeatmapCollection collection, IBeatmapInfo beatmap) - : base(collection.Name.Value, MenuItemType.Standard, state => + public CollectionToggleMenuItem(RealmBeatmapCollection collection, IBeatmapInfo beatmap) + : base(collection.Name, MenuItemType.Standard, state => { if (state) - collection.BeatmapHashes.Add(beatmap.MD5Hash); + collection.BeatmapMD5Hashes.Add(beatmap.MD5Hash); else - collection.BeatmapHashes.Remove(beatmap.MD5Hash); + collection.BeatmapMD5Hashes.Remove(beatmap.MD5Hash); }) { - State.Value = collection.BeatmapHashes.Contains(beatmap.MD5Hash); + State.Value = collection.BeatmapMD5Hashes.Contains(beatmap.MD5Hash); } } } diff --git a/osu.Game/Collections/DeleteCollectionDialog.cs b/osu.Game/Collections/DeleteCollectionDialog.cs index 1da2870913..33c2174623 100644 --- a/osu.Game/Collections/DeleteCollectionDialog.cs +++ b/osu.Game/Collections/DeleteCollectionDialog.cs @@ -6,16 +6,17 @@ using System; using Humanizer; using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; using osu.Game.Overlays.Dialog; namespace osu.Game.Collections { public class DeleteCollectionDialog : PopupDialog { - public DeleteCollectionDialog(BeatmapCollection collection, Action deleteAction) + public DeleteCollectionDialog(RealmBeatmapCollection collection, Action deleteAction) { HeaderText = "Confirm deletion of"; - BodyText = $"{collection.Name.Value} ({"beatmap".ToQuantity(collection.BeatmapHashes.Count)})"; + BodyText = $"{collection.Name} ({"beatmap".ToQuantity(collection.BeatmapMD5Hashes.Count)})"; Icon = FontAwesome.Regular.TrashAlt; diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 4fe5733c2f..63f04641f4 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -7,28 +7,31 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osuTK; namespace osu.Game.Collections { /// - /// Visualises a list of s. + /// Visualises a list of s. /// - public class DrawableCollectionList : OsuRearrangeableListContainer + public class DrawableCollectionList : OsuRearrangeableListContainer { private Scroll scroll; protected override ScrollContainer CreateScrollContainer() => scroll = new Scroll(); - protected override FillFlowContainer> CreateListFillFlowContainer() => new Flow + protected override FillFlowContainer> CreateListFillFlowContainer() => new Flow { DragActive = { BindTarget = DragActive } }; - protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapCollection item) + // TODO: source from realm + + protected override OsuRearrangeableListItem CreateOsuDrawable(RealmBeatmapCollection item) { - if (item == scroll.PlaceholderItem.Model) + if (item.ID == scroll.PlaceholderItem.Model.ID) return scroll.ReplacePlaceholder(); return new DrawableCollectionListItem(item, true); @@ -95,7 +98,7 @@ namespace osu.Game.Collections var previous = PlaceholderItem; placeholderContainer.Clear(false); - placeholderContainer.Add(PlaceholderItem = new DrawableCollectionListItem(new BeatmapCollection(), false)); + placeholderContainer.Add(PlaceholderItem = new DrawableCollectionListItem(new RealmBeatmapCollection(), false)); return previous; } @@ -104,7 +107,7 @@ namespace osu.Game.Collections /// /// The flow of . Disables layout easing unless a drag is in progress. /// - private class Flow : FillFlowContainer> + private class Flow : FillFlowContainer> { public readonly IBindable DragActive = new Bindable(); diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 4596fc0e52..a29b2ef81c 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -12,6 +12,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -22,15 +24,15 @@ using osuTK.Graphics; namespace osu.Game.Collections { /// - /// Visualises a inside a . + /// Visualises a inside a . /// - public class DrawableCollectionListItem : OsuRearrangeableListItem + public class DrawableCollectionListItem : OsuRearrangeableListItem { private const float item_height = 35; private const float button_width = item_height * 0.75f; /// - /// Whether the currently exists inside the . + /// Whether the currently exists inside realm. /// public IBindable IsCreated => isCreated; @@ -39,9 +41,9 @@ namespace osu.Game.Collections /// /// Creates a new . /// - /// The . - /// Whether currently exists inside the . - public DrawableCollectionListItem(BeatmapCollection item, bool isCreated) + /// The . + /// Whether currently exists inside realm. + public DrawableCollectionListItem(RealmBeatmapCollection item, bool isCreated) : base(item) { this.isCreated.Value = isCreated; @@ -61,24 +63,18 @@ namespace osu.Game.Collections { public readonly Bindable IsCreated = new Bindable(); - private readonly IBindable collectionName; - private readonly BeatmapCollection collection; - - [Resolved(CanBeNull = true)] - private CollectionManager collectionManager { get; set; } + private readonly RealmBeatmapCollection collection; private Container textBoxPaddingContainer; private ItemTextBox textBox; - public ItemContent(BeatmapCollection collection) + public ItemContent(RealmBeatmapCollection collection) { this.collection = collection; RelativeSizeAxes = Axes.X; Height = item_height; Masking = true; - - collectionName = collection.Name.GetBoundCopy(); } [BackgroundDependencyLoader] @@ -111,14 +107,17 @@ namespace osu.Game.Collections }; } + [Resolved] + private RealmAccess realm { get; set; } + protected override void LoadComplete() { base.LoadComplete(); // Bind late, as the collection name may change externally while still loading. - textBox.Current = collection.Name; + textBox.Current.Value = collection.Name; + textBox.Current.BindValueChanged(_ => createNewCollection(), true); - collectionName.BindValueChanged(_ => createNewCollection(), true); IsCreated.BindValueChanged(created => textBoxPaddingContainer.Padding = new MarginPadding { Right = created.NewValue ? button_width : 0 }, true); } @@ -127,11 +126,11 @@ namespace osu.Game.Collections if (IsCreated.Value) return; - if (string.IsNullOrEmpty(collectionName.Value)) + if (string.IsNullOrEmpty(textBox.Current.Value)) return; // Add the new collection and disable our placeholder. If all text is removed, the placeholder should not show back again. - collectionManager?.Collections.Add(collection); + realm.Write(r => r.Add(collection)); textBox.PlaceholderText = string.Empty; // When this item changes from placeholder to non-placeholder (via changing containers), its textbox will lose focus, so it needs to be re-focused. @@ -162,15 +161,15 @@ namespace osu.Game.Collections [Resolved(CanBeNull = true)] private IDialogOverlay dialogOverlay { get; set; } - [Resolved(CanBeNull = true)] - private CollectionManager collectionManager { get; set; } + [Resolved] + private RealmAccess realmAccess { get; set; } - private readonly BeatmapCollection collection; + private readonly RealmBeatmapCollection collection; private Drawable fadeContainer; private Drawable background; - public DeleteButton(BeatmapCollection collection) + public DeleteButton(RealmBeatmapCollection collection) { this.collection = collection; RelativeSizeAxes = Axes.Y; @@ -227,7 +226,7 @@ namespace osu.Game.Collections { background.FlashColour(Color4.White, 150); - if (collection.BeatmapHashes.Count == 0) + if (collection.BeatmapMD5Hashes.Count == 0) deleteCollection(); else dialogOverlay?.Push(new DeleteCollectionDialog(collection, deleteCollection)); @@ -235,7 +234,7 @@ namespace osu.Game.Collections return true; } - private void deleteCollection() => collectionManager?.Collections.Remove(collection); + private void deleteCollection() => realmAccess.Write(r => r.Remove(collection)); } } } diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index a9d699bc9f..721e0d632e 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -26,9 +25,6 @@ namespace osu.Game.Collections private AudioFilter lowPassFilter; - [Resolved(CanBeNull = true)] - private CollectionManager collectionManager { get; set; } - public ManageCollectionsDialog() { Anchor = Anchor.Centre; @@ -107,7 +103,6 @@ namespace osu.Game.Collections new DrawableCollectionList { RelativeSizeAxes = Axes.Both, - Items = { BindTarget = collectionManager?.Collections ?? new BindableList() } } } } diff --git a/osu.Game/Database/LegacyCollectionImporter.cs b/osu.Game/Database/LegacyCollectionImporter.cs index 8168419e80..aa98c491b1 100644 --- a/osu.Game/Database/LegacyCollectionImporter.cs +++ b/osu.Game/Database/LegacyCollectionImporter.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.Logging; -using osu.Game.Collections; +using osu.Game.Beatmaps; using osu.Game.IO; using osu.Game.IO.Legacy; using osu.Game.Overlays.Notifications; @@ -17,17 +16,17 @@ namespace osu.Game.Database { public class LegacyCollectionImporter { - private readonly CollectionManager collections; + public Action? PostNotification { protected get; set; } - public LegacyCollectionImporter(CollectionManager collections) - { - this.collections = collections; - } - - public Action PostNotification { protected get; set; } + private readonly RealmAccess realm; private const string database_name = "collection.db"; + public LegacyCollectionImporter(RealmAccess realm) + { + this.realm = realm; + } + public Task GetAvailableCount(StableStorage stableStorage) { if (!stableStorage.Exists(database_name)) @@ -76,26 +75,30 @@ namespace osu.Game.Database notification.State = ProgressNotificationState.Completed; } - private Task importCollections(List newCollections) + private Task importCollections(List newCollections) { var tcs = new TaskCompletionSource(); - // Schedule(() => - // { try { - foreach (var newCol in newCollections) + realm.Write(r => { - var existing = collections.Collections.FirstOrDefault(c => c.Name.Value == newCol.Name.Value); - if (existing == null) - collections.Collections.Add(existing = new BeatmapCollection { Name = { Value = newCol.Name.Value } }); - - foreach (string newBeatmap in newCol.BeatmapHashes) + foreach (var collection in newCollections) { - if (!existing.BeatmapHashes.Contains(newBeatmap)) - existing.BeatmapHashes.Add(newBeatmap); + var existing = r.All().FirstOrDefault(c => c.Name == collection.Name); + + if (existing != null) + { + foreach (string newBeatmap in existing.BeatmapMD5Hashes) + { + if (!existing.BeatmapMD5Hashes.Contains(newBeatmap)) + existing.BeatmapMD5Hashes.Add(newBeatmap); + } + } + else + r.Add(collection); } - } + }); tcs.SetResult(true); } @@ -104,12 +107,11 @@ namespace osu.Game.Database Logger.Error(e, "Failed to import collection."); tcs.SetException(e); } - // }); return tcs.Task; } - private List readCollections(Stream stream, ProgressNotification notification = null) + private List readCollections(Stream stream, ProgressNotification? notification = null) { if (notification != null) { @@ -117,7 +119,7 @@ namespace osu.Game.Database notification.Progress = 0; } - var result = new List(); + var result = new List(); try { @@ -133,7 +135,7 @@ namespace osu.Game.Database if (notification?.CancellationToken.IsCancellationRequested == true) return result; - var collection = new BeatmapCollection { Name = { Value = sr.ReadString() } }; + var collection = new RealmBeatmapCollection(sr.ReadString()); int mapCount = sr.ReadInt32(); for (int j = 0; j < mapCount; j++) @@ -143,7 +145,7 @@ namespace osu.Game.Database string checksum = sr.ReadString(); - collection.BeatmapHashes.Add(checksum); + collection.BeatmapMD5Hashes.Add(checksum); } if (notification != null) diff --git a/osu.Game/Database/LegacyImportManager.cs b/osu.Game/Database/LegacyImportManager.cs index 05bd5ceb54..baa117fe07 100644 --- a/osu.Game/Database/LegacyImportManager.cs +++ b/osu.Game/Database/LegacyImportManager.cs @@ -13,7 +13,6 @@ using osu.Framework.Extensions.EnumExtensions; using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Game.Beatmaps; -using osu.Game.Collections; using osu.Game.IO; using osu.Game.Overlays; using osu.Game.Overlays.Settings.Sections.Maintenance; @@ -36,15 +35,15 @@ namespace osu.Game.Database [Resolved] private ScoreManager scores { get; set; } - [Resolved] - private CollectionManager collections { get; set; } - [Resolved(canBeNull: true)] private OsuGame game { get; set; } [Resolved] private IDialogOverlay dialogOverlay { get; set; } + [Resolved] + private RealmAccess realmAccess { get; set; } + [Resolved(canBeNull: true)] private DesktopGameHost desktopGameHost { get; set; } @@ -72,7 +71,7 @@ namespace osu.Game.Database return await new LegacySkinImporter(skins).GetAvailableCount(stableStorage); case StableContent.Collections: - return await new LegacyCollectionImporter(collections).GetAvailableCount(stableStorage); + return await new LegacyCollectionImporter(realmAccess).GetAvailableCount(stableStorage); case StableContent.Scores: return await new LegacyScoreImporter(scores).GetAvailableCount(stableStorage); @@ -109,7 +108,7 @@ namespace osu.Game.Database importTasks.Add(new LegacySkinImporter(skins).ImportFromStableAsync(stableStorage)); if (content.HasFlagFast(StableContent.Collections)) - importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyCollectionImporter(collections).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); + importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyCollectionImporter(realmAccess).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); if (content.HasFlagFast(StableContent.Scores)) importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyScoreImporter(scores).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 8d8864a46a..78cc4d7f70 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -858,11 +858,6 @@ namespace osu.Game d.Origin = Anchor.TopRight; }), rightFloatingOverlayContent.Add, true); - loadComponentSingleFile(new CollectionManager - { - PostNotification = n => Notifications.Post(n), - }, Add, true); - loadComponentSingleFile(legacyImportManager, Add); loadComponentSingleFile(screenshotManager, Add); diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 5367f644ca..0b17ab9c6c 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -3,9 +3,10 @@ using osu.Framework.Allocation; using osu.Framework.Localisation; -using osu.Game.Collections; +using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Localisation; +using osu.Game.Overlays.Notifications; namespace osu.Game.Overlays.Settings.Sections.Maintenance { @@ -15,11 +16,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private SettingsButton importCollectionsButton = null!; - [BackgroundDependencyLoader] - private void load(CollectionManager? collectionManager, LegacyImportManager? legacyImportManager, IDialogOverlay? dialogOverlay) - { - if (collectionManager == null) return; + [Resolved] + private RealmAccess realm { get; set; } = null!; + [Resolved] + private INotificationOverlay notificationOverlay { get; set; } = null!; + + [BackgroundDependencyLoader] + private void load(LegacyImportManager? legacyImportManager, IDialogOverlay? dialogOverlay) + { if (legacyImportManager?.SupportsImportFromStable == true) { Add(importCollectionsButton = new SettingsButton @@ -38,9 +43,15 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Text = MaintenanceSettingsStrings.DeleteAllCollections, Action = () => { - dialogOverlay?.Push(new MassDeleteConfirmationDialog(collectionManager.DeleteAll)); + dialogOverlay?.Push(new MassDeleteConfirmationDialog(deleteAllCollections)); } }); } + + private void deleteAllCollections() + { + realm.Write(r => r.RemoveAll()); + notificationOverlay.Post(new ProgressCompletionNotification { Text = "Deleted all collections!" }); + } } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index f38077a9a7..b17c4934cd 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -94,6 +94,9 @@ namespace osu.Game.Screens.OnlinePlay private PanelBackground panelBackground; private FillFlowContainer mainFillFlow; + [Resolved] + private RealmAccess realm { get; set; } + [Resolved] private RulesetStore rulesets { get; set; } @@ -112,9 +115,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(CanBeNull = true)] private BeatmapSetOverlay beatmapOverlay { get; set; } - [Resolved(CanBeNull = true)] - private CollectionManager collectionManager { get; set; } - [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } @@ -495,11 +495,11 @@ namespace osu.Game.Screens.OnlinePlay if (beatmapOverlay != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(Item.Beatmap.OnlineID))); - if (collectionManager != null && beatmap != null) + if (beatmap != null) { if (beatmaps.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID) is BeatmapInfo local && !local.BeatmapSet.AsNonNull().DeletePending) { - var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); + var collectionItems = realm.Realm.All().Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 50e30c68d5..bfc93b34e2 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -22,6 +22,7 @@ using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Collections; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Sprites; @@ -63,12 +64,12 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } - [Resolved(CanBeNull = true)] - private CollectionManager collectionManager { get; set; } - [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } + [Resolved] + private RealmAccess realm { get; set; } + private IBindable starDifficultyBindable; private CancellationTokenSource starDifficultyCancellationSource; @@ -237,14 +238,11 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapInfo.OnlineID > 0 && beatmapOverlay != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmapInfo.OnlineID))); - if (collectionManager != null) - { - var collectionItems = collectionManager.Collections.Select(c => new CollectionToggleMenuItem(c, beatmapInfo)).Cast().ToList(); - if (manageCollectionsDialog != null) - collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + var collectionItems = realm.Realm.All().Select(c => new CollectionToggleMenuItem(c, beatmapInfo)).Cast().ToList(); + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); - items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); - } + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); if (hideRequested != null) items.Add(new OsuMenuItem(CommonStrings.ButtonsHide.ToSentence(), MenuItemType.Destructive, () => hideRequested(beatmapInfo))); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 8c266c8dff..3726d955bd 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Collections; +using osu.Game.Database; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; @@ -32,12 +33,12 @@ namespace osu.Game.Screens.Select.Carousel [Resolved(CanBeNull = true)] private IDialogOverlay dialogOverlay { get; set; } - [Resolved(CanBeNull = true)] - private CollectionManager collectionManager { get; set; } - [Resolved(CanBeNull = true)] private ManageCollectionsDialog manageCollectionsDialog { get; set; } + [Resolved] + private RealmAccess realm { get; set; } + public IEnumerable DrawableBeatmaps => beatmapContainer?.IsLoaded != true ? Enumerable.Empty() : beatmapContainer.AliveChildren; [CanBeNull] @@ -223,14 +224,11 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapSet.OnlineID > 0 && viewDetails != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineID))); - if (collectionManager != null) - { - var collectionItems = collectionManager.Collections.Select(createCollectionMenuItem).ToList(); - if (manageCollectionsDialog != null) - collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); + var collectionItems = realm.Realm.All().AsEnumerable().Select(createCollectionMenuItem).ToList(); + if (manageCollectionsDialog != null) + collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); - items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); - } + items.Add(new OsuMenuItem("Collections") { Items = collectionItems }); if (beatmapSet.Beatmaps.Any(b => b.Hidden)) items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => restoreHiddenRequested(beatmapSet))); @@ -241,13 +239,13 @@ namespace osu.Game.Screens.Select.Carousel } } - private MenuItem createCollectionMenuItem(BeatmapCollection collection) + private MenuItem createCollectionMenuItem(RealmBeatmapCollection collection) { Debug.Assert(beatmapSet != null); TernaryState state; - int countExisting = beatmapSet.Beatmaps.Count(b => collection.BeatmapHashes.Contains(b.MD5Hash)); + int countExisting = beatmapSet.Beatmaps.Count(b => collection.BeatmapMD5Hashes.Contains(b.MD5Hash)); if (countExisting == beatmapSet.Beatmaps.Count) state = TernaryState.True; @@ -256,21 +254,21 @@ namespace osu.Game.Screens.Select.Carousel else state = TernaryState.False; - return new TernaryStateToggleMenuItem(collection.Name.Value, MenuItemType.Standard, s => + return new TernaryStateToggleMenuItem(collection.Name, MenuItemType.Standard, s => { foreach (var b in beatmapSet.Beatmaps) { switch (s) { case TernaryState.True: - if (collection.BeatmapHashes.Contains(b.MD5Hash)) + if (collection.BeatmapMD5Hashes.Contains(b.MD5Hash)) continue; - collection.BeatmapHashes.Add(b.MD5Hash); + collection.BeatmapMD5Hashes.Add(b.MD5Hash); break; case TernaryState.False: - collection.BeatmapHashes.Remove(b.MD5Hash); + collection.BeatmapMD5Hashes.Remove(b.MD5Hash); break; } } From 41393616d80440b78000abd8db504e8b73f19ef1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 16:46:23 +0900 Subject: [PATCH 352/481] Replace `BeatmapCollection` with `RealmBeatmapCollection` --- .../Collections/IO/ImportCollectionsTest.cs | 14 ++-- .../TestSceneManageCollectionsDialog.cs | 38 +++++----- .../SongSelect/TestSceneFilterControl.cs | 26 +++---- osu.Game/Beatmaps/RealmBeatmapCollection.cs | 40 ----------- osu.Game/Collections/BeatmapCollection.cs | 32 +++++++-- .../Collections/CollectionFilterDropdown.cs | 70 +++++++++++-------- .../Collections/CollectionFilterMenuItem.cs | 17 ++--- .../Collections/CollectionToggleMenuItem.cs | 2 +- .../Collections/DeleteCollectionDialog.cs | 5 +- .../Collections/DrawableCollectionList.cs | 21 +++--- .../Collections/DrawableCollectionListItem.cs | 45 ++++++------ osu.Game/Database/LegacyCollectionImporter.cs | 12 ++-- osu.Game/Overlays/Music/Playlist.cs | 2 +- .../Maintenance/CollectionsSettings.cs | 4 +- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- .../Select/Carousel/CarouselBeatmap.cs | 2 +- .../Carousel/DrawableCarouselBeatmap.cs | 2 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 4 +- 18 files changed, 158 insertions(+), 180 deletions(-) delete mode 100644 osu.Game/Beatmaps/RealmBeatmapCollection.cs diff --git a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs index 32503cdb12..604b87dc4c 100644 --- a/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs +++ b/osu.Game.Tests/Collections/IO/ImportCollectionsTest.cs @@ -12,7 +12,7 @@ using NUnit.Framework; using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Framework.Testing; -using osu.Game.Beatmaps; +using osu.Game.Collections; using osu.Game.Database; using osu.Game.Tests.Resources; @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Collections.IO osu.Realm.Run(realm => { - var collections = realm.All().ToList(); + var collections = realm.All().ToList(); Assert.That(collections.Count, Is.Zero); }); } @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Collections.IO osu.Realm.Run(realm => { - var collections = realm.All().ToList(); + var collections = realm.All().ToList(); Assert.That(collections.Count, Is.EqualTo(2)); // Even with no beatmaps imported, collections are tracking the hashes and will continue to. @@ -93,7 +93,7 @@ namespace osu.Game.Tests.Collections.IO osu.Realm.Run(realm => { - var collections = realm.All().ToList(); + var collections = realm.All().ToList(); Assert.That(collections.Count, Is.EqualTo(2)); @@ -141,7 +141,7 @@ namespace osu.Game.Tests.Collections.IO Assert.That(exceptionThrown, Is.False); osu.Realm.Run(realm => { - var collections = realm.All().ToList(); + var collections = realm.All().ToList(); Assert.That(collections.Count, Is.EqualTo(0)); }); } @@ -171,7 +171,7 @@ namespace osu.Game.Tests.Collections.IO // ReSharper disable once MethodHasAsyncOverload osu.Realm.Write(realm => { - var collections = realm.All().ToList(); + var collections = realm.All().ToList(); // Move first beatmap from second collection into the first. collections[0].BeatmapMD5Hashes.Add(collections[1].BeatmapMD5Hashes[0]); @@ -196,7 +196,7 @@ namespace osu.Game.Tests.Collections.IO osu.Realm.Run(realm => { - var collections = realm.All().ToList(); + var collections = realm.All().ToList(); Assert.That(collections.Count, Is.EqualTo(2)); Assert.That(collections[0].Name, Is.EqualTo("First")); diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 21d2b0328b..8de38eb4e7 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Collections [SetUp] public void SetUp() => Schedule(() => { - Realm.Write(r => r.RemoveAll()); + Realm.Write(r => r.RemoveAll()); Child = dialog = new ManageCollectionsDialog(); }); @@ -77,11 +77,11 @@ namespace osu.Game.Tests.Visual.Collections [Test] public void TestAddCollectionExternal() { - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "First collection")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "First collection")))); assertCollectionCount(1); assertCollectionName(0, "First collection"); - AddStep("add another collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "Second collection")))); + AddStep("add another collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "Second collection")))); assertCollectionCount(2); assertCollectionName(1, "Second collection"); } @@ -110,7 +110,7 @@ namespace osu.Game.Tests.Visual.Collections }); // Done directly via the collection since InputManager methods cannot add text to textbox... - AddStep("change collection name", () => placeholderItem.Model.Name = "a"); + AddStep("change collection name", () => placeholderItem.Model.PerformWrite(c => c.Name = "a")); assertCollectionCount(1); AddAssert("collection now exists", () => placeholderItem.Model.IsManaged); @@ -120,7 +120,7 @@ namespace osu.Game.Tests.Visual.Collections [Test] public void TestRemoveCollectionExternal() { - RealmBeatmapCollection first = null!; + BeatmapCollection first = null!; AddStep("add two collections", () => { @@ -128,13 +128,13 @@ namespace osu.Game.Tests.Visual.Collections { r.Add(new[] { - first = new RealmBeatmapCollection(name: "1"), - new RealmBeatmapCollection(name: "2"), + first = new BeatmapCollection(name: "1"), + new BeatmapCollection(name: "2"), }); }); }); - AddStep("change first collection name", () => Realm.Write(r => r.Remove(first))); + AddStep("remove first collection", () => Realm.Write(r => r.Remove(first))); assertCollectionCount(1); assertCollectionName(0, "2"); } @@ -154,8 +154,8 @@ namespace osu.Game.Tests.Visual.Collections }); AddStep("add two collections with same name", () => Realm.Write(r => r.Add(new[] { - new RealmBeatmapCollection(name: "1"), - new RealmBeatmapCollection(name: "1") + new BeatmapCollection(name: "1"), + new BeatmapCollection(name: "1") { BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } }, @@ -167,8 +167,8 @@ namespace osu.Game.Tests.Visual.Collections { AddStep("add two collections", () => Realm.Write(r => r.Add(new[] { - new RealmBeatmapCollection(name: "1"), - new RealmBeatmapCollection(name: "2") + new BeatmapCollection(name: "1"), + new BeatmapCollection(name: "2") { BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } }, @@ -207,7 +207,7 @@ namespace osu.Game.Tests.Visual.Collections { AddStep("add collection", () => Realm.Write(r => r.Add(new[] { - new RealmBeatmapCollection(name: "1") + new BeatmapCollection(name: "1") { BeatmapMD5Hashes = { beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0].MD5Hash } }, @@ -234,7 +234,7 @@ namespace osu.Game.Tests.Visual.Collections [Test] public void TestCollectionRenamedExternal() { - RealmBeatmapCollection first = null!; + BeatmapCollection first = null!; AddStep("add two collections", () => { @@ -242,8 +242,8 @@ namespace osu.Game.Tests.Visual.Collections { r.Add(new[] { - first = new RealmBeatmapCollection(name: "1"), - new RealmBeatmapCollection(name: "2"), + first = new BeatmapCollection(name: "1"), + new BeatmapCollection(name: "2"), }); }); }); @@ -256,7 +256,7 @@ namespace osu.Game.Tests.Visual.Collections [Test] public void TestCollectionRenamedOnTextChange() { - RealmBeatmapCollection first = null!; + BeatmapCollection first = null!; AddStep("add two collections", () => { @@ -264,8 +264,8 @@ namespace osu.Game.Tests.Visual.Collections { r.Add(new[] { - first = new RealmBeatmapCollection(name: "1"), - new RealmBeatmapCollection(name: "2"), + first = new BeatmapCollection(name: "1"), + new BeatmapCollection(name: "2"), }); }); }); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index e4d69334a3..2a4613c37b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.SongSelect [SetUp] public void SetUp() => Schedule(() => { - Realm.Write(r => r.RemoveAll()); + Realm.Write(r => r.RemoveAll()); Child = control = new FilterControl { @@ -69,8 +69,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestCollectionAddedToDropdown() { - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "2")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "2")))); assertCollectionDropdownContains("1"); assertCollectionDropdownContains("2"); } @@ -78,10 +78,10 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestCollectionRemovedFromDropdown() { - var first = new RealmBeatmapCollection(name: "1"); + var first = new BeatmapCollection(name: "1"); AddStep("add collection", () => Realm.Write(r => r.Add(first))); - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "2")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "2")))); AddStep("remove collection", () => Realm.Write(r => r.Remove(first))); assertCollectionDropdownContains("1", false); @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestCollectionRenamed() { - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); AddStep("select collection", () => { var dropdown = control.ChildrenOfType().Single(); @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestCollectionFilterHasAddButton() { addExpandHeaderStep(); - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); AddStep("hover collection", () => InputManager.MoveMouseTo(getAddOrRemoveButton(1))); AddAssert("collection has add button", () => getAddOrRemoveButton(1).IsPresent); } @@ -128,7 +128,7 @@ namespace osu.Game.Tests.Visual.SongSelect { addExpandHeaderStep(); - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddAssert("button enabled", () => getAddOrRemoveButton(1).Enabled.Value); @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); AddStep("add beatmap to collection", () => getFirstCollection().BeatmapMD5Hashes.Add(Beatmap.Value.BeatmapInfo.MD5Hash)); @@ -161,7 +161,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); addClickAddOrRemoveButtonStep(1); @@ -178,7 +178,7 @@ namespace osu.Game.Tests.Visual.SongSelect { addExpandHeaderStep(); - AddStep("add collection", () => Realm.Write(r => r.Add(new RealmBeatmapCollection(name: "1")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); AddStep("select collection", () => { InputManager.MoveMouseTo(getCollectionDropdownItems().ElementAt(1)); @@ -193,10 +193,10 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Click(MouseButton.Left); }); - AddAssert("collection filter still selected", () => control.CreateCriteria().Collection?.Name.Value == "1"); + AddAssert("collection filter still selected", () => control.CreateCriteria().Collection?.Name == "1"); } - private RealmBeatmapCollection getFirstCollection() => Realm.Run(r => r.All().First()); + private BeatmapCollection getFirstCollection() => Realm.Run(r => r.All().First()); private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) => AddAssert($"collection dropdown header displays '{collectionName}'", diff --git a/osu.Game/Beatmaps/RealmBeatmapCollection.cs b/osu.Game/Beatmaps/RealmBeatmapCollection.cs deleted file mode 100644 index 22ba9d5789..0000000000 --- a/osu.Game/Beatmaps/RealmBeatmapCollection.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using JetBrains.Annotations; -using osu.Game.Database; -using Realms; - -namespace osu.Game.Beatmaps -{ - public class RealmBeatmapCollection : RealmObject, IHasGuidPrimaryKey - { - [PrimaryKey] - public Guid ID { get; } - - public string Name { get; set; } = string.Empty; - - public IList BeatmapMD5Hashes { get; } = null!; - - /// - /// The date when this collection was last modified. - /// - public DateTimeOffset LastModified { get; set; } - - public RealmBeatmapCollection(string? name = null, IList? beatmapMD5Hashes = null) - { - ID = Guid.NewGuid(); - Name = name ?? string.Empty; - BeatmapMD5Hashes = beatmapMD5Hashes ?? new List(); - - LastModified = DateTimeOffset.UtcNow; - } - - [UsedImplicitly] - private RealmBeatmapCollection() - { - } - } -} diff --git a/osu.Game/Collections/BeatmapCollection.cs b/osu.Game/Collections/BeatmapCollection.cs index abfd0e6dd0..2ffe17d9e6 100644 --- a/osu.Game/Collections/BeatmapCollection.cs +++ b/osu.Game/Collections/BeatmapCollection.cs @@ -1,32 +1,50 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; -using osu.Framework.Bindables; +using System.Collections.Generic; +using JetBrains.Annotations; using osu.Game.Beatmaps; +using osu.Game.Database; +using Realms; namespace osu.Game.Collections { /// /// A collection of beatmaps grouped by a name. /// - public class BeatmapCollection + public class BeatmapCollection : RealmObject, IHasGuidPrimaryKey { + [PrimaryKey] + public Guid ID { get; set; } + /// /// The collection's name. /// - public readonly Bindable Name = new Bindable(); + public string Name { get; set; } = string.Empty; /// /// The es of beatmaps contained by the collection. /// - public readonly BindableList BeatmapHashes = new BindableList(); + public IList BeatmapMD5Hashes { get; } = null!; /// /// The date when this collection was last modified. /// - public DateTimeOffset LastModifyDate { get; private set; } = DateTimeOffset.UtcNow; + public DateTimeOffset LastModified { get; set; } + + public BeatmapCollection(string? name = null, IList? beatmapMD5Hashes = null) + { + ID = Guid.NewGuid(); + Name = name ?? string.Empty; + BeatmapMD5Hashes = beatmapMD5Hashes ?? new List(); + + LastModified = DateTimeOffset.UtcNow; + } + + [UsedImplicitly] + private BeatmapCollection() + { + } } } diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index ed2c0c7cfb..1315cebc8b 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.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 System.Collections.Specialized; using System.Diagnostics; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -15,6 +13,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK; @@ -43,8 +42,8 @@ namespace osu.Game.Collections private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); - [Resolved(CanBeNull = true)] - private ManageCollectionsDialog manageCollectionsDialog { get; set; } + [Resolved] + private ManageCollectionsDialog? manageCollectionsDialog { get; set; } public CollectionFilterDropdown() { @@ -81,7 +80,7 @@ namespace osu.Game.Collections if (ShowManageCollectionsItem) filters.Add(new ManageCollectionsFilterMenuItem()); - Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection == selectedItem) ?? filters[0]; + Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection.ID == selectedItem?.ID) ?? filters[0]; } /// @@ -92,11 +91,12 @@ namespace osu.Game.Collections // Binding the beatmaps will trigger a collection change event, which results in an infinite-loop. This is rebound later, when it's safe to do so. beatmaps.CollectionChanged -= filterBeatmapsChanged; - if (filter.OldValue?.Collection != null) - beatmaps.UnbindFrom(filter.OldValue.Collection.BeatmapHashes); - - if (filter.NewValue?.Collection != null) - beatmaps.BindTo(filter.NewValue.Collection.BeatmapHashes); + // TODO: binding with realm + // if (filter.OldValue?.Collection != null) + // beatmaps.UnbindFrom(filter.OldValue.Collection.BeatmapMD5Hashes); + // + // if (filter.NewValue?.Collection != null) + // beatmaps.BindTo(filter.NewValue.Collection.BeatmapMD5Hashes); beatmaps.CollectionChanged += filterBeatmapsChanged; @@ -187,26 +187,24 @@ namespace osu.Game.Collections protected class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem { - [NotNull] protected new CollectionFilterMenuItem Item => ((DropdownMenuItem)base.Item).Value; [Resolved] - private IBindable beatmap { get; set; } + private IBindable beatmap { get; set; } = null!; - [CanBeNull] - private readonly BindableList collectionBeatmaps; - - [NotNull] private readonly Bindable collectionName; - private IconButton addOrRemoveButton; - private Content content; + private IconButton addOrRemoveButton = null!; + private Content content = null!; private bool beatmapInCollection; + private IDisposable? realmSubscription; + + private BeatmapCollection? collection => Item.Collection; + public CollectionDropdownMenuItem(MenuItem item) : base(item) { - collectionBeatmaps = Item.Collection?.BeatmapHashes.GetBoundCopy(); collectionName = Item.CollectionName.GetBoundCopy(); } @@ -223,14 +221,17 @@ namespace osu.Game.Collections }); } + [Resolved] + private RealmAccess realm { get; set; } = null!; + protected override void LoadComplete() { base.LoadComplete(); - if (collectionBeatmaps != null) + if (Item.Collection != null) { - collectionBeatmaps.CollectionChanged += (_, _) => collectionChanged(); - beatmap.BindValueChanged(_ => collectionChanged(), true); + realmSubscription = realm.SubscribeToPropertyChanged(r => r.Find(Item.Collection.ID), c => c.BeatmapMD5Hashes, _ => hashesChanged()); + beatmap.BindValueChanged(_ => hashesChanged(), true); } // Although the DrawableMenuItem binds to value changes of the item's text, the item is an internal implementation detail of Dropdown that has no knowledge @@ -252,11 +253,11 @@ namespace osu.Game.Collections base.OnHoverLost(e); } - private void collectionChanged() + private void hashesChanged() { - Debug.Assert(collectionBeatmaps != null); + Debug.Assert(collection != null); - beatmapInCollection = collectionBeatmaps.Contains(beatmap.Value.BeatmapInfo.MD5Hash); + beatmapInCollection = collection.BeatmapMD5Hashes.Contains(beatmap.Value.BeatmapInfo.MD5Hash); addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; addOrRemoveButton.Icon = beatmapInCollection ? FontAwesome.Solid.MinusSquare : FontAwesome.Solid.PlusSquare; @@ -273,7 +274,7 @@ namespace osu.Game.Collections private void updateButtonVisibility() { - if (collectionBeatmaps == null) + if (collection == null) addOrRemoveButton.Alpha = 0; else addOrRemoveButton.Alpha = IsHovered || IsPreSelected || beatmapInCollection ? 1 : 0; @@ -281,13 +282,22 @@ namespace osu.Game.Collections private void addOrRemove() { - Debug.Assert(collectionBeatmaps != null); + Debug.Assert(collection != null); - if (!collectionBeatmaps.Remove(beatmap.Value.BeatmapInfo.MD5Hash)) - collectionBeatmaps.Add(beatmap.Value.BeatmapInfo.MD5Hash); + realm.Write(r => + { + if (!collection.BeatmapMD5Hashes.Remove(beatmap.Value.BeatmapInfo.MD5Hash)) + collection.BeatmapMD5Hashes.Add(beatmap.Value.BeatmapInfo.MD5Hash); + }); } protected override Drawable CreateContent() => content = (Content)base.CreateContent(); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + realmSubscription?.Dispose(); + } } } } diff --git a/osu.Game/Collections/CollectionFilterMenuItem.cs b/osu.Game/Collections/CollectionFilterMenuItem.cs index 031f05c0b4..4c132ba7b7 100644 --- a/osu.Game/Collections/CollectionFilterMenuItem.cs +++ b/osu.Game/Collections/CollectionFilterMenuItem.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.Bindables; namespace osu.Game.Collections @@ -18,26 +15,26 @@ namespace osu.Game.Collections /// The collection to filter beatmaps from. /// May be null to not filter by collection (include all beatmaps). /// - [CanBeNull] - public readonly BeatmapCollection Collection; + public readonly BeatmapCollection? Collection; /// /// The name of the collection. /// - [NotNull] public readonly Bindable CollectionName; /// /// Creates a new . /// /// The collection to filter beatmaps from. - public CollectionFilterMenuItem([CanBeNull] BeatmapCollection collection) + public CollectionFilterMenuItem(BeatmapCollection? collection) { Collection = collection; - CollectionName = Collection?.Name.GetBoundCopy() ?? new Bindable("All beatmaps"); + CollectionName = new Bindable(collection?.Name ?? "All beatmaps"); } - public bool Equals(CollectionFilterMenuItem other) + // TODO: track name changes i guess? + + public bool Equals(CollectionFilterMenuItem? other) { if (other == null) return false; @@ -45,7 +42,7 @@ namespace osu.Game.Collections // collections may have the same name, so compare first on reference equality. // this relies on the assumption that only one instance of the BeatmapCollection exists game-wide, managed by CollectionManager. if (Collection != null) - return Collection == other.Collection; + return Collection.ID == other.Collection?.ID; // fallback to name-based comparison. // this is required for special dropdown items which don't have a collection (all beatmaps / manage collections items below). diff --git a/osu.Game/Collections/CollectionToggleMenuItem.cs b/osu.Game/Collections/CollectionToggleMenuItem.cs index 632249913d..8c0e3c587b 100644 --- a/osu.Game/Collections/CollectionToggleMenuItem.cs +++ b/osu.Game/Collections/CollectionToggleMenuItem.cs @@ -8,7 +8,7 @@ namespace osu.Game.Collections { public class CollectionToggleMenuItem : ToggleMenuItem { - public CollectionToggleMenuItem(RealmBeatmapCollection collection, IBeatmapInfo beatmap) + public CollectionToggleMenuItem(BeatmapCollection collection, IBeatmapInfo beatmap) : base(collection.Name, MenuItemType.Standard, state => { if (state) diff --git a/osu.Game/Collections/DeleteCollectionDialog.cs b/osu.Game/Collections/DeleteCollectionDialog.cs index 33c2174623..7594978870 100644 --- a/osu.Game/Collections/DeleteCollectionDialog.cs +++ b/osu.Game/Collections/DeleteCollectionDialog.cs @@ -1,19 +1,16 @@ // 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 Humanizer; using osu.Framework.Graphics.Sprites; -using osu.Game.Beatmaps; using osu.Game.Overlays.Dialog; namespace osu.Game.Collections { public class DeleteCollectionDialog : PopupDialog { - public DeleteCollectionDialog(RealmBeatmapCollection collection, Action deleteAction) + public DeleteCollectionDialog(BeatmapCollection collection, Action deleteAction) { HeaderText = "Confirm deletion of"; BodyText = $"{collection.Name} ({"beatmap".ToQuantity(collection.BeatmapMD5Hashes.Count)})"; diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 63f04641f4..f376d18224 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -1,35 +1,33 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - +using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osuTK; namespace osu.Game.Collections { /// - /// Visualises a list of s. + /// Visualises a list of s. /// - public class DrawableCollectionList : OsuRearrangeableListContainer + public class DrawableCollectionList : OsuRearrangeableListContainer { - private Scroll scroll; + private Scroll scroll = null!; protected override ScrollContainer CreateScrollContainer() => scroll = new Scroll(); - protected override FillFlowContainer> CreateListFillFlowContainer() => new Flow + protected override FillFlowContainer> CreateListFillFlowContainer() => new Flow { DragActive = { BindTarget = DragActive } }; // TODO: source from realm - protected override OsuRearrangeableListItem CreateOsuDrawable(RealmBeatmapCollection item) + protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapCollection item) { if (item.ID == scroll.PlaceholderItem.Model.ID) return scroll.ReplacePlaceholder(); @@ -49,7 +47,7 @@ namespace osu.Game.Collections /// /// The currently-displayed placeholder item. /// - public DrawableCollectionListItem PlaceholderItem { get; private set; } + public DrawableCollectionListItem PlaceholderItem { get; private set; } = null!; protected override Container Content => content; private readonly Container content; @@ -79,6 +77,7 @@ namespace osu.Game.Collections }); ReplacePlaceholder(); + Debug.Assert(PlaceholderItem != null); } protected override void Update() @@ -98,7 +97,7 @@ namespace osu.Game.Collections var previous = PlaceholderItem; placeholderContainer.Clear(false); - placeholderContainer.Add(PlaceholderItem = new DrawableCollectionListItem(new RealmBeatmapCollection(), false)); + placeholderContainer.Add(PlaceholderItem = new DrawableCollectionListItem(new BeatmapCollection(), false)); return previous; } @@ -107,7 +106,7 @@ namespace osu.Game.Collections /// /// The flow of . Disables layout easing unless a drag is in progress. /// - private class Flow : FillFlowContainer> + private class Flow : FillFlowContainer> { public readonly IBindable DragActive = new Bindable(); diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index a29b2ef81c..6093e69deb 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.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; @@ -12,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -24,15 +21,15 @@ using osuTK.Graphics; namespace osu.Game.Collections { /// - /// Visualises a inside a . + /// Visualises a inside a . /// - public class DrawableCollectionListItem : OsuRearrangeableListItem + public class DrawableCollectionListItem : OsuRearrangeableListItem { private const float item_height = 35; private const float button_width = item_height * 0.75f; /// - /// Whether the currently exists inside realm. + /// Whether the currently exists inside realm. /// public IBindable IsCreated => isCreated; @@ -41,9 +38,9 @@ namespace osu.Game.Collections /// /// Creates a new . /// - /// The . + /// The . /// Whether currently exists inside realm. - public DrawableCollectionListItem(RealmBeatmapCollection item, bool isCreated) + public DrawableCollectionListItem(BeatmapCollection item, bool isCreated) : base(item) { this.isCreated.Value = isCreated; @@ -63,12 +60,15 @@ namespace osu.Game.Collections { public readonly Bindable IsCreated = new Bindable(); - private readonly RealmBeatmapCollection collection; + private readonly BeatmapCollection collection; - private Container textBoxPaddingContainer; - private ItemTextBox textBox; + private Container textBoxPaddingContainer = null!; + private ItemTextBox textBox = null!; - public ItemContent(RealmBeatmapCollection collection) + [Resolved] + private RealmAccess realm { get; set; } = null!; + + public ItemContent(BeatmapCollection collection) { this.collection = collection; @@ -107,9 +107,6 @@ namespace osu.Game.Collections }; } - [Resolved] - private RealmAccess realm { get; set; } - protected override void LoadComplete() { base.LoadComplete(); @@ -156,20 +153,20 @@ namespace osu.Game.Collections { public readonly IBindable IsCreated = new Bindable(); - public Func IsTextBoxHovered; - - [Resolved(CanBeNull = true)] - private IDialogOverlay dialogOverlay { get; set; } + public Func IsTextBoxHovered = null!; [Resolved] - private RealmAccess realmAccess { get; set; } + private IDialogOverlay? dialogOverlay { get; set; } - private readonly RealmBeatmapCollection collection; + [Resolved] + private RealmAccess realmAccess { get; set; } = null!; - private Drawable fadeContainer; - private Drawable background; + private readonly BeatmapCollection collection; - public DeleteButton(RealmBeatmapCollection collection) + private Drawable fadeContainer = null!; + private Drawable background = null!; + + public DeleteButton(BeatmapCollection collection) { this.collection = collection; RelativeSizeAxes = Axes.Y; diff --git a/osu.Game/Database/LegacyCollectionImporter.cs b/osu.Game/Database/LegacyCollectionImporter.cs index aa98c491b1..bd32b8c446 100644 --- a/osu.Game/Database/LegacyCollectionImporter.cs +++ b/osu.Game/Database/LegacyCollectionImporter.cs @@ -7,7 +7,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using osu.Framework.Logging; -using osu.Game.Beatmaps; +using osu.Game.Collections; using osu.Game.IO; using osu.Game.IO.Legacy; using osu.Game.Overlays.Notifications; @@ -75,7 +75,7 @@ namespace osu.Game.Database notification.State = ProgressNotificationState.Completed; } - private Task importCollections(List newCollections) + private Task importCollections(List newCollections) { var tcs = new TaskCompletionSource(); @@ -85,7 +85,7 @@ namespace osu.Game.Database { foreach (var collection in newCollections) { - var existing = r.All().FirstOrDefault(c => c.Name == collection.Name); + var existing = r.All().FirstOrDefault(c => c.Name == collection.Name); if (existing != null) { @@ -111,7 +111,7 @@ namespace osu.Game.Database return tcs.Task; } - private List readCollections(Stream stream, ProgressNotification? notification = null) + private List readCollections(Stream stream, ProgressNotification? notification = null) { if (notification != null) { @@ -119,7 +119,7 @@ namespace osu.Game.Database notification.Progress = 0; } - var result = new List(); + var result = new List(); try { @@ -135,7 +135,7 @@ namespace osu.Game.Database if (notification?.CancellationToken.IsCancellationRequested == true) return result; - var collection = new RealmBeatmapCollection(sr.ReadString()); + var collection = new BeatmapCollection(sr.ReadString()); int mapCount = sr.ReadInt32(); for (int j = 0; j < mapCount; j++) diff --git a/osu.Game/Overlays/Music/Playlist.cs b/osu.Game/Overlays/Music/Playlist.cs index 954c4de493..19b1b3f84e 100644 --- a/osu.Game/Overlays/Music/Playlist.cs +++ b/osu.Game/Overlays/Music/Playlist.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Music else { item.InSelectedCollection = item.Model.Value.Beatmaps.Select(b => b.MD5Hash) - .Any(criteria.Collection.BeatmapHashes.Contains); + .Any(criteria.Collection.BeatmapMD5Hashes.Contains); } } diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 0b17ab9c6c..498859db46 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -3,7 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Localisation; -using osu.Game.Beatmaps; +using osu.Game.Collections; using osu.Game.Database; using osu.Game.Localisation; using osu.Game.Overlays.Notifications; @@ -50,7 +50,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private void deleteAllCollections() { - realm.Write(r => r.RemoveAll()); + realm.Write(r => r.RemoveAll()); notificationOverlay.Post(new ProgressCompletionNotification { Text = "Deleted all collections!" }); } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index b17c4934cd..0826c4144b 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -499,7 +499,7 @@ namespace osu.Game.Screens.OnlinePlay { if (beatmaps.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID) is BeatmapInfo local && !local.BeatmapSet.AsNonNull().DeletePending) { - var collectionItems = realm.Realm.All().Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); + var collectionItems = realm.Realm.All().Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 81734745c4..9267434a66 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Select.Carousel } if (match) - match &= criteria.Collection?.BeatmapHashes.Contains(BeatmapInfo.MD5Hash) ?? true; + match &= criteria.Collection?.BeatmapMD5Hashes.Contains(BeatmapInfo.MD5Hash) ?? true; if (match && criteria.RulesetCriteria != null) match &= criteria.RulesetCriteria.Matches(BeatmapInfo); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index bfc93b34e2..a1c433b440 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -238,7 +238,7 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapInfo.OnlineID > 0 && beatmapOverlay != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmapInfo.OnlineID))); - var collectionItems = realm.Realm.All().Select(c => new CollectionToggleMenuItem(c, beatmapInfo)).Cast().ToList(); + var collectionItems = realm.Realm.All().AsEnumerable().Select(c => new CollectionToggleMenuItem(c, beatmapInfo)).Cast().ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 3726d955bd..75c9daf1b1 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -224,7 +224,7 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapSet.OnlineID > 0 && viewDetails != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineID))); - var collectionItems = realm.Realm.All().AsEnumerable().Select(createCollectionMenuItem).ToList(); + var collectionItems = realm.Realm.All().AsEnumerable().Select(createCollectionMenuItem).ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); @@ -239,7 +239,7 @@ namespace osu.Game.Screens.Select.Carousel } } - private MenuItem createCollectionMenuItem(RealmBeatmapCollection collection) + private MenuItem createCollectionMenuItem(BeatmapCollection collection) { Debug.Assert(beatmapSet != null); From 438067a18b4748501c49a57db0201212eb22829c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 17:17:43 +0900 Subject: [PATCH 353/481] Convert realm data propagation to more correctly use `Live` wip --- .../SongSelect/TestSceneFilterControl.cs | 8 +++--- .../Collections/CollectionFilterDropdown.cs | 13 ++++----- .../Collections/CollectionFilterMenuItem.cs | 7 ++--- .../Collections/CollectionToggleMenuItem.cs | 18 ++++++++----- osu.Game/Overlays/Music/FilterCriteria.cs | 3 ++- osu.Game/Overlays/Music/Playlist.cs | 6 +++-- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- .../Select/Carousel/CarouselBeatmap.cs | 2 +- .../Carousel/DrawableCarouselBeatmap.cs | 2 +- .../Carousel/DrawableCarouselBeatmapSet.cs | 27 +++++++++++-------- osu.Game/Screens/Select/FilterControl.cs | 2 +- osu.Game/Screens/Select/FilterCriteria.cs | 4 +-- 12 files changed, 54 insertions(+), 40 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 2a4613c37b..aaaa0aec95 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -147,10 +147,10 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); - AddStep("add beatmap to collection", () => getFirstCollection().BeatmapMD5Hashes.Add(Beatmap.Value.BeatmapInfo.MD5Hash)); + AddStep("add beatmap to collection", () => Realm.Write(r => getFirstCollection().BeatmapMD5Hashes.Add(Beatmap.Value.BeatmapInfo.MD5Hash))); AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); - AddStep("remove beatmap from collection", () => getFirstCollection().BeatmapMD5Hashes.Clear()); + AddStep("remove beatmap from collection", () => Realm.Write(r => getFirstCollection().BeatmapMD5Hashes.Clear())); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); } @@ -178,7 +178,7 @@ namespace osu.Game.Tests.Visual.SongSelect { addExpandHeaderStep(); - AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); + AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1", new List { "abc" })))); AddStep("select collection", () => { InputManager.MoveMouseTo(getCollectionDropdownItems().ElementAt(1)); @@ -193,7 +193,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Click(MouseButton.Left); }); - AddAssert("collection filter still selected", () => control.CreateCriteria().Collection?.Name == "1"); + AddAssert("collection filter still selected", () => control.CreateCriteria().CollectionBeatmapMD5Hashes.Any()); } private BeatmapCollection getFirstCollection() => Realm.Run(r => r.All().First()); diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index 1315cebc8b..c04f617eb9 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -38,7 +38,7 @@ namespace osu.Game.Collections set => current.Current = value; } - private readonly IBindableList collections = new BindableList(); + private readonly IBindableList> collections = new BindableList>(); private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); @@ -114,6 +114,7 @@ namespace osu.Game.Collections /// private void filterBeatmapsChanged(object sender, NotifyCollectionChangedEventArgs e) { + // TODO: fuck this shit right off // The filtered beatmaps have changed, without the filter having changed itself. So a change in filter must be notified. // Note that this does NOT propagate to bound bindables, so the FilterControl must bind directly to the value change event of this bindable. Current.TriggerChange(); @@ -200,7 +201,7 @@ namespace osu.Game.Collections private IDisposable? realmSubscription; - private BeatmapCollection? collection => Item.Collection; + private Live? collection => Item.Collection; public CollectionDropdownMenuItem(MenuItem item) : base(item) @@ -257,7 +258,7 @@ namespace osu.Game.Collections { Debug.Assert(collection != null); - beatmapInCollection = collection.BeatmapMD5Hashes.Contains(beatmap.Value.BeatmapInfo.MD5Hash); + beatmapInCollection = collection.PerformRead(c => c.BeatmapMD5Hashes.Contains(beatmap.Value.BeatmapInfo.MD5Hash)); addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; addOrRemoveButton.Icon = beatmapInCollection ? FontAwesome.Solid.MinusSquare : FontAwesome.Solid.PlusSquare; @@ -284,10 +285,10 @@ namespace osu.Game.Collections { Debug.Assert(collection != null); - realm.Write(r => + collection.PerformWrite(c => { - if (!collection.BeatmapMD5Hashes.Remove(beatmap.Value.BeatmapInfo.MD5Hash)) - collection.BeatmapMD5Hashes.Add(beatmap.Value.BeatmapInfo.MD5Hash); + if (!c.BeatmapMD5Hashes.Remove(beatmap.Value.BeatmapInfo.MD5Hash)) + c.BeatmapMD5Hashes.Add(beatmap.Value.BeatmapInfo.MD5Hash); }); } diff --git a/osu.Game/Collections/CollectionFilterMenuItem.cs b/osu.Game/Collections/CollectionFilterMenuItem.cs index 4c132ba7b7..fd9e333915 100644 --- a/osu.Game/Collections/CollectionFilterMenuItem.cs +++ b/osu.Game/Collections/CollectionFilterMenuItem.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Bindables; +using osu.Game.Database; namespace osu.Game.Collections { @@ -15,7 +16,7 @@ namespace osu.Game.Collections /// The collection to filter beatmaps from. /// May be null to not filter by collection (include all beatmaps). /// - public readonly BeatmapCollection? Collection; + public readonly Live? Collection; /// /// The name of the collection. @@ -26,10 +27,10 @@ namespace osu.Game.Collections /// Creates a new . /// /// The collection to filter beatmaps from. - public CollectionFilterMenuItem(BeatmapCollection? collection) + public CollectionFilterMenuItem(Live? collection) { Collection = collection; - CollectionName = new Bindable(collection?.Name ?? "All beatmaps"); + CollectionName = new Bindable(collection?.PerformRead(c => c.Name) ?? "All beatmaps"); } // TODO: track name changes i guess? diff --git a/osu.Game/Collections/CollectionToggleMenuItem.cs b/osu.Game/Collections/CollectionToggleMenuItem.cs index 8c0e3c587b..5ad06a72c0 100644 --- a/osu.Game/Collections/CollectionToggleMenuItem.cs +++ b/osu.Game/Collections/CollectionToggleMenuItem.cs @@ -2,22 +2,26 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Beatmaps; +using osu.Game.Database; using osu.Game.Graphics.UserInterface; namespace osu.Game.Collections { public class CollectionToggleMenuItem : ToggleMenuItem { - public CollectionToggleMenuItem(BeatmapCollection collection, IBeatmapInfo beatmap) - : base(collection.Name, MenuItemType.Standard, state => + public CollectionToggleMenuItem(Live collection, IBeatmapInfo beatmap) + : base(collection.PerformRead(c => c.Name), MenuItemType.Standard, state => { - if (state) - collection.BeatmapMD5Hashes.Add(beatmap.MD5Hash); - else - collection.BeatmapMD5Hashes.Remove(beatmap.MD5Hash); + collection.PerformWrite(c => + { + if (state) + c.BeatmapMD5Hashes.Add(beatmap.MD5Hash); + else + c.BeatmapMD5Hashes.Remove(beatmap.MD5Hash); + }); }) { - State.Value = collection.BeatmapMD5Hashes.Contains(beatmap.MD5Hash); + State.Value = collection.PerformRead(c => c.BeatmapMD5Hashes.Contains(beatmap.MD5Hash)); } } } diff --git a/osu.Game/Overlays/Music/FilterCriteria.cs b/osu.Game/Overlays/Music/FilterCriteria.cs index f435c4e6e4..ad491be845 100644 --- a/osu.Game/Overlays/Music/FilterCriteria.cs +++ b/osu.Game/Overlays/Music/FilterCriteria.cs @@ -5,6 +5,7 @@ using JetBrains.Annotations; using osu.Game.Collections; +using osu.Game.Database; namespace osu.Game.Overlays.Music { @@ -19,6 +20,6 @@ namespace osu.Game.Overlays.Music /// The collection to filter beatmaps from. /// [CanBeNull] - public BeatmapCollection Collection; + public Live Collection; } } diff --git a/osu.Game/Overlays/Music/Playlist.cs b/osu.Game/Overlays/Music/Playlist.cs index 19b1b3f84e..2bb0ff1085 100644 --- a/osu.Game/Overlays/Music/Playlist.cs +++ b/osu.Game/Overlays/Music/Playlist.cs @@ -31,14 +31,16 @@ namespace osu.Game.Overlays.Music { var items = (SearchContainer>>)ListContainer; + string[] currentCollectionHashes = criteria.Collection?.PerformRead(c => c.BeatmapMD5Hashes.ToArray()); + foreach (var item in items.OfType()) { - if (criteria.Collection == null) + if (currentCollectionHashes == null) item.InSelectedCollection = true; else { item.InSelectedCollection = item.Model.Value.Beatmaps.Select(b => b.MD5Hash) - .Any(criteria.Collection.BeatmapMD5Hashes.Contains); + .Any(currentCollectionHashes.Contains); } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 0826c4144b..492bb8ada5 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -499,7 +499,7 @@ namespace osu.Game.Screens.OnlinePlay { if (beatmaps.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID) is BeatmapInfo local && !local.BeatmapSet.AsNonNull().DeletePending) { - var collectionItems = realm.Realm.All().Select(c => new CollectionToggleMenuItem(c, beatmap)).Cast().ToList(); + var collectionItems = realm.Realm.All().Select(c => new CollectionToggleMenuItem(c.ToLive(realm), beatmap)).Cast().ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 9267434a66..5b17b412ae 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Select.Carousel } if (match) - match &= criteria.Collection?.BeatmapMD5Hashes.Contains(BeatmapInfo.MD5Hash) ?? true; + match &= criteria.CollectionBeatmapMD5Hashes?.Contains(BeatmapInfo.MD5Hash) ?? true; if (match && criteria.RulesetCriteria != null) match &= criteria.RulesetCriteria.Matches(BeatmapInfo); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index a1c433b440..c3cb04680b 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -238,7 +238,7 @@ namespace osu.Game.Screens.Select.Carousel if (beatmapInfo.OnlineID > 0 && beatmapOverlay != null) items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmapInfo.OnlineID))); - var collectionItems = realm.Realm.All().AsEnumerable().Select(c => new CollectionToggleMenuItem(c, beatmapInfo)).Cast().ToList(); + var collectionItems = realm.Realm.All().AsEnumerable().Select(c => new CollectionToggleMenuItem(c.ToLive(realm), beatmapInfo)).Cast().ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 75c9daf1b1..040f954bba 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -254,24 +254,29 @@ namespace osu.Game.Screens.Select.Carousel else state = TernaryState.False; + var liveCollection = collection.ToLive(realm); + return new TernaryStateToggleMenuItem(collection.Name, MenuItemType.Standard, s => { - foreach (var b in beatmapSet.Beatmaps) + liveCollection.PerformWrite(c => { - switch (s) + foreach (var b in beatmapSet.Beatmaps) { - case TernaryState.True: - if (collection.BeatmapMD5Hashes.Contains(b.MD5Hash)) - continue; + switch (s) + { + case TernaryState.True: + if (c.BeatmapMD5Hashes.Contains(b.MD5Hash)) + continue; - collection.BeatmapMD5Hashes.Add(b.MD5Hash); - break; + c.BeatmapMD5Hashes.Add(b.MD5Hash); + break; - case TernaryState.False: - collection.BeatmapMD5Hashes.Remove(b.MD5Hash); - break; + case TernaryState.False: + c.BeatmapMD5Hashes.Remove(b.MD5Hash); + break; + } } - } + }); }) { State = { Value = state } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index d39862b65f..5f5344a338 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Select Sort = sortMode.Value, AllowConvertedBeatmaps = showConverted.Value, Ruleset = ruleset.Value, - Collection = collectionDropdown?.Current.Value?.Collection + CollectionBeatmapMD5Hashes = collectionDropdown?.Current.Value?.Collection?.PerformRead(c => c.BeatmapMD5Hashes) }; if (!minimumStars.IsDefault) diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index c7e6e8496a..320bfb1b45 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -68,10 +68,10 @@ namespace osu.Game.Screens.Select } /// - /// The collection to filter beatmaps from. + /// Hashes from the to filter to. /// [CanBeNull] - public BeatmapCollection Collection; + public IEnumerable CollectionBeatmapMD5Hashes { get; set; } [CanBeNull] public IRulesetFilterCriteria RulesetCriteria { get; set; } From 804bb33aedbc4f44a8aed6fc7c90d7528106b1df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 18:24:37 +0900 Subject: [PATCH 354/481] Hook up remaining data flows --- .../Collections/CollectionFilterDropdown.cs | 147 +++++------------- .../Collections/CollectionFilterMenuItem.cs | 21 +-- 2 files changed, 54 insertions(+), 114 deletions(-) diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index c04f617eb9..ec409df4d6 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Specialized; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; @@ -17,6 +16,7 @@ using osu.Game.Database; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK; +using Realms; namespace osu.Game.Collections { @@ -38,13 +38,15 @@ namespace osu.Game.Collections set => current.Current = value; } - private readonly IBindableList> collections = new BindableList>(); private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); [Resolved] private ManageCollectionsDialog? manageCollectionsDialog { get; set; } + [Resolved] + private RealmAccess realm { get; set; } = null!; + public CollectionFilterDropdown() { ItemSource = filters; @@ -55,51 +57,49 @@ namespace osu.Game.Collections { base.LoadComplete(); - // TODO: bind to realm data + realm.RegisterForNotifications(r => r.All(), collectionsChanged); // Dropdown has logic which triggers a change on the bindable with every change to the contained items. // This is not desirable here, as it leads to multiple filter operations running even though nothing has changed. // An extra bindable is enough to subvert this behaviour. base.Current = Current; - collections.BindCollectionChanged((_, _) => collectionsChanged(), true); - Current.BindValueChanged(filterChanged, true); + Current.BindValueChanged(currentChanged, true); } /// /// Occurs when a collection has been added or removed. /// - private void collectionsChanged() + private void collectionsChanged(IRealmCollection collections, ChangeSet? changes, Exception error) { var selectedItem = SelectedItem?.Value?.Collection; filters.Clear(); filters.Add(new AllBeatmapsCollectionFilterMenuItem()); - filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c))); + filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c.ToLive(realm)))); if (ShowManageCollectionsItem) filters.Add(new ManageCollectionsFilterMenuItem()); Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection.ID == selectedItem?.ID) ?? filters[0]; + + // Trigger a re-filter if the current item was in the changeset. + if (selectedItem != null && changes != null) + { + foreach (int index in changes.ModifiedIndices) + { + if (collections[index].ID == selectedItem.ID) + { + // The filtered beatmaps have changed, without the filter having changed itself. So a change in filter must be notified. + // Note that this does NOT propagate to bound bindables, so the FilterControl must bind directly to the value change event of this bindable. + Current.TriggerChange(); + } + } + } } - /// - /// Occurs when the selection has changed. - /// - private void filterChanged(ValueChangedEvent filter) + private void currentChanged(ValueChangedEvent filter) { - // Binding the beatmaps will trigger a collection change event, which results in an infinite-loop. This is rebound later, when it's safe to do so. - beatmaps.CollectionChanged -= filterBeatmapsChanged; - - // TODO: binding with realm - // if (filter.OldValue?.Collection != null) - // beatmaps.UnbindFrom(filter.OldValue.Collection.BeatmapMD5Hashes); - // - // if (filter.NewValue?.Collection != null) - // beatmaps.BindTo(filter.NewValue.Collection.BeatmapMD5Hashes); - - beatmaps.CollectionChanged += filterBeatmapsChanged; - // Never select the manage collection filter - rollback to the previous filter. // This is done after the above since it is important that bindable is unbound from OldValue, which is lost after forcing it back to the old value. if (filter.NewValue is ManageCollectionsFilterMenuItem) @@ -109,18 +109,7 @@ namespace osu.Game.Collections } } - /// - /// Occurs when the beatmaps contained by a have changed. - /// - private void filterBeatmapsChanged(object sender, NotifyCollectionChangedEventArgs e) - { - // TODO: fuck this shit right off - // The filtered beatmaps have changed, without the filter having changed itself. So a change in filter must be notified. - // Note that this does NOT propagate to bound bindables, so the FilterControl must bind directly to the value change event of this bindable. - Current.TriggerChange(); - } - - protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName.Value; + protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName; protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader().With(d => { @@ -136,13 +125,6 @@ namespace osu.Game.Collections public class CollectionDropdownHeader : OsuDropdownHeader { public readonly Bindable SelectedItem = new Bindable(); - private readonly Bindable collectionName = new Bindable(); - - protected override LocalisableString Label - { - get => base.Label; - set { } // See updateText(). - } public CollectionDropdownHeader() { @@ -150,26 +132,6 @@ namespace osu.Game.Collections Icon.Size = new Vector2(16); Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 }; } - - protected override void LoadComplete() - { - base.LoadComplete(); - - SelectedItem.BindValueChanged(_ => updateBindable(), true); - } - - private void updateBindable() - { - collectionName.UnbindAll(); - - if (SelectedItem.Value != null) - collectionName.BindTo(SelectedItem.Value.CollectionName); - - collectionName.BindValueChanged(_ => updateText(), true); - } - - // Dropdowns don't bind to value changes, so the real name is copied directly from the selected item here. - private void updateText() => base.Label = collectionName.Value; } protected class CollectionDropdownMenu : OsuDropdownMenu @@ -190,23 +152,16 @@ namespace osu.Game.Collections { protected new CollectionFilterMenuItem Item => ((DropdownMenuItem)base.Item).Value; - [Resolved] - private IBindable beatmap { get; set; } = null!; - - private readonly Bindable collectionName; - private IconButton addOrRemoveButton = null!; - private Content content = null!; + private bool beatmapInCollection; - private IDisposable? realmSubscription; - - private Live? collection => Item.Collection; + [Resolved] + private IBindable beatmap { get; set; } = null!; public CollectionDropdownMenuItem(MenuItem item) : base(item) { - collectionName = Item.CollectionName.GetBoundCopy(); } [BackgroundDependencyLoader] @@ -222,22 +177,25 @@ namespace osu.Game.Collections }); } - [Resolved] - private RealmAccess realm { get; set; } = null!; - protected override void LoadComplete() { base.LoadComplete(); if (Item.Collection != null) { - realmSubscription = realm.SubscribeToPropertyChanged(r => r.Find(Item.Collection.ID), c => c.BeatmapMD5Hashes, _ => hashesChanged()); - beatmap.BindValueChanged(_ => hashesChanged(), true); - } + beatmap.BindValueChanged(_ => + { + Debug.Assert(Item.Collection != null); - // Although the DrawableMenuItem binds to value changes of the item's text, the item is an internal implementation detail of Dropdown that has no knowledge - // of the underlying CollectionFilter value and its accompanying name, so the real name has to be copied here. Without this, the collection name wouldn't update when changed. - collectionName.BindValueChanged(name => content.Text = name.NewValue, true); + beatmapInCollection = Item.Collection.PerformRead(c => c.BeatmapMD5Hashes.Contains(beatmap.Value.BeatmapInfo.MD5Hash)); + + addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; + addOrRemoveButton.Icon = beatmapInCollection ? FontAwesome.Solid.MinusSquare : FontAwesome.Solid.PlusSquare; + addOrRemoveButton.TooltipText = beatmapInCollection ? "Remove selected beatmap" : "Add selected beatmap"; + + updateButtonVisibility(); + }, true); + } updateButtonVisibility(); } @@ -254,19 +212,6 @@ namespace osu.Game.Collections base.OnHoverLost(e); } - private void hashesChanged() - { - Debug.Assert(collection != null); - - beatmapInCollection = collection.PerformRead(c => c.BeatmapMD5Hashes.Contains(beatmap.Value.BeatmapInfo.MD5Hash)); - - addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; - addOrRemoveButton.Icon = beatmapInCollection ? FontAwesome.Solid.MinusSquare : FontAwesome.Solid.PlusSquare; - addOrRemoveButton.TooltipText = beatmapInCollection ? "Remove selected beatmap" : "Add selected beatmap"; - - updateButtonVisibility(); - } - protected override void OnSelectChange() { base.OnSelectChange(); @@ -275,7 +220,7 @@ namespace osu.Game.Collections private void updateButtonVisibility() { - if (collection == null) + if (Item.Collection == null) addOrRemoveButton.Alpha = 0; else addOrRemoveButton.Alpha = IsHovered || IsPreSelected || beatmapInCollection ? 1 : 0; @@ -283,22 +228,16 @@ namespace osu.Game.Collections private void addOrRemove() { - Debug.Assert(collection != null); + Debug.Assert(Item.Collection != null); - collection.PerformWrite(c => + Item.Collection.PerformWrite(c => { if (!c.BeatmapMD5Hashes.Remove(beatmap.Value.BeatmapInfo.MD5Hash)) c.BeatmapMD5Hashes.Add(beatmap.Value.BeatmapInfo.MD5Hash); }); } - protected override Drawable CreateContent() => content = (Content)base.CreateContent(); - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - realmSubscription?.Dispose(); - } + protected override Drawable CreateContent() => (Content)base.CreateContent(); } } } diff --git a/osu.Game/Collections/CollectionFilterMenuItem.cs b/osu.Game/Collections/CollectionFilterMenuItem.cs index fd9e333915..2ac5784f09 100644 --- a/osu.Game/Collections/CollectionFilterMenuItem.cs +++ b/osu.Game/Collections/CollectionFilterMenuItem.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Bindables; using osu.Game.Database; namespace osu.Game.Collections @@ -21,19 +20,22 @@ namespace osu.Game.Collections /// /// The name of the collection. /// - public readonly Bindable CollectionName; + public string CollectionName { get; } /// /// Creates a new . /// /// The collection to filter beatmaps from. - public CollectionFilterMenuItem(Live? collection) + public CollectionFilterMenuItem(Live collection) + : this(collection.PerformRead(c => c.Name)) { Collection = collection; - CollectionName = new Bindable(collection?.PerformRead(c => c.Name) ?? "All beatmaps"); } - // TODO: track name changes i guess? + protected CollectionFilterMenuItem(string name) + { + CollectionName = name; + } public bool Equals(CollectionFilterMenuItem? other) { @@ -47,16 +49,16 @@ namespace osu.Game.Collections // fallback to name-based comparison. // this is required for special dropdown items which don't have a collection (all beatmaps / manage collections items below). - return CollectionName.Value == other.CollectionName.Value; + return CollectionName == other.CollectionName; } - public override int GetHashCode() => CollectionName.Value.GetHashCode(); + public override int GetHashCode() => CollectionName.GetHashCode(); } public class AllBeatmapsCollectionFilterMenuItem : CollectionFilterMenuItem { public AllBeatmapsCollectionFilterMenuItem() - : base(null) + : base("All beatmaps") { } } @@ -64,9 +66,8 @@ namespace osu.Game.Collections public class ManageCollectionsFilterMenuItem : CollectionFilterMenuItem { public ManageCollectionsFilterMenuItem() - : base(null) + : base("Manage collections...") { - CollectionName.Value = "Manage collections..."; } } } From 537f64c75ecd9608b7f8f2b0e184dc18ba4bfa58 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Wed, 27 Jul 2022 22:12:52 +0800 Subject: [PATCH 355/481] Make original hit objects and random properties as local variable. --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 37 ++++++++-------------- 1 file changed, 13 insertions(+), 24 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 36f21ba291..9e5d997353 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -98,10 +98,6 @@ namespace osu.Game.Rulesets.Osu.Mods private ControlPointInfo? controlPointInfo; - private List? originalHitObjects; - - private Random? rng; - #endregion #region Sudden Death (IApplicableFailOverride) @@ -171,16 +167,16 @@ namespace osu.Game.Rulesets.Osu.Mods public override void ApplyToBeatmap(IBeatmap beatmap) { Seed.Value ??= RNG.Next(); - rng = new Random(Seed.Value.Value); + var rng = new Random(Seed.Value.Value); var osuBeatmap = (OsuBeatmap)beatmap; if (osuBeatmap.HitObjects.Count == 0) return; controlPointInfo = osuBeatmap.ControlPointInfo; - originalHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList(); - var hitObjects = generateBeats(osuBeatmap) + var originalHitObjects = osuBeatmap.HitObjects.OrderBy(x => x.StartTime).ToList(); + var hitObjects = generateBeats(osuBeatmap, originalHitObjects) .Select(beat => { var newCircle = new HitCircle(); @@ -189,21 +185,19 @@ namespace osu.Game.Rulesets.Osu.Mods return (OsuHitObject)newCircle; }).ToList(); - addHitSamples(hitObjects); + addHitSamples(originalHitObjects, hitObjects); - fixComboInfo(hitObjects); + fixComboInfo(originalHitObjects, hitObjects); - randomizeCirclePos(hitObjects); + randomizeCirclePos(rng, hitObjects); osuBeatmap.HitObjects = hitObjects; base.ApplyToBeatmap(beatmap); } - private IEnumerable generateBeats(IBeatmap beatmap) + private IEnumerable generateBeats(IBeatmap beatmap, IReadOnlyCollection originalHitObjects) { - Debug.Assert(originalHitObjects != null); - double startTime = originalHitObjects.First().StartTime; double endTime = originalHitObjects.Last().GetEndTime(); @@ -215,7 +209,7 @@ namespace osu.Game.Rulesets.Osu.Mods // Remove beats before startTime .Where(beat => almostBigger(beat, startTime)) // Remove beats during breaks - .Where(beat => !isInsideBreakPeriod(beatmap.Breaks, beat)) + .Where(beat => !isInsideBreakPeriod(originalHitObjects, beatmap.Breaks, beat)) .ToList(); // Remove beats that are too close to the next one (e.g. due to timing point changes) @@ -230,10 +224,8 @@ namespace osu.Game.Rulesets.Osu.Mods return beats; } - private void addHitSamples(IEnumerable hitObjects) + private void addHitSamples(List originalHitObjects, IEnumerable hitObjects) { - Debug.Assert(originalHitObjects != null); - foreach (var obj in hitObjects) { var samples = getSamplesAtTime(originalHitObjects, obj.StartTime); @@ -244,10 +236,8 @@ namespace osu.Game.Rulesets.Osu.Mods } } - private void fixComboInfo(List hitObjects) + private void fixComboInfo(List originalHitObjects, List hitObjects) { - Debug.Assert(originalHitObjects != null); - // Copy combo indices from an original object at the same time or from the closest preceding object // (Objects lying between two combos are assumed to belong to the preceding combo) hitObjects.ForEach(newObj => @@ -280,7 +270,7 @@ namespace osu.Game.Rulesets.Osu.Mods } } - private void randomizeCirclePos(IReadOnlyList hitObjects) + private void randomizeCirclePos(Random rng, IReadOnlyList hitObjects) { if (hitObjects.Count == 0) return; @@ -361,12 +351,11 @@ namespace osu.Game.Rulesets.Osu.Mods /// The given time is also considered to be inside a break if it is earlier than the /// start time of the first original hit object after the break. /// + /// Hit objects order by time. /// The breaks of the beatmap. /// The time to be checked.= - private bool isInsideBreakPeriod(IEnumerable breaks, double time) + private bool isInsideBreakPeriod(IReadOnlyCollection originalHitObjects, IEnumerable breaks, double time) { - Debug.Assert(originalHitObjects != null); - return breaks.Any(breakPeriod => { var firstObjAfterBreak = originalHitObjects.First(obj => almostBigger(obj.StartTime, breakPeriod.EndTime)); From 4120c20968b157fe1d8a2ab2bfba823986e8d7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 23:43:15 +0800 Subject: [PATCH 356/481] Remove the nullable disable annotation in the Mania ruleset. --- osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModMuted.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs | 2 -- osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs | 2 -- 36 files changed, 72 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs b/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs index 0bae893810..410386c9d5 100644 --- a/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/IPlayfieldTypeMod.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs index 8f8b7cb091..050b302bd8 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaKeyMod.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.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs index f9c51bf6a2..d444c9b634 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs index 8d54923e7b..f0db742eac 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs index 603d096ed7..073dda9de8 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModClassic.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs index 20dfc14f09..614ef76a3b 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModConstantSpeed.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics.Sprites; using osu.Game.Configuration; using osu.Game.Rulesets.Mania.Objects; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs index b166f3ebc3..bec0a6a1d3 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDaycore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs index d053e64315..0817f8f9fc 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDifficultyAdjust.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs index 66627e6ed3..a302f95966 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDoubleTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs index f25a77278b..c78bf72979 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModDualStages.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs index ff236d33bf..4093aeb2a7 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModEasy.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs index ddbd7c5d6a..f80c9e1f7c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.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.Game.Rulesets.Mania.UI; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs index 58005df561..8ef5bfd94c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFlashlight.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.Bindables; using osu.Framework.Graphics; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs index 87c81c2866..014954dd60 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHalfTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs index 380edca515..d9de06a811 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHardRock.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs index 698555ddc4..e3ac624a6e 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHidden.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.Game.Rulesets.Mania.UI; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs index 8c4fd0a8fc..a65938184c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModHoldOff.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.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs index f202b859b1..4cbdaee323 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModInvert.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs index 9ce4fb6a48..948979505c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey1.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey1 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs index f378ce3435..684370fc3d 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey10.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey10 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs index 5812df80f5..de91902ca8 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey2.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey2 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs index 4116ed5ceb..8575a96bde 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey3.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey3 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs index 9879fec686..54ea3afa07 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey4.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey4 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs index 646386b0d8..e9a9bba5bd 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey5.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey5 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs index 56af9ed589..b9606d1cb5 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey6.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey6 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs index a0a7116ed7..b80d794085 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey7.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey7 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs index fc8ecdb9ea..3462d634a4 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey8.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey8 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs index c495a6c82f..83c505c048 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModKey9.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Mania.Mods { public class ManiaModKey9 : ManiaKeyMod diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs index 6e56981fc8..9c3744ea98 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMirror.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModMuted.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModMuted.cs index 076f634968..33ebcf303a 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModMuted.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModMuted.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs index 9ae664e1f6..4cc712060c 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNightcore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs index 487f32dc26..e8988be548 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModNoFail.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs index 2789a2a06e..2e22e23dbd 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPerfect.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs index 5da29e5a1d..3c24e91d54 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModPlayfieldCover.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using osu.Framework.Graphics; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs index 22347d21b8..dfb02408d2 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModRandom.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.Extensions.IEnumerableExtensions; diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs index 17759d718e..ecc343ecaa 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModSuddenDeath.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods From 9f79e80d8b23159db0cbfe5a1541d1536498124b Mon Sep 17 00:00:00 2001 From: andy840119 Date: Wed, 20 Jul 2022 20:31:04 +0800 Subject: [PATCH 357/481] Remove nullable disable annotation in the Mania test case. --- .../Mods/TestSceneManiaModConstantSpeed.cs | 2 -- osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs | 2 -- osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs | 2 -- osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs | 2 -- 4 files changed, 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs index 538c8b13d1..60363aaeef 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModConstantSpeed.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs index 4222be0359..7970d5b594 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModHoldOff.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs index 4c97f65b07..f2cc254e38 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModInvert.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Game.Rulesets.Mania.Mods; using osu.Game.Tests.Visual; diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs index 9612543483..2e3b21aed7 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Objects; From d766052be4872917bb75b6db821ea72be8e8c70e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Sun, 10 Jul 2022 23:59:14 +0800 Subject: [PATCH 358/481] Remove nullable disable annotation in the Taiko ruleset. --- osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModMuted.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs | 2 -- osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs | 2 -- 19 files changed, 38 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs index 6c01bae027..4b74b4991e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModAutoplay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs index d1c192f7fa..fee0cb2744 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModCinema.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index 233179c9ec..d18b88761d 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs index 873aa7f992..84aa5e6bba 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDaycore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs index 564d023c5a..99a064d35f 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDifficultyAdjust.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.Game.Beatmaps; using osu.Game.Configuration; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs index b19c2eaccf..89581c57bd 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModDoubleTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs index a37e1c6f5c..ad6fdf59e2 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModEasy.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index fe02a6caf9..43230bc035 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Layout; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs index ddfc2d1174..68d6305fbf 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHalfTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs index 7780936e7d..ba41175461 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHardRock.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index fe3e5ca11c..538d0a9d7a 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModMuted.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModMuted.cs index 874e15406d..0f1e0b2885 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModMuted.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModMuted.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs index e02a16f62f..7cb14635ff 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModNightcore.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs index 57ecf0224f..bf1006f1aa 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModNoFail.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs index c65dba243b..b107b14a03 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModPerfect.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs index f58f59aaf2..307a37bf2e 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRandom.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.Utils; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs index a3a644ab99..7be70d9ac3 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModRelax.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs index 037e376ad2..7a0f6c7cd1 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModSuddenDeath.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Taiko.Mods diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs index c9cba59760..3cb337c41d 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModSwap.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.Game.Beatmaps; From 860e9d42ff2236841d309facc8c29284f9c6fc93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=82=BA=E4=BB=80=E9=BA=BC?= Date: Mon, 11 Jul 2022 00:00:48 +0800 Subject: [PATCH 359/481] Mark the property as nullable and add some assert check. --- osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs | 5 ++++- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 5 +++-- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 5 ++++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index d18b88761d..f7fdd447d6 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; @@ -10,7 +11,7 @@ namespace osu.Game.Rulesets.Taiko.Mods { public class TaikoModClassic : ModClassic, IApplicableToDrawableRuleset, IUpdatableByPlayfield { - private DrawableTaikoRuleset drawableTaikoRuleset; + private DrawableTaikoRuleset? drawableTaikoRuleset; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { @@ -23,6 +24,8 @@ namespace osu.Game.Rulesets.Taiko.Mods public void Update(Playfield playfield) { + Debug.Assert(drawableTaikoRuleset != null); + // Classic taiko scrolls at a constant 100px per 1000ms. More notes become visible as the playfield is lengthened. const float scroll_rate = 10; diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 43230bc035..3820e55e75 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Layout; using osu.Game.Configuration; @@ -36,9 +37,9 @@ namespace osu.Game.Rulesets.Taiko.Mods public override float DefaultFlashlightSize => 250; - protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield); + protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield.AsNonNull()); - private TaikoPlayfield playfield; + private TaikoPlayfield? playfield; public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index 538d0a9d7a..dab2279351 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Framework.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -30,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Mods /// private const float fade_out_duration = 0.375f; - private DrawableTaikoRuleset drawableRuleset; + private DrawableTaikoRuleset? drawableRuleset; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { @@ -44,6 +45,8 @@ namespace osu.Game.Rulesets.Taiko.Mods protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) { + Debug.Assert(drawableRuleset != null); + switch (hitObject) { case DrawableDrumRollTick: From 3510c1656672962aa026b7a0bcbd4123408941a5 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Wed, 20 Jul 2022 20:38:02 +0800 Subject: [PATCH 360/481] Remove nullable disable annotation in the Taiko test case. --- osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs | 2 -- osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs | 2 -- osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs | 2 -- 3 files changed, 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs index ca2f8102b7..3090facf8c 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TaikoModTestScene.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests.Mods diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs index 0b28bfee2e..7abbb9d186 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModHidden.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Linq; using NUnit.Framework; using osu.Game.Rulesets.Taiko.Mods; diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs index 917462c128..a83cc16413 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Taiko.Mods; From 45c11f2b7b2765c42d19069949fa5b3a14c35c6c Mon Sep 17 00:00:00 2001 From: Nitrous Date: Thu, 28 Jul 2022 08:01:38 +0800 Subject: [PATCH 361/481] account for gameplay start time --- osu.Game/Screens/Play/HUD/SongProgress.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index c245a47554..5c7c7d28c6 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; @@ -20,9 +21,12 @@ namespace osu.Game.Screens.Play.HUD { public bool UsesFixedAnchor { get; set; } - [Resolved] + [Resolved(canBeNull: true)] private GameplayClock gameplayClock { get; set; } + [Resolved(canBeNull: true)] + private GameplayClockContainer gameplayClockContainer { get; set; } + [Resolved(canBeNull: true)] private DrawableRuleset drawableRuleset { get; set; } @@ -69,12 +73,13 @@ namespace osu.Game.Screens.Play.HUD if (objects == null) return; - double gameplayTime = gameplayClock?.CurrentTime ?? Time.Current; + double gameplayTime = gameplayClockContainer?.GameplayClock.CurrentTime ?? gameplayClock?.CurrentTime ?? Time.Current; double frameStableTime = referenceClock?.CurrentTime ?? gameplayTime; if (frameStableTime < FirstHitTime) { - UpdateProgress((frameStableTime - FirstEventTime) / (FirstHitTime - FirstEventTime), gameplayTime, true); + double earliest = Math.Min(FirstEventTime, gameplayClockContainer?.StartTime ?? 0); + UpdateProgress((frameStableTime - earliest) / (FirstHitTime - earliest), gameplayTime, true); } else { From 9088caa377247a7c5ace117d24d12b1af707b0ee Mon Sep 17 00:00:00 2001 From: Nitrous Date: Thu, 28 Jul 2022 08:36:26 +0800 Subject: [PATCH 362/481] move `LegacyComboCounter` to `osu.Game.Skinning` --- .../TestSceneCatchPlayerLegacySkin.cs | 1 - .../Skinning/Legacy/CatchLegacySkinTransformer.cs | 1 - .../Visual/Gameplay/TestSceneSkinnableComboCounter.cs | 1 + osu.Game/{Screens/Play/HUD => Skinning}/LegacyComboCounter.cs | 4 ++-- 4 files changed, 3 insertions(+), 4 deletions(-) rename osu.Game/{Screens/Play/HUD => Skinning}/LegacyComboCounter.cs (99%) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs index 8dd6f82c57..2078e1453f 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayerLegacySkin.cs @@ -9,7 +9,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; -using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osu.Game.Tests.Visual; using osuTK; diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs index c5e5e59dd2..c06d9f520f 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/CatchLegacySkinTransformer.cs @@ -6,7 +6,6 @@ using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osuTK.Graphics; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs index eacab6d34f..ef56f456ea 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinnableComboCounter.cs @@ -10,6 +10,7 @@ using osu.Framework.Testing; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play.HUD; +using osu.Game.Skinning; namespace osu.Game.Tests.Visual.Gameplay { diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Skinning/LegacyComboCounter.cs similarity index 99% rename from osu.Game/Screens/Play/HUD/LegacyComboCounter.cs rename to osu.Game/Skinning/LegacyComboCounter.cs index 6e36ffb54e..bfa6d5a255 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Skinning/LegacyComboCounter.cs @@ -9,10 +9,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Rulesets.Scoring; -using osu.Game.Skinning; +using osu.Game.Screens.Play.HUD; using osuTK; -namespace osu.Game.Screens.Play.HUD +namespace osu.Game.Skinning { /// /// Uses the 'x' symbol and has a pop-out effect while rolling over. From ac39d3a1424470e41ea107ed7246088d51d20cbc Mon Sep 17 00:00:00 2001 From: Andrew Hong Date: Wed, 27 Jul 2022 21:52:28 -0400 Subject: [PATCH 363/481] "Copied URL" -> "URL copied" --- osu.Game/Graphics/UserInterface/ExternalLinkButton.cs | 2 +- osu.Game/Localisation/ToastStrings.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 810cd6ef58..87d67939bf 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -76,7 +76,7 @@ namespace osu.Game.Graphics.UserInterface if (Link != null) { items.Add(new OsuMenuItem("Open", MenuItemType.Standard, () => host.OpenUrlExternally(Link))); - items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, () => copyUrl())); + items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, copyUrl)); } return items.ToArray(); diff --git a/osu.Game/Localisation/ToastStrings.cs b/osu.Game/Localisation/ToastStrings.cs index d6771fcd96..da798a3937 100644 --- a/osu.Game/Localisation/ToastStrings.cs +++ b/osu.Game/Localisation/ToastStrings.cs @@ -45,9 +45,9 @@ namespace osu.Game.Localisation public static LocalisableString SkinSaved => new TranslatableString(getKey(@"skin_saved"), @"Skin saved"); /// - /// "Copied URL" + /// "URL copied" /// - public static LocalisableString CopiedUrl => new TranslatableString(getKey(@"copied_url"), @"Copied URL"); + public static LocalisableString UrlCopied => new TranslatableString(getKey(@"url_copied"), @"URL copied"); private static string getKey(string key) => $@"{prefix}:{key}"; } From f097064eea6ab93cc6ca8702317ab783e0bc5c70 Mon Sep 17 00:00:00 2001 From: Andrew Hong Date: Wed, 27 Jul 2022 21:52:38 -0400 Subject: [PATCH 364/481] Adjust to reviews --- .../UserInterface/ExternalLinkButton.cs | 23 ++++++++----------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 87d67939bf..762b4bd90d 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -50,21 +50,10 @@ namespace osu.Game.Graphics.UserInterface }; } - private class CopyUrlToast : Toast - { - public CopyUrlToast(LocalisableString value) - : base(UserInterfaceStrings.GeneralHeader, value, "") - { - } - } - private void copyUrl() { - if (Link != null) - { - host.GetClipboard()?.SetText(Link); - onScreenDisplay?.Display(new CopyUrlToast(ToastStrings.CopiedUrl)); - } + host.GetClipboard()?.SetText(Link); + onScreenDisplay?.Display(new CopyUrlToast(ToastStrings.UrlCopied)); } public MenuItem[] ContextMenuItems @@ -109,5 +98,13 @@ namespace osu.Game.Graphics.UserInterface } public LocalisableString TooltipText => "view in browser"; + + private class CopyUrlToast : Toast + { + public CopyUrlToast(LocalisableString value) + : base(UserInterfaceStrings.GeneralHeader, value, "") + { + } + } } } From f01c3972207ca20b26a7e6361e2b492e3d44eeb4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 12:12:46 +0900 Subject: [PATCH 365/481] Apply nullability --- .../Graphics/UserInterface/ExternalLinkButton.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 762b4bd90d..7b9abf6781 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -23,19 +21,19 @@ namespace osu.Game.Graphics.UserInterface { public class ExternalLinkButton : CompositeDrawable, IHasTooltip, IHasContextMenu { - public string Link { get; set; } + public string? Link { get; set; } private Color4 hoverColour; [Resolved] - private GameHost host { get; set; } + private GameHost host { get; set; } = null!; - [Resolved(canBeNull: true)] - private OnScreenDisplay onScreenDisplay { get; set; } + [Resolved] + private OnScreenDisplay? onScreenDisplay { get; set; } private readonly SpriteIcon linkIcon; - public ExternalLinkButton(string link = null) + public ExternalLinkButton(string? link = null) { Link = link; Size = new Vector2(12); From f44a4c865293ae77f6b0bf2255f7b976c6a6ea6e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 12:13:45 +0900 Subject: [PATCH 366/481] Reorder file content to match expectations --- .../UserInterface/ExternalLinkButton.cs | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs index 7b9abf6781..7f86a060ad 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -48,28 +48,6 @@ namespace osu.Game.Graphics.UserInterface }; } - private void copyUrl() - { - host.GetClipboard()?.SetText(Link); - onScreenDisplay?.Display(new CopyUrlToast(ToastStrings.UrlCopied)); - } - - public MenuItem[] ContextMenuItems - { - get - { - List items = new List(); - - if (Link != null) - { - items.Add(new OsuMenuItem("Open", MenuItemType.Standard, () => host.OpenUrlExternally(Link))); - items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, copyUrl)); - } - - return items.ToArray(); - } - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -97,6 +75,28 @@ namespace osu.Game.Graphics.UserInterface public LocalisableString TooltipText => "view in browser"; + public MenuItem[] ContextMenuItems + { + get + { + List items = new List(); + + if (Link != null) + { + items.Add(new OsuMenuItem("Open", MenuItemType.Standard, () => host.OpenUrlExternally(Link))); + items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, copyUrl)); + } + + return items.ToArray(); + } + } + + private void copyUrl() + { + host.GetClipboard()?.SetText(Link); + onScreenDisplay?.Display(new CopyUrlToast(ToastStrings.UrlCopied)); + } + private class CopyUrlToast : Toast { public CopyUrlToast(LocalisableString value) From 8da499fb0fc1b1e0febcac88cccda792707a5da6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 12:16:21 +0900 Subject: [PATCH 367/481] Add proper test coverage --- .../Visual/Online/TestSceneExternalLinkButton.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneExternalLinkButton.cs b/osu.Game.Tests/Visual/Online/TestSceneExternalLinkButton.cs index fdcde0f2a5..4185d56833 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneExternalLinkButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneExternalLinkButton.cs @@ -3,6 +3,8 @@ #nullable disable +using osu.Framework.Graphics; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osuTK; @@ -12,9 +14,15 @@ namespace osu.Game.Tests.Visual.Online { public TestSceneExternalLinkButton() { - Child = new ExternalLinkButton("https://osu.ppy.sh/home") + Child = new OsuContextMenuContainer { - Size = new Vector2(50) + RelativeSizeAxes = Axes.Both, + Child = new ExternalLinkButton("https://osu.ppy.sh/home") + { + Size = new Vector2(50), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } }; } } From 0913cf013cbe8555a5450924a7ebfd3c2a8d8820 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 13:24:14 +0900 Subject: [PATCH 368/481] Split out tests and fix variable conflict --- .../Visual/Gameplay/TestSceneSongProgress.cs | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index f32a7e7cab..897284ed80 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -19,7 +19,8 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public class TestSceneSongProgress : SkinnableHUDComponentTestScene { - private DefaultSongProgress progress; + private DefaultSongProgress defaultProgress; + private readonly List progresses = new List(); private readonly StopwatchClock clock; @@ -45,14 +46,19 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("display max values", displayMaxValues); AddStep("start", clock.Start); - AddStep("allow seeking", () => progress.AllowSeeking.Value = true); - AddStep("hide graph", () => progress.ShowGraph.Value = false); - AddStep("disallow seeking", () => progress.AllowSeeking.Value = false); - AddStep("allow seeking", () => progress.AllowSeeking.Value = true); - AddStep("show graph", () => progress.ShowGraph.Value = true); AddStep("stop", clock.Stop); } + [Test] + public void TestToggleSeeking() + { + AddStep("allow seeking", () => defaultProgress.AllowSeeking.Value = true); + AddStep("hide graph", () => defaultProgress.ShowGraph.Value = false); + AddStep("disallow seeking", () => defaultProgress.AllowSeeking.Value = false); + AddStep("allow seeking", () => defaultProgress.AllowSeeking.Value = true); + AddStep("show graph", () => defaultProgress.ShowGraph.Value = true); + } + private void displayMaxValues() { var objects = new List(); @@ -64,11 +70,11 @@ namespace osu.Game.Tests.Visual.Gameplay private void replaceObjects(List objects) { - progress.RequestSeek = pos => clock.Seek(pos); + defaultProgress.RequestSeek = pos => clock.Seek(pos); - foreach (var progress in progresses) + foreach (var p in progresses) { - progress.Objects = objects; + p.Objects = objects; } } @@ -80,15 +86,15 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Drawable CreateDefaultImplementation() { - progress = new DefaultSongProgress + defaultProgress = new DefaultSongProgress { RelativeSizeAxes = Axes.X, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, }; - progresses.Add(progress); - return progress; + progresses.Add(defaultProgress); + return defaultProgress; } protected override Drawable CreateLegacyImplementation() From 67c7f324ee899d652e127903a82917c980896e95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 19:02:43 +0900 Subject: [PATCH 369/481] Simplify `CollectionFilterDropdown` filter flow weirdness --- .../SongSelect/TestSceneFilterControl.cs | 10 +++++ .../Collections/CollectionFilterDropdown.cs | 43 +++++++------------ osu.Game/Screens/Select/FilterControl.cs | 20 +++------ 3 files changed, 32 insertions(+), 41 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index aaaa0aec95..feb71def5d 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -176,6 +176,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestManageCollectionsFilterIsNotSelected() { + bool received = false; + addExpandHeaderStep(); AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1", new List { "abc" })))); @@ -187,6 +189,12 @@ namespace osu.Game.Tests.Visual.SongSelect addExpandHeaderStep(); + AddStep("watch for filter requests", () => + { + received = false; + control.ChildrenOfType().First().RequestFilter = () => received = true; + }); + AddStep("click manage collections filter", () => { InputManager.MoveMouseTo(getCollectionDropdownItems().Last()); @@ -194,6 +202,8 @@ namespace osu.Game.Tests.Visual.SongSelect }); AddAssert("collection filter still selected", () => control.CreateCriteria().CollectionBeatmapMD5Hashes.Any()); + + AddAssert("filter request not fired", () => !received); } private BeatmapCollection getFirstCollection() => Realm.Run(r => r.All().First()); diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index ec409df4d6..fa15e2b56f 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -30,15 +30,8 @@ namespace osu.Game.Collections /// protected virtual bool ShowManageCollectionsItem => true; - private readonly BindableWithCurrent current = new BindableWithCurrent(); + public Action? RequestFilter { private get; set; } - public new Bindable Current - { - get => current.Current; - set => current.Current = value; - } - - private readonly IBindableList beatmaps = new BindableList(); private readonly BindableList filters = new BindableList(); [Resolved] @@ -50,6 +43,7 @@ namespace osu.Game.Collections public CollectionFilterDropdown() { ItemSource = filters; + Current.Value = new AllBeatmapsCollectionFilterMenuItem(); } @@ -59,17 +53,9 @@ namespace osu.Game.Collections realm.RegisterForNotifications(r => r.All(), collectionsChanged); - // Dropdown has logic which triggers a change on the bindable with every change to the contained items. - // This is not desirable here, as it leads to multiple filter operations running even though nothing has changed. - // An extra bindable is enough to subvert this behaviour. - base.Current = Current; - - Current.BindValueChanged(currentChanged, true); + Current.BindValueChanged(currentChanged); } - /// - /// Occurs when a collection has been added or removed. - /// private void collectionsChanged(IRealmCollection collections, ChangeSet? changes, Exception error) { var selectedItem = SelectedItem?.Value?.Collection; @@ -83,38 +69,41 @@ namespace osu.Game.Collections Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection.ID == selectedItem?.ID) ?? filters[0]; - // Trigger a re-filter if the current item was in the changeset. + // Trigger a re-filter if the current item was in the change set. if (selectedItem != null && changes != null) { foreach (int index in changes.ModifiedIndices) { if (collections[index].ID == selectedItem.ID) - { - // The filtered beatmaps have changed, without the filter having changed itself. So a change in filter must be notified. - // Note that this does NOT propagate to bound bindables, so the FilterControl must bind directly to the value change event of this bindable. - Current.TriggerChange(); - } + RequestFilter?.Invoke(); } } } private void currentChanged(ValueChangedEvent filter) { + // May be null during .Clear(). + if (filter.NewValue == null) + return; + // Never select the manage collection filter - rollback to the previous filter. // This is done after the above since it is important that bindable is unbound from OldValue, which is lost after forcing it back to the old value. if (filter.NewValue is ManageCollectionsFilterMenuItem) { Current.Value = filter.OldValue; manageCollectionsDialog?.Show(); + return; } + + // This dropdown be weird. + // We only care about filtering if the actual collection has changed. + if (filter.OldValue?.Collection != null || filter.NewValue?.Collection != null) + RequestFilter?.Invoke(); } protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName; - protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader().With(d => - { - d.SelectedItem.BindTarget = Current; - }); + protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader().With(d => d.SelectedItem.BindTarget = Current); protected sealed override DropdownMenu CreateMenu() => CreateCollectionMenu(); diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 5f5344a338..f07817d5dc 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -39,6 +39,10 @@ namespace osu.Game.Screens.Select private Bindable groupMode; + private SeekLimitedSearchTextBox searchTextBox; + + private CollectionFilterDropdown collectionDropdown; + public FilterCriteria CreateCriteria() { string query = searchTextBox.Text; @@ -49,7 +53,7 @@ namespace osu.Game.Screens.Select Sort = sortMode.Value, AllowConvertedBeatmaps = showConverted.Value, Ruleset = ruleset.Value, - CollectionBeatmapMD5Hashes = collectionDropdown?.Current.Value?.Collection?.PerformRead(c => c.BeatmapMD5Hashes) + CollectionBeatmapMD5Hashes = collectionDropdown.Current.Value?.Collection?.PerformRead(c => c.BeatmapMD5Hashes) }; if (!minimumStars.IsDefault) @@ -64,10 +68,6 @@ namespace osu.Game.Screens.Select return criteria; } - private SeekLimitedSearchTextBox searchTextBox; - - private CollectionFilterDropdown collectionDropdown; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || sortTabs.ReceivePositionalInputAt(screenSpacePos); @@ -183,6 +183,7 @@ namespace osu.Game.Screens.Select { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, + RequestFilter = updateCriteria, RelativeSizeAxes = Axes.X, Y = 4, Width = 0.5f, @@ -209,15 +210,6 @@ namespace osu.Game.Screens.Select groupMode.BindValueChanged(_ => updateCriteria()); sortMode.BindValueChanged(_ => updateCriteria()); - collectionDropdown.Current.ValueChanged += val => - { - if (val.NewValue == null) - // may be null briefly while menu is repopulated. - return; - - updateCriteria(); - }; - searchTextBox.Current.ValueChanged += _ => updateCriteria(); updateCriteria(); From 34a2d1a6e1921baa76a8521ffbfa7d3b4d30f7fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 19:35:25 +0900 Subject: [PATCH 370/481] Fix `ManageCollectionsDialog` and remove weird placeholder logic --- .../TestSceneManageCollectionsDialog.cs | 27 +++++- .../Collections/CollectionFilterDropdown.cs | 33 ++++--- .../Collections/DeleteCollectionDialog.cs | 5 +- .../Collections/DrawableCollectionList.cs | 30 ++++-- .../Collections/DrawableCollectionListItem.cs | 97 ++++++------------- .../Collections/ManageCollectionsDialog.cs | 4 +- 6 files changed, 103 insertions(+), 93 deletions(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 8de38eb4e7..afcb511a6a 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -109,10 +109,15 @@ namespace osu.Game.Tests.Visual.Collections InputManager.Click(MouseButton.Left); }); - // Done directly via the collection since InputManager methods cannot add text to textbox... - AddStep("change collection name", () => placeholderItem.Model.PerformWrite(c => c.Name = "a")); + assertCollectionCount(0); + + AddStep("change collection name", () => + { + placeholderItem.ChildrenOfType().First().Text = "test text"; + InputManager.Key(Key.Enter); + }); + assertCollectionCount(1); - AddAssert("collection now exists", () => placeholderItem.Model.IsManaged); AddAssert("last item is placeholder", () => !dialog.ChildrenOfType().Last().Model.IsManaged); } @@ -257,6 +262,7 @@ namespace osu.Game.Tests.Visual.Collections public void TestCollectionRenamedOnTextChange() { BeatmapCollection first = null!; + DrawableCollectionListItem firstItem = null!; AddStep("add two collections", () => { @@ -272,12 +278,23 @@ namespace osu.Game.Tests.Visual.Collections assertCollectionCount(2); - AddStep("change first collection name", () => dialog.ChildrenOfType().First().Text = "First"); + AddStep("focus first collection", () => + { + InputManager.MoveMouseTo(firstItem = dialog.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + + AddStep("change first collection name", () => + { + firstItem.ChildrenOfType().First().Text = "First"; + InputManager.Key(Key.Enter); + }); + AddUntilStep("collection has new name", () => first.Name == "First"); } private void assertCollectionCount(int count) - => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count(i => i.IsCreated.Value) == count); + => AddUntilStep($"{count} collections shown", () => dialog.ChildrenOfType().Count() == count + 1); // +1 for placeholder private void assertCollectionName(int index, string name) => AddUntilStep($"item {index + 1} has correct name", () => dialog.ChildrenOfType().ElementAt(index).ChildrenOfType().First().Text == name); diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionFilterDropdown.cs index fa15e2b56f..790a23d2e5 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionFilterDropdown.cs @@ -53,21 +53,27 @@ namespace osu.Game.Collections realm.RegisterForNotifications(r => r.All(), collectionsChanged); - Current.BindValueChanged(currentChanged); + Current.BindValueChanged(selectionChanged); } private void collectionsChanged(IRealmCollection collections, ChangeSet? changes, Exception error) { var selectedItem = SelectedItem?.Value?.Collection; + var allBeatmaps = new AllBeatmapsCollectionFilterMenuItem(); + filters.Clear(); - filters.Add(new AllBeatmapsCollectionFilterMenuItem()); + filters.Add(allBeatmaps); filters.AddRange(collections.Select(c => new CollectionFilterMenuItem(c.ToLive(realm)))); if (ShowManageCollectionsItem) filters.Add(new ManageCollectionsFilterMenuItem()); - Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection.ID == selectedItem?.ID) ?? filters[0]; + // This current update and schedule is required to work around dropdown headers not updating text even when the selected item + // changes. It's not great but honestly the whole dropdown menu structure isn't great. This needs to be fixed, but I'll issue + // a warning that it's going to be a frustrating journey. + Current.Value = allBeatmaps; + Schedule(() => Current.Value = filters.SingleOrDefault(f => f.Collection != null && f.Collection.ID == selectedItem?.ID) ?? filters[0]); // Trigger a re-filter if the current item was in the change set. if (selectedItem != null && changes != null) @@ -80,7 +86,9 @@ namespace osu.Game.Collections } } - private void currentChanged(ValueChangedEvent filter) + private Live? lastFiltered; + + private void selectionChanged(ValueChangedEvent filter) { // May be null during .Clear(). if (filter.NewValue == null) @@ -95,15 +103,20 @@ namespace osu.Game.Collections return; } + var newCollection = filter.NewValue?.Collection; + // This dropdown be weird. // We only care about filtering if the actual collection has changed. - if (filter.OldValue?.Collection != null || filter.NewValue?.Collection != null) + if (newCollection != lastFiltered) + { RequestFilter?.Invoke(); + lastFiltered = newCollection; + } } protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName; - protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader().With(d => d.SelectedItem.BindTarget = Current); + protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader(); protected sealed override DropdownMenu CreateMenu() => CreateCollectionMenu(); @@ -113,8 +126,6 @@ namespace osu.Game.Collections public class CollectionDropdownHeader : OsuDropdownHeader { - public readonly Bindable SelectedItem = new Bindable(); - public CollectionDropdownHeader() { Height = 25; @@ -130,14 +141,14 @@ namespace osu.Game.Collections MaxHeight = 200; } - protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new CollectionDropdownMenuItem(item) + protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new CollectionDropdownDrawableMenuItem(item) { BackgroundColourHover = HoverColour, BackgroundColourSelected = SelectionColour }; } - protected class CollectionDropdownMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem + protected class CollectionDropdownDrawableMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem { protected new CollectionFilterMenuItem Item => ((DropdownMenuItem)base.Item).Value; @@ -148,7 +159,7 @@ namespace osu.Game.Collections [Resolved] private IBindable beatmap { get; set; } = null!; - public CollectionDropdownMenuItem(MenuItem item) + public CollectionDropdownDrawableMenuItem(MenuItem item) : base(item) { } diff --git a/osu.Game/Collections/DeleteCollectionDialog.cs b/osu.Game/Collections/DeleteCollectionDialog.cs index 7594978870..f3f038a7f0 100644 --- a/osu.Game/Collections/DeleteCollectionDialog.cs +++ b/osu.Game/Collections/DeleteCollectionDialog.cs @@ -4,16 +4,17 @@ using System; using Humanizer; using osu.Framework.Graphics.Sprites; +using osu.Game.Database; using osu.Game.Overlays.Dialog; namespace osu.Game.Collections { public class DeleteCollectionDialog : PopupDialog { - public DeleteCollectionDialog(BeatmapCollection collection, Action deleteAction) + public DeleteCollectionDialog(Live collection, Action deleteAction) { HeaderText = "Confirm deletion of"; - BodyText = $"{collection.Name} ({"beatmap".ToQuantity(collection.BeatmapMD5Hashes.Count)})"; + BodyText = collection.PerformRead(c => $"{c.Name} ({"beatmap".ToQuantity(c.BeatmapMD5Hashes.Count)})"); Icon = FontAwesome.Regular.TrashAlt; diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index f376d18224..1639afd362 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -1,33 +1,51 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Diagnostics; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Database; using osu.Game.Graphics.Containers; using osuTK; +using Realms; namespace osu.Game.Collections { /// /// Visualises a list of s. /// - public class DrawableCollectionList : OsuRearrangeableListContainer + public class DrawableCollectionList : OsuRearrangeableListContainer> { private Scroll scroll = null!; protected override ScrollContainer CreateScrollContainer() => scroll = new Scroll(); - protected override FillFlowContainer> CreateListFillFlowContainer() => new Flow + [Resolved] + private RealmAccess realm { get; set; } = null!; + + protected override FillFlowContainer>> CreateListFillFlowContainer() => new Flow { DragActive = { BindTarget = DragActive } }; - // TODO: source from realm + protected override void LoadComplete() + { + base.LoadComplete(); - protected override OsuRearrangeableListItem CreateOsuDrawable(BeatmapCollection item) + realm.RegisterForNotifications(r => r.All(), collectionsChanged); + } + + private void collectionsChanged(IRealmCollection collections, ChangeSet? changes, Exception error) + { + Items.Clear(); + Items.AddRange(collections.AsEnumerable().Select(c => c.ToLive(realm))); + } + + protected override OsuRearrangeableListItem> CreateOsuDrawable(Live item) { if (item.ID == scroll.PlaceholderItem.Model.ID) return scroll.ReplacePlaceholder(); @@ -97,7 +115,7 @@ namespace osu.Game.Collections var previous = PlaceholderItem; placeholderContainer.Clear(false); - placeholderContainer.Add(PlaceholderItem = new DrawableCollectionListItem(new BeatmapCollection(), false)); + placeholderContainer.Add(PlaceholderItem = new DrawableCollectionListItem(new BeatmapCollection().ToLiveUnmanaged(), false)); return previous; } @@ -106,7 +124,7 @@ namespace osu.Game.Collections /// /// The flow of . Disables layout easing unless a drag is in progress. /// - private class Flow : FillFlowContainer> + private class Flow : FillFlowContainer>> { public readonly IBindable DragActive = new Bindable(); diff --git a/osu.Game/Collections/DrawableCollectionListItem.cs b/osu.Game/Collections/DrawableCollectionListItem.cs index 6093e69deb..d1e40f6262 100644 --- a/osu.Game/Collections/DrawableCollectionListItem.cs +++ b/osu.Game/Collections/DrawableCollectionListItem.cs @@ -3,12 +3,12 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Database; using osu.Game.Graphics; @@ -23,52 +23,37 @@ namespace osu.Game.Collections /// /// Visualises a inside a . /// - public class DrawableCollectionListItem : OsuRearrangeableListItem + public class DrawableCollectionListItem : OsuRearrangeableListItem> { private const float item_height = 35; private const float button_width = item_height * 0.75f; - /// - /// Whether the currently exists inside realm. - /// - public IBindable IsCreated => isCreated; - - private readonly Bindable isCreated = new Bindable(); - /// /// Creates a new . /// /// The . /// Whether currently exists inside realm. - public DrawableCollectionListItem(BeatmapCollection item, bool isCreated) + public DrawableCollectionListItem(Live item, bool isCreated) : base(item) { - this.isCreated.Value = isCreated; - - ShowDragHandle.BindTo(this.isCreated); + ShowDragHandle.Value = item.IsManaged; } - protected override Drawable CreateContent() => new ItemContent(Model) - { - IsCreated = { BindTarget = isCreated } - }; + protected override Drawable CreateContent() => new ItemContent(Model); /// /// The main content of the . /// private class ItemContent : CircularContainer { - public readonly Bindable IsCreated = new Bindable(); + private readonly Live collection; - private readonly BeatmapCollection collection; - - private Container textBoxPaddingContainer = null!; private ItemTextBox textBox = null!; [Resolved] private RealmAccess realm { get; set; } = null!; - public ItemContent(BeatmapCollection collection) + public ItemContent(Live collection) { this.collection = collection; @@ -80,19 +65,20 @@ namespace osu.Game.Collections [BackgroundDependencyLoader] private void load() { - Children = new Drawable[] + Children = new[] { - new DeleteButton(collection) - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - IsCreated = { BindTarget = IsCreated }, - IsTextBoxHovered = v => textBox.ReceivePositionalInputAt(v) - }, - textBoxPaddingContainer = new Container + collection.IsManaged + ? new DeleteButton(collection) + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + IsTextBoxHovered = v => textBox.ReceivePositionalInputAt(v) + } + : Empty(), + new Container { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = button_width }, + Padding = new MarginPadding { Right = collection.IsManaged ? button_width : 0 }, Children = new Drawable[] { textBox = new ItemTextBox @@ -100,7 +86,7 @@ namespace osu.Game.Collections RelativeSizeAxes = Axes.Both, Size = Vector2.One, CornerRadius = item_height / 2, - PlaceholderText = IsCreated.Value ? string.Empty : "Create a new collection" + PlaceholderText = collection.IsManaged ? string.Empty : "Create a new collection" }, } }, @@ -112,28 +98,18 @@ namespace osu.Game.Collections base.LoadComplete(); // Bind late, as the collection name may change externally while still loading. - textBox.Current.Value = collection.Name; - textBox.Current.BindValueChanged(_ => createNewCollection(), true); - - IsCreated.BindValueChanged(created => textBoxPaddingContainer.Padding = new MarginPadding { Right = created.NewValue ? button_width : 0 }, true); + textBox.Current.Value = collection.PerformRead(c => c.IsValid ? c.Name : string.Empty); + textBox.OnCommit += onCommit; } - private void createNewCollection() + private void onCommit(TextBox sender, bool newText) { - if (IsCreated.Value) - return; + if (collection.IsManaged) + collection.PerformWrite(c => c.Name = textBox.Current.Value); + else if (!string.IsNullOrEmpty(textBox.Current.Value)) + realm.Write(r => r.Add(new BeatmapCollection(textBox.Current.Value))); - if (string.IsNullOrEmpty(textBox.Current.Value)) - return; - - // Add the new collection and disable our placeholder. If all text is removed, the placeholder should not show back again. - realm.Write(r => r.Add(collection)); - textBox.PlaceholderText = string.Empty; - - // When this item changes from placeholder to non-placeholder (via changing containers), its textbox will lose focus, so it needs to be re-focused. - Schedule(() => GetContainingInputManager().ChangeFocus(textBox)); - - IsCreated.Value = true; + textBox.Text = string.Empty; } } @@ -151,22 +127,17 @@ namespace osu.Game.Collections public class DeleteButton : CompositeDrawable { - public readonly IBindable IsCreated = new Bindable(); - public Func IsTextBoxHovered = null!; [Resolved] private IDialogOverlay? dialogOverlay { get; set; } - [Resolved] - private RealmAccess realmAccess { get; set; } = null!; - - private readonly BeatmapCollection collection; + private readonly Live collection; private Drawable fadeContainer = null!; private Drawable background = null!; - public DeleteButton(BeatmapCollection collection) + public DeleteButton(Live collection) { this.collection = collection; RelativeSizeAxes = Axes.Y; @@ -200,12 +171,6 @@ namespace osu.Game.Collections }; } - protected override void LoadComplete() - { - base.LoadComplete(); - IsCreated.BindValueChanged(created => Alpha = created.NewValue ? 1 : 0, true); - } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && !IsTextBoxHovered(screenSpacePos); protected override bool OnHover(HoverEvent e) @@ -223,7 +188,7 @@ namespace osu.Game.Collections { background.FlashColour(Color4.White, 150); - if (collection.BeatmapMD5Hashes.Count == 0) + if (collection.PerformRead(c => c.BeatmapMD5Hashes.Count) == 0) deleteCollection(); else dialogOverlay?.Push(new DeleteCollectionDialog(collection, deleteCollection)); @@ -231,7 +196,7 @@ namespace osu.Game.Collections return true; } - private void deleteCollection() => realmAccess.Write(r => r.Remove(collection)); + private void deleteCollection() => collection.PerformWrite(c => c.Realm.Remove(c)); } } } diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 721e0d632e..13737dbd78 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; @@ -23,7 +21,7 @@ namespace osu.Game.Collections private const double enter_duration = 500; private const double exit_duration = 200; - private AudioFilter lowPassFilter; + private AudioFilter lowPassFilter = null!; public ManageCollectionsDialog() { From 1669208a54a232dc2e097bc129b3839209be3b2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 Jul 2022 23:19:00 +0900 Subject: [PATCH 371/481] Add migration of existing collections database --- osu.Game/Database/LegacyCollectionImporter.cs | 14 +++++------ osu.Game/Database/LegacyImportManager.cs | 2 +- osu.Game/Database/RealmAccess.cs | 25 ++++++++++++++++++- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/osu.Game/Database/LegacyCollectionImporter.cs b/osu.Game/Database/LegacyCollectionImporter.cs index bd32b8c446..4bb28bf731 100644 --- a/osu.Game/Database/LegacyCollectionImporter.cs +++ b/osu.Game/Database/LegacyCollectionImporter.cs @@ -7,8 +7,8 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using osu.Framework.Logging; +using osu.Framework.Platform; using osu.Game.Collections; -using osu.Game.IO; using osu.Game.IO.Legacy; using osu.Game.Overlays.Notifications; @@ -27,14 +27,14 @@ namespace osu.Game.Database this.realm = realm; } - public Task GetAvailableCount(StableStorage stableStorage) + public Task GetAvailableCount(Storage storage) { - if (!stableStorage.Exists(database_name)) + if (!storage.Exists(database_name)) return Task.FromResult(0); return Task.Run(() => { - using (var stream = stableStorage.GetStream(database_name)) + using (var stream = storage.GetStream(database_name)) return readCollections(stream).Count; }); } @@ -42,9 +42,9 @@ namespace osu.Game.Database /// /// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future. /// - public Task ImportFromStableAsync(StableStorage stableStorage) + public Task ImportFromStorage(Storage storage) { - if (!stableStorage.Exists(database_name)) + if (!storage.Exists(database_name)) { // This handles situations like when the user does not have a collections.db file Logger.Log($"No {database_name} available in osu!stable installation", LoggingTarget.Information, LogLevel.Error); @@ -53,7 +53,7 @@ namespace osu.Game.Database return Task.Run(async () => { - using (var stream = stableStorage.GetStream(database_name)) + using (var stream = storage.GetStream(database_name)) await Import(stream).ConfigureAwait(false); }); } diff --git a/osu.Game/Database/LegacyImportManager.cs b/osu.Game/Database/LegacyImportManager.cs index baa117fe07..96f4aaf67c 100644 --- a/osu.Game/Database/LegacyImportManager.cs +++ b/osu.Game/Database/LegacyImportManager.cs @@ -108,7 +108,7 @@ namespace osu.Game.Database importTasks.Add(new LegacySkinImporter(skins).ImportFromStableAsync(stableStorage)); if (content.HasFlagFast(StableContent.Collections)) - importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyCollectionImporter(realmAccess).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); + importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyCollectionImporter(realmAccess).ImportFromStorage(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); if (content.HasFlagFast(StableContent.Scores)) importTasks.Add(beatmapImportTask.ContinueWith(_ => new LegacyScoreImporter(scores).ImportFromStableAsync(stableStorage), TaskContinuationOptions.OnlyOnRanToCompletion)); diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index a93fdea35b..6a0d4d34db 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -14,6 +14,7 @@ using System.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Development; +using osu.Framework.Extensions; using osu.Framework.Input.Bindings; using osu.Framework.Logging; using osu.Framework.Platform; @@ -64,8 +65,9 @@ namespace osu.Game.Database /// 18 2022-07-19 Added OnlineMD5Hash and LastOnlineUpdate to BeatmapInfo. /// 19 2022-07-19 Added DateSubmitted and DateRanked to BeatmapSetInfo. /// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo, changed default value of BeatmapInfo.StarRating to -1. + /// 21 2022-07-27 Migrate collections to realm (BeatmapCollection). /// - private const int schema_version = 20; + private const int schema_version = 21; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. @@ -790,6 +792,27 @@ namespace osu.Game.Database beatmap.StarRating = -1; break; + + case 21: + try + { + // Migrate collections from external file to inside realm. + // We use the "legacy" importer because that is how things were actually being saved out until now. + var legacyCollectionImporter = new LegacyCollectionImporter(this); + + if (legacyCollectionImporter.GetAvailableCount(storage).GetResultSafely() > 0) + { + legacyCollectionImporter.ImportFromStorage(storage); + storage.Delete("collection.db"); + } + } + catch (Exception e) + { + // can be removed 20221027 (just for initial safety). + Logger.Error(e, "Collections could not be migrated to realm. Please provide your \"collection.db\" to the dev team."); + } + + break; } } From 226eefcc5c0a87d26962b4c22b68b2e53211535e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 13:37:01 +0900 Subject: [PATCH 372/481] Add note about hash storage --- osu.Game/Collections/BeatmapCollection.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Collections/BeatmapCollection.cs b/osu.Game/Collections/BeatmapCollection.cs index 2ffe17d9e6..ca5f8dbe53 100644 --- a/osu.Game/Collections/BeatmapCollection.cs +++ b/osu.Game/Collections/BeatmapCollection.cs @@ -26,6 +26,13 @@ namespace osu.Game.Collections /// /// The es of beatmaps contained by the collection. /// + /// + /// We store as hashes rather than references to s to allow collections to maintain + /// references to beatmaps even if they are removed. This helps with cases like importing collections before + /// importing the beatmaps they contain, or when sharing collections between users. + /// + /// This can probably change in the future as we build the system up. + /// public IList BeatmapMD5Hashes { get; } = null!; /// From ad482b8afcb23dedba8939b0ebaf7c960c4f5c25 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 13:48:15 +0900 Subject: [PATCH 373/481] Tidy up naming of collection dropdowns --- .../TestSceneManageCollectionsDialog.cs | 2 +- .../Navigation/TestSceneScreenNavigation.cs | 4 +-- .../SongSelect/TestSceneFilterControl.cs | 8 ++--- ...ilterDropdown.cs => CollectionDropdown.cs} | 33 +++++++++++-------- osu.Game/Overlays/Music/FilterControl.cs | 4 +-- ...own.cs => NowPlayingCollectionDropdown.cs} | 4 +-- osu.Game/Screens/Select/FilterControl.cs | 4 +-- 7 files changed, 33 insertions(+), 26 deletions(-) rename osu.Game/Collections/{CollectionFilterDropdown.cs => CollectionDropdown.cs} (89%) rename osu.Game/Overlays/Music/{CollectionDropdown.cs => NowPlayingCollectionDropdown.cs} (93%) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index afcb511a6a..6a88ce1ba6 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -149,7 +149,7 @@ namespace osu.Game.Tests.Visual.Collections { AddStep("add dropdown", () => { - Add(new CollectionFilterDropdown + Add(new CollectionDropdown { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index a61352f954..58898d8386 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -74,14 +74,14 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("set filter again", () => songSelect.ChildrenOfType().Single().Current.Value = "test"); AddStep("open collections dropdown", () => { - InputManager.MoveMouseTo(songSelect.ChildrenOfType().Single()); + InputManager.MoveMouseTo(songSelect.ChildrenOfType().Single()); InputManager.Click(MouseButton.Left); }); AddStep("press back once", () => InputManager.Click(MouseButton.Button1)); AddAssert("still at song select", () => Game.ScreenStack.CurrentScreen == songSelect); AddAssert("collections dropdown closed", () => songSelect - .ChildrenOfType().Single() + .ChildrenOfType().Single() .ChildrenOfType.DropdownMenu>().Single().State == MenuState.Closed); AddStep("press back a second time", () => InputManager.Click(MouseButton.Button1)); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index feb71def5d..a07bfaee2a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); AddStep("select collection", () => { - var dropdown = control.ChildrenOfType().Single(); + var dropdown = control.ChildrenOfType().Single(); dropdown.Current.Value = dropdown.ItemSource.ElementAt(1); }); @@ -210,7 +210,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) => AddAssert($"collection dropdown header displays '{collectionName}'", - () => shouldDisplay == (control.ChildrenOfType().Single().ChildrenOfType().First().Text == collectionName)); + () => shouldDisplay == (control.ChildrenOfType().Single().ChildrenOfType().First().Text == collectionName)); private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) => AddAssert($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'", @@ -222,7 +222,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void addExpandHeaderStep() => AddStep("expand header", () => { - InputManager.MoveMouseTo(control.ChildrenOfType().Single()); + InputManager.MoveMouseTo(control.ChildrenOfType().Single()); InputManager.Click(MouseButton.Left); }); @@ -233,6 +233,6 @@ namespace osu.Game.Tests.Visual.SongSelect }); private IEnumerable.DropdownMenu.DrawableDropdownMenuItem> getCollectionDropdownItems() - => control.ChildrenOfType().Single().ChildrenOfType.DropdownMenu.DrawableDropdownMenuItem>(); + => control.ChildrenOfType().Single().ChildrenOfType.DropdownMenu.DrawableDropdownMenuItem>(); } } diff --git a/osu.Game/Collections/CollectionFilterDropdown.cs b/osu.Game/Collections/CollectionDropdown.cs similarity index 89% rename from osu.Game/Collections/CollectionFilterDropdown.cs rename to osu.Game/Collections/CollectionDropdown.cs index 790a23d2e5..197e0d1837 100644 --- a/osu.Game/Collections/CollectionFilterDropdown.cs +++ b/osu.Game/Collections/CollectionDropdown.cs @@ -21,9 +21,9 @@ using Realms; namespace osu.Game.Collections { /// - /// A dropdown to select the to filter beatmaps using. + /// A dropdown to select the collection to be used to filter results. /// - public class CollectionFilterDropdown : OsuDropdown + public class CollectionDropdown : OsuDropdown { /// /// Whether to show the "manage collections..." menu item in the dropdown. @@ -40,7 +40,9 @@ namespace osu.Game.Collections [Resolved] private RealmAccess realm { get; set; } = null!; - public CollectionFilterDropdown() + private IDisposable? realmSubscription; + + public CollectionDropdown() { ItemSource = filters; @@ -51,7 +53,7 @@ namespace osu.Game.Collections { base.LoadComplete(); - realm.RegisterForNotifications(r => r.All(), collectionsChanged); + realmSubscription = realm.RegisterForNotifications(r => r.All(), collectionsChanged); Current.BindValueChanged(selectionChanged); } @@ -114,6 +116,12 @@ namespace osu.Game.Collections } } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + realmSubscription?.Dispose(); + } + protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName; protected sealed override DropdownHeader CreateHeader() => CreateCollectionHeader(); @@ -150,18 +158,19 @@ namespace osu.Game.Collections protected class CollectionDropdownDrawableMenuItem : OsuDropdownMenu.DrawableOsuDropdownMenuItem { - protected new CollectionFilterMenuItem Item => ((DropdownMenuItem)base.Item).Value; - private IconButton addOrRemoveButton = null!; private bool beatmapInCollection; + private readonly Live? collection; + [Resolved] private IBindable beatmap { get; set; } = null!; public CollectionDropdownDrawableMenuItem(MenuItem item) : base(item) { + collection = ((DropdownMenuItem)item).Value.Collection; } [BackgroundDependencyLoader] @@ -181,13 +190,11 @@ namespace osu.Game.Collections { base.LoadComplete(); - if (Item.Collection != null) + if (collection != null) { beatmap.BindValueChanged(_ => { - Debug.Assert(Item.Collection != null); - - beatmapInCollection = Item.Collection.PerformRead(c => c.BeatmapMD5Hashes.Contains(beatmap.Value.BeatmapInfo.MD5Hash)); + beatmapInCollection = collection.PerformRead(c => c.BeatmapMD5Hashes.Contains(beatmap.Value.BeatmapInfo.MD5Hash)); addOrRemoveButton.Enabled.Value = !beatmap.IsDefault; addOrRemoveButton.Icon = beatmapInCollection ? FontAwesome.Solid.MinusSquare : FontAwesome.Solid.PlusSquare; @@ -220,7 +227,7 @@ namespace osu.Game.Collections private void updateButtonVisibility() { - if (Item.Collection == null) + if (collection == null) addOrRemoveButton.Alpha = 0; else addOrRemoveButton.Alpha = IsHovered || IsPreSelected || beatmapInCollection ? 1 : 0; @@ -228,9 +235,9 @@ namespace osu.Game.Collections private void addOrRemove() { - Debug.Assert(Item.Collection != null); + Debug.Assert(collection != null); - Item.Collection.PerformWrite(c => + collection.PerformWrite(c => { if (!c.BeatmapMD5Hashes.Remove(beatmap.Value.BeatmapInfo.MD5Hash)) c.BeatmapMD5Hashes.Add(beatmap.Value.BeatmapInfo.MD5Hash); diff --git a/osu.Game/Overlays/Music/FilterControl.cs b/osu.Game/Overlays/Music/FilterControl.cs index eb12a62864..ffa50c3a35 100644 --- a/osu.Game/Overlays/Music/FilterControl.cs +++ b/osu.Game/Overlays/Music/FilterControl.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.Music public Action FilterChanged; public readonly FilterTextBox Search; - private readonly CollectionDropdown collectionDropdown; + private readonly NowPlayingCollectionDropdown collectionDropdown; public FilterControl() { @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Music RelativeSizeAxes = Axes.X, Height = 40, }, - collectionDropdown = new CollectionDropdown { RelativeSizeAxes = Axes.X } + collectionDropdown = new NowPlayingCollectionDropdown { RelativeSizeAxes = Axes.X } }, }, }; diff --git a/osu.Game/Overlays/Music/CollectionDropdown.cs b/osu.Game/Overlays/Music/NowPlayingCollectionDropdown.cs similarity index 93% rename from osu.Game/Overlays/Music/CollectionDropdown.cs rename to osu.Game/Overlays/Music/NowPlayingCollectionDropdown.cs index c1ba16788e..635a2e5044 100644 --- a/osu.Game/Overlays/Music/CollectionDropdown.cs +++ b/osu.Game/Overlays/Music/NowPlayingCollectionDropdown.cs @@ -15,9 +15,9 @@ using osu.Game.Graphics; namespace osu.Game.Overlays.Music { /// - /// A for use in the . + /// A for use in the . /// - public class CollectionDropdown : CollectionFilterDropdown + public class NowPlayingCollectionDropdown : CollectionDropdown { protected override bool ShowManageCollectionsItem => false; diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index f07817d5dc..ae82285fba 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Select private SeekLimitedSearchTextBox searchTextBox; - private CollectionFilterDropdown collectionDropdown; + private CollectionDropdown collectionDropdown; public FilterCriteria CreateCriteria() { @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.Both, Width = 0.48f, }, - collectionDropdown = new CollectionFilterDropdown + collectionDropdown = new CollectionDropdown { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, From da0646789120f33153946618d0ed2e414171bb17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 13:50:19 +0900 Subject: [PATCH 374/481] Add missing realm subscription cleanup --- osu.Game/Collections/DrawableCollectionList.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 1639afd362..8546ba53c2 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -20,13 +20,15 @@ namespace osu.Game.Collections /// public class DrawableCollectionList : OsuRearrangeableListContainer> { - private Scroll scroll = null!; - protected override ScrollContainer CreateScrollContainer() => scroll = new Scroll(); [Resolved] private RealmAccess realm { get; set; } = null!; + private Scroll scroll = null!; + + private IDisposable? realmSubscription; + protected override FillFlowContainer>> CreateListFillFlowContainer() => new Flow { DragActive = { BindTarget = DragActive } @@ -36,7 +38,7 @@ namespace osu.Game.Collections { base.LoadComplete(); - realm.RegisterForNotifications(r => r.All(), collectionsChanged); + realmSubscription = realm.RegisterForNotifications(r => r.All(), collectionsChanged); } private void collectionsChanged(IRealmCollection collections, ChangeSet? changes, Exception error) @@ -53,6 +55,12 @@ namespace osu.Game.Collections return new DrawableCollectionListItem(item, true); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + realmSubscription?.Dispose(); + } + /// /// The scroll container for this . /// Contains the main flow of and attaches a placeholder item to the end of the list. From 72961ec3362168e058f16c45f69e1e1f4c7bd5b8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 28 Jul 2022 08:04:53 +0300 Subject: [PATCH 375/481] Flip method parameters to make sense See https://github.com/ppy/osu/pull/19407/commits/537f64c75ecd9608b7f8f2b0e184dc18ba4bfa58#r931785228 --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 9e5d997353..c793ed4937 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -167,6 +167,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override void ApplyToBeatmap(IBeatmap beatmap) { Seed.Value ??= RNG.Next(); + var rng = new Random(Seed.Value.Value); var osuBeatmap = (OsuBeatmap)beatmap; @@ -185,11 +186,11 @@ namespace osu.Game.Rulesets.Osu.Mods return (OsuHitObject)newCircle; }).ToList(); - addHitSamples(originalHitObjects, hitObjects); + addHitSamples(hitObjects, originalHitObjects); - fixComboInfo(originalHitObjects, hitObjects); + fixComboInfo(hitObjects, originalHitObjects); - randomizeCirclePos(rng, hitObjects); + randomizeCirclePos(hitObjects, rng); osuBeatmap.HitObjects = hitObjects; @@ -224,7 +225,7 @@ namespace osu.Game.Rulesets.Osu.Mods return beats; } - private void addHitSamples(List originalHitObjects, IEnumerable hitObjects) + private void addHitSamples(IEnumerable hitObjects, List originalHitObjects) { foreach (var obj in hitObjects) { @@ -236,7 +237,7 @@ namespace osu.Game.Rulesets.Osu.Mods } } - private void fixComboInfo(List originalHitObjects, List hitObjects) + private void fixComboInfo(List hitObjects, List originalHitObjects) { // Copy combo indices from an original object at the same time or from the closest preceding object // (Objects lying between two combos are assumed to belong to the preceding combo) @@ -270,7 +271,7 @@ namespace osu.Game.Rulesets.Osu.Mods } } - private void randomizeCirclePos(Random rng, IReadOnlyList hitObjects) + private void randomizeCirclePos(IReadOnlyList hitObjects, Random rng) { if (hitObjects.Count == 0) return; From 392cb352cc71da8b0f82aa0877ea6a7febfc54b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 14:07:42 +0900 Subject: [PATCH 376/481] Force alphabetical ordering for now --- osu.Game/Collections/CollectionDropdown.cs | 2 +- osu.Game/Collections/DrawableCollectionList.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Collections/CollectionDropdown.cs b/osu.Game/Collections/CollectionDropdown.cs index 197e0d1837..43a4d90aa8 100644 --- a/osu.Game/Collections/CollectionDropdown.cs +++ b/osu.Game/Collections/CollectionDropdown.cs @@ -53,7 +53,7 @@ namespace osu.Game.Collections { base.LoadComplete(); - realmSubscription = realm.RegisterForNotifications(r => r.All(), collectionsChanged); + realmSubscription = realm.RegisterForNotifications(r => r.All().OrderBy(c => c.Name), collectionsChanged); Current.BindValueChanged(selectionChanged); } diff --git a/osu.Game/Collections/DrawableCollectionList.cs b/osu.Game/Collections/DrawableCollectionList.cs index 8546ba53c2..0f4362fff3 100644 --- a/osu.Game/Collections/DrawableCollectionList.cs +++ b/osu.Game/Collections/DrawableCollectionList.cs @@ -38,7 +38,7 @@ namespace osu.Game.Collections { base.LoadComplete(); - realmSubscription = realm.RegisterForNotifications(r => r.All(), collectionsChanged); + realmSubscription = realm.RegisterForNotifications(r => r.All().OrderBy(c => c.Name), collectionsChanged); } private void collectionsChanged(IRealmCollection collections, ChangeSet? changes, Exception error) From ca6857447345d4dc35aadeba1241f91b63dcc645 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 14:35:27 +0900 Subject: [PATCH 377/481] Make `NotificationOverlay` dependency optional in `CollectionSettings` --- .../Settings/Sections/Maintenance/CollectionsSettings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs index 498859db46..5a91213eb8 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/CollectionsSettings.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private RealmAccess realm { get; set; } = null!; [Resolved] - private INotificationOverlay notificationOverlay { get; set; } = null!; + private INotificationOverlay? notificationOverlay { get; set; } [BackgroundDependencyLoader] private void load(LegacyImportManager? legacyImportManager, IDialogOverlay? dialogOverlay) @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance private void deleteAllCollections() { realm.Write(r => r.RemoveAll()); - notificationOverlay.Post(new ProgressCompletionNotification { Text = "Deleted all collections!" }); + notificationOverlay?.Post(new ProgressCompletionNotification { Text = "Deleted all collections!" }); } } } From 2ae5a34c0e3ca9cca42b2880e5f9c3300a40a2d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 15:02:58 +0900 Subject: [PATCH 378/481] Add test coverage of beatmap updates transferring collection hashes --- .../Database/BeatmapImporterUpdateTests.cs | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index a82386fd51..90648c616e 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -9,6 +9,7 @@ using System.Linq.Expressions; using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Beatmaps; +using osu.Game.Collections; using osu.Game.Database; using osu.Game.Models; using osu.Game.Overlays.Notifications; @@ -400,6 +401,122 @@ namespace osu.Game.Tests.Database }); } + /// + /// If all difficulties in the original beatmap set are in a collection, presume the user also wants new difficulties added. + /// + [TestCase(false)] + [TestCase(true)] + public void TestCollectionTransferNewBeatmap(bool allOriginalBeatmapsInCollection) + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var rulesets = new RealmRulesetStore(realm, storage); + + using var __ = getBeatmapArchive(out string pathOriginal); + using var _ = getBeatmapArchiveWithModifications(out string pathMissingOneBeatmap, directory => + { + // remove one difficulty before first import + directory.GetFiles("*.osu").First().Delete(); + }); + + var importBeforeUpdate = await importer.Import(new ImportTask(pathMissingOneBeatmap)); + + Assert.That(importBeforeUpdate, Is.Not.Null); + Debug.Assert(importBeforeUpdate != null); + + int beatmapsToAddToCollection = 0; + + importBeforeUpdate.PerformWrite(s => + { + var beatmapCollection = s.Realm.Add(new BeatmapCollection("test collection")); + beatmapsToAddToCollection = s.Beatmaps.Count - (allOriginalBeatmapsInCollection ? 0 : 1); + + for (int i = 0; i < beatmapsToAddToCollection; i++) + beatmapCollection.BeatmapMD5Hashes.Add(s.Beatmaps[i].MD5Hash); + }); + + // Second import matches first but contains one extra .osu file. + var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginal), importBeforeUpdate.Value); + + Assert.That(importAfterUpdate, Is.Not.Null); + Debug.Assert(importAfterUpdate != null); + + importAfterUpdate.PerformRead(updated => + { + string[] hashes = updated.Realm.All().Single().BeatmapMD5Hashes.ToArray(); + + if (allOriginalBeatmapsInCollection) + { + Assert.That(updated.Beatmaps.Count, Is.EqualTo(beatmapsToAddToCollection + 1)); + Assert.That(hashes, Has.Length.EqualTo(updated.Beatmaps.Count)); + } + else + { + // Collection contains one less than the original beatmap, and two less after update (new difficulty included). + Assert.That(updated.Beatmaps.Count, Is.EqualTo(beatmapsToAddToCollection + 2)); + Assert.That(hashes, Has.Length.EqualTo(beatmapsToAddToCollection)); + } + }); + }); + } + + /// + /// If a difficulty in the original beatmap set is modified, the updated version should remain in any collections it was in. + /// + [Test] + public void TestCollectionTransferModifiedBeatmap() + { + RunTestWithRealmAsync(async (realm, storage) => + { + var importer = new BeatmapImporter(storage, realm); + using var rulesets = new RealmRulesetStore(realm, storage); + + using var __ = getBeatmapArchive(out string pathOriginal); + using var _ = getBeatmapArchiveWithModifications(out string pathModified, directory => + { + // Modify one .osu file with different content. + var firstOsuFile = directory.GetFiles("*[Hard]*.osu").First(); + + string existingContent = File.ReadAllText(firstOsuFile.FullName); + + File.WriteAllText(firstOsuFile.FullName, existingContent + "\n# I am new content"); + }); + + var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal)); + + Assert.That(importBeforeUpdate, Is.Not.Null); + Debug.Assert(importBeforeUpdate != null); + + string originalHash = string.Empty; + + importBeforeUpdate.PerformWrite(s => + { + var beatmapCollection = s.Realm.Add(new BeatmapCollection("test collection")); + originalHash = s.Beatmaps.Single(b => b.DifficultyName == "Hard").MD5Hash; + + beatmapCollection.BeatmapMD5Hashes.Add(originalHash); + }); + + // Second import matches first but contains one extra .osu file. + var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathModified), importBeforeUpdate.Value); + + Assert.That(importAfterUpdate, Is.Not.Null); + Debug.Assert(importAfterUpdate != null); + + importAfterUpdate.PerformRead(updated => + { + string[] hashes = updated.Realm.All().Single().BeatmapMD5Hashes.ToArray(); + string updatedHash = updated.Beatmaps.Single(b => b.DifficultyName == "Hard").MD5Hash; + + Assert.That(hashes, Has.Length.EqualTo(1)); + Assert.That(hashes.First(), Is.EqualTo(updatedHash)); + + Assert.That(updatedHash, Is.Not.EqualTo(originalHash)); + }); + }); + } + private static void checkCount(RealmAccess realm, int expected, Expression>? condition = null) where T : RealmObject { var query = realm.Realm.All(); From 2209afd0e8c82405701b8b6807d9a280cbf1aacc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 15:03:08 +0900 Subject: [PATCH 379/481] Mark `Live` methods as `InstantHandleAttribute` --- osu.Game/Database/Live.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/Live.cs b/osu.Game/Database/Live.cs index e9f99e1e44..52e1d420f7 100644 --- a/osu.Game/Database/Live.cs +++ b/osu.Game/Database/Live.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; namespace osu.Game.Database { @@ -18,19 +19,19 @@ namespace osu.Game.Database /// Perform a read operation on this live object. /// /// The action to perform. - public abstract void PerformRead(Action perform); + public abstract void PerformRead([InstantHandle] Action perform); /// /// Perform a read operation on this live object. /// /// The action to perform. - public abstract TReturn PerformRead(Func perform); + public abstract TReturn PerformRead([InstantHandle] Func perform); /// /// Perform a write operation on this live object. /// /// The action to perform. - public abstract void PerformWrite(Action perform); + public abstract void PerformWrite([InstantHandle] Action perform); /// /// Whether this instance is tracking data which is managed by the database backing. From 070f56c30ce0ce51b3d30781b36fca5bf64e52a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 15:03:23 +0900 Subject: [PATCH 380/481] Add collection transfer logic to beatmap import-as-update flow --- osu.Game/Beatmaps/BeatmapImporter.cs | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index ef0e76234a..d1f7a5c6a8 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -14,6 +14,7 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps.Formats; +using osu.Game.Collections; using osu.Game.Database; using osu.Game.Extensions; using osu.Game.IO; @@ -71,6 +72,8 @@ namespace osu.Game.Beatmaps // Transfer local values which should be persisted across a beatmap update. updated.DateAdded = original.DateAdded; + transferCollectionReferences(realm, original, updated); + foreach (var beatmap in original.Beatmaps.ToArray()) { var updatedBeatmap = updated.Beatmaps.FirstOrDefault(b => b.Hash == beatmap.Hash); @@ -112,6 +115,40 @@ namespace osu.Game.Beatmaps return first; } + private static void transferCollectionReferences(Realm realm, BeatmapSetInfo original, BeatmapSetInfo updated) + { + // First check if every beatmap in the original set is in any collections. + // In this case, we will assume they also want any newly added difficulties added to the collection. + foreach (var c in realm.All()) + { + if (original.Beatmaps.Select(b => b.MD5Hash).All(c.BeatmapMD5Hashes.Contains)) + { + foreach (var b in original.Beatmaps) + c.BeatmapMD5Hashes.Remove(b.MD5Hash); + + foreach (var b in updated.Beatmaps) + c.BeatmapMD5Hashes.Add(b.MD5Hash); + } + } + + // Handle collections using permissive difficulty name to track difficulties. + foreach (var originalBeatmap in original.Beatmaps) + { + var updatedBeatmap = updated.Beatmaps.FirstOrDefault(b => b.DifficultyName == originalBeatmap.DifficultyName); + + if (updatedBeatmap == null) + continue; + + var collections = realm.All().AsEnumerable().Where(c => c.BeatmapMD5Hashes.Contains(originalBeatmap.MD5Hash)); + + foreach (var c in collections) + { + c.BeatmapMD5Hashes.Remove(originalBeatmap.MD5Hash); + c.BeatmapMD5Hashes.Add(updatedBeatmap.MD5Hash); + } + } + } + protected override bool ShouldDeleteArchive(string path) => Path.GetExtension(path).ToLowerInvariant() == ".osz"; protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader? archive, Realm realm, CancellationToken cancellationToken = default) From 485d140a218fa92d5b800be9997104f71c831494 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 15:15:41 +0900 Subject: [PATCH 381/481] Add realm refresh calls to fix intermittent test failures on new update tests --- .../Database/BeatmapImporterUpdateTests.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index fdc9f2569d..81baeddbd7 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -51,6 +51,8 @@ namespace osu.Game.Tests.Database Assert.That(importBeforeUpdate, Is.Not.Null); Debug.Assert(importBeforeUpdate != null); + realm.Run(r => r.Refresh()); + checkCount(realm, 1, s => !s.DeletePending); Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps - 1)); @@ -60,6 +62,8 @@ namespace osu.Game.Tests.Database Assert.That(importAfterUpdate, Is.Not.Null); Debug.Assert(importAfterUpdate != null); + realm.Run(r => r.Refresh()); + checkCount(realm, count_beatmaps); checkCount(realm, count_beatmaps); checkCount(realm, 1); @@ -103,6 +107,8 @@ namespace osu.Game.Tests.Database beatmap.ResetOnlineInfo(); }); + realm.Run(r => r.Refresh()); + checkCount(realm, 1, s => !s.DeletePending); Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps - 1)); @@ -112,6 +118,8 @@ namespace osu.Game.Tests.Database Assert.That(importAfterUpdate, Is.Not.Null); Debug.Assert(importAfterUpdate != null); + realm.Run(r => r.Refresh()); + checkCount(realm, count_beatmaps); checkCount(realm, count_beatmaps); checkCount(realm, 1); @@ -148,6 +156,8 @@ namespace osu.Game.Tests.Database Assert.That(importBeforeUpdate, Is.Not.Null); Debug.Assert(importBeforeUpdate != null); + realm.Run(r => r.Refresh()); + checkCount(realm, 1, s => !s.DeletePending); Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps)); @@ -161,6 +171,8 @@ namespace osu.Game.Tests.Database Assert.That(importBeforeUpdate.Value.Beatmaps, Has.Count.EqualTo(1)); Assert.That(importAfterUpdate.Value.Beatmaps, Has.Count.EqualTo(count_beatmaps)); + realm.Run(r => r.Refresh()); + checkCount(realm, count_beatmaps + 1); checkCount(realm, count_beatmaps + 1); @@ -198,6 +210,8 @@ namespace osu.Game.Tests.Database Assert.That(importAfterUpdate, Is.Not.Null); Debug.Assert(importAfterUpdate != null); + realm.Run(r => r.Refresh()); + checkCount(realm, count_beatmaps); checkCount(realm, count_beatmaps); checkCount(realm, 2); @@ -234,6 +248,8 @@ namespace osu.Game.Tests.Database var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathEmpty), importBeforeUpdate.Value); Assert.That(importAfterUpdate, Is.Null); + realm.Run(r => r.Refresh()); + checkCount(realm, 1); checkCount(realm, count_beatmaps); checkCount(realm, count_beatmaps); @@ -263,6 +279,8 @@ namespace osu.Game.Tests.Database Assert.That(importAfterUpdate, Is.Not.Null); Debug.Assert(importAfterUpdate != null); + realm.Run(r => r.Refresh()); + checkCount(realm, 1); checkCount(realm, count_beatmaps); checkCount(realm, count_beatmaps); @@ -307,6 +325,8 @@ namespace osu.Game.Tests.Database s.Realm.Add(new ScoreInfo(beatmapInfo, s.Realm.All().First(), new RealmUser())); }); + realm.Run(r => r.Refresh()); + checkCount(realm, 1); var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathMissingOneBeatmap), importBeforeUpdate.Value); @@ -314,6 +334,8 @@ namespace osu.Game.Tests.Database Assert.That(importAfterUpdate, Is.Not.Null); Debug.Assert(importAfterUpdate != null); + realm.Run(r => r.Refresh()); + checkCount(realm, count_beatmaps); checkCount(realm, count_beatmaps); checkCount(realm, 2); @@ -348,6 +370,8 @@ namespace osu.Game.Tests.Database s.Realm.Add(new ScoreInfo(beatmapInfo, s.Realm.All().First(), new RealmUser())); }); + realm.Run(r => r.Refresh()); + checkCount(realm, 1); using var _ = getBeatmapArchiveWithModifications(out string pathModified, directory => @@ -365,6 +389,8 @@ namespace osu.Game.Tests.Database Assert.That(importAfterUpdate, Is.Not.Null); Debug.Assert(importAfterUpdate != null); + realm.Run(r => r.Refresh()); + checkCount(realm, count_beatmaps + 1); checkCount(realm, count_beatmaps + 1); checkCount(realm, 2); From 67c44552cb25f00ae7b46d87fb811956b5e4902b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 15:18:04 +0900 Subject: [PATCH 382/481] Add realm `Refresh` calls to ensure tests are stable --- osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index 90648c616e..07bd08d326 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -444,6 +444,8 @@ namespace osu.Game.Tests.Database importAfterUpdate.PerformRead(updated => { + updated.Realm.Refresh(); + string[] hashes = updated.Realm.All().Single().BeatmapMD5Hashes.ToArray(); if (allOriginalBeatmapsInCollection) @@ -506,6 +508,8 @@ namespace osu.Game.Tests.Database importAfterUpdate.PerformRead(updated => { + updated.Realm.Refresh(); + string[] hashes = updated.Realm.All().Single().BeatmapMD5Hashes.ToArray(); string updatedHash = updated.Beatmaps.Single(b => b.DifficultyName == "Hard").MD5Hash; From 8ac886a247c9cb1d25d8eba6c8048b6654561d2f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 15:20:25 +0900 Subject: [PATCH 383/481] Update test to account for sort order --- .../Visual/Collections/TestSceneManageCollectionsDialog.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 6a88ce1ba6..13393254a6 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -253,9 +253,14 @@ namespace osu.Game.Tests.Visual.Collections }); }); + assertCollectionName(0, "1"); + assertCollectionName(1, "1"); + AddStep("change first collection name", () => Realm.Write(_ => first.Name = "First")); - assertCollectionName(0, "First"); + // Item will have moved due to alphabetical sorting. + assertCollectionName(0, "2"); + assertCollectionName(1, "First"); } [Test] From 452d82f29239337af44a287e3b14d4e61730496f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 15:41:28 +0900 Subject: [PATCH 384/481] Add more comprehensive xmldoc for beatmap model classes --- osu.Game/Beatmaps/Beatmap.cs | 3 --- osu.Game/Beatmaps/BeatmapInfo.cs | 6 +++++- osu.Game/Beatmaps/BeatmapMetadata.cs | 11 +++++++++++ osu.Game/Beatmaps/BeatmapSetInfo.cs | 3 +++ osu.Game/Beatmaps/IBeatmap.cs | 7 +++++++ osu.Game/Beatmaps/IWorkingBeatmap.cs | 7 ++++++- 6 files changed, 32 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index c499bccb68..2d02fb6200 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -14,9 +14,6 @@ using osu.Game.IO.Serialization.Converters; namespace osu.Game.Beatmaps { - /// - /// A Beatmap containing converted HitObjects. - /// public class Beatmap : IBeatmap where T : HitObject { diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 66c1995c8b..f368f369ae 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -20,8 +20,12 @@ using Realms; namespace osu.Game.Beatmaps { /// - /// A single beatmap difficulty. + /// A realm model containing metadata for a single beatmap difficulty. + /// This should generally include anything which is required to be filtered on at song select, or anything pertaining to storage of beatmaps in the client. /// + /// + /// There are some legacy fields in this model which are not persisted to realm. These are isolated in a code region within the class and should eventually be migrated to `Beatmap`. + /// [ExcludeFromDynamicCompile] [Serializable] [MapTo("Beatmap")] diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index feb9d34f44..f645d914b1 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -12,6 +12,17 @@ using Realms; namespace osu.Game.Beatmaps { + /// + /// A realm model containing metadata for a beatmap. + /// + /// + /// This is currently stored against each beatmap difficulty, even when it is duplicated. + /// It is also provided via for convenience and historical purposes. + /// A future effort could see this converted to an or potentially de-duped + /// and shared across multiple difficulties in the same set, if required. + /// + /// Note that difficulty name is not stored in this metadata but in . + /// [ExcludeFromDynamicCompile] [Serializable] [MapTo("BeatmapMetadata")] diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index b404f0b34d..7f65d1291d 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -14,6 +14,9 @@ using Realms; namespace osu.Game.Beatmaps { + /// + /// A realm model containing metadata for a beatmap set (containing multiple ). + /// [ExcludeFromDynamicCompile] [MapTo("BeatmapSet")] public class BeatmapSetInfo : RealmObject, IHasGuidPrimaryKey, IHasRealmFiles, ISoftDelete, IEquatable, IBeatmapSetInfo diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 25b147c267..0e892b6581 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -11,6 +11,10 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Beatmaps { + /// + /// A materialised beatmap. + /// Generally this interface will be implemented alongside , which exposes the ruleset-typed hit objects. + /// public interface IBeatmap { /// @@ -65,6 +69,9 @@ namespace osu.Game.Beatmaps IBeatmap Clone(); } + /// + /// A materialised beatmap containing converted HitObjects. + /// public interface IBeatmap : IBeatmap where T : HitObject { diff --git a/osu.Game/Beatmaps/IWorkingBeatmap.cs b/osu.Game/Beatmaps/IWorkingBeatmap.cs index 2188bd6a2b..548341cc77 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmap.cs @@ -18,7 +18,12 @@ using osu.Game.Storyboards; namespace osu.Game.Beatmaps { /// - /// Provides access to the multiple resources offered by a beatmap model (textures, skins, playable beatmaps etc.) + /// A more expensive representation of a beatmap which allows access to various associated resources. + /// - Access textures and other resources via . + /// - Access the storyboard via . + /// - Access a local skin via . + /// - Access the track via (and then for subsequent accesses). + /// - Create a playable via . /// public interface IWorkingBeatmap { From 6bf293e130b98c35f7c896d8f5e174b0b436f04b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 28 Jul 2022 15:45:32 +0900 Subject: [PATCH 385/481] Fix managed object reused between test runs --- osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index a07bfaee2a..d3b3238bb0 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -78,9 +78,9 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestCollectionRemovedFromDropdown() { - var first = new BeatmapCollection(name: "1"); + BeatmapCollection first = null!; - AddStep("add collection", () => Realm.Write(r => r.Add(first))); + AddStep("add collection", () => Realm.Write(r => r.Add(first = new BeatmapCollection(name: "1")))); AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "2")))); AddStep("remove collection", () => Realm.Write(r => r.Remove(first))); From 525e4a20193a8f5d644595fecddea0f9ef516f97 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 15:51:18 +0900 Subject: [PATCH 386/481] Fix crash in `DrawableRoomPlaylistItem` context menu creation due to incorrect enumeration casting --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 492bb8ada5..8dccc3d82f 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -499,7 +499,7 @@ namespace osu.Game.Screens.OnlinePlay { if (beatmaps.QueryBeatmap(b => b.OnlineID == beatmap.OnlineID) is BeatmapInfo local && !local.BeatmapSet.AsNonNull().DeletePending) { - var collectionItems = realm.Realm.All().Select(c => new CollectionToggleMenuItem(c.ToLive(realm), beatmap)).Cast().ToList(); + var collectionItems = realm.Realm.All().AsEnumerable().Select(c => new CollectionToggleMenuItem(c.ToLive(realm), beatmap)).Cast().ToList(); if (manageCollectionsDialog != null) collectionItems.Add(new OsuMenuItem("Manage...", MenuItemType.Standard, manageCollectionsDialog.Show)); From c1aaf27c54bc2d996086c275c19556a7f163f9e5 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 28 Jul 2022 16:02:19 +0900 Subject: [PATCH 387/481] Link to correct model in xmldoc --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 7f65d1291d..d27d9d9192 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -15,7 +15,7 @@ using Realms; namespace osu.Game.Beatmaps { /// - /// A realm model containing metadata for a beatmap set (containing multiple ). + /// A realm model containing metadata for a beatmap set (containing multiple s). /// [ExcludeFromDynamicCompile] [MapTo("BeatmapSet")] From db62d4be3a006d8782de23ad39d50bf93da7cb04 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Thu, 28 Jul 2022 15:12:49 +0800 Subject: [PATCH 388/481] apply suggestions - refactor `SongProgress` - make`UpdateProgress` more readable - enable NRT on new classes - refactor `TestSceneSongProgress` to use `GameplayClockContainer` --- .../Visual/Gameplay/TestSceneSongProgress.cs | 89 +++++++------------ .../Screens/Play/HUD/DefaultSongProgress.cs | 11 ++- osu.Game/Screens/Play/HUD/SongProgress.cs | 47 ++++------ osu.Game/Skinning/LegacySongProgress.cs | 9 +- 4 files changed, 63 insertions(+), 93 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index 897284ed80..911b9bb7ec 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -4,11 +4,12 @@ #nullable disable using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Testing; -using osu.Framework.Timing; using osu.Game.Rulesets.Objects; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; @@ -19,44 +20,43 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public class TestSceneSongProgress : SkinnableHUDComponentTestScene { - private DefaultSongProgress defaultProgress; + private DefaultSongProgress progress => this.ChildrenOfType().Single(); + private GameplayClockContainer gameplayClockContainer; + private const double gameplay_start_time = -2000; - private readonly List progresses = new List(); - - private readonly StopwatchClock clock; - private readonly FramedClock framedClock; - - [Cached] - private readonly GameplayClock gameplayClock; - - public TestSceneSongProgress() + [BackgroundDependencyLoader] + private void load() { - clock = new StopwatchClock(); - gameplayClock = new GameplayClock(framedClock = new FramedClock(clock)); + var working = CreateWorkingBeatmap(Ruleset.Value); + working.LoadTrack(); + Add(gameplayClockContainer = new MasterGameplayClockContainer(working, gameplay_start_time)); + Dependencies.CacheAs(gameplayClockContainer); + Dependencies.CacheAs(gameplayClockContainer.GameplayClock); } [SetUpSteps] public void SetupSteps() { - AddStep("reset clock", clock.Reset); + AddStep("reset clock", () => gameplayClockContainer.Reset(false)); } [Test] public void TestDisplay() { AddStep("display max values", displayMaxValues); - AddStep("start", clock.Start); - AddStep("stop", clock.Stop); + AddStep("seek to intro", () => gameplayClockContainer.Seek(gameplay_start_time)); + AddStep("start", gameplayClockContainer.Start); + AddStep("stop", gameplayClockContainer.Stop); } [Test] public void TestToggleSeeking() { - AddStep("allow seeking", () => defaultProgress.AllowSeeking.Value = true); - AddStep("hide graph", () => defaultProgress.ShowGraph.Value = false); - AddStep("disallow seeking", () => defaultProgress.AllowSeeking.Value = false); - AddStep("allow seeking", () => defaultProgress.AllowSeeking.Value = true); - AddStep("show graph", () => defaultProgress.ShowGraph.Value = true); + AddStep("allow seeking", () => progress.AllowSeeking.Value = true); + AddStep("hide graph", () => progress.ShowGraph.Value = false); + AddStep("disallow seeking", () => progress.AllowSeeking.Value = false); + AddStep("allow seeking", () => progress.AllowSeeking.Value = true); + AddStep("show graph", () => progress.ShowGraph.Value = true); } private void displayMaxValues() @@ -65,48 +65,25 @@ namespace osu.Game.Tests.Visual.Gameplay for (double i = 0; i < 5000; i++) objects.Add(new HitObject { StartTime = i }); - replaceObjects(objects); + setObjects(objects); } - private void replaceObjects(List objects) + private void setObjects(List objects) { - defaultProgress.RequestSeek = pos => clock.Seek(pos); - - foreach (var p in progresses) - { - p.Objects = objects; - } + this.ChildrenOfType().ForEach(progress => progress.Objects = objects); } - protected override void Update() + protected override Drawable CreateDefaultImplementation() => new DefaultSongProgress { - base.Update(); - framedClock.ProcessFrame(); - } + RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }; - protected override Drawable CreateDefaultImplementation() + protected override Drawable CreateLegacyImplementation() => new LegacySongProgress { - defaultProgress = new DefaultSongProgress - { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - }; - - progresses.Add(defaultProgress); - return defaultProgress; - } - - protected override Drawable CreateLegacyImplementation() - { - var progress = new LegacySongProgress - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; - - progresses.Add(progress); - return progress; - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; } } diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 7c2d8a9de2..347bd797ac 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -49,6 +49,9 @@ namespace osu.Game.Screens.Play.HUD protected override bool BlockScrollInput => false; + [Resolved(canBeNull: true)] + private GameplayClock gameplayClock { get; set; } + [Resolved(canBeNull: true)] private Player player { get; set; } @@ -178,10 +181,12 @@ namespace osu.Game.Screens.Play.HUD bar.EndTime = LastHitTime; } - protected override void UpdateProgress(double progress, double time, bool isIntro) + protected override void UpdateProgress(double progress, bool isIntro) { - bar.CurrentTime = time; - graph.Progress = (int)(graph.ColumnCount * progress); + bar.CurrentTime = gameplayClock?.CurrentTime ?? Time.Current; + + if (!isIntro) + graph.Progress = (int)(graph.ColumnCount * progress); } protected override void Update() diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index 5c7c7d28c6..e0663b42ea 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -1,16 +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.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Skinning; @@ -21,20 +16,14 @@ namespace osu.Game.Screens.Play.HUD { public bool UsesFixedAnchor { get; set; } - [Resolved(canBeNull: true)] - private GameplayClock gameplayClock { get; set; } + [Resolved] + private GameplayClockContainer? gameplayClockContainer { get; set; } [Resolved(canBeNull: true)] - private GameplayClockContainer gameplayClockContainer { get; set; } + private DrawableRuleset? drawableRuleset { get; set; } - [Resolved(canBeNull: true)] - private DrawableRuleset drawableRuleset { get; set; } - - [Resolved(canBeNull: true)] - private IBindable beatmap { get; set; } - - private IClock referenceClock; - private IEnumerable objects; + private IClock? referenceClock; + private IEnumerable? objects; public IEnumerable Objects { @@ -46,9 +35,7 @@ namespace osu.Game.Screens.Play.HUD //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). protected double LastHitTime => objects.LastOrDefault()?.GetEndTime() ?? 0; - protected double FirstEventTime { get; private set; } - - protected abstract void UpdateProgress(double progress, double time, bool isIntro); + protected abstract void UpdateProgress(double progress, bool isIntro); protected abstract void UpdateObjects(IEnumerable objects); [BackgroundDependencyLoader] @@ -59,11 +46,6 @@ namespace osu.Game.Screens.Play.HUD Objects = drawableRuleset.Objects; referenceClock = drawableRuleset.FrameStableClock; } - - if (beatmap != null) - { - FirstEventTime = beatmap.Value.Storyboard.EarliestEventTime ?? 0; - } } protected override void Update() @@ -73,17 +55,22 @@ namespace osu.Game.Screens.Play.HUD if (objects == null) return; - double gameplayTime = gameplayClockContainer?.GameplayClock.CurrentTime ?? gameplayClock?.CurrentTime ?? Time.Current; - double frameStableTime = referenceClock?.CurrentTime ?? gameplayTime; + // The reference clock is used to accurately tell the playfield's time. This is obtained from the drawable ruleset. + // However, if no drawable ruleset is available (i.e. used in tests), we fall back to either the gameplay clock container or this drawable's own clock. + double gameplayTime = referenceClock?.CurrentTime ?? gameplayClockContainer?.GameplayClock.CurrentTime ?? Time.Current; - if (frameStableTime < FirstHitTime) + if (gameplayTime < FirstHitTime) { - double earliest = Math.Min(FirstEventTime, gameplayClockContainer?.StartTime ?? 0); - UpdateProgress((frameStableTime - earliest) / (FirstHitTime - earliest), gameplayTime, true); + double earliest = gameplayClockContainer?.StartTime ?? 0; + double introDuration = FirstHitTime - earliest; + double currentIntroTime = gameplayTime - earliest; + UpdateProgress(currentIntroTime / introDuration, true); } else { - UpdateProgress((frameStableTime - FirstHitTime) / (LastHitTime - FirstHitTime), gameplayTime, false); + double duration = LastHitTime - FirstHitTime; + double currentTime = gameplayTime - FirstHitTime; + UpdateProgress(currentTime / duration, false); } } } diff --git a/osu.Game/Skinning/LegacySongProgress.cs b/osu.Game/Skinning/LegacySongProgress.cs index 5f27d73761..ee071ad3ed 100644 --- a/osu.Game/Skinning/LegacySongProgress.cs +++ b/osu.Game/Skinning/LegacySongProgress.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -17,7 +15,7 @@ namespace osu.Game.Skinning { public class LegacySongProgress : SongProgress { - private CircularProgress pie; + private CircularProgress? pie; [BackgroundDependencyLoader] private void load() @@ -72,8 +70,11 @@ namespace osu.Game.Skinning { } - protected override void UpdateProgress(double progress, double time, bool isIntro) + protected override void UpdateProgress(double progress, bool isIntro) { + if (pie == null) + return; + if (isIntro) { pie.Scale = new Vector2(-1, 1); From 2b9d46d80392579086c0b26d77646d0f0e30cc10 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 16:19:05 +0900 Subject: [PATCH 389/481] Remove unused `RulesetStore` from `BeatmapManager` constructor --- osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs | 2 +- .../Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 2 +- .../Visual/Background/TestSceneUserDimBackgrounds.cs | 2 +- .../Visual/Collections/TestSceneManageCollectionsDialog.cs | 5 ++--- .../Visual/Gameplay/TestScenePlayerLocalScoreImport.cs | 2 +- osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs | 5 ++--- .../Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs | 5 ++--- osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs | 5 ++--- .../Multiplayer/TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../Multiplayer/TestSceneMultiplayerMatchSubScreen.cs | 5 ++--- .../Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs | 5 ++--- .../Visual/Multiplayer/TestSceneMultiplayerQueueList.cs | 5 ++--- .../Multiplayer/TestSceneMultiplayerSpectateButton.cs | 5 ++--- .../Visual/Multiplayer/TestScenePlaylistsSongSelect.cs | 6 ++---- osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs | 5 ++--- .../Visual/Playlists/TestScenePlaylistsRoomCreation.cs | 5 ++--- .../Visual/SongSelect/TestSceneBeatmapLeaderboard.cs | 2 +- osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs | 5 ++--- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 2 +- osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs | 2 +- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 5 ++--- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- 24 files changed, 37 insertions(+), 51 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs index 0348e47d4a..f14288e7ba 100644 --- a/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs +++ b/osu.Game.Tests/Beatmaps/WorkingBeatmapManagerTest.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Beatmaps [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio, RulesetStore rulesets) { - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); } [SetUpSteps] diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 536322805b..3f20f843a7 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -208,7 +208,7 @@ namespace osu.Game.Tests.Online public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) - : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap) + : base(storage, realm, api, audioManager, resources, host, defaultBeatmap) { } diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index aaccea09d4..5aadd6f56a 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Background private void load(GameHost host, AudioManager audio) { Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new OsuConfigManager(LocalStorage)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 3f30fa367c..8f2146dac5 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -30,7 +30,6 @@ namespace osu.Game.Tests.Visual.Collections private DialogOverlay dialogOverlay; private CollectionManager manager; - private RulesetStore rulesets; private BeatmapManager beatmapManager; private ManageCollectionsDialog dialog; @@ -38,8 +37,8 @@ namespace osu.Game.Tests.Visual.Collections [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, Audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs index 6491987abe..ddb585a73c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLocalScoreImport.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Gameplay private void load(GameHost host, AudioManager audio) { Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(new ScoreManager(rulesets, () => beatmaps, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 2b461cf6f6..ca4d926866 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -35,7 +35,6 @@ namespace osu.Game.Tests.Visual.Multiplayer protected IScreen CurrentSubScreen => multiplayerComponents.MultiplayerScreen.CurrentSubScreen; private BeatmapManager beatmaps; - private RulesetStore rulesets; private BeatmapSetInfo importedSet; private TestMultiplayerComponents multiplayerComponents; @@ -45,8 +44,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 1797c82fb9..73d1222156 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -38,13 +38,12 @@ namespace osu.Game.Tests.Visual.Multiplayer private TestPlaylist playlist; private BeatmapManager manager; - private RulesetStore rulesets; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index bf9b99e3e2..269867be73 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -49,7 +49,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneMultiplayer : ScreenTestScene { private BeatmapManager beatmaps = null!; - private RulesetStore rulesets = null!; private BeatmapSetInfo importedSet = null!; private TestMultiplayerComponents multiplayerComponents = null!; @@ -63,8 +62,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index ab4f9c37b2..2281235f25 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void load(GameHost host, AudioManager audio) { Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); importedBeatmapSet = manager.Import(TestResources.CreateTestBeatmapSetInfo(8, rulesets.AvailableRulesets.ToArray())); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 5d6a6c8104..5ebafbaabb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -40,7 +40,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private MultiplayerMatchSubScreen screen; private BeatmapManager beatmaps; - private RulesetStore rulesets; private BeatmapSetInfo importedSet; public TestSceneMultiplayerMatchSubScreen() @@ -51,8 +50,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 5ee385810b..75e6088b0d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -31,15 +31,14 @@ namespace osu.Game.Tests.Visual.Multiplayer { private MultiplayerPlaylist list; private BeatmapManager beatmaps; - private RulesetStore rulesets; private BeatmapSetInfo importedSet; private BeatmapInfo importedBeatmap; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index e709a955b3..f31261dc1f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -29,15 +29,14 @@ namespace osu.Game.Tests.Visual.Multiplayer { private MultiplayerQueueList playlist; private BeatmapManager beatmaps; - private RulesetStore rulesets; private BeatmapSetInfo importedSet; private BeatmapInfo importedBeatmap; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index 91c87548c7..e70c414bef 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -35,13 +35,12 @@ namespace osu.Game.Tests.Visual.Multiplayer private BeatmapSetInfo importedSet; private BeatmapManager beatmaps; - private RulesetStore rulesets; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 88afe1ce7c..2eddf1a17e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -28,15 +28,13 @@ namespace osu.Game.Tests.Visual.Multiplayer { private BeatmapManager manager; - private RulesetStore rulesets; - private TestPlaylistsSongSelect songSelect; [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); var beatmapSet = TestResources.CreateTestBeatmapSetInfo(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index d80537a2e5..ef2a431b8f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -30,7 +30,6 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneTeamVersus : ScreenTestScene { private BeatmapManager beatmaps; - private RulesetStore rulesets; private BeatmapSetInfo importedSet; private TestMultiplayerComponents multiplayerComponents; @@ -40,8 +39,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index c0cd2d9157..e798f72891 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -32,7 +32,6 @@ namespace osu.Game.Tests.Visual.Playlists public class TestScenePlaylistsRoomCreation : OnlinePlayTestScene { private BeatmapManager manager; - private RulesetStore rulesets; private TestPlaylistsRoomSubScreen match; @@ -41,8 +40,8 @@ namespace osu.Game.Tests.Visual.Playlists [BackgroundDependencyLoader] private void load(GameHost host, AudioManager audio) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, API, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, API, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index abcb888cd4..aeb30c94e1 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.SongSelect var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 6807180640..b25e0f2b34 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -30,7 +30,6 @@ namespace osu.Game.Tests.Visual.SongSelect private CollectionManager collectionManager; - private RulesetStore rulesets; private BeatmapManager beatmapManager; private FilterControl control; @@ -38,8 +37,8 @@ namespace osu.Game.Tests.Visual.SongSelect [BackgroundDependencyLoader] private void load(GameHost host) { - Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, Audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(new RealmRulesetStore(Realm)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, Audio, Resources, host, Beatmap.Default)); Dependencies.Cache(Realm); beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 159a3b1923..12f15b04dc 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.SongSelect // At a point we have isolated interactive test runs enough, this can likely be removed. Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(Realm); - Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, defaultBeatmap = Beatmap.Default)); Dependencies.Cache(music = new MusicController()); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs index 05b5c5c0cd..72d78ededb 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneTopLocalRank.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void load(GameHost host, AudioManager audio) { Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); - Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesets, null, audio, Resources, host, Beatmap.Default)); + Dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default)); Dependencies.Cache(scoreManager = new ScoreManager(rulesets, () => beatmapManager, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index e59914f69a..3beade9d4f 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -37,7 +37,6 @@ namespace osu.Game.Tests.Visual.UserInterface private readonly ContextMenuContainer contextMenuContainer; private readonly BeatmapLeaderboard leaderboard; - private RulesetStore rulesetStore; private BeatmapManager beatmapManager; private ScoreManager scoreManager; @@ -72,8 +71,8 @@ namespace osu.Game.Tests.Visual.UserInterface { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.Cache(rulesetStore = new RealmRulesetStore(Realm)); - dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); + dependencies.Cache(new RealmRulesetStore(Realm)); + dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, Realm, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(dependencies.Get(), () => beatmapManager, LocalStorage, Realm, Scheduler, API)); Dependencies.Cache(Realm); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index debe4c6829..5db9311e7b 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -44,7 +44,7 @@ namespace osu.Game.Beatmaps public Action? ProcessBeatmap { private get; set; } - public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, + public BeatmapManager(Storage storage, RealmAccess realm, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, BeatmapDifficultyCache? difficultyCache = null, bool performOnlineLookups = false) : base(storage, realm) { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7b6cda17a2..b399fdc175 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -271,7 +271,7 @@ namespace osu.Game // ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup() dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, () => BeatmapManager, Storage, realm, Scheduler, API, difficultyCache, LocalConfig)); - dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, RulesetStore, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true)); + dependencies.Cache(BeatmapManager = new BeatmapManager(Storage, realm, API, Audio, Resources, Host, defaultBeatmap, difficultyCache, performOnlineLookups: true)); dependencies.Cache(BeatmapDownloader = new BeatmapModelDownloader(BeatmapManager, API)); dependencies.Cache(ScoreDownloader = new ScoreModelDownloader(ScoreManager, API)); diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 31036247ab..ced72aa593 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -139,7 +139,7 @@ namespace osu.Game.Tests.Visual public WorkingBeatmap TestBeatmap; public TestBeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host, WorkingBeatmap defaultBeatmap) - : base(storage, realm, rulesets, api, audioManager, resources, host, defaultBeatmap) + : base(storage, realm, api, audioManager, resources, host, defaultBeatmap) { } From 70ed347b0624502761040067fd7e8fe18158bb6a Mon Sep 17 00:00:00 2001 From: Nitrous Date: Thu, 28 Jul 2022 15:19:35 +0800 Subject: [PATCH 390/481] simplify helper methods --- osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index 911b9bb7ec..4a786f2ebe 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -38,12 +38,12 @@ namespace osu.Game.Tests.Visual.Gameplay public void SetupSteps() { AddStep("reset clock", () => gameplayClockContainer.Reset(false)); + AddStep("set hit objects", setHitObjects); } [Test] public void TestDisplay() { - AddStep("display max values", displayMaxValues); AddStep("seek to intro", () => gameplayClockContainer.Seek(gameplay_start_time)); AddStep("start", gameplayClockContainer.Start); AddStep("stop", gameplayClockContainer.Stop); @@ -59,17 +59,12 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("show graph", () => progress.ShowGraph.Value = true); } - private void displayMaxValues() + private void setHitObjects() { var objects = new List(); for (double i = 0; i < 5000; i++) objects.Add(new HitObject { StartTime = i }); - setObjects(objects); - } - - private void setObjects(List objects) - { this.ChildrenOfType().ForEach(progress => progress.Objects = objects); } From bca3994d918a344cf9001dd1637564dfc94ba76f Mon Sep 17 00:00:00 2001 From: Nitrous Date: Thu, 28 Jul 2022 15:25:12 +0800 Subject: [PATCH 391/481] set `FirstHitTime` and `LastHitTime` once --- osu.Game/Screens/Play/HUD/SongProgress.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index e0663b42ea..55c85a1d91 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -27,13 +27,19 @@ namespace osu.Game.Screens.Play.HUD public IEnumerable Objects { - set => UpdateObjects(objects = value); + set + { + objects = value; + FirstHitTime = objects.FirstOrDefault()?.StartTime ?? 0; + LastHitTime = objects.LastOrDefault()?.GetEndTime() ?? 0; + UpdateObjects(objects); + } } - protected double FirstHitTime => objects.FirstOrDefault()?.StartTime ?? 0; + protected double FirstHitTime { get; private set; } //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). - protected double LastHitTime => objects.LastOrDefault()?.GetEndTime() ?? 0; + protected double LastHitTime { get; private set; } protected abstract void UpdateProgress(double progress, bool isIntro); protected abstract void UpdateObjects(IEnumerable objects); From 0d36907cad09b4d45305c1f2c3e342e570872252 Mon Sep 17 00:00:00 2001 From: Nitrous Date: Thu, 28 Jul 2022 15:30:45 +0800 Subject: [PATCH 392/481] apply code quality fixes --- osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs | 2 +- osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 4 +++- osu.Game/Screens/Play/HUD/SongProgress.cs | 6 +++--- osu.Game/Skinning/LegacySongProgress.cs | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index 4a786f2ebe..3f81757fcd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Gameplay [SetUpSteps] public void SetupSteps() { - AddStep("reset clock", () => gameplayClockContainer.Reset(false)); + AddStep("reset clock", () => gameplayClockContainer.Reset()); AddStep("set hit objects", setHitObjects); } diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 347bd797ac..654884c0d5 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -174,7 +174,9 @@ namespace osu.Game.Screens.Play.HUD protected override void UpdateObjects(IEnumerable objects) { - graph.Objects = objects; + if (objects != null) + graph.Objects = objects; + info.StartTime = FirstHitTime; info.EndTime = LastHitTime; bar.StartTime = FirstHitTime; diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index 55c85a1d91..78f0142dba 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -30,8 +30,8 @@ namespace osu.Game.Screens.Play.HUD set { objects = value; - FirstHitTime = objects.FirstOrDefault()?.StartTime ?? 0; - LastHitTime = objects.LastOrDefault()?.GetEndTime() ?? 0; + FirstHitTime = objects?.FirstOrDefault()?.StartTime ?? 0; + LastHitTime = objects?.LastOrDefault()?.GetEndTime() ?? 0; UpdateObjects(objects); } } @@ -42,7 +42,7 @@ namespace osu.Game.Screens.Play.HUD protected double LastHitTime { get; private set; } protected abstract void UpdateProgress(double progress, bool isIntro); - protected abstract void UpdateObjects(IEnumerable objects); + protected abstract void UpdateObjects(IEnumerable? objects); [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Skinning/LegacySongProgress.cs b/osu.Game/Skinning/LegacySongProgress.cs index ee071ad3ed..0d1110df47 100644 --- a/osu.Game/Skinning/LegacySongProgress.cs +++ b/osu.Game/Skinning/LegacySongProgress.cs @@ -66,7 +66,7 @@ namespace osu.Game.Skinning { } - protected override void UpdateObjects(IEnumerable objects) + protected override void UpdateObjects(IEnumerable? objects) { } From 17a3fd30fbf535cfb16cc8d22e65b9f16fc4724f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 16:08:27 +0900 Subject: [PATCH 393/481] Move scheduler from `OnlineLookupQueue` to `BeatmapUpdater` --- osu.Game/Beatmaps/BeatmapUpdater.cs | 22 ++++++++++----- ...eue.cs => BeatmapUpdaterMetadataLookup.cs} | 27 ++++--------------- 2 files changed, 21 insertions(+), 28 deletions(-) rename osu.Game/Beatmaps/{BeatmapOnlineLookupQueue.cs => BeatmapUpdaterMetadataLookup.cs} (85%) diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index d2c5e5616a..5ffe4ee291 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Framework.Threading; using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Rulesets.Objects; @@ -20,15 +21,21 @@ namespace osu.Game.Beatmaps public class BeatmapUpdater : IDisposable { private readonly IWorkingBeatmapCache workingBeatmapCache; - private readonly BeatmapOnlineLookupQueue onlineLookupQueue; + private readonly BeatmapDifficultyCache difficultyCache; + private readonly BeatmapUpdaterMetadataLookup metadataLookup; + + private const int update_queue_request_concurrency = 4; + + private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(update_queue_request_concurrency, nameof(BeatmapUpdaterMetadataLookup)); + public BeatmapUpdater(IWorkingBeatmapCache workingBeatmapCache, BeatmapDifficultyCache difficultyCache, IAPIProvider api, Storage storage) { this.workingBeatmapCache = workingBeatmapCache; this.difficultyCache = difficultyCache; - onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); + metadataLookup = new BeatmapUpdaterMetadataLookup(api, storage); } /// @@ -37,7 +44,7 @@ namespace osu.Game.Beatmaps public void Queue(Live beatmap) { Logger.Log($"Queueing change for local beatmap {beatmap}"); - Task.Factory.StartNew(() => beatmap.PerformRead(Process)); + Task.Factory.StartNew(() => beatmap.PerformRead(b => Process(b)), default, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); } /// @@ -50,7 +57,7 @@ namespace osu.Game.Beatmaps // TODO: this call currently uses the local `online.db` lookup. // We probably don't want this to happen after initial import (as the data may be stale). - onlineLookupQueue.Update(beatmapSet); + metadataLookup.Update(beatmapSet); foreach (var beatmap in beatmapSet.Beatmaps) { @@ -90,8 +97,11 @@ namespace osu.Game.Beatmaps public void Dispose() { - if (onlineLookupQueue.IsNotNull()) - onlineLookupQueue.Dispose(); + if (metadataLookup.IsNotNull()) + metadataLookup.Dispose(); + + if (updateScheduler.IsNotNull()) + updateScheduler.Dispose(); } #endregion diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs similarity index 85% rename from osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs rename to osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs index 6a3383cc92..94846599e8 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs @@ -6,8 +6,6 @@ using System; using System.Diagnostics; using System.IO; -using System.Linq; -using System.Threading; using System.Threading.Tasks; using Microsoft.Data.Sqlite; using osu.Framework.Development; @@ -15,7 +13,6 @@ using osu.Framework.IO.Network; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Testing; -using osu.Framework.Threading; using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -32,20 +29,16 @@ namespace osu.Game.Beatmaps /// This will always be checked before doing a second online query to get required metadata. /// [ExcludeFromDynamicCompile] - public class BeatmapOnlineLookupQueue : IDisposable + public class BeatmapUpdaterMetadataLookup : IDisposable { private readonly IAPIProvider api; private readonly Storage storage; - private const int update_queue_request_concurrency = 4; - - private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(update_queue_request_concurrency, nameof(BeatmapOnlineLookupQueue)); - private FileWebRequest cacheDownloadRequest; private const string cache_database_name = "online.db"; - public BeatmapOnlineLookupQueue(IAPIProvider api, Storage storage) + public BeatmapUpdaterMetadataLookup(IAPIProvider api, Storage storage) { this.api = api; this.storage = storage; @@ -61,15 +54,6 @@ namespace osu.Game.Beatmaps lookup(beatmapSet, b); } - public Task UpdateAsync(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken) - { - return Task.WhenAll(beatmapSet.Beatmaps.Select(b => UpdateAsync(beatmapSet, b, cancellationToken)).ToArray()); - } - - // todo: expose this when we need to do individual difficulty lookups. - protected Task UpdateAsync(BeatmapSetInfo beatmapSet, BeatmapInfo beatmapInfo, CancellationToken cancellationToken) - => Task.Factory.StartNew(() => lookup(beatmapSet, beatmapInfo), cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); - private void lookup(BeatmapSetInfo set, BeatmapInfo beatmapInfo) { if (checkLocalCache(set, beatmapInfo)) @@ -134,7 +118,7 @@ namespace osu.Game.Beatmaps File.Delete(compressedCacheFilePath); File.Delete(cacheFilePath); - Logger.Log($"{nameof(BeatmapOnlineLookupQueue)}'s online cache download failed: {ex}", LoggingTarget.Database); + Logger.Log($"{nameof(BeatmapUpdaterMetadataLookup)}'s online cache download failed: {ex}", LoggingTarget.Database); }; cacheDownloadRequest.Finished += () => @@ -151,7 +135,7 @@ namespace osu.Game.Beatmaps } catch (Exception ex) { - Logger.Log($"{nameof(BeatmapOnlineLookupQueue)}'s online cache extraction failed: {ex}", LoggingTarget.Database); + Logger.Log($"{nameof(BeatmapUpdaterMetadataLookup)}'s online cache extraction failed: {ex}", LoggingTarget.Database); File.Delete(cacheFilePath); } finally @@ -238,12 +222,11 @@ namespace osu.Game.Beatmaps } private void logForModel(BeatmapSetInfo set, string message) => - RealmArchiveModelImporter.LogForModel(set, $"[{nameof(BeatmapOnlineLookupQueue)}] {message}"); + RealmArchiveModelImporter.LogForModel(set, $"[{nameof(BeatmapUpdaterMetadataLookup)}] {message}"); public void Dispose() { cacheDownloadRequest?.Dispose(); - updateScheduler?.Dispose(); } } } From c35da6222469b366890960a89d4c33d253ebd916 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 16:18:30 +0900 Subject: [PATCH 394/481] Add flow for bypassing local cache lookups when refreshing beatmap metadata --- osu.Game/Beatmaps/BeatmapImporter.cs | 8 ++++---- osu.Game/Beatmaps/BeatmapManager.cs | 6 +++--- osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs | 2 +- osu.Game/Beatmaps/BeatmapUpdater.cs | 10 ++++------ osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs | 8 ++++---- osu.Game/Database/RealmArchiveModelImporter.cs | 5 +++-- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Scoring/ScoreImporter.cs | 4 ++-- 8 files changed, 22 insertions(+), 23 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index ef0e76234a..df8b18313c 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps protected override string[] HashableFileTypes => new[] { ".osu" }; - public Action? ProcessBeatmap { private get; set; } + public Action? ProcessBeatmap { private get; set; } public BeatmapImporter(Storage storage, RealmAccess realm) : base(storage, realm) @@ -168,11 +168,11 @@ namespace osu.Game.Beatmaps } } - protected override void PostImport(BeatmapSetInfo model, Realm realm) + protected override void PostImport(BeatmapSetInfo model, Realm realm, bool batchImport) { - base.PostImport(model, realm); + base.PostImport(model, realm, batchImport); - ProcessBeatmap?.Invoke(model); + ProcessBeatmap?.Invoke(model, batchImport); } private void validateOnlineIds(BeatmapSetInfo beatmapSet, Realm realm) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index debe4c6829..abf3d43d94 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -42,7 +42,7 @@ namespace osu.Game.Beatmaps private readonly WorkingBeatmapCache workingBeatmapCache; - public Action? ProcessBeatmap { private get; set; } + public Action? ProcessBeatmap { private get; set; } public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, BeatmapDifficultyCache? difficultyCache = null, bool performOnlineLookups = false) @@ -62,7 +62,7 @@ namespace osu.Game.Beatmaps BeatmapTrackStore = audioManager.GetTrackStore(userResources); beatmapImporter = CreateBeatmapImporter(storage, realm); - beatmapImporter.ProcessBeatmap = obj => ProcessBeatmap?.Invoke(obj); + beatmapImporter.ProcessBeatmap = (obj, isBatch) => ProcessBeatmap?.Invoke(obj, isBatch); beatmapImporter.PostNotification = obj => PostNotification?.Invoke(obj); workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host); @@ -323,7 +323,7 @@ namespace osu.Game.Beatmaps setInfo.CopyChangesToRealm(liveBeatmapSet); - ProcessBeatmap?.Invoke(liveBeatmapSet); + ProcessBeatmap?.Invoke(liveBeatmapSet, false); }); } diff --git a/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs b/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs index b6968f4e06..5d0765641b 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineChangeIngest.cs @@ -36,7 +36,7 @@ namespace osu.Game.Beatmaps var matchingSet = r.All().FirstOrDefault(s => s.OnlineID == id); if (matchingSet != null) - beatmapUpdater.Queue(matchingSet.ToLive(realm)); + beatmapUpdater.Queue(matchingSet.ToLive(realm), true); } }); } diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index 5ffe4ee291..a86aab4ac1 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -41,23 +41,21 @@ namespace osu.Game.Beatmaps /// /// Queue a beatmap for background processing. /// - public void Queue(Live beatmap) + public void Queue(Live beatmap, bool forceOnlineFetch = false) { Logger.Log($"Queueing change for local beatmap {beatmap}"); - Task.Factory.StartNew(() => beatmap.PerformRead(b => Process(b)), default, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); + Task.Factory.StartNew(() => beatmap.PerformRead(b => Process(b, forceOnlineFetch)), default, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); } /// /// Run all processing on a beatmap immediately. /// - public void Process(BeatmapSetInfo beatmapSet) => beatmapSet.Realm.Write(r => + public void Process(BeatmapSetInfo beatmapSet, bool forceOnlineFetch = false) => beatmapSet.Realm.Write(r => { // Before we use below, we want to invalidate. workingBeatmapCache.Invalidate(beatmapSet); - // TODO: this call currently uses the local `online.db` lookup. - // We probably don't want this to happen after initial import (as the data may be stale). - metadataLookup.Update(beatmapSet); + metadataLookup.Update(beatmapSet, forceOnlineFetch); foreach (var beatmap in beatmapSet.Beatmaps) { diff --git a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs index 94846599e8..c7c8f0ceb0 100644 --- a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs +++ b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs @@ -48,15 +48,15 @@ namespace osu.Game.Beatmaps prepareLocalCache(); } - public void Update(BeatmapSetInfo beatmapSet) + public void Update(BeatmapSetInfo beatmapSet, bool forceOnlineFetch) { foreach (var b in beatmapSet.Beatmaps) - lookup(beatmapSet, b); + lookup(beatmapSet, b, forceOnlineFetch); } - private void lookup(BeatmapSetInfo set, BeatmapInfo beatmapInfo) + private void lookup(BeatmapSetInfo set, BeatmapInfo beatmapInfo, bool forceOnlineFetch) { - if (checkLocalCache(set, beatmapInfo)) + if (!forceOnlineFetch && checkLocalCache(set, beatmapInfo)) return; if (api?.State.Value != APIState.Online) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index a0cf98b978..b340d0ee4b 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -340,7 +340,7 @@ namespace osu.Game.Database // import to store realm.Add(item); - PostImport(item, realm); + PostImport(item, realm, batchImport); transaction.Commit(); } @@ -485,7 +485,8 @@ namespace osu.Game.Database /// /// The model prepared for import. /// The current realm context. - protected virtual void PostImport(TModel model, Realm realm) + /// Whether the import was part of a batch. + protected virtual void PostImport(TModel model, Realm realm, bool batchImport) { } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7b6cda17a2..6514fc6aee 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -287,7 +287,7 @@ namespace osu.Game AddInternal(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient)); - BeatmapManager.ProcessBeatmap = set => beatmapUpdater.Process(set); + BeatmapManager.ProcessBeatmap = (set, isBatch) => beatmapUpdater.Process(set, forceOnlineFetch: !isBatch); dependencies.Cache(userCache = new UserLookupCache()); AddInternal(userCache); diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 4107c66dfe..0902f1636b 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -75,9 +75,9 @@ namespace osu.Game.Scoring model.StatisticsJson = JsonConvert.SerializeObject(model.Statistics); } - protected override void PostImport(ScoreInfo model, Realm realm) + protected override void PostImport(ScoreInfo model, Realm realm, bool batchImport) { - base.PostImport(model, realm); + base.PostImport(model, realm, batchImport); var userRequest = new GetUserRequest(model.RealmUser.Username); From cd01c5d3acefc5678be6dd6f7e3653acdbce8fc7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 28 Jul 2022 16:34:31 +0900 Subject: [PATCH 395/481] Fix assertion --- .../Visual/Collections/TestSceneManageCollectionsDialog.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs index 13393254a6..4c89fc1ab9 100644 --- a/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs +++ b/osu.Game.Tests/Visual/Collections/TestSceneManageCollectionsDialog.cs @@ -254,7 +254,7 @@ namespace osu.Game.Tests.Visual.Collections }); assertCollectionName(0, "1"); - assertCollectionName(1, "1"); + assertCollectionName(1, "2"); AddStep("change first collection name", () => Realm.Write(_ => first.Name = "First")); From 6d4023b933dd97a65b9f3abe977f1671de8b91d0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 28 Jul 2022 16:56:11 +0900 Subject: [PATCH 396/481] Adjust comment --- osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs index 07bd08d326..76339d4a1c 100644 --- a/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterUpdateTests.cs @@ -500,7 +500,7 @@ namespace osu.Game.Tests.Database beatmapCollection.BeatmapMD5Hashes.Add(originalHash); }); - // Second import matches first but contains one extra .osu file. + // Second import matches first but contains a modified .osu file. var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathModified), importBeforeUpdate.Value); Assert.That(importAfterUpdate, Is.Not.Null); From 8cb4fb35e005b414fd5055dd080e6f74313cf957 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 16:55:46 +0900 Subject: [PATCH 397/481] Rename parameter to read better (and still use local cache if no online API is available) --- osu.Game/Beatmaps/BeatmapUpdater.cs | 14 +++++++++----- .../Beatmaps/BeatmapUpdaterMetadataLookup.cs | 19 ++++++++++++++----- osu.Game/OsuGameBase.cs | 2 +- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapUpdater.cs b/osu.Game/Beatmaps/BeatmapUpdater.cs index a86aab4ac1..d7b1fac7b3 100644 --- a/osu.Game/Beatmaps/BeatmapUpdater.cs +++ b/osu.Game/Beatmaps/BeatmapUpdater.cs @@ -41,21 +41,25 @@ namespace osu.Game.Beatmaps /// /// Queue a beatmap for background processing. /// - public void Queue(Live beatmap, bool forceOnlineFetch = false) + /// The managed beatmap set to update. A transaction will be opened to apply changes. + /// Whether metadata from an online source should be preferred. If true, the local cache will be skipped to ensure the freshest data state possible. + public void Queue(Live beatmapSet, bool preferOnlineFetch = false) { - Logger.Log($"Queueing change for local beatmap {beatmap}"); - Task.Factory.StartNew(() => beatmap.PerformRead(b => Process(b, forceOnlineFetch)), default, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); + Logger.Log($"Queueing change for local beatmap {beatmapSet}"); + Task.Factory.StartNew(() => beatmapSet.PerformRead(b => Process(b, preferOnlineFetch)), default, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); } /// /// Run all processing on a beatmap immediately. /// - public void Process(BeatmapSetInfo beatmapSet, bool forceOnlineFetch = false) => beatmapSet.Realm.Write(r => + /// The managed beatmap set to update. A transaction will be opened to apply changes. + /// Whether metadata from an online source should be preferred. If true, the local cache will be skipped to ensure the freshest data state possible. + public void Process(BeatmapSetInfo beatmapSet, bool preferOnlineFetch = false) => beatmapSet.Realm.Write(r => { // Before we use below, we want to invalidate. workingBeatmapCache.Invalidate(beatmapSet); - metadataLookup.Update(beatmapSet, forceOnlineFetch); + metadataLookup.Update(beatmapSet, preferOnlineFetch); foreach (var beatmap in beatmapSet.Beatmaps) { diff --git a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs index c7c8f0ceb0..02fb69b8f5 100644 --- a/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs +++ b/osu.Game/Beatmaps/BeatmapUpdaterMetadataLookup.cs @@ -48,18 +48,27 @@ namespace osu.Game.Beatmaps prepareLocalCache(); } - public void Update(BeatmapSetInfo beatmapSet, bool forceOnlineFetch) + /// + /// Queue an update for a beatmap set. + /// + /// The beatmap set to update. Updates will be applied directly (so a transaction should be started if this instance is managed). + /// Whether metadata from an online source should be preferred. If true, the local cache will be skipped to ensure the freshest data state possible. + public void Update(BeatmapSetInfo beatmapSet, bool preferOnlineFetch) { foreach (var b in beatmapSet.Beatmaps) - lookup(beatmapSet, b, forceOnlineFetch); + lookup(beatmapSet, b, preferOnlineFetch); } - private void lookup(BeatmapSetInfo set, BeatmapInfo beatmapInfo, bool forceOnlineFetch) + private void lookup(BeatmapSetInfo set, BeatmapInfo beatmapInfo, bool preferOnlineFetch) { - if (!forceOnlineFetch && checkLocalCache(set, beatmapInfo)) + bool apiAvailable = api?.State.Value == APIState.Online; + + bool useLocalCache = !apiAvailable || !preferOnlineFetch; + + if (useLocalCache && checkLocalCache(set, beatmapInfo)) return; - if (api?.State.Value != APIState.Online) + if (!apiAvailable) return; var req = new GetBeatmapRequest(beatmapInfo); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 6514fc6aee..41eeece76d 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -287,7 +287,7 @@ namespace osu.Game AddInternal(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient)); - BeatmapManager.ProcessBeatmap = (set, isBatch) => beatmapUpdater.Process(set, forceOnlineFetch: !isBatch); + BeatmapManager.ProcessBeatmap = (set, isBatch) => beatmapUpdater.Process(set, preferOnlineFetch: !isBatch); dependencies.Cache(userCache = new UserLookupCache()); AddInternal(userCache); From 628a30193ff7b94c10815d1c8d8be7db7b1e4187 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 17:49:17 +0900 Subject: [PATCH 398/481] Remove incorrect `TrackLoaded` override from `TestWorkingBeatmap` --- osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs index 2306fd1c3e..3d7ebad831 100644 --- a/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestWorkingBeatmap.cs @@ -31,8 +31,6 @@ namespace osu.Game.Tests.Beatmaps this.storyboard = storyboard; } - public override bool TrackLoaded => true; - public override bool BeatmapLoaded => true; protected override IBeatmap GetBeatmap() => beatmap; From 1039338d804dd33faf12ec8bc96b361cad280de4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 28 Jul 2022 17:58:07 +0900 Subject: [PATCH 399/481] Fix intermittent HUD tests --- osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs | 1 + osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs | 3 +++ 2 files changed, 4 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs index dd0f965914..fb97f94dbb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneHUDOverlay.cs @@ -159,6 +159,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded); AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType().Single().Alpha == 0); + AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType().All(c => c.ComponentsLoaded)); AddStep("bind on update", () => { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index f319290441..bd274dfef5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -14,6 +14,7 @@ using osu.Game.Overlays.Settings; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play.HUD.HitErrorMeters; +using osu.Game.Skinning; using osu.Game.Skinning.Editor; using osuTK.Input; @@ -33,6 +34,8 @@ namespace osu.Game.Tests.Visual.Gameplay { base.SetUpSteps(); + AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded)); + AddStep("reload skin editor", () => { skinEditor?.Expire(); From a21aee4e9cabcbb71fb74e0185c0cc62f3de737d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 17:58:13 +0900 Subject: [PATCH 400/481] Reduce calls to `LoadTrack` by implicitly running on test/dummy classes --- .../Gameplay/TestSceneMasterGameplayClockContainer.cs | 6 ------ osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | 2 -- osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs | 7 +++++-- osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs | 1 - osu.Game/Beatmaps/DummyWorkingBeatmap.cs | 4 ++++ osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs | 1 - osu.Game/Tests/Visual/OsuTestScene.cs | 5 +++++ 7 files changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs index 0395ae9d99..5f403f9487 100644 --- a/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneMasterGameplayClockContainer.cs @@ -41,8 +41,6 @@ namespace osu.Game.Tests.Gameplay AddStep("create container", () => { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - working.LoadTrack(); - Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0); }); @@ -58,8 +56,6 @@ namespace osu.Game.Tests.Gameplay AddStep("create container", () => { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - working.LoadTrack(); - Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0); }); @@ -102,8 +98,6 @@ namespace osu.Game.Tests.Gameplay AddStep("create container", () => { working = new ClockBackedTestWorkingBeatmap(new OsuRuleset().RulesetInfo, new FramedClock(new ManualClock()), Audio); - working.LoadTrack(); - Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0); gameplayClockContainer.Reset(startClock: !whileStopped); diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index a9c6bacc65..a432cc9648 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -69,7 +69,6 @@ namespace osu.Game.Tests.Gameplay AddStep("create container", () => { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - working.LoadTrack(); Add(gameplayContainer = new MasterGameplayClockContainer(working, 0) { @@ -96,7 +95,6 @@ namespace osu.Game.Tests.Gameplay AddStep("create container", () => { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - working.LoadTrack(); const double start_time = 1000; diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index f4cea2c8cc..e82a5b57d9 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -32,7 +32,6 @@ namespace osu.Game.Tests.Skins imported?.PerformRead(s => { beatmap = beatmaps.GetWorkingBeatmap(s.Beatmaps[0]); - beatmap.LoadTrack(); }); } @@ -40,6 +39,10 @@ namespace osu.Game.Tests.Skins public void TestRetrieveOggSample() => AddAssert("sample is non-null", () => beatmap.Skin.GetSample(new SampleInfo("sample")) != null); [Test] - public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => !(beatmap.Track is TrackVirtual)); + public void TestRetrieveOggTrack() => AddAssert("track is non-null", () => + { + using (var track = beatmap.LoadTrack()) + return track is not TrackVirtual; + }); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs index e1fc65404d..5c73db15df 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkipOverlay.cs @@ -33,7 +33,6 @@ namespace osu.Game.Tests.Visual.Gameplay increment = skip_time; var working = CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); - working.LoadTrack(); Child = gameplayClockContainer = new MasterGameplayClockContainer(working, 0) { diff --git a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs index 9610dbcc78..0b390a2ab5 100644 --- a/osu.Game/Beatmaps/DummyWorkingBeatmap.cs +++ b/osu.Game/Beatmaps/DummyWorkingBeatmap.cs @@ -44,6 +44,10 @@ namespace osu.Game.Beatmaps }, audio) { this.textures = textures; + + // We are guaranteed to have a virtual track. + // To ease usability, ensure the track is available from point of construction. + LoadTrack(); } protected override IBeatmap GetBeatmap() => new Beatmap(); diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs index 58bfced3ed..0d4496a6a3 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs @@ -161,7 +161,6 @@ namespace osu.Game.Overlays.FirstRunSetup private void load(AudioManager audio, TextureStore textures, RulesetStore rulesets) { Beatmap.Value = new DummyWorkingBeatmap(audio, textures); - Beatmap.Value.LoadTrack(); Ruleset.Value = rulesets.AvailableRulesets.First(); diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 012c512266..5a297fd109 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -365,6 +365,11 @@ namespace osu.Game.Tests.Visual } else track = audio?.Tracks.GetVirtual(trackLength); + + // We are guaranteed to have a virtual track. + // To ease testability, ensure the track is available from point of construction. + // (Usually this would be done by MusicController for us). + LoadTrack(); } ~ClockBackedTestWorkingBeatmap() From 71085538833a34e5eeea9b8087a531008394608a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 18:20:08 +0900 Subject: [PATCH 401/481] Tidy up various things everywhere --- .../Visual/Gameplay/TestSceneSongProgress.cs | 35 ++++++++++--------- .../Screens/Play/HUD/DefaultSongProgress.cs | 2 -- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index 3f81757fcd..5e9cf8839d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -11,6 +9,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; @@ -20,18 +19,20 @@ namespace osu.Game.Tests.Visual.Gameplay [TestFixture] public class TestSceneSongProgress : SkinnableHUDComponentTestScene { - private DefaultSongProgress progress => this.ChildrenOfType().Single(); - private GameplayClockContainer gameplayClockContainer; - private const double gameplay_start_time = -2000; + private GameplayClockContainer gameplayClockContainer = null!; + + private const double skip_target_time = -2000; [BackgroundDependencyLoader] private void load() { - var working = CreateWorkingBeatmap(Ruleset.Value); - working.LoadTrack(); - Add(gameplayClockContainer = new MasterGameplayClockContainer(working, gameplay_start_time)); - Dependencies.CacheAs(gameplayClockContainer); - Dependencies.CacheAs(gameplayClockContainer.GameplayClock); + Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + Beatmap.Value.LoadTrack(); + + Add(gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, skip_target_time)); + + Dependencies.CacheAs(gameplayClockContainer); // required for StartTime + Dependencies.CacheAs(gameplayClockContainer.GameplayClock); // required for everything else } [SetUpSteps] @@ -44,7 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestDisplay() { - AddStep("seek to intro", () => gameplayClockContainer.Seek(gameplay_start_time)); + AddStep("seek to intro", () => gameplayClockContainer.Seek(skip_target_time)); AddStep("start", gameplayClockContainer.Start); AddStep("stop", gameplayClockContainer.Stop); } @@ -52,11 +53,13 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestToggleSeeking() { - AddStep("allow seeking", () => progress.AllowSeeking.Value = true); - AddStep("hide graph", () => progress.ShowGraph.Value = false); - AddStep("disallow seeking", () => progress.AllowSeeking.Value = false); - AddStep("allow seeking", () => progress.AllowSeeking.Value = true); - AddStep("show graph", () => progress.ShowGraph.Value = true); + DefaultSongProgress getDefaultProgress() => this.ChildrenOfType().Single(); + + AddStep("allow seeking", () => getDefaultProgress().AllowSeeking.Value = true); + AddStep("hide graph", () => getDefaultProgress().ShowGraph.Value = false); + AddStep("disallow seeking", () => getDefaultProgress().AllowSeeking.Value = false); + AddStep("allow seeking", () => getDefaultProgress().AllowSeeking.Value = true); + AddStep("show graph", () => getDefaultProgress().ShowGraph.Value = true); } private void setHitObjects() diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 654884c0d5..ac184c6407 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -19,8 +19,6 @@ namespace osu.Game.Screens.Play.HUD { public class DefaultSongProgress : SongProgress { - public const float MAX_HEIGHT = info_height + bottom_bar_height + graph_height + handle_height; - private const float info_height = 20; private const float bottom_bar_height = 5; private const float graph_height = SquareGraph.Column.WIDTH * 6; From d5e5761892138216f2d55ff0e6b6bc6dcaec8a59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 18:25:24 +0900 Subject: [PATCH 402/481] Fix `DefaultSongProgress` graph not resetting if time is in intro --- osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index ac184c6407..36b172cb44 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -185,7 +185,9 @@ namespace osu.Game.Screens.Play.HUD { bar.CurrentTime = gameplayClock?.CurrentTime ?? Time.Current; - if (!isIntro) + if (isIntro) + graph.Progress = 0; + else graph.Progress = (int)(graph.ColumnCount * progress); } From bfa026879c342fa1c44c46ac6cae6d5a83551252 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 18:28:03 +0900 Subject: [PATCH 403/481] Remove pointless null check --- osu.Game/Skinning/LegacySongProgress.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Skinning/LegacySongProgress.cs b/osu.Game/Skinning/LegacySongProgress.cs index 0d1110df47..963209d4ce 100644 --- a/osu.Game/Skinning/LegacySongProgress.cs +++ b/osu.Game/Skinning/LegacySongProgress.cs @@ -15,7 +15,7 @@ namespace osu.Game.Skinning { public class LegacySongProgress : SongProgress { - private CircularProgress? pie; + private CircularProgress pie = null!; [BackgroundDependencyLoader] private void load() @@ -72,9 +72,6 @@ namespace osu.Game.Skinning protected override void UpdateProgress(double progress, bool isIntro) { - if (pie == null) - return; - if (isIntro) { pie.Scale = new Vector2(-1, 1); From ea027eda46ebdb8ed8b13674fcbbdca3b96360c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 18:29:49 +0900 Subject: [PATCH 404/481] Move initial show to base implementation and add transition for legacy version --- osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 2 -- osu.Game/Screens/Play/HUD/SongProgress.cs | 9 ++++++++- osu.Game/Skinning/LegacySongProgress.cs | 8 ++------ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 36b172cb44..b4eade0709 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -104,8 +104,6 @@ namespace osu.Game.Screens.Play.HUD protected override void LoadComplete() { - Show(); - AllowSeeking.BindValueChanged(_ => updateBarVisibility(), true); ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true); diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index 78f0142dba..35847b4b16 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -36,13 +36,20 @@ namespace osu.Game.Screens.Play.HUD } } + protected override void LoadComplete() + { + base.LoadComplete(); + + Show(); + } + protected double FirstHitTime { get; private set; } //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). protected double LastHitTime { get; private set; } protected abstract void UpdateProgress(double progress, bool isIntro); - protected abstract void UpdateObjects(IEnumerable? objects); + protected virtual void UpdateObjects(IEnumerable? objects) { } [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Skinning/LegacySongProgress.cs b/osu.Game/Skinning/LegacySongProgress.cs index 963209d4ce..3fba0e5abe 100644 --- a/osu.Game/Skinning/LegacySongProgress.cs +++ b/osu.Game/Skinning/LegacySongProgress.cs @@ -1,13 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; -using osu.Game.Rulesets.Objects; using osu.Game.Screens.Play.HUD; using osuTK; @@ -60,14 +58,12 @@ namespace osu.Game.Skinning protected override void PopIn() { + this.FadeIn(500, Easing.OutQuint); } protected override void PopOut() { - } - - protected override void UpdateObjects(IEnumerable? objects) - { + this.FadeOut(100); } protected override void UpdateProgress(double progress, bool isIntro) From 86c2b7e449ed0dd670480e9826f46b695fa4113d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 18:37:17 +0900 Subject: [PATCH 405/481] Apply nullability to `DefaultSongProgress` and clean up more stuff --- .../Screens/Play/HUD/DefaultSongProgress.cs | 65 ++++++++----------- osu.Game/Screens/Play/HUD/SongProgress.cs | 31 +++++---- 2 files changed, 46 insertions(+), 50 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index b4eade0709..7b9453f2ed 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -1,9 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -32,8 +29,6 @@ namespace osu.Game.Screens.Play.HUD private readonly SongProgressGraph graph; private readonly SongProgressInfo info; - public Action RequestSeek; - /// /// Whether seeking is allowed and the progress bar should be shown. /// @@ -47,14 +42,20 @@ namespace osu.Game.Screens.Play.HUD protected override bool BlockScrollInput => false; - [Resolved(canBeNull: true)] - private GameplayClock gameplayClock { get; set; } + [Resolved] + private GameplayClock? gameplayClock { get; set; } - [Resolved(canBeNull: true)] - private Player player { get; set; } + [Resolved] + private Player? player { get; set; } - [Resolved(canBeNull: true)] - private DrawableRuleset drawableRuleset { get; set; } + [Resolved] + private DrawableRuleset? drawableRuleset { get; set; } + + [Resolved] + private OsuConfigManager config { get; set; } = null!; + + [Resolved] + private SkinManager skinManager { get; set; } = null!; public DefaultSongProgress() { @@ -110,12 +111,6 @@ namespace osu.Game.Screens.Play.HUD migrateSettingFromConfig(); } - [Resolved] - private OsuConfigManager config { get; set; } - - [Resolved] - private SkinManager skinManager { get; set; } - /// /// This setting has been migrated to a per-component level. /// Only take the value from the config if it is in a non-default state (then reset it to default so it only applies once). @@ -131,29 +126,26 @@ namespace osu.Game.Screens.Play.HUD ShowGraph.Value = configShowGraph.Value; // This is pretty ugly, but the only way to make this stick... - if (skinManager != null) + var skinnableTarget = this.FindClosestParent(); + + if (skinnableTarget != null) { - var skinnableTarget = this.FindClosestParent(); + // If the skin is not mutable, a mutable instance will be created, causing this migration logic to run again on the correct skin. + // Therefore we want to avoid resetting the config value on this invocation. + if (skinManager.EnsureMutableSkin()) + return; - if (skinnableTarget != null) + // If `EnsureMutableSkin` actually changed the skin, default layout may take a frame to apply. + // See `SkinnableTargetComponentsContainer`'s use of ScheduleAfterChildren. + ScheduleAfterChildren(() => { - // If the skin is not mutable, a mutable instance will be created, causing this migration logic to run again on the correct skin. - // Therefore we want to avoid resetting the config value on this invocation. - if (skinManager.EnsureMutableSkin()) - return; + var skin = skinManager.CurrentSkin.Value; + skin.UpdateDrawableTarget(skinnableTarget); - // If `EnsureMutableSkin` actually changed the skin, default layout may take a frame to apply. - // See `SkinnableTargetComponentsContainer`'s use of ScheduleAfterChildren. - ScheduleAfterChildren(() => - { - var skin = skinManager.CurrentSkin.Value; - skin.UpdateDrawableTarget(skinnableTarget); + skinManager.Save(skin); + }); - skinManager.Save(skin); - }); - - configShowGraph.SetDefault(); - } + configShowGraph.SetDefault(); } } } @@ -170,8 +162,7 @@ namespace osu.Game.Screens.Play.HUD protected override void UpdateObjects(IEnumerable objects) { - if (objects != null) - graph.Objects = objects; + graph.Objects = objects; info.StartTime = FirstHitTime; info.EndTime = LastHitTime; diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index 35847b4b16..aaef141349 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -30,8 +30,9 @@ namespace osu.Game.Screens.Play.HUD set { objects = value; - FirstHitTime = objects?.FirstOrDefault()?.StartTime ?? 0; - LastHitTime = objects?.LastOrDefault()?.GetEndTime() ?? 0; + FirstHitTime = objects.FirstOrDefault()?.StartTime ?? 0; + //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). + LastHitTime = objects.LastOrDefault()?.GetEndTime() ?? 0; UpdateObjects(objects); } } @@ -45,11 +46,10 @@ namespace osu.Game.Screens.Play.HUD protected double FirstHitTime { get; private set; } - //TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list). protected double LastHitTime { get; private set; } protected abstract void UpdateProgress(double progress, bool isIntro); - protected virtual void UpdateObjects(IEnumerable? objects) { } + protected virtual void UpdateObjects(IEnumerable objects) { } [BackgroundDependencyLoader] private void load() @@ -70,20 +70,25 @@ namespace osu.Game.Screens.Play.HUD // The reference clock is used to accurately tell the playfield's time. This is obtained from the drawable ruleset. // However, if no drawable ruleset is available (i.e. used in tests), we fall back to either the gameplay clock container or this drawable's own clock. - double gameplayTime = referenceClock?.CurrentTime ?? gameplayClockContainer?.GameplayClock.CurrentTime ?? Time.Current; + double currentTime = referenceClock?.CurrentTime ?? gameplayClockContainer?.GameplayClock.CurrentTime ?? Time.Current; - if (gameplayTime < FirstHitTime) + bool isInIntro = currentTime < FirstHitTime; + + if (isInIntro) { - double earliest = gameplayClockContainer?.StartTime ?? 0; - double introDuration = FirstHitTime - earliest; - double currentIntroTime = gameplayTime - earliest; - UpdateProgress(currentIntroTime / introDuration, true); + double introStartTime = gameplayClockContainer?.StartTime ?? 0; + + double introOffsetCurrent = currentTime - introStartTime; + double introDuration = FirstHitTime - introStartTime; + + UpdateProgress(introOffsetCurrent / introDuration, true); } else { - double duration = LastHitTime - FirstHitTime; - double currentTime = gameplayTime - FirstHitTime; - UpdateProgress(currentTime / duration, false); + double objectOffsetCurrent = currentTime - FirstHitTime; + + double objectDuration = LastHitTime - FirstHitTime; + UpdateProgress(objectOffsetCurrent / objectDuration, false); } } } From 4b140e1f5a3fbcccc816eb91c8a3b51f6219342d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 18:49:37 +0900 Subject: [PATCH 406/481] Adjust metrics --- osu.Game/Skinning/LegacySongProgress.cs | 26 ++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game/Skinning/LegacySongProgress.cs b/osu.Game/Skinning/LegacySongProgress.cs index 3fba0e5abe..f828e301f2 100644 --- a/osu.Game/Skinning/LegacySongProgress.cs +++ b/osu.Game/Skinning/LegacySongProgress.cs @@ -13,22 +13,22 @@ namespace osu.Game.Skinning { public class LegacySongProgress : SongProgress { - private CircularProgress pie = null!; + private CircularProgress circularProgress = null!; [BackgroundDependencyLoader] private void load() { - Size = new Vector2(35); + Size = new Vector2(33); InternalChildren = new Drawable[] { new Container { - Size = new Vector2(0.95f), Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Child = pie = new CircularProgress + Size = new Vector2(0.92f), + Child = circularProgress = new CircularProgress { RelativeSizeAxes = Axes.Both, }, @@ -51,7 +51,7 @@ namespace osu.Game.Skinning Anchor = Anchor.Centre, Origin = Anchor.Centre, Colour = Colour4.White, - Size = new Vector2(3), + Size = new Vector2(4), } }; } @@ -70,17 +70,17 @@ namespace osu.Game.Skinning { if (isIntro) { - pie.Scale = new Vector2(-1, 1); - pie.Anchor = Anchor.TopRight; - pie.Colour = new Colour4(199, 255, 47, 153); - pie.Current.Value = 1 - progress; + circularProgress.Scale = new Vector2(-1, 1); + circularProgress.Anchor = Anchor.TopRight; + circularProgress.Colour = new Colour4(199, 255, 47, 153); + circularProgress.Current.Value = 1 - progress; } else { - pie.Scale = new Vector2(1); - pie.Anchor = Anchor.TopLeft; - pie.Colour = new Colour4(255, 255, 255, 153); - pie.Current.Value = progress; + circularProgress.Scale = new Vector2(1); + circularProgress.Anchor = Anchor.TopLeft; + circularProgress.Colour = new Colour4(255, 255, 255, 153); + circularProgress.Current.Value = progress; } } } From ce694123ebeb2b0364c149a28e8f290828aed30d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 28 Jul 2022 20:44:02 +0900 Subject: [PATCH 407/481] Move spectator begin/end playing to SubmittingPlayer --- .../Gameplay/TestSceneSpectatorPlayback.cs | 59 +++++++++---------- osu.Game/Rulesets/UI/ReplayRecorder.cs | 12 ---- osu.Game/Screens/Play/Player.cs | 9 --- osu.Game/Screens/Play/SubmittingPlayer.cs | 9 +++ 4 files changed, 37 insertions(+), 52 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 5fad661e9b..9c41c70a0e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -27,7 +27,6 @@ using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.UI; using osu.Game.Scoring; -using osu.Game.Screens.Play; using osu.Game.Tests.Gameplay; using osu.Game.Tests.Mods; using osu.Game.Tests.Visual.Spectator; @@ -41,16 +40,12 @@ namespace osu.Game.Tests.Visual.Gameplay private TestRulesetInputManager playbackManager; private TestRulesetInputManager recordingManager; - private Replay replay; - + private Score recordingScore; + private Replay playbackReplay; private TestSpectatorClient spectatorClient; - private ManualClock manualClock; - private TestReplayRecorder recorder; - private OsuSpriteText latencyDisplay; - private TestFramedReplayInputHandler replayHandler; [SetUpSteps] @@ -58,7 +53,16 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("Setup containers", () => { - replay = new Replay(); + recordingScore = new Score + { + ScoreInfo = + { + BeatmapInfo = new BeatmapInfo(), + Ruleset = new OsuRuleset().RulesetInfo, + } + }; + + playbackReplay = new Replay(); manualClock = new ManualClock(); Child = new DependencyProvidingContainer @@ -67,7 +71,6 @@ namespace osu.Game.Tests.Visual.Gameplay CachedDependencies = new[] { (typeof(SpectatorClient), (object)(spectatorClient = new TestSpectatorClient())), - (typeof(GameplayState), TestGameplayState.Create(new OsuRuleset())) }, Children = new Drawable[] { @@ -81,7 +84,7 @@ namespace osu.Game.Tests.Visual.Gameplay { recordingManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { - Recorder = recorder = new TestReplayRecorder + Recorder = recorder = new TestReplayRecorder(recordingScore) { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), }, @@ -112,7 +115,7 @@ namespace osu.Game.Tests.Visual.Gameplay playbackManager = new TestRulesetInputManager(TestCustomisableModRuleset.CreateTestRulesetInfo(), 0, SimultaneousBindingMode.Unique) { Clock = new FramedClock(manualClock), - ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(replay) + ReplayInputHandler = replayHandler = new TestFramedReplayInputHandler(playbackReplay) { GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), }, @@ -144,6 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay } }; + spectatorClient.BeginPlaying(TestGameplayState.Create(new OsuRuleset()), recordingScore); spectatorClient.OnNewFrames += onNewFrames; }); } @@ -151,15 +155,15 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestBasic() { - AddUntilStep("received frames", () => replay.Frames.Count > 50); + AddUntilStep("received frames", () => playbackReplay.Frames.Count > 50); AddStep("stop sending frames", () => recorder.Expire()); - AddUntilStep("wait for all frames received", () => replay.Frames.Count == recorder.SentFrames.Count); + AddUntilStep("wait for all frames received", () => playbackReplay.Frames.Count == recorder.SentFrames.Count); } [Test] public void TestWithSendFailure() { - AddUntilStep("received frames", () => replay.Frames.Count > 50); + AddUntilStep("received frames", () => playbackReplay.Frames.Count > 50); int framesReceivedSoFar = 0; int frameSendAttemptsSoFar = 0; @@ -172,21 +176,21 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for next send attempt", () => { - framesReceivedSoFar = replay.Frames.Count; + framesReceivedSoFar = playbackReplay.Frames.Count; return spectatorClient.FrameSendAttempts > frameSendAttemptsSoFar + 1; }); AddUntilStep("wait for more send attempts", () => spectatorClient.FrameSendAttempts > frameSendAttemptsSoFar + 10); - AddAssert("frames did not increase", () => framesReceivedSoFar == replay.Frames.Count); + AddAssert("frames did not increase", () => framesReceivedSoFar == playbackReplay.Frames.Count); AddStep("stop failing sends", () => spectatorClient.ShouldFailSendingFrames = false); - AddUntilStep("wait for next frames", () => framesReceivedSoFar < replay.Frames.Count); + AddUntilStep("wait for next frames", () => framesReceivedSoFar < playbackReplay.Frames.Count); AddStep("stop sending frames", () => recorder.Expire()); - AddUntilStep("wait for all frames received", () => replay.Frames.Count == recorder.SentFrames.Count); - AddAssert("ensure frames were received in the correct sequence", () => replay.Frames.Select(f => f.Time).SequenceEqual(recorder.SentFrames.Select(f => f.Time))); + AddUntilStep("wait for all frames received", () => playbackReplay.Frames.Count == recorder.SentFrames.Count); + AddAssert("ensure frames were received in the correct sequence", () => playbackReplay.Frames.Select(f => f.Time).SequenceEqual(recorder.SentFrames.Select(f => f.Time))); } private void onNewFrames(int userId, FrameDataBundle frames) @@ -195,10 +199,10 @@ namespace osu.Game.Tests.Visual.Gameplay { var frame = new TestReplayFrame(); frame.FromLegacy(legacyFrame, null); - replay.Frames.Add(frame); + playbackReplay.Frames.Add(frame); } - Logger.Log($"Received {frames.Frames.Count} new frames (total {replay.Frames.Count} of {recorder.SentFrames.Count})"); + Logger.Log($"Received {frames.Frames.Count} new frames (total {playbackReplay.Frames.Count} of {recorder.SentFrames.Count})"); } private double latency = SpectatorClient.TIME_BETWEEN_SENDS; @@ -219,7 +223,7 @@ namespace osu.Game.Tests.Visual.Gameplay if (!replayHandler.HasFrames) return; - var lastFrame = replay.Frames.LastOrDefault(); + var lastFrame = playbackReplay.Frames.LastOrDefault(); // this isn't perfect as we basically can't be aware of the rate-of-send here (the streamer is not sending data when not being moved). // in gameplay playback, the case where NextFrame is null would pause gameplay and handle this correctly; it's strictly a test limitation / best effort implementation. @@ -360,15 +364,8 @@ namespace osu.Game.Tests.Visual.Gameplay { public List SentFrames = new List(); - public TestReplayRecorder() - : base(new Score - { - ScoreInfo = - { - BeatmapInfo = new BeatmapInfo(), - Ruleset = new OsuRuleset().RulesetInfo, - } - }) + public TestReplayRecorder(Score score) + : base(score) { } diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index b04807e475..79da56fc8a 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -14,7 +14,6 @@ using osu.Framework.Input.Events; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Replays; using osu.Game.Scoring; -using osu.Game.Screens.Play; using osuTK; namespace osu.Game.Rulesets.UI @@ -33,9 +32,6 @@ namespace osu.Game.Rulesets.UI [Resolved] private SpectatorClient spectatorClient { get; set; } - [Resolved] - private GameplayState gameplayState { get; set; } - protected ReplayRecorder(Score target) { this.target = target; @@ -48,15 +44,7 @@ namespace osu.Game.Rulesets.UI protected override void LoadComplete() { base.LoadComplete(); - inputManager = GetContainingInputManager(); - spectatorClient.BeginPlaying(gameplayState, target); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - spectatorClient?.EndPlaying(gameplayState); } protected override void Update() diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9a058e45c5..9c08c77d91 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -26,7 +26,6 @@ using osu.Game.Extensions; using osu.Game.Graphics.Containers; using osu.Game.IO.Archives; using osu.Game.Online.API; -using osu.Game.Online.Spectator; using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -101,9 +100,6 @@ namespace osu.Game.Screens.Play [Resolved] private MusicController musicController { get; set; } - [Resolved] - private SpectatorClient spectatorClient { get; set; } - public GameplayState GameplayState { get; private set; } private Ruleset ruleset; @@ -1030,11 +1026,6 @@ namespace osu.Game.Screens.Play // if arriving here and the results screen preparation task hasn't run, it's safe to say the user has not completed the beatmap. if (prepareScoreForDisplayTask == null) ScoreProcessor.FailScore(Score.ScoreInfo); - - // EndPlaying() is typically called from ReplayRecorder.Dispose(). Disposal is currently asynchronous. - // To resolve test failures, forcefully end playing synchronously when this screen exits. - // Todo: Replace this with a more permanent solution once osu-framework has a synchronous cleanup method. - spectatorClient.EndPlaying(GameplayState); } // GameplayClockContainer performs seeks / start / stop operations on the beatmap's track. diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index ad63925b93..02a95ae9eb 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -15,6 +15,7 @@ using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.Rooms; +using osu.Game.Online.Spectator; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -33,6 +34,9 @@ namespace osu.Game.Screens.Play [Resolved] private IAPIProvider api { get; set; } + [Resolved] + private SpectatorClient spectatorClient { get; set; } + private TaskCompletionSource scoreSubmissionSource; protected SubmittingPlayer(PlayerConfiguration configuration = null) @@ -134,6 +138,8 @@ namespace osu.Game.Screens.Play if (realmBeatmap != null) realmBeatmap.LastPlayed = DateTimeOffset.Now; }); + + spectatorClient.BeginPlaying(GameplayState, Score); } public override bool OnExiting(ScreenExitEvent e) @@ -141,7 +147,10 @@ namespace osu.Game.Screens.Play bool exiting = base.OnExiting(e); if (LoadedBeatmapSuccessfully) + { submitScore(Score.DeepClone()); + spectatorClient.EndPlaying(GameplayState); + } return exiting; } From e664690fe2051d529861347183784f2e3f2ba550 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 Jul 2022 22:19:46 +0900 Subject: [PATCH 408/481] Remove unnecessary `LoadTrack` call --- osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index 5e9cf8839d..428c056fc6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -27,7 +27,6 @@ namespace osu.Game.Tests.Visual.Gameplay private void load() { Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); - Beatmap.Value.LoadTrack(); Add(gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, skip_target_time)); From fd091559909c8d7720d5d91a1455dce23aa054d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Jul 2022 12:24:53 +0900 Subject: [PATCH 409/481] Revert blocking call when sending spectator frames There are a lot of these requests, and we don't really care about waiting on them to finish sending. This may have negatively affected send performance for users with very high latency. Reverts part of 0533249d11ac220e223369f774fafcabc3f30c51. Addresses concerns in https://github.com/ppy/osu/discussions/19429#discussioncomment-3276400. --- osu.Game/Online/Spectator/OnlineSpectatorClient.cs | 2 +- osu.Game/Online/Spectator/SpectatorClient.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index 030ca724c4..a012bf49b6 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -80,7 +80,7 @@ namespace osu.Game.Online.Spectator Debug.Assert(connection != null); - return connection.InvokeAsync(nameof(ISpectatorServer.SendFrameData), bundle); + return connection.SendAsync(nameof(ISpectatorServer.SendFrameData), bundle); } protected override Task EndPlayingInternal(SpectatorState state) diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index b5e1c8a45f..745c968992 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -304,7 +304,7 @@ namespace osu.Game.Online.Spectator SendFramesInternal(bundle).ContinueWith(t => { - // Handle exception outside of `Schedule` to ensure it doesn't go unovserved. + // Handle exception outside of `Schedule` to ensure it doesn't go unobserved. bool wasSuccessful = t.Exception == null; return Schedule(() => From aaa6f963bd782c0630a5f6a5d1201b3ba0833665 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 29 Jul 2022 15:27:39 +0900 Subject: [PATCH 410/481] Fix potential test failures due to Setup/SetUpSteps ordering --- .../TestSceneDrawableRoomParticipantsList.cs | 44 +++++---- .../TestSceneLoungeRoomsContainer.cs | 32 ++++--- .../TestSceneMatchBeatmapDetailArea.cs | 25 ++--- .../Multiplayer/TestSceneMatchLeaderboard.cs | 95 ++++++++++--------- .../TestSceneMultiSpectatorLeaderboard.cs | 4 +- .../TestSceneMultiSpectatorScreen.cs | 8 +- .../TestSceneMultiplayerMatchFooter.cs | 31 +++--- .../TestSceneMultiplayerMatchSubScreen.cs | 13 ++- .../TestSceneMultiplayerPlaylist.cs | 27 +++--- .../TestSceneMultiplayerSpectateButton.cs | 60 ++++++------ .../TestSceneStarRatingRangeDisplay.cs | 20 ++-- .../TestScenePlaylistsMatchSettingsOverlay.cs | 20 ++-- .../TestScenePlaylistsParticipantsList.cs | 26 ++--- .../Multiplayer/MultiplayerTestScene.cs | 15 ++- .../Visual/OnlinePlay/OnlinePlayTestScene.cs | 59 ++++++------ 15 files changed, 258 insertions(+), 221 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs index 0a59e0e858..b26481387d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomParticipantsList.cs @@ -19,29 +19,33 @@ namespace osu.Game.Tests.Visual.Multiplayer { private DrawableRoomParticipantsList list; - [SetUp] - public new void Setup() => Schedule(() => + public override void SetUpSteps() { - SelectedRoom.Value = new Room - { - Name = { Value = "test room" }, - Host = - { - Value = new APIUser - { - Id = 2, - Username = "peppy", - } - } - }; + base.SetUpSteps(); - Child = list = new DrawableRoomParticipantsList + AddStep("create list", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - NumberOfCircles = 4 - }; - }); + SelectedRoom.Value = new Room + { + Name = { Value = "test room" }, + Host = + { + Value = new APIUser + { + Id = 2, + Username = "peppy", + } + } + }; + + Child = list = new DrawableRoomParticipantsList + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + NumberOfCircles = 4 + }; + }); + } [Test] public void TestCircleCountNearLimit() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 82e7bf8969..3d6d4f0a90 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -25,23 +25,27 @@ namespace osu.Game.Tests.Visual.Multiplayer private RoomsContainer container; - [SetUp] - public new void Setup() => Schedule(() => + public override void SetUpSteps() { - Child = new PopoverContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 0.5f, + base.SetUpSteps(); - Child = container = new RoomsContainer + AddStep("create container", () => + { + Child = new PopoverContainer { - SelectedRoom = { BindTarget = SelectedRoom } - } - }; - }); + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.5f, + + Child = container = new RoomsContainer + { + SelectedRoom = { BindTarget = SelectedRoom } + } + }; + }); + } [Test] public void TestBasicListChanges() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index 8cdcdfdfdf..b113352117 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -3,7 +3,6 @@ #nullable disable -using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online.API; using osu.Game.Online.Rooms; @@ -18,19 +17,23 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMatchBeatmapDetailArea : OnlinePlayTestScene { - [SetUp] - public new void Setup() => Schedule(() => + public override void SetUpSteps() { - SelectedRoom.Value = new Room(); + base.SetUpSteps(); - Child = new MatchBeatmapDetailArea + AddStep("create area", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500), - CreateNewItem = createNewItem - }; - }); + SelectedRoom.Value = new Room(); + + Child = new MatchBeatmapDetailArea + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500), + CreateNewItem = createNewItem + }; + }); + } private void createNewItem() { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index 506d7541a7..d2468ae005 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -4,8 +4,6 @@ #nullable disable using System.Collections.Generic; -using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; @@ -19,59 +17,62 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMatchLeaderboard : OnlinePlayTestScene { - [BackgroundDependencyLoader] - private void load() + public override void SetUpSteps() { - ((DummyAPIAccess)API).HandleRequest = r => + base.SetUpSteps(); + + AddStep("setup API", () => { - switch (r) + ((DummyAPIAccess)API).HandleRequest = r => { - case GetRoomLeaderboardRequest leaderboardRequest: - leaderboardRequest.TriggerSuccess(new APILeaderboard - { - Leaderboard = new List + switch (r) + { + case GetRoomLeaderboardRequest leaderboardRequest: + leaderboardRequest.TriggerSuccess(new APILeaderboard { - new APIUserScoreAggregate + Leaderboard = new List { - UserID = 2, - User = new APIUser { Id = 2, Username = "peppy" }, - TotalScore = 995533, - RoomID = 3, - CompletedBeatmaps = 1, - TotalAttempts = 6, - Accuracy = 0.9851 - }, - new APIUserScoreAggregate - { - UserID = 1040328, - User = new APIUser { Id = 1040328, Username = "smoogipoo" }, - TotalScore = 981100, - RoomID = 3, - CompletedBeatmaps = 1, - TotalAttempts = 9, - Accuracy = 0.937 + new APIUserScoreAggregate + { + UserID = 2, + User = new APIUser { Id = 2, Username = "peppy" }, + TotalScore = 995533, + RoomID = 3, + CompletedBeatmaps = 1, + TotalAttempts = 6, + Accuracy = 0.9851 + }, + new APIUserScoreAggregate + { + UserID = 1040328, + User = new APIUser { Id = 1040328, Username = "smoogipoo" }, + TotalScore = 981100, + RoomID = 3, + CompletedBeatmaps = 1, + TotalAttempts = 9, + Accuracy = 0.937 + } } - } - }); - return true; - } + }); + return true; + } - return false; - }; - } + return false; + }; + }); - [SetUp] - public new void Setup() => Schedule(() => - { - SelectedRoom.Value = new Room { RoomID = { Value = 3 } }; - - Child = new MatchLeaderboard + AddStep("create leaderboard", () => { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Size = new Vector2(550f, 450f), - Scope = MatchLeaderboardScope.Overall, - }; - }); + SelectedRoom.Value = new Room { RoomID = { Value = 3 } }; + + Child = new MatchLeaderboard + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Size = new Vector2(550f, 450f), + Scope = MatchLeaderboardScope.Overall, + }; + }); + } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs index 80c356ec67..9e6941738a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorLeaderboard.cs @@ -22,8 +22,10 @@ namespace osu.Game.Tests.Visual.Multiplayer private MultiSpectatorLeaderboard leaderboard; [SetUpSteps] - public new void SetUpSteps() + public override void SetUpSteps() { + base.SetUpSteps(); + AddStep("reset", () => { leaderboard?.RemoveAndDisposeImmediately(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index 7df68392cf..d626426e6d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -56,8 +56,12 @@ namespace osu.Game.Tests.Visual.Multiplayer importedBeatmapId = importedBeatmap.OnlineID; } - [SetUp] - public new void Setup() => Schedule(() => playingUsers.Clear()); + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("clear playing users", () => playingUsers.Clear()); + } [Test] public void TestDelayedStart() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs index a98030e1e3..83e7ef6a81 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchFooter.cs @@ -3,7 +3,6 @@ #nullable disable -using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -13,23 +12,27 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerMatchFooter : MultiplayerTestScene { - [SetUp] - public new void Setup() => Schedule(() => + public override void SetUpSteps() { - Child = new PopoverContainer + base.SetUpSteps(); + + AddStep("create footer", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Child = new Container + Child = new PopoverContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = 50, - Child = new MultiplayerMatchFooter() - } - }; - }); + RelativeSizeAxes = Axes.Both, + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = 50, + Child = new MultiplayerMatchFooter() + } + }; + }); + } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 5ebafbaabb..8d31e9c723 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -59,16 +59,15 @@ namespace osu.Game.Tests.Visual.Multiplayer importedSet = beatmaps.GetAllUsableBeatmapSets().First(); } - [SetUp] - public new void Setup() => Schedule(() => - { - SelectedRoom.Value = new Room { Name = { Value = "Test Room" } }; - }); - [SetUpSteps] public void SetupSteps() { - AddStep("load match", () => LoadScreen(screen = new MultiplayerMatchSubScreen(SelectedRoom.Value))); + AddStep("load match", () => + { + SelectedRoom.Value = new Room { Name = { Value = "Test Room" } }; + LoadScreen(screen = new MultiplayerMatchSubScreen(SelectedRoom.Value)); + }); + AddUntilStep("wait for load", () => screen.IsCurrentScreen()); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs index 75e6088b0d..8dbad4e330 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlaylist.cs @@ -42,21 +42,22 @@ namespace osu.Game.Tests.Visual.Multiplayer Dependencies.Cache(Realm); } - [SetUp] - public new void Setup() => Schedule(() => - { - Child = list = new MultiplayerPlaylist - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(0.4f, 0.8f) - }; - }); - [SetUpSteps] - public new void SetUpSteps() + public override void SetUpSteps() { + base.SetUpSteps(); + + AddStep("create list", () => + { + Child = list = new MultiplayerPlaylist + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(0.4f, 0.8f) + }; + }); + AddStep("import beatmap", () => { beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs index e70c414bef..9b4cb722f3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerSpectateButton.cs @@ -46,43 +46,47 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely(); } - [SetUp] - public new void Setup() => Schedule(() => + public override void SetUpSteps() { - AvailabilityTracker.SelectedItem.BindTo(selectedItem); + base.SetUpSteps(); - importedSet = beatmaps.GetAllUsableBeatmapSets().First(); - Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); - selectedItem.Value = new PlaylistItem(Beatmap.Value.BeatmapInfo) + AddStep("create button", () => { - RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, - }; + AvailabilityTracker.SelectedItem.BindTo(selectedItem); - Child = new PopoverContainer - { - RelativeSizeAxes = Axes.Both, - Child = new FillFlowContainer + importedSet = beatmaps.GetAllUsableBeatmapSets().First(); + Beatmap.Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First()); + selectedItem.Value = new PlaylistItem(Beatmap.Value.BeatmapInfo) { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] + RulesetID = Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, + }; + + Child = new PopoverContainer + { + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer { - spectateButton = new MultiplayerSpectateButton + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(200, 50), - }, - startControl = new MatchStartControl - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(200, 50), + spectateButton = new MultiplayerSpectateButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 50), + }, + startControl = new MatchStartControl + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(200, 50), + } } } - } - }; - }); + }; + }); + } [TestCase(MultiplayerRoomState.Open)] [TestCase(MultiplayerRoomState.WaitingForLoad)] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs index 321e0c2c89..5bccabcf2f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneStarRatingRangeDisplay.cs @@ -14,17 +14,21 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneStarRatingRangeDisplay : OnlinePlayTestScene { - [SetUp] - public new void Setup() => Schedule(() => + public override void SetUpSteps() { - SelectedRoom.Value = new Room(); + base.SetUpSteps(); - Child = new StarRatingRangeDisplay + AddStep("create display", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }; - }); + SelectedRoom.Value = new Room(); + + Child = new StarRatingRangeDisplay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + }); + } [Test] public void TestRange([Values(0, 2, 3, 4, 6, 7)] double min, [Values(0, 2, 3, 4, 6, 7)] double max) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs index e6882081dd..c71bdb3a06 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsMatchSettingsOverlay.cs @@ -25,17 +25,21 @@ namespace osu.Game.Tests.Visual.Playlists protected override OnlinePlayTestSceneDependencies CreateOnlinePlayDependencies() => new TestDependencies(); - [SetUp] - public new void Setup() => Schedule(() => + public override void SetUpSteps() { - SelectedRoom.Value = new Room(); + base.SetUpSteps(); - Child = settings = new TestRoomSettings(SelectedRoom.Value) + AddStep("create overlay", () => { - RelativeSizeAxes = Axes.Both, - State = { Value = Visibility.Visible } - }; - }); + SelectedRoom.Value = new Room(); + + Child = settings = new TestRoomSettings(SelectedRoom.Value) + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible } + }; + }); + } [Test] public void TestButtonEnabledOnlyWithNameAndBeatmap() diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs index 5961ed74ad..9a0dda056a 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsParticipantsList.cs @@ -15,21 +15,25 @@ namespace osu.Game.Tests.Visual.Playlists { public class TestScenePlaylistsParticipantsList : OnlinePlayTestScene { - [SetUp] - public new void Setup() => Schedule(() => + public override void SetUpSteps() { - SelectedRoom.Value = new Room { RoomID = { Value = 7 } }; + base.SetUpSteps(); - for (int i = 0; i < 50; i++) + AddStep("create list", () => { - SelectedRoom.Value.RecentParticipants.Add(new APIUser + SelectedRoom.Value = new Room { RoomID = { Value = 7 } }; + + for (int i = 0; i < 50; i++) { - Username = "peppy", - Statistics = new UserStatistics { GlobalRank = 1234 }, - Id = 2 - }); - } - }); + SelectedRoom.Value.RecentParticipants.Add(new APIUser + { + Username = "peppy", + Statistics = new UserStatistics { GlobalRank = 1234 }, + Id = 2 + }); + } + }); + } [Test] public void TestHorizontalLayout() diff --git a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs index 5ea98bdbb1..101a347749 100644 --- a/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/Multiplayer/MultiplayerTestScene.cs @@ -3,7 +3,6 @@ #nullable disable -using NUnit.Framework; using osu.Game.Online.Rooms; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual.OnlinePlay; @@ -34,13 +33,6 @@ namespace osu.Game.Tests.Visual.Multiplayer this.joinRoom = joinRoom; } - [SetUp] - public new void Setup() => Schedule(() => - { - if (joinRoom) - SelectedRoom.Value = CreateRoom(); - }); - protected virtual Room CreateRoom() { return new Room @@ -63,7 +55,12 @@ namespace osu.Game.Tests.Visual.Multiplayer if (joinRoom) { - AddStep("join room", () => RoomManager.CreateRoom(SelectedRoom.Value)); + AddStep("join room", () => + { + SelectedRoom.Value = CreateRoom(); + RoomManager.CreateRoom(SelectedRoom.Value); + }); + AddUntilStep("wait for room join", () => RoomJoined); } } diff --git a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs index 6577057c17..b9c293c3aa 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/OnlinePlayTestScene.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -56,39 +55,43 @@ namespace osu.Game.Tests.Visual.OnlinePlay return dependencies; } - [SetUp] - public void Setup() => Schedule(() => + public override void SetUpSteps() { - // Reset the room dependencies to a fresh state. - drawableDependenciesContainer.Clear(); - dependencies.OnlinePlayDependencies = CreateOnlinePlayDependencies(); - drawableDependenciesContainer.AddRange(OnlinePlayDependencies.DrawableComponents); + base.SetUpSteps(); - var handler = OnlinePlayDependencies.RequestsHandler; - - // Resolving the BeatmapManager in the test scene will inject the game-wide BeatmapManager, while many test scenes cache their own BeatmapManager instead. - // To get around this, the BeatmapManager is looked up from the dependencies provided to the children of the test scene instead. - var beatmapManager = dependencies.Get(); - - ((DummyAPIAccess)API).HandleRequest = request => + AddStep("setup dependencies", () => { - try + // Reset the room dependencies to a fresh state. + drawableDependenciesContainer.Clear(); + dependencies.OnlinePlayDependencies = CreateOnlinePlayDependencies(); + drawableDependenciesContainer.AddRange(OnlinePlayDependencies.DrawableComponents); + + var handler = OnlinePlayDependencies.RequestsHandler; + + // Resolving the BeatmapManager in the test scene will inject the game-wide BeatmapManager, while many test scenes cache their own BeatmapManager instead. + // To get around this, the BeatmapManager is looked up from the dependencies provided to the children of the test scene instead. + var beatmapManager = dependencies.Get(); + + ((DummyAPIAccess)API).HandleRequest = request => { - return handler.HandleRequest(request, API.LocalUser.Value, beatmapManager); - } - catch (ObjectDisposedException) - { - // These requests can be fired asynchronously, but potentially arrive after game components - // have been disposed (ie. realm in BeatmapManager). - // This only happens in tests and it's easiest to ignore them for now. - Logger.Log($"Handled {nameof(ObjectDisposedException)} in test request handling"); - return true; - } - }; - }); + try + { + return handler.HandleRequest(request, API.LocalUser.Value, beatmapManager); + } + catch (ObjectDisposedException) + { + // These requests can be fired asynchronously, but potentially arrive after game components + // have been disposed (ie. realm in BeatmapManager). + // This only happens in tests and it's easiest to ignore them for now. + Logger.Log($"Handled {nameof(ObjectDisposedException)} in test request handling"); + return true; + } + }; + }); + } /// - /// Creates the room dependencies. Called every . + /// Creates the room dependencies. Called every . /// /// /// Any custom dependencies required for online play sub-classes should be added here. From e07e761c1011cf7202d3baa9360c1f6dc31e46ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Jul 2022 15:54:03 +0900 Subject: [PATCH 411/481] Ensure realm is in a good state before asserts in `TestSceneFilterControl` --- .../SongSelect/TestSceneFilterControl.cs | 42 +++++++++++-------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 56f252f47d..82314f9764 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -19,6 +20,7 @@ using osu.Game.Rulesets; using osu.Game.Screens.Select; using osu.Game.Tests.Resources; using osuTK.Input; +using Realms; namespace osu.Game.Tests.Visual.SongSelect { @@ -47,7 +49,7 @@ namespace osu.Game.Tests.Visual.SongSelect [SetUp] public void SetUp() => Schedule(() => { - Realm.Write(r => r.RemoveAll()); + writeAndRefresh(r => r.RemoveAll()); Child = control = new FilterControl { @@ -68,8 +70,8 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestCollectionAddedToDropdown() { - AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); - AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "2")))); + AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1")))); + AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "2")))); assertCollectionDropdownContains("1"); assertCollectionDropdownContains("2"); } @@ -79,9 +81,9 @@ namespace osu.Game.Tests.Visual.SongSelect { BeatmapCollection first = null!; - AddStep("add collection", () => Realm.Write(r => r.Add(first = new BeatmapCollection(name: "1")))); - AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "2")))); - AddStep("remove collection", () => Realm.Write(r => r.Remove(first))); + AddStep("add collection", () => writeAndRefresh(r => r.Add(first = new BeatmapCollection(name: "1")))); + AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "2")))); + AddStep("remove collection", () => writeAndRefresh(r => r.Remove(first))); assertCollectionDropdownContains("1", false); assertCollectionDropdownContains("2"); @@ -90,7 +92,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestCollectionRenamed() { - AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); + AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1")))); AddStep("select collection", () => { var dropdown = control.ChildrenOfType().Single(); @@ -99,7 +101,7 @@ namespace osu.Game.Tests.Visual.SongSelect addExpandHeaderStep(); - AddStep("change name", () => Realm.Write(_ => getFirstCollection().Name = "First")); + AddStep("change name", () => writeAndRefresh(_ => getFirstCollection().Name = "First")); assertCollectionDropdownContains("First"); assertCollectionHeaderDisplays("First"); @@ -117,7 +119,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestCollectionFilterHasAddButton() { addExpandHeaderStep(); - AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); + AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1")))); AddStep("hover collection", () => InputManager.MoveMouseTo(getAddOrRemoveButton(1))); AddAssert("collection has add button", () => getAddOrRemoveButton(1).IsPresent); } @@ -127,7 +129,7 @@ namespace osu.Game.Tests.Visual.SongSelect { addExpandHeaderStep(); - AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); + AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1")))); AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddAssert("button enabled", () => getAddOrRemoveButton(1).Enabled.Value); @@ -143,13 +145,13 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); - AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); + AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1")))); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); - AddStep("add beatmap to collection", () => Realm.Write(r => getFirstCollection().BeatmapMD5Hashes.Add(Beatmap.Value.BeatmapInfo.MD5Hash))); + AddStep("add beatmap to collection", () => writeAndRefresh(r => getFirstCollection().BeatmapMD5Hashes.Add(Beatmap.Value.BeatmapInfo.MD5Hash))); AddAssert("button is minus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.MinusSquare)); - AddStep("remove beatmap from collection", () => Realm.Write(r => getFirstCollection().BeatmapMD5Hashes.Clear())); + AddStep("remove beatmap from collection", () => writeAndRefresh(r => getFirstCollection().BeatmapMD5Hashes.Clear())); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); } @@ -160,7 +162,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); - AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1")))); + AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1")))); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); addClickAddOrRemoveButtonStep(1); @@ -179,7 +181,7 @@ namespace osu.Game.Tests.Visual.SongSelect addExpandHeaderStep(); - AddStep("add collection", () => Realm.Write(r => r.Add(new BeatmapCollection(name: "1", new List { "abc" })))); + AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1", new List { "abc" })))); AddStep("select collection", () => { InputManager.MoveMouseTo(getCollectionDropdownItems().ElementAt(1)); @@ -205,14 +207,20 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("filter request not fired", () => !received); } + private void writeAndRefresh(Action action) => Realm.Write(r => + { + action(r); + r.Refresh(); + }); + private BeatmapCollection getFirstCollection() => Realm.Run(r => r.All().First()); private void assertCollectionHeaderDisplays(string collectionName, bool shouldDisplay = true) - => AddAssert($"collection dropdown header displays '{collectionName}'", + => AddUntilStep($"collection dropdown header displays '{collectionName}'", () => shouldDisplay == (control.ChildrenOfType().Single().ChildrenOfType().First().Text == collectionName)); private void assertCollectionDropdownContains(string collectionName, bool shouldContain = true) => - AddAssert($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'", + AddUntilStep($"collection dropdown {(shouldContain ? "contains" : "does not contain")} '{collectionName}'", // A bit of a roundabout way of going about this, see: https://github.com/ppy/osu-framework/issues/3871 + https://github.com/ppy/osu-framework/issues/3872 () => shouldContain == (getCollectionDropdownItems().Any(i => i.ChildrenOfType().OfType().First().Text == collectionName))); From 2ff6ff06d3ed25fce9bd69939b8fcae5c8f9ddb0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Jul 2022 16:05:41 +0900 Subject: [PATCH 412/481] Use tuple to better explain new `bool` parameter --- osu.Game/Beatmaps/BeatmapImporter.cs | 5 ++--- osu.Game/Beatmaps/BeatmapManager.cs | 6 +++--- osu.Game/OsuGameBase.cs | 2 +- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index df8b18313c..6e954e0356 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps protected override string[] HashableFileTypes => new[] { ".osu" }; - public Action? ProcessBeatmap { private get; set; } + public Action<(BeatmapSetInfo beatmapSet, bool isBatch)>? ProcessBeatmap { private get; set; } public BeatmapImporter(Storage storage, RealmAccess realm) : base(storage, realm) @@ -171,8 +171,7 @@ namespace osu.Game.Beatmaps protected override void PostImport(BeatmapSetInfo model, Realm realm, bool batchImport) { base.PostImport(model, realm, batchImport); - - ProcessBeatmap?.Invoke(model, batchImport); + ProcessBeatmap?.Invoke((model, batchImport)); } private void validateOnlineIds(BeatmapSetInfo beatmapSet, Realm realm) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index abf3d43d94..387daf6906 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -42,7 +42,7 @@ namespace osu.Game.Beatmaps private readonly WorkingBeatmapCache workingBeatmapCache; - public Action? ProcessBeatmap { private get; set; } + public Action<(BeatmapSetInfo beatmapSet, bool isBatch)>? ProcessBeatmap { private get; set; } public BeatmapManager(Storage storage, RealmAccess realm, RulesetStore rulesets, IAPIProvider? api, AudioManager audioManager, IResourceStore gameResources, GameHost? host = null, WorkingBeatmap? defaultBeatmap = null, BeatmapDifficultyCache? difficultyCache = null, bool performOnlineLookups = false) @@ -62,7 +62,7 @@ namespace osu.Game.Beatmaps BeatmapTrackStore = audioManager.GetTrackStore(userResources); beatmapImporter = CreateBeatmapImporter(storage, realm); - beatmapImporter.ProcessBeatmap = (obj, isBatch) => ProcessBeatmap?.Invoke(obj, isBatch); + beatmapImporter.ProcessBeatmap = args => ProcessBeatmap?.Invoke(args); beatmapImporter.PostNotification = obj => PostNotification?.Invoke(obj); workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, gameResources, userResources, defaultBeatmap, host); @@ -323,7 +323,7 @@ namespace osu.Game.Beatmaps setInfo.CopyChangesToRealm(liveBeatmapSet); - ProcessBeatmap?.Invoke(liveBeatmapSet, false); + ProcessBeatmap?.Invoke((liveBeatmapSet, false)); }); } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 41eeece76d..dc7e2b47cf 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -287,7 +287,7 @@ namespace osu.Game AddInternal(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient)); - BeatmapManager.ProcessBeatmap = (set, isBatch) => beatmapUpdater.Process(set, preferOnlineFetch: !isBatch); + BeatmapManager.ProcessBeatmap = args => beatmapUpdater.Process(args.beatmapSet, !args.isBatch); dependencies.Cache(userCache = new UserLookupCache()); AddInternal(userCache); From 9d457535c630d2d1cc8bee08f005e77f42052d0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Jul 2022 16:22:54 +0900 Subject: [PATCH 413/481] Add confirmation dialog when about to discard a playlist The confirmation will only show if items have been added to the playlist. Closes https://github.com/ppy/osu/issues/19444. --- .../Menu/ConfirmDiscardChangesDialog.cs | 39 +++++++++++++++++++ osu.Game/Screens/Menu/ConfirmExitDialog.cs | 4 +- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 24 ++++++++++-- 3 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Screens/Menu/ConfirmDiscardChangesDialog.cs diff --git a/osu.Game/Screens/Menu/ConfirmDiscardChangesDialog.cs b/osu.Game/Screens/Menu/ConfirmDiscardChangesDialog.cs new file mode 100644 index 0000000000..450c559450 --- /dev/null +++ b/osu.Game/Screens/Menu/ConfirmDiscardChangesDialog.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics.Sprites; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.Menu +{ + public class ConfirmDiscardChangesDialog : PopupDialog + { + /// + /// Construct a new discard changes confirmation dialog. + /// + /// An action to perform on confirmation. + /// An optional action to perform on cancel. + public ConfirmDiscardChangesDialog(Action onConfirm, Action? onCancel = null) + { + HeaderText = "Are you sure you want to go back?"; + BodyText = "This will discard any unsaved changes"; + + Icon = FontAwesome.Solid.ExclamationTriangle; + + Buttons = new PopupDialogButton[] + { + new PopupDialogDangerousButton + { + Text = @"Yes", + Action = onConfirm + }, + new PopupDialogCancelButton + { + Text = @"No I didn't mean to", + Action = onCancel + }, + }; + } + } +} diff --git a/osu.Game/Screens/Menu/ConfirmExitDialog.cs b/osu.Game/Screens/Menu/ConfirmExitDialog.cs index 734ff6b23f..20fa889986 100644 --- a/osu.Game/Screens/Menu/ConfirmExitDialog.cs +++ b/osu.Game/Screens/Menu/ConfirmExitDialog.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.Sprites; using osu.Game.Overlays.Dialog; @@ -16,7 +14,7 @@ namespace osu.Game.Screens.Menu /// /// An action to perform on confirmation. /// An optional action to perform on cancel. - public ConfirmExitDialog(Action onConfirm, Action onCancel = null) + public ConfirmExitDialog(Action onConfirm, Action? onCancel = null) { HeaderText = "Are you sure you want to exit osu!?"; BodyText = "Last chance to turn back"; diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index f5af110372..5b3ed0059d 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -28,6 +28,7 @@ using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Menu; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Multiplayer; @@ -280,13 +281,30 @@ namespace osu.Game.Screens.OnlinePlay.Match }; } + [Resolved(canBeNull: true)] + private IDialogOverlay dialogOverlay { get; set; } + public override bool OnBackButton() { if (Room.RoomID.Value == null) { - // room has not been created yet; exit immediately. - settingsOverlay.Hide(); - return base.OnBackButton(); + if (dialogOverlay == null || Room.Playlist.Count == 0) + { + settingsOverlay.Hide(); + return base.OnBackButton(); + } + + // if the dialog is already displayed, block exiting until the user explicitly makes a decision. + if (dialogOverlay.CurrentDialog is ConfirmDiscardChangesDialog) + return true; + + dialogOverlay?.Push(new ConfirmDiscardChangesDialog(() => + { + settingsOverlay.Hide(); + this.Exit(); + })); + + return true; } if (UserModsSelectOverlay.State.Value == Visibility.Visible) From 0a2265b0e88c853108b4fb7eeba81c2381a7cd42 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Jul 2022 17:05:51 +0900 Subject: [PATCH 414/481] Add test coverage of playlist exit confirmation --- .../Navigation/TestSceneScreenNavigation.cs | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 58898d8386..8fce43f9b0 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -26,6 +26,7 @@ using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; using osu.Game.Screens.Menu; using osu.Game.Screens.OnlinePlay.Lounge; +using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; @@ -45,6 +46,57 @@ namespace osu.Game.Tests.Visual.Navigation private Vector2 optionsButtonPosition => Game.ToScreenSpace(new Vector2(click_padding, click_padding)); + [TestCase(false)] + [TestCase(true)] + public void TestConfirmationRequiredToDiscardPlaylist(bool withPlaylistItemAdded) + { + Screens.OnlinePlay.Playlists.Playlists playlistScreen = null; + + AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType().SingleOrDefault() != null); + + PushAndConfirm(() => playlistScreen = new Screens.OnlinePlay.Playlists.Playlists()); + + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + + AddStep("open create screen", () => + { + InputManager.MoveMouseTo(playlistScreen.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + if (withPlaylistItemAdded) + { + AddUntilStep("wait for settings displayed", + () => (playlistScreen.CurrentSubScreen as PlaylistsRoomSubScreen)?.ChildrenOfType().SingleOrDefault()?.State.Value == Visibility.Visible); + + AddStep("edit playlist", () => InputManager.Key(Key.Enter)); + + AddUntilStep("wait for song select", () => (playlistScreen.CurrentSubScreen as PlaylistsSongSelect)?.BeatmapSetsLoaded == true); + + AddUntilStep("wait for selection", () => !Game.Beatmap.IsDefault); + + AddStep("add item", () => InputManager.Key(Key.Enter)); + + AddUntilStep("wait for return to playlist screen", () => playlistScreen.CurrentSubScreen is PlaylistsRoomSubScreen); + + pushEscape(); + AddAssert("confirmation dialog shown", () => Game.ChildrenOfType().Single().CurrentDialog is not null); + + AddStep("confirm exit", () => InputManager.Key(Key.Enter)); + + AddAssert("dialog dismissed", () => Game.ChildrenOfType().Single().CurrentDialog == null); + + exitViaEscapeAndConfirm(); + } + else + { + pushEscape(); + AddAssert("confirmation dialog not shown", () => Game.ChildrenOfType().Single().CurrentDialog == null); + + exitViaEscapeAndConfirm(); + } + } + [Test] public void TestExitSongSelectWithEscape() { From 07e3765b3492b1023039804e9e54b71c42e51cda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Jul 2022 17:25:30 +0900 Subject: [PATCH 415/481] Ensure collection is added to dropdown before trying to click it --- osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs index 82314f9764..d0523b58fa 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneFilterControl.cs @@ -93,6 +93,7 @@ namespace osu.Game.Tests.Visual.SongSelect public void TestCollectionRenamed() { AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1")))); + assertCollectionDropdownContains("1"); AddStep("select collection", () => { var dropdown = control.ChildrenOfType().Single(); @@ -120,6 +121,7 @@ namespace osu.Game.Tests.Visual.SongSelect { addExpandHeaderStep(); AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1")))); + assertCollectionDropdownContains("1"); AddStep("hover collection", () => InputManager.MoveMouseTo(getAddOrRemoveButton(1))); AddAssert("collection has add button", () => getAddOrRemoveButton(1).IsPresent); } @@ -130,6 +132,7 @@ namespace osu.Game.Tests.Visual.SongSelect addExpandHeaderStep(); AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1")))); + assertCollectionDropdownContains("1"); AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddAssert("button enabled", () => getAddOrRemoveButton(1).Enabled.Value); @@ -146,6 +149,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1")))); + assertCollectionDropdownContains("1"); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); AddStep("add beatmap to collection", () => writeAndRefresh(r => getFirstCollection().BeatmapMD5Hashes.Add(Beatmap.Value.BeatmapInfo.MD5Hash))); @@ -163,6 +167,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select available beatmap", () => Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps[0])); AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1")))); + assertCollectionDropdownContains("1"); AddAssert("button is plus", () => getAddOrRemoveButton(1).Icon.Equals(FontAwesome.Solid.PlusSquare)); addClickAddOrRemoveButtonStep(1); @@ -182,6 +187,8 @@ namespace osu.Game.Tests.Visual.SongSelect addExpandHeaderStep(); AddStep("add collection", () => writeAndRefresh(r => r.Add(new BeatmapCollection(name: "1", new List { "abc" })))); + assertCollectionDropdownContains("1"); + AddStep("select collection", () => { InputManager.MoveMouseTo(getCollectionDropdownItems().ElementAt(1)); From 8f1e3b01547884ca4687e8c38a1ca0d75ac29a9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Jul 2022 18:52:50 +0900 Subject: [PATCH 416/481] Fix editor summary timeline not responding to kiai changes correctly --- .../Summary/Parts/EffectPointVisualisation.cs | 80 ++++++++++++------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index b2007273e8..3dca1b1e8c 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs @@ -4,6 +4,7 @@ #nullable disable using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -38,37 +39,62 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts private void load() { kiai = effect.KiaiModeBindable.GetBoundCopy(); - kiai.BindValueChanged(_ => + kiai.BindValueChanged(_ => refreshDisplay(), true); + } + + [CanBeNull] + private EffectControlPoint nextControlPoint; + + protected override void LoadComplete() + { + base.LoadComplete(); + + // Due to the limitations of ControlPointInfo, it's impossible to know via event flow when the next kiai point has changed. + // This is due to the fact that an EffectPoint can be added to an existing group. We would need to bind to ItemAdded on *every* + // future group to track this. + // + // I foresee this being a potential performance issue on beatmaps with many control points, so let's limit how often we check + // for changes. ControlPointInfo needs a refactor to make this flow better, but it should do for now. + Scheduler.AddDelayed(() => { - ClearInternal(); + var next = beatmap.ControlPointInfo.EffectPoints.FirstOrDefault(c => c.Time > effect.Time); - AddInternal(new ControlPointVisualisation(effect)); - - if (!kiai.Value) - return; - - var endControlPoint = beatmap.ControlPointInfo.EffectPoints.FirstOrDefault(c => c.Time > effect.Time && !c.KiaiMode); - - // handle kiai duration - // eventually this will be simpler when we have control points with durations. - if (endControlPoint != null) + if (!ReferenceEquals(nextControlPoint, next)) { - RelativeSizeAxes = Axes.Both; - Origin = Anchor.TopLeft; - - Width = (float)(endControlPoint.Time - effect.Time); - - AddInternal(new PointVisualisation - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.TopLeft, - Width = 1, - Height = 0.25f, - Depth = float.MaxValue, - Colour = effect.GetRepresentingColour(colours).Darken(0.5f), - }); + nextControlPoint = next; + refreshDisplay(); } - }, true); + }, 100, true); + } + + private void refreshDisplay() + { + ClearInternal(); + + AddInternal(new ControlPointVisualisation(effect)); + + if (!kiai.Value) + return; + + // handle kiai duration + // eventually this will be simpler when we have control points with durations. + if (nextControlPoint != null) + { + RelativeSizeAxes = Axes.Both; + Origin = Anchor.TopLeft; + + Width = (float)(nextControlPoint.Time - effect.Time); + + AddInternal(new PointVisualisation + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.TopLeft, + Width = 1, + Height = 0.25f, + Depth = float.MaxValue, + Colour = effect.GetRepresentingColour(colours).Darken(0.5f), + }); + } } // kiai sections display duration, so are required to be visualised. From 3f72e76348955667bd9e61a26fe5587a14516f9a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 29 Jul 2022 16:12:52 +0300 Subject: [PATCH 417/481] Expose `StartTime` from gameplay clock --- osu.Game/Screens/Play/GameplayClock.cs | 9 +++++++++ osu.Game/Screens/Play/GameplayClockContainer.cs | 16 +++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayClock.cs b/osu.Game/Screens/Play/GameplayClock.cs index e6248014c5..6af795cfd8 100644 --- a/osu.Game/Screens/Play/GameplayClock.cs +++ b/osu.Game/Screens/Play/GameplayClock.cs @@ -35,6 +35,15 @@ namespace osu.Game.Screens.Play UnderlyingClock = underlyingClock; } + /// + /// The time from which the clock should start. Will be seeked to on calling . + /// + /// + /// If not set, a value of zero will be used. + /// Importantly, the value will be inferred from the current ruleset in unless specified. + /// + public double? StartTime { get; internal set; } + public double CurrentTime => UnderlyingClock.CurrentTime; public double Rate => UnderlyingClock.Rate; diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 9396b3311f..7b75decb51 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -44,6 +44,8 @@ namespace osu.Game.Screens.Play /// public event Action OnSeek; + private double? startTime; + /// /// The time from which the clock should start. Will be seeked to on calling . /// @@ -51,7 +53,17 @@ namespace osu.Game.Screens.Play /// If not set, a value of zero will be used. /// Importantly, the value will be inferred from the current ruleset in unless specified. /// - public double? StartTime { get; set; } + public double? StartTime + { + get => startTime; + set + { + startTime = value; + + if (GameplayClock != null) + GameplayClock.StartTime = value; + } + } /// /// Creates a new . @@ -72,6 +84,8 @@ namespace osu.Game.Screens.Play var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.CacheAs(GameplayClock = CreateGameplayClock(AdjustableSource)); + + GameplayClock.StartTime = StartTime; GameplayClock.IsPaused.BindTo(IsPaused); return dependencies; From 905bbdc8eebe51dacd9a63535996edd7253df358 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 29 Jul 2022 16:17:25 +0300 Subject: [PATCH 418/481] Remove caching of `GameplayClockContainer` in favour of `GameplayClock` Also fixes `SongProgress` being displayed in skin editor on non-gameplay screens, due to `GameplayClock` not marked as a required dependency. --- osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs | 3 +-- osu.Game/Screens/Play/GameplayClockContainer.cs | 1 - osu.Game/Screens/Play/HUD/DefaultSongProgress.cs | 5 +---- osu.Game/Screens/Play/HUD/SongProgress.cs | 8 ++++---- 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index 428c056fc6..efd3fae147 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -30,8 +30,7 @@ namespace osu.Game.Tests.Visual.Gameplay Add(gameplayClockContainer = new MasterGameplayClockContainer(Beatmap.Value, skip_target_time)); - Dependencies.CacheAs(gameplayClockContainer); // required for StartTime - Dependencies.CacheAs(gameplayClockContainer.GameplayClock); // required for everything else + Dependencies.CacheAs(gameplayClockContainer.GameplayClock); } [SetUpSteps] diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 7b75decb51..b37d15e06c 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -16,7 +16,6 @@ namespace osu.Game.Screens.Play /// /// Encapsulates gameplay timing logic and provides a via DI for gameplay components to use. /// - [Cached] public abstract class GameplayClockContainer : Container, IAdjustableClock { /// diff --git a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs index 7b9453f2ed..96a6c56860 100644 --- a/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs +++ b/osu.Game/Screens/Play/HUD/DefaultSongProgress.cs @@ -42,9 +42,6 @@ namespace osu.Game.Screens.Play.HUD protected override bool BlockScrollInput => false; - [Resolved] - private GameplayClock? gameplayClock { get; set; } - [Resolved] private Player? player { get; set; } @@ -172,7 +169,7 @@ namespace osu.Game.Screens.Play.HUD protected override void UpdateProgress(double progress, bool isIntro) { - bar.CurrentTime = gameplayClock?.CurrentTime ?? Time.Current; + bar.CurrentTime = GameplayClock.CurrentTime; if (isIntro) graph.Progress = 0; diff --git a/osu.Game/Screens/Play/HUD/SongProgress.cs b/osu.Game/Screens/Play/HUD/SongProgress.cs index aaef141349..702d2f7c6f 100644 --- a/osu.Game/Screens/Play/HUD/SongProgress.cs +++ b/osu.Game/Screens/Play/HUD/SongProgress.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Play.HUD public bool UsesFixedAnchor { get; set; } [Resolved] - private GameplayClockContainer? gameplayClockContainer { get; set; } + protected GameplayClock GameplayClock { get; private set; } = null!; [Resolved(canBeNull: true)] private DrawableRuleset? drawableRuleset { get; set; } @@ -69,14 +69,14 @@ namespace osu.Game.Screens.Play.HUD return; // The reference clock is used to accurately tell the playfield's time. This is obtained from the drawable ruleset. - // However, if no drawable ruleset is available (i.e. used in tests), we fall back to either the gameplay clock container or this drawable's own clock. - double currentTime = referenceClock?.CurrentTime ?? gameplayClockContainer?.GameplayClock.CurrentTime ?? Time.Current; + // However, if no drawable ruleset is available (i.e. used in tests), we fall back to the gameplay clock. + double currentTime = referenceClock?.CurrentTime ?? GameplayClock.CurrentTime; bool isInIntro = currentTime < FirstHitTime; if (isInIntro) { - double introStartTime = gameplayClockContainer?.StartTime ?? 0; + double introStartTime = GameplayClock.StartTime ?? 0; double introOffsetCurrent = currentTime - introStartTime; double introDuration = FirstHitTime - introStartTime; From 3b1a76b1906d0b99f8690ed8cda037a94e870f53 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 29 Jul 2022 15:40:56 +0300 Subject: [PATCH 419/481] Remove redundant/overwritten specifications --- .../Visual/Gameplay/TestSceneSongProgress.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs index efd3fae147..9eb71b9cf7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSongProgress.cs @@ -69,17 +69,8 @@ namespace osu.Game.Tests.Visual.Gameplay this.ChildrenOfType().ForEach(progress => progress.Objects = objects); } - protected override Drawable CreateDefaultImplementation() => new DefaultSongProgress - { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - }; + protected override Drawable CreateDefaultImplementation() => new DefaultSongProgress(); - protected override Drawable CreateLegacyImplementation() => new LegacySongProgress - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; + protected override Drawable CreateLegacyImplementation() => new LegacySongProgress(); } } From acf9ad1429ae0162e72f1d1f4738a97f2b18ccca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 Jul 2022 23:26:38 +0900 Subject: [PATCH 420/481] Apply nullability to `EffectPointVisualisation` --- .../Summary/Parts/EffectPointVisualisation.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs index 3dca1b1e8c..b61fcf4482 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/EffectPointVisualisation.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.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -19,13 +16,13 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts public class EffectPointVisualisation : CompositeDrawable, IControlPointVisualisation { private readonly EffectControlPoint effect; - private Bindable kiai; + private Bindable kiai = null!; [Resolved] - private EditorBeatmap beatmap { get; set; } + private EditorBeatmap beatmap { get; set; } = null!; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; public EffectPointVisualisation(EffectControlPoint point) { @@ -42,8 +39,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts kiai.BindValueChanged(_ => refreshDisplay(), true); } - [CanBeNull] - private EffectControlPoint nextControlPoint; + private EffectControlPoint? nextControlPoint; protected override void LoadComplete() { From eea211eb45864bf7708acf41ff6f90199d103ce4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 30 Jul 2022 02:46:39 +0900 Subject: [PATCH 421/481] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 97fc97153c..9aba8e236c 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d95753179f..ecf5972797 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 5455c94998..74d8f0d471 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 4e32d510c33ee3f501610818cb7bfa5dc7da2704 Mon Sep 17 00:00:00 2001 From: NaiPofo <50967056+naipofo@users.noreply.github.com> Date: Fri, 29 Jul 2022 20:08:32 +0200 Subject: [PATCH 422/481] Invalidate flashlightProperties on DrawInfo --- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 3820e55e75..f0fce3d078 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Taiko.Mods private class TaikoFlashlight : Flashlight { - private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize); + private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize | Invalidation.DrawInfo); private readonly TaikoPlayfield taikoPlayfield; public TaikoFlashlight(TaikoModFlashlight modFlashlight, TaikoPlayfield taikoPlayfield) From e0107fc3dc296f9b863f2bc029604ab43a14a4e0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 29 Jul 2022 20:54:57 +0300 Subject: [PATCH 423/481] Use `RequiredParentSizeToFit` to handle misc geometry changes --- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index f0fce3d078..8872de4d7a 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Taiko.Mods private class TaikoFlashlight : Flashlight { - private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.DrawSize | Invalidation.DrawInfo); + private readonly LayoutValue flashlightProperties = new LayoutValue(Invalidation.RequiredParentSizeToFit | Invalidation.DrawInfo); private readonly TaikoPlayfield taikoPlayfield; public TaikoFlashlight(TaikoModFlashlight modFlashlight, TaikoPlayfield taikoPlayfield) From 0940e703b3bf8ff4a6b23be6d3d49f40905a91c4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Jul 2022 09:14:55 +0300 Subject: [PATCH 424/481] Fix normal skin hitsounds prioritised over default taiko hitsounds --- .../Skinning/Legacy/TaikoLegacySkinTransformer.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs index 888271f32d..c49884c00f 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs @@ -173,9 +173,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { foreach (string name in base.LookupNames) yield return name.Insert(name.LastIndexOf('/') + 1, "taiko-"); - - foreach (string name in base.LookupNames) - yield return name; } } } From 40858c4cb7afec09e6a3b4a54b2d2e5c1f6e31f8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Jul 2022 09:32:39 +0300 Subject: [PATCH 425/481] Adjust existing test coverage --- .../TestSceneTaikoHitObjectSamples.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs index 674ac5670f..f8e04df78f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs @@ -17,7 +17,6 @@ namespace osu.Game.Rulesets.Taiko.Tests protected override IResourceStore RulesetResources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneTaikoHitObjectSamples))); [TestCase("taiko-normal-hitnormal")] - [TestCase("normal-hitnormal")] [TestCase("hitnormal")] public void TestDefaultCustomSampleFromBeatmap(string expectedSample) { @@ -29,7 +28,6 @@ namespace osu.Game.Rulesets.Taiko.Tests } [TestCase("taiko-normal-hitnormal")] - [TestCase("normal-hitnormal")] [TestCase("hitnormal")] public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample) { @@ -41,7 +39,6 @@ namespace osu.Game.Rulesets.Taiko.Tests } [TestCase("taiko-normal-hitnormal2")] - [TestCase("normal-hitnormal2")] public void TestUserSkinLookupIgnoresSampleBank(string unwantedSample) { SetupSkins(string.Empty, unwantedSample); From b32ff68a9517990dcb02484981ec9d2aaa5ae609 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Jul 2022 09:33:23 +0300 Subject: [PATCH 426/481] Enable NRT on taiko legacy skin transformer and tests --- .../TestSceneTaikoHitObjectSamples.cs | 2 -- .../Skinning/Legacy/TaikoLegacySkinTransformer.cs | 6 ++---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs index f8e04df78f..c674f87f80 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectSamples.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.Reflection; using NUnit.Framework; using osu.Framework.IO.Stores; diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs index c49884c00f..992316ca53 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using osu.Framework.Audio.Sample; @@ -24,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy hasExplosion = new Lazy(() => GetTexture(getHitName(TaikoSkinComponents.TaikoExplosionGreat)) != null); } - public override Drawable GetDrawableComponent(ISkinComponent component) + public override Drawable? GetDrawableComponent(ISkinComponent component) { if (component is GameplaySkinComponent) { @@ -151,7 +149,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy throw new ArgumentOutOfRangeException(nameof(component), $"Invalid component type: {component}"); } - public override ISample GetSample(ISampleInfo sampleInfo) + public override ISample? GetSample(ISampleInfo sampleInfo) { if (sampleInfo is HitSampleInfo hitSampleInfo) return base.GetSample(new LegacyTaikoSampleInfo(hitSampleInfo)); From 0c125db1972a619ec613f79406ba33b1575cb6d8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Jul 2022 09:57:18 +0300 Subject: [PATCH 427/481] Fix potential nullref on `TestSceneAutoplay` check steps --- osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 47c8dc0f8d..f2fe55d719 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -31,20 +31,20 @@ namespace osu.Game.Tests.Visual.Gameplay protected override void AddCheckSteps() { - // It doesn't matter which ruleset is used - this beatmap is only used for reference. - var beatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + // we only want this beatmap for time reference. + var referenceBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo); AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0); AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2)); - seekTo(beatmap.Beatmap.Breaks[0].StartTime); + seekTo(referenceBeatmap.Breaks[0].StartTime); AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting); AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType().Single().AccuracyDisplay.Current.Value == 1); AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000)); AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); - seekTo(beatmap.Beatmap.HitObjects[^1].GetEndTime()); + seekTo(referenceBeatmap.HitObjects[^1].GetEndTime()); AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true); AddAssert("score has combo", () => getResultsScreen().Score.Combo > 100); From ec1a7994cccda03d7eaac4a37454e6ba9758aeb9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Jul 2022 09:58:17 +0300 Subject: [PATCH 428/481] Switch method to statement body for better readability Almost thought the method was not wrapped in an `AddStep`. --- .../TestSceneDrawableScrollingRuleset.cs | 41 ++++++++++--------- .../Gameplay/TestScenePoolingRuleset.cs | 29 +++++++------ 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index a79ba0ae5d..334d8f1452 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -263,27 +263,30 @@ namespace osu.Game.Tests.Visual.Gameplay return beatmap; } - private void createTest(IBeatmap beatmap, Action overrideAction = null) => AddStep("create test", () => + private void createTest(IBeatmap beatmap, Action overrideAction = null) { - var ruleset = new TestScrollingRuleset(); - - drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo)); - drawableRuleset.FrameStablePlayback = false; - - overrideAction?.Invoke(drawableRuleset); - - Child = new Container + AddStep("create test", () => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Height = 0.75f, - Width = 400, - Masking = true, - Clock = new FramedClock(testClock), - Child = drawableRuleset - }; - }); + var ruleset = new TestScrollingRuleset(); + + drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo)); + drawableRuleset.FrameStablePlayback = false; + + overrideAction?.Invoke(drawableRuleset); + + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 0.75f, + Width = 400, + Masking = true, + Clock = new FramedClock(testClock), + Child = drawableRuleset + }; + }); + } #region Ruleset diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs index 1fa4885b7a..618ffbcb0e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePoolingRuleset.cs @@ -158,21 +158,24 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("clean up", () => drawableRuleset.NewResult -= onNewResult); } - private void createTest(IBeatmap beatmap, int poolSize, Func createClock = null) => AddStep("create test", () => + private void createTest(IBeatmap beatmap, int poolSize, Func createClock = null) { - var ruleset = new TestPoolingRuleset(); - - drawableRuleset = (TestDrawablePoolingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo)); - drawableRuleset.FrameStablePlayback = true; - drawableRuleset.PoolSize = poolSize; - - Child = new Container + AddStep("create test", () => { - RelativeSizeAxes = Axes.Both, - Clock = createClock?.Invoke() ?? new FramedOffsetClock(Clock, false) { Offset = -Clock.CurrentTime }, - Child = drawableRuleset - }; - }); + var ruleset = new TestPoolingRuleset(); + + drawableRuleset = (TestDrawablePoolingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap).GetPlayableBeatmap(ruleset.RulesetInfo)); + drawableRuleset.FrameStablePlayback = true; + drawableRuleset.PoolSize = poolSize; + + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Clock = createClock?.Invoke() ?? new FramedOffsetClock(Clock, false) { Offset = -Clock.CurrentTime }, + Child = drawableRuleset + }; + }); + } #region Ruleset From 369ab10212c87ea468749e3e16cd2e7805cdc60e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Jul 2022 10:31:23 +0300 Subject: [PATCH 429/481] Fix exit confirmation dialog not blocking all exit cases --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 45 ++++++++++++------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 5b3ed0059d..1b90d557d1 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -288,23 +288,11 @@ namespace osu.Game.Screens.OnlinePlay.Match { if (Room.RoomID.Value == null) { - if (dialogOverlay == null || Room.Playlist.Count == 0) - { - settingsOverlay.Hide(); - return base.OnBackButton(); - } - - // if the dialog is already displayed, block exiting until the user explicitly makes a decision. - if (dialogOverlay.CurrentDialog is ConfirmDiscardChangesDialog) + if (!ensureExitConfirmed()) return true; - dialogOverlay?.Push(new ConfirmDiscardChangesDialog(() => - { - settingsOverlay.Hide(); - this.Exit(); - })); - - return true; + settingsOverlay.Hide(); + return base.OnBackButton(); } if (UserModsSelectOverlay.State.Value == Visibility.Visible) @@ -348,8 +336,13 @@ namespace osu.Game.Screens.OnlinePlay.Match Scheduler.AddOnce(updateRuleset); } + private bool exitConfirmed; + public override bool OnExiting(ScreenExitEvent e) { + if (!ensureExitConfirmed()) + return true; + RoomManager?.PartRoom(); Mods.Value = Array.Empty(); @@ -358,6 +351,28 @@ namespace osu.Game.Screens.OnlinePlay.Match return base.OnExiting(e); } + private bool ensureExitConfirmed() + { + if (exitConfirmed) + return true; + + if (dialogOverlay == null || Room.RoomID.Value != null || Room.Playlist.Count == 0) + return true; + + // if the dialog is already displayed, block exiting until the user explicitly makes a decision. + if (dialogOverlay.CurrentDialog is ConfirmDiscardChangesDialog) + return false; + + dialogOverlay.Push(new ConfirmDiscardChangesDialog(() => + { + exitConfirmed = true; + settingsOverlay.Hide(); + this.Exit(); + })); + + return false; + } + protected void StartPlay() { // User may be at song select or otherwise when the host starts gameplay. From 8ca8484f0e387b1a8a86b943b566350232e398ba Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Jul 2022 11:49:33 +0300 Subject: [PATCH 430/481] Fix failing tests --- .../TestSceneMultiplayerMatchSubScreen.cs | 30 ++++++++++++++++++- .../TestScenePlaylistsRoomCreation.cs | 20 +++++++++++++ .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 6 ++-- 3 files changed, 52 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs index 8d31e9c723..9fc42dc68b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSubScreen.cs @@ -4,6 +4,7 @@ #nullable disable using System.Linq; +using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -17,6 +18,8 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; +using osu.Game.Overlays; +using osu.Game.Overlays.Dialog; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; @@ -24,6 +27,7 @@ using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Taiko; using osu.Game.Rulesets.Taiko.Mods; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Menu; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Screens.OnlinePlay.Multiplayer; @@ -65,7 +69,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("load match", () => { SelectedRoom.Value = new Room { Name = { Value = "Test Room" } }; - LoadScreen(screen = new MultiplayerMatchSubScreen(SelectedRoom.Value)); + LoadScreen(screen = new TestMultiplayerMatchSubScreen(SelectedRoom.Value)); }); AddUntilStep("wait for load", () => screen.IsCurrentScreen()); @@ -281,5 +285,29 @@ namespace osu.Game.Tests.Visual.Multiplayer return lastItem.IsSelectedItem; }); } + + private class TestMultiplayerMatchSubScreen : MultiplayerMatchSubScreen + { + [Resolved(canBeNull: true)] + [CanBeNull] + private IDialogOverlay dialogOverlay { get; set; } + + public TestMultiplayerMatchSubScreen(Room room) + : base(room) + { + } + + public override bool OnExiting(ScreenExitEvent e) + { + // For testing purposes allow the screen to exit without confirming on second attempt. + if (!ExitConfirmed && dialogOverlay?.CurrentDialog is ConfirmDiscardChangesDialog confirmDialog) + { + confirmDialog.PerformAction(); + return true; + } + + return base.OnExiting(e); + } + } } } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index e798f72891..b304b34275 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -6,6 +6,7 @@ using System; using System.Diagnostics; using System.Linq; +using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -16,9 +17,12 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.Rooms; +using osu.Game.Overlays; +using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Menu; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Match.Components; using osu.Game.Screens.OnlinePlay.Playlists; @@ -221,10 +225,26 @@ namespace osu.Game.Tests.Visual.Playlists public new Bindable Beatmap => base.Beatmap; + [Resolved(canBeNull: true)] + [CanBeNull] + private IDialogOverlay dialogOverlay { get; set; } + public TestPlaylistsRoomSubScreen(Room room) : base(room) { } + + public override bool OnExiting(ScreenExitEvent e) + { + // For testing purposes allow the screen to exit without confirming on second attempt. + if (!ExitConfirmed && dialogOverlay?.CurrentDialog is ConfirmDiscardChangesDialog confirmDialog) + { + confirmDialog.PerformAction(); + return true; + } + + return base.OnExiting(e); + } } } } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 1b90d557d1..25f2a94a3c 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -336,7 +336,7 @@ namespace osu.Game.Screens.OnlinePlay.Match Scheduler.AddOnce(updateRuleset); } - private bool exitConfirmed; + protected bool ExitConfirmed { get; private set; } public override bool OnExiting(ScreenExitEvent e) { @@ -353,7 +353,7 @@ namespace osu.Game.Screens.OnlinePlay.Match private bool ensureExitConfirmed() { - if (exitConfirmed) + if (ExitConfirmed) return true; if (dialogOverlay == null || Room.RoomID.Value != null || Room.Playlist.Count == 0) @@ -365,7 +365,7 @@ namespace osu.Game.Screens.OnlinePlay.Match dialogOverlay.Push(new ConfirmDiscardChangesDialog(() => { - exitConfirmed = true; + ExitConfirmed = true; settingsOverlay.Hide(); this.Exit(); })); From 38a8b9cf0af9e5bbd523378710ab2adbd908a36d Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sat, 30 Jul 2022 14:26:19 +0200 Subject: [PATCH 431/481] Add battery info for desktop platforms --- osu.Android/OsuGameAndroid.cs | 4 +-- osu.Desktop/OsuGameDesktop.cs | 22 ++++++++++++++++ .../Visual/Gameplay/TestScenePlayerLoader.cs | 25 ++++++++++--------- osu.Game/Screens/Play/PlayerLoader.cs | 4 ++- osu.Game/Utils/BatteryInfo.cs | 12 ++++++--- osu.iOS/OsuGameIOS.cs | 4 +-- 6 files changed, 51 insertions(+), 20 deletions(-) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 062f2ce10c..6b88f21bcd 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -106,9 +106,9 @@ namespace osu.Android private class AndroidBatteryInfo : BatteryInfo { - public override double ChargeLevel => Battery.ChargeLevel; + public override double? ChargeLevel => Battery.ChargeLevel; - public override bool IsCharging => Battery.PowerSource != BatteryPowerSource.Battery; + public override bool OnBattery => Battery.PowerSource == BatteryPowerSource.Battery; } } } diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 524436235e..d9ad95f96a 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -29,6 +29,8 @@ using osu.Game.IPC; using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections; using osu.Game.Overlays.Settings.Sections.Input; +using osu.Game.Utils; +using SDL2; namespace osu.Desktop { @@ -166,6 +168,8 @@ namespace osu.Desktop } } + protected override BatteryInfo CreateBatteryInfo() => new SDL2BatteryInfo(); + private readonly List importableFiles = new List(); private ScheduledDelegate? importSchedule; @@ -206,5 +210,23 @@ namespace osu.Desktop base.Dispose(isDisposing); osuSchemeLinkIPCChannel?.Dispose(); } + + private class SDL2BatteryInfo : BatteryInfo + { + public override double? ChargeLevel + { + get + { + SDL.SDL_GetPowerInfo(out _, out int percentage); + + if (percentage == -1) + return null; + + return percentage / 100.0; + } + } + + public override bool OnBattery => SDL.SDL_GetPowerInfo(out _, out _) == SDL.SDL_PowerState.SDL_POWERSTATE_ON_BATTERY; + } } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 56588e4d4e..05474e3d39 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -308,17 +308,18 @@ namespace osu.Game.Tests.Visual.Gameplay } } - [TestCase(false, 1.0, false)] // not charging, above cutoff --> no warning - [TestCase(true, 0.1, false)] // charging, below cutoff --> no warning - [TestCase(false, 0.25, true)] // not charging, at cutoff --> warning - public void TestLowBatteryNotification(bool isCharging, double chargeLevel, bool shouldWarn) + [TestCase(true, 1.0, false)] // on battery, above cutoff --> no warning + [TestCase(false, 0.1, false)] // not on battery, below cutoff --> no warning + [TestCase(true, 0.25, true)] // on battery, at cutoff --> warning + [TestCase(true, null, false)] // on battery, level unknown --> no warning + public void TestLowBatteryNotification(bool onBattery, double? chargeLevel, bool shouldWarn) { AddStep("reset notification lock", () => sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce).Value = false); // set charge status and level AddStep("load player", () => resetPlayer(false, () => { - batteryInfo.SetCharging(isCharging); + batteryInfo.SetOnBattery(onBattery); batteryInfo.SetChargeLevel(chargeLevel); })); AddUntilStep("wait for player", () => player?.LoadState == LoadState.Ready); @@ -408,19 +409,19 @@ namespace osu.Game.Tests.Visual.Gameplay /// private class LocalBatteryInfo : BatteryInfo { - private bool isCharging = true; - private double chargeLevel = 1; + private bool onBattery; + private double? chargeLevel; - public override bool IsCharging => isCharging; + public override bool OnBattery => onBattery; - public override double ChargeLevel => chargeLevel; + public override double? ChargeLevel => chargeLevel; - public void SetCharging(bool value) + public void SetOnBattery(bool value) { - isCharging = value; + onBattery = value; } - public void SetChargeLevel(double value) + public void SetChargeLevel(double? value) { chargeLevel = value; } diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 5d7319c51f..674490d595 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -549,6 +549,8 @@ namespace osu.Game.Screens.Play #region Low battery warning + private const double low_battery_threshold = 0.25; + private Bindable batteryWarningShownOnce = null!; private void showBatteryWarningIfNeeded() @@ -557,7 +559,7 @@ namespace osu.Game.Screens.Play if (!batteryWarningShownOnce.Value) { - if (!batteryInfo.IsCharging && batteryInfo.ChargeLevel <= 0.25) + if (batteryInfo.OnBattery && batteryInfo.ChargeLevel <= low_battery_threshold) { notificationOverlay?.Post(new BatteryWarningNotification()); batteryWarningShownOnce.Value = true; diff --git a/osu.Game/Utils/BatteryInfo.cs b/osu.Game/Utils/BatteryInfo.cs index dd9b695e1f..ef75857a26 100644 --- a/osu.Game/Utils/BatteryInfo.cs +++ b/osu.Game/Utils/BatteryInfo.cs @@ -9,10 +9,16 @@ namespace osu.Game.Utils public abstract class BatteryInfo { /// - /// The charge level of the battery, from 0 to 1. + /// The charge level of the battery, from 0 to 1, or null if a battery isn't present. /// - public abstract double ChargeLevel { get; } + public abstract double? ChargeLevel { get; } - public abstract bool IsCharging { get; } + /// + /// Whether the current power source is the battery. + /// + /// + /// This is false when the device is charging or doesn't have a battery. + /// + public abstract bool OnBattery { get; } } } diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index 452b573389..ecbea42d74 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -43,9 +43,9 @@ namespace osu.iOS private class IOSBatteryInfo : BatteryInfo { - public override double ChargeLevel => Battery.ChargeLevel; + public override double? ChargeLevel => Battery.ChargeLevel; - public override bool IsCharging => Battery.PowerSource != BatteryPowerSource.Battery; + public override bool OnBattery => Battery.PowerSource == BatteryPowerSource.Battery; } } } From e5118130db0dd53f3f46a7a5404d23524a55083f Mon Sep 17 00:00:00 2001 From: Susko3 Date: Sat, 30 Jul 2022 16:05:35 +0200 Subject: [PATCH 432/481] Add 'SDL' acronym --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index b16e309e52..3ad29ea6db 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -350,6 +350,7 @@ PM RGB RNG + SDL SHA SRGB TK From b95aff3e5f4bae1917cc8c546dab295bc9874201 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Jul 2022 18:50:41 +0300 Subject: [PATCH 433/481] Add failing test case --- .../SongSelect/TestScenePlaySongSelect.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 12f15b04dc..3d8f496c9a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -10,6 +10,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; using osu.Framework.Screens; @@ -282,6 +283,28 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("filter count is 2", () => songSelect.FilterCount == 2); } + [Test] + public void TestCarouselSelectionUpdatesOnResume() + { + addRulesetImportStep(0); + + createSongSelect(); + + AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child"))); + AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); + + AddStep("update beatmap", () => + { + var selectedBeatmap = Beatmap.Value.BeatmapInfo; + var anotherBeatmap = Beatmap.Value.BeatmapSetInfo.Beatmaps.Except(selectedBeatmap.Yield()).First(); + Beatmap.Value = manager.GetWorkingBeatmap(anotherBeatmap); + }); + + AddStep("return", () => songSelect.MakeCurrent()); + AddUntilStep("wait for current", () => songSelect.IsCurrentScreen()); + AddAssert("carousel updated", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(Beatmap.Value.BeatmapInfo)); + } + [Test] public void TestAudioResuming() { From 8e06d55960bdcc7c6f741cbd6480fe4c678f264f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 Jul 2022 00:53:39 +0900 Subject: [PATCH 434/481] Fix collection migration incorrectly running asynchronously --- 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 6a0d4d34db..bcfb6828f8 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -802,8 +802,8 @@ namespace osu.Game.Database if (legacyCollectionImporter.GetAvailableCount(storage).GetResultSafely() > 0) { - legacyCollectionImporter.ImportFromStorage(storage); storage.Delete("collection.db"); + legacyCollectionImporter.ImportFromStorage(storage).WaitSafely(); } } catch (Exception e) From 80ffa2cf200ac634865f767a6957c61a96d296a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 Jul 2022 00:54:00 +0900 Subject: [PATCH 435/481] Move collection database rather than deleting post-migration for safety --- 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 bcfb6828f8..8ab04cce3e 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -802,8 +802,8 @@ namespace osu.Game.Database if (legacyCollectionImporter.GetAvailableCount(storage).GetResultSafely() > 0) { - storage.Delete("collection.db"); legacyCollectionImporter.ImportFromStorage(storage).WaitSafely(); + storage.Move("collection.db", "collection.db.migrated"); } } catch (Exception e) From 6ad86ce5b780c6b74fbd3f37bc2e80c3d4476310 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 Jul 2022 01:06:55 +0900 Subject: [PATCH 436/481] Run collection import process asynchronously Actually required to avoid deadlocking.. --- osu.Game/Database/RealmAccess.cs | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 8ab04cce3e..1a0c03af7d 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -794,22 +794,23 @@ namespace osu.Game.Database break; case 21: - try - { - // Migrate collections from external file to inside realm. - // We use the "legacy" importer because that is how things were actually being saved out until now. - var legacyCollectionImporter = new LegacyCollectionImporter(this); + // Migrate collections from external file to inside realm. + // We use the "legacy" importer because that is how things were actually being saved out until now. + var legacyCollectionImporter = new LegacyCollectionImporter(this); - if (legacyCollectionImporter.GetAvailableCount(storage).GetResultSafely() > 0) - { - legacyCollectionImporter.ImportFromStorage(storage).WaitSafely(); - storage.Move("collection.db", "collection.db.migrated"); - } - } - catch (Exception e) + if (legacyCollectionImporter.GetAvailableCount(storage).GetResultSafely() > 0) { - // can be removed 20221027 (just for initial safety). - Logger.Error(e, "Collections could not be migrated to realm. Please provide your \"collection.db\" to the dev team."); + legacyCollectionImporter.ImportFromStorage(storage).ContinueWith(task => + { + if (task.Exception != null) + { + // can be removed 20221027 (just for initial safety). + Logger.Error(task.Exception.InnerException, "Collections could not be migrated to realm. Please provide your \"collection.db\" to the dev team."); + return; + } + + storage.Move("collection.db", "collection.db.migrated"); + }); } break; From faefda9143ec1185cecc5ea445d6825cd67d3830 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 30 Jul 2022 18:51:19 +0300 Subject: [PATCH 437/481] Fix song select not updating selected beatmap card on editor resume --- osu.Game/Screens/Select/SongSelect.cs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 7abcbfca42..596a8eb896 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -405,20 +405,21 @@ namespace osu.Game.Screens.Select private ScheduledDelegate selectionChangedDebounce; - private void workingBeatmapChanged(ValueChangedEvent e) + private void updateCarouselSelection(ValueChangedEvent e = null) { - if (e.NewValue is DummyWorkingBeatmap || !this.IsCurrentScreen()) return; + var beatmap = e?.NewValue ?? Beatmap.Value; + if (beatmap is DummyWorkingBeatmap || !this.IsCurrentScreen()) return; - Logger.Log($"Song select working beatmap updated to {e.NewValue}"); + Logger.Log($"Song select working beatmap updated to {beatmap}"); - if (!Carousel.SelectBeatmap(e.NewValue.BeatmapInfo, false)) + if (!Carousel.SelectBeatmap(beatmap.BeatmapInfo, false)) { // A selection may not have been possible with filters applied. // There was possibly a ruleset mismatch. This is a case we can help things along by updating the game-wide ruleset to match. - if (!e.NewValue.BeatmapInfo.Ruleset.Equals(decoupledRuleset.Value)) + if (!beatmap.BeatmapInfo.Ruleset.Equals(decoupledRuleset.Value)) { - Ruleset.Value = e.NewValue.BeatmapInfo.Ruleset; + Ruleset.Value = beatmap.BeatmapInfo.Ruleset; transferRulesetValue(); } @@ -426,10 +427,10 @@ namespace osu.Game.Screens.Select // we still want to temporarily show the new beatmap, bypassing filters. // This will be undone the next time the user changes the filter. var criteria = FilterControl.CreateCriteria(); - criteria.SelectedBeatmapSet = e.NewValue.BeatmapInfo.BeatmapSet; + criteria.SelectedBeatmapSet = beatmap.BeatmapInfo.BeatmapSet; Carousel.Filter(criteria); - Carousel.SelectBeatmap(e.NewValue.BeatmapInfo); + Carousel.SelectBeatmap(beatmap.BeatmapInfo); } } @@ -597,6 +598,8 @@ namespace osu.Game.Screens.Select if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) { + updateCarouselSelection(); + updateComponentFromBeatmap(Beatmap.Value); if (ControlGlobalMusic) @@ -805,7 +808,7 @@ namespace osu.Game.Screens.Select }; decoupledRuleset.DisabledChanged += r => Ruleset.Disabled = r; - Beatmap.BindValueChanged(workingBeatmapChanged); + Beatmap.BindValueChanged(updateCarouselSelection); boundLocalBindables = true; } From 93b783d9eaa6203de4be2987e6da816ad27dc32f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 Jul 2022 03:25:38 +0900 Subject: [PATCH 438/481] Fix previous skins not loading due to namespace changes --- osu.Game/Skinning/Skin.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index d6aa9cdaad..7d93aeb897 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -108,6 +108,13 @@ namespace osu.Game.Skinning try { string jsonContent = Encoding.UTF8.GetString(bytes); + + // handle namespace changes... + + // can be removed 2023-01-31 + jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress"); + jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter"); + var deserializedContent = JsonConvert.DeserializeObject>(jsonContent); if (deserializedContent == null) From 632577389df1890a6c3a987954156c8cb4634a4b Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 31 Jul 2022 21:43:16 +0800 Subject: [PATCH 439/481] Mark the property as non-nullable. --- osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs | 11 +++-------- osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs | 5 +---- osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs | 5 ++--- osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs | 5 +---- osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs | 5 ++--- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 10 +++------- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 7 ++----- 7 files changed, 14 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs index 83c1deb3b9..872fcf7e9b 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModAutopilot.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.StateChanges; @@ -29,20 +28,16 @@ namespace osu.Game.Rulesets.Osu.Mods public bool RestartOnFail => false; - private OsuInputManager? inputManager; + private OsuInputManager inputManager = null!; - private IFrameStableClock? gameplayClock; + private IFrameStableClock gameplayClock = null!; - private List? replayFrames; + private List replayFrames = null!; private int currentFrame; public void Update(Playfield playfield) { - Debug.Assert(inputManager != null); - Debug.Assert(gameplayClock != null); - Debug.Assert(replayFrames != null); - if (currentFrame == replayFrames.Count - 1) return; double time = gameplayClock.CurrentTime; diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs index 808e7720a4..56665db770 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModBlinds.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -32,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.12 : 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModFlashlight) }; - private DrawableOsuBlinds? blinds; + private DrawableOsuBlinds blinds = null!; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { @@ -41,8 +40,6 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToHealthProcessor(HealthProcessor healthProcessor) { - Debug.Assert(blinds != null); - healthProcessor.Health.ValueChanged += health => { blinds.AnimateClosedness((float)health.NewValue); }; } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs index 510e95b13f..e5a458488e 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModFlashlight.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; @@ -52,14 +51,14 @@ namespace osu.Game.Rulesets.Osu.Mods public override float DefaultFlashlightSize => 180; - private OsuFlashlight? flashlight; + private OsuFlashlight flashlight = null!; protected override Flashlight CreateFlashlight() => flashlight = new OsuFlashlight(this); public void ApplyToDrawableHitObject(DrawableHitObject drawable) { if (drawable is DrawableSlider s) - s.Tracking.ValueChanged += flashlight.AsNonNull().OnSliderTrackingChange; + s.Tracking.ValueChanged += flashlight.OnSliderTrackingChange; } private class OsuFlashlight : Flashlight, IRequireHighFrequencyMousePosition diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs index 107eac6430..9316f9ed74 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMagnetised.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; @@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods public override double ScoreMultiplier => 1; public override Type[] IncompatibleMods => new[] { typeof(OsuModAutopilot), typeof(OsuModWiggle), typeof(OsuModTransform), typeof(ModAutoplay), typeof(OsuModRelax), typeof(OsuModRepel) }; - private IFrameStableClock? gameplayClock; + private IFrameStableClock gameplayClock = null!; [SettingSource("Attraction strength", "How strong the pull is.", 0)] public BindableFloat AttractionStrength { get; } = new BindableFloat(0.5f) @@ -75,8 +74,6 @@ namespace osu.Game.Rulesets.Osu.Mods { double dampLength = Interpolation.Lerp(3000, 40, AttractionStrength.Value); - Debug.Assert(gameplayClock != null); - float x = (float)Interpolation.DampContinuously(hitObject.X, destination.X, dampLength, gameplayClock.ElapsedFrameTime); float y = (float)Interpolation.DampContinuously(hitObject.Y, destination.Y, dampLength, gameplayClock.ElapsedFrameTime); diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs index 0197b7cb1b..3eb8982f5d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs @@ -4,7 +4,6 @@ using System; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Mods { public override string Description => "Where's the cursor?"; - private PeriodTracker? spinnerPeriods; + private PeriodTracker spinnerPeriods = null!; [SettingSource( "Hidden at combo", @@ -42,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Mods public void Update(Playfield playfield) { - bool shouldAlwaysShowCursor = IsBreakTime.Value || spinnerPeriods.AsNonNull().IsInAny(playfield.Clock.CurrentTime); + bool shouldAlwaysShowCursor = IsBreakTime.Value || spinnerPeriods.IsInAny(playfield.Clock.CurrentTime); float targetAlpha = shouldAlwaysShowCursor ? 1 : ComboBasedAlpha; playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index ac45ce2ade..908bb34ed6 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -29,9 +29,9 @@ namespace osu.Game.Rulesets.Osu.Mods private bool isDownState; private bool wasLeft; - private OsuInputManager? osuInputManager; + private OsuInputManager osuInputManager = null!; - private ReplayState? state; + private ReplayState state = null!; private double lastStateChangeTime; private bool hasReplay; @@ -44,8 +44,6 @@ namespace osu.Game.Rulesets.Osu.Mods public void ApplyToPlayer(Player player) { - Debug.Assert(osuInputManager != null); - if (osuInputManager.ReplayInputHandler != null) { hasReplay = true; @@ -134,9 +132,7 @@ namespace osu.Game.Rulesets.Osu.Mods wasLeft = !wasLeft; } - Debug.Assert(osuInputManager != null); - - state?.Apply(osuInputManager.CurrentState, osuInputManager); + state.Apply(osuInputManager.CurrentState, osuInputManager); } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index c793ed4937..c273da2462 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions.ObjectExtensions; @@ -96,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.Mods #region Private Fields - private ControlPointInfo? controlPointInfo; + private ControlPointInfo controlPointInfo = null!; #endregion @@ -156,7 +155,7 @@ namespace osu.Game.Rulesets.Osu.Mods circle.ApproachCircle.Hide(); } - using (circle.BeginAbsoluteSequence(startTime - controlPointInfo.AsNonNull().TimingPointAt(startTime).BeatLength - undim_duration)) + using (circle.BeginAbsoluteSequence(startTime - controlPointInfo.TimingPointAt(startTime).BeatLength - undim_duration)) circle.FadeColour(Color4.White, undim_duration); } @@ -372,8 +371,6 @@ namespace osu.Game.Rulesets.Osu.Mods int i = 0; double currentTime = timingPoint.Time; - Debug.Assert(controlPointInfo != null); - while (!definitelyBigger(currentTime, mapEndTime) && ReferenceEquals(controlPointInfo.TimingPointAt(currentTime), timingPoint)) { beats.Add(Math.Floor(currentTime)); From 6c964dee308a48b57699fa5877e846e2cfda3ce9 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 31 Jul 2022 22:00:14 +0800 Subject: [PATCH 440/481] Rename the nullable disable annotation in the Audio namespace and mark some properties as nullable. --- osu.Game/Audio/Effects/AudioFilter.cs | 2 -- osu.Game/Audio/Effects/ITransformableFilter.cs | 2 -- osu.Game/Audio/IPreviewTrackOwner.cs | 2 -- osu.Game/Audio/ISampleInfo.cs | 2 -- osu.Game/Audio/ISamplePlaybackDisabler.cs | 2 -- osu.Game/Audio/PreviewTrack.cs | 6 ++---- osu.Game/Audio/PreviewTrackManager.cs | 8 +++----- osu.Game/Audio/SampleInfo.cs | 4 +--- 8 files changed, 6 insertions(+), 22 deletions(-) diff --git a/osu.Game/Audio/Effects/AudioFilter.cs b/osu.Game/Audio/Effects/AudioFilter.cs index 5c318eb957..9446967173 100644 --- a/osu.Game/Audio/Effects/AudioFilter.cs +++ b/osu.Game/Audio/Effects/AudioFilter.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 ManagedBass.Fx; using osu.Framework.Audio.Mixing; diff --git a/osu.Game/Audio/Effects/ITransformableFilter.cs b/osu.Game/Audio/Effects/ITransformableFilter.cs index 02149b362c..fb6a924f68 100644 --- a/osu.Game/Audio/Effects/ITransformableFilter.cs +++ b/osu.Game/Audio/Effects/ITransformableFilter.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics; using osu.Framework.Graphics.Transforms; diff --git a/osu.Game/Audio/IPreviewTrackOwner.cs b/osu.Game/Audio/IPreviewTrackOwner.cs index 6a3acc2059..8ab93257a5 100644 --- a/osu.Game/Audio/IPreviewTrackOwner.cs +++ b/osu.Game/Audio/IPreviewTrackOwner.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Audio { /// diff --git a/osu.Game/Audio/ISampleInfo.cs b/osu.Game/Audio/ISampleInfo.cs index 8f58415587..4f81d37e78 100644 --- a/osu.Game/Audio/ISampleInfo.cs +++ b/osu.Game/Audio/ISampleInfo.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; namespace osu.Game.Audio diff --git a/osu.Game/Audio/ISamplePlaybackDisabler.cs b/osu.Game/Audio/ISamplePlaybackDisabler.cs index 250e004b05..4167316780 100644 --- a/osu.Game/Audio/ISamplePlaybackDisabler.cs +++ b/osu.Game/Audio/ISamplePlaybackDisabler.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.Game.Skinning; diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs index 8ff8cd5c54..cfedd581e5 100644 --- a/osu.Game/Audio/PreviewTrack.cs +++ b/osu.Game/Audio/PreviewTrack.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.Audio.Track; @@ -18,13 +16,13 @@ namespace osu.Game.Audio /// Invoked when this has stopped playing. /// Not invoked in a thread-safe context. /// - public event Action Stopped; + public event Action? Stopped; /// /// Invoked when this has started playing. /// Not invoked in a thread-safe context. /// - public event Action Started; + public event Action? Started; protected Track Track { get; private set; } diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index d19fdbd94c..a0537d7a4e 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; @@ -20,9 +18,9 @@ namespace osu.Game.Audio private readonly BindableDouble muteBindable = new BindableDouble(); - private ITrackStore trackStore; + private ITrackStore trackStore = null!; - protected TrackManagerPreviewTrack CurrentTrack; + protected TrackManagerPreviewTrack? CurrentTrack; public PreviewTrackManager(IAdjustableAudioComponent mainTrackAdjustments) { @@ -90,7 +88,7 @@ namespace osu.Game.Audio public class TrackManagerPreviewTrack : PreviewTrack { [Resolved(canBeNull: true)] - public IPreviewTrackOwner Owner { get; private set; } + public IPreviewTrackOwner? Owner { get; private set; } private readonly IBeatmapSetInfo beatmapSetInfo; private readonly ITrackStore trackManager; diff --git a/osu.Game/Audio/SampleInfo.cs b/osu.Game/Audio/SampleInfo.cs index 54b380f23a..19c78f34b2 100644 --- a/osu.Game/Audio/SampleInfo.cs +++ b/osu.Game/Audio/SampleInfo.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; using System.Collections.Generic; @@ -34,7 +32,7 @@ namespace osu.Game.Audio Volume); } - public bool Equals(SampleInfo other) + public bool Equals(SampleInfo? other) => other != null && sampleNames.SequenceEqual(other.sampleNames); public override bool Equals(object obj) From 094793bbe3fdfcdc5769e821a5d19bf3c0d5e843 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 31 Jul 2022 22:01:30 +0800 Subject: [PATCH 441/481] Mark the GetTrack() return type as nullable. --- osu.Game/Audio/PreviewTrack.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs index cfedd581e5..7fb92f9f9d 100644 --- a/osu.Game/Audio/PreviewTrack.cs +++ b/osu.Game/Audio/PreviewTrack.cs @@ -24,7 +24,7 @@ namespace osu.Game.Audio /// public event Action? Started; - protected Track Track { get; private set; } + protected Track? Track { get; private set; } private bool hasStarted; @@ -56,7 +56,7 @@ namespace osu.Game.Audio /// public bool IsRunning => Track?.IsRunning ?? false; - private ScheduledDelegate startDelegate; + private ScheduledDelegate? startDelegate; /// /// Starts playing this . @@ -104,6 +104,6 @@ namespace osu.Game.Audio /// /// Retrieves the audio track. /// - protected abstract Track GetTrack(); + protected abstract Track? GetTrack(); } } From 5dd641bc60065e5555a8c50b04387cdd0febe33b Mon Sep 17 00:00:00 2001 From: andy840119 Date: Sun, 31 Jul 2022 22:02:07 +0800 Subject: [PATCH 442/481] Remove the nullable disable annotation in the test project. --- osu.Game.Tests/Audio/SampleInfoEqualityTest.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Audio/SampleInfoEqualityTest.cs b/osu.Game.Tests/Audio/SampleInfoEqualityTest.cs index d05eb7994b..149096608f 100644 --- a/osu.Game.Tests/Audio/SampleInfoEqualityTest.cs +++ b/osu.Game.Tests/Audio/SampleInfoEqualityTest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Game.Audio; From db4c6aa3d3aaaa570d6800ec8219a2ac4682e247 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 Jul 2022 23:47:48 +0900 Subject: [PATCH 443/481] Add test skin layout json resources --- .../Archives/modified-classic-20220723.osk | Bin 0 -> 32757 bytes .../Archives/modified-default-20220723.osk | Bin 0 -> 1383 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Tests/Resources/Archives/modified-classic-20220723.osk create mode 100644 osu.Game.Tests/Resources/Archives/modified-default-20220723.osk diff --git a/osu.Game.Tests/Resources/Archives/modified-classic-20220723.osk b/osu.Game.Tests/Resources/Archives/modified-classic-20220723.osk new file mode 100644 index 0000000000000000000000000000000000000000..8e7a1b42df38ea5d2f9a0c325c1fabf3ade153f0 GIT binary patch literal 32757 zcmV)KK)SzBO9KQH0000800;>IRfyT-wTu7&0H*)|00;m80CQ_;ZZ2tVX&uZl4#F@D zK+!#?FdImS?A@5ym|#K-ZtT*C#72pus%K;1NL+!I`9J;d`{O-aMutIC(kaRVf{L*i zOjrS%X-10yQTM zyE`wU`;S7xT4R81R*FMp+PP!1QA-C`d5TWreo#vT1QY-O00;mG2?14X;C7sG0001n z0000D0001UYiVw2Zf0*TYIARHHOxT@!axj0;eAgLvr;H?0JjQK5I5oh!gQvDwtt|L zA|l>h2X*`Sc+ZB2FES_I6I)p~agtIr(ss69v!;H>Z8e7_iQ7`y4I}K1Z|_iC;3}?$ z4%vRAaQaNcbyPqxgk#FGPJT{<#7C_y{Sh~>H&9Ch1QY-O00;mG2?13vP#|?sdH?|D zd;kCr0001RaCu*BZ)a~UGA?j#XMDK@P+UQ`EsVPdcNyH>-4h_e-3Qm;7Cg8O8ax34 z!QCY|gF|qKAPKI)A@7iTzx!_e_18NE#mwpM-Q9byz4qEg^gA_0Of*t77#J8#WhFTc z7#P^AmtPb(;Eh~^(i-rG#6w=sL(|3D!`s}=3Pu|2VqrzC>|}0ZrD0_b_I*EWB?bcn ze`%+s=b@*nB4X*{#9{t2hr`Fo73Sp;lk{;lw{)=bpti8Gv2zxuJ#GI^OKk@hr`6?G zcD900x|_Jj|(moE)9qMSR3*|JfA*KEJ%? zq^15h#lu0I_P-9Lr}~as*2T?=T9AW_-I9xoi&{{a18i<#ZfRk`M$N;;!_CRX$H~pd z&c!9dD=fmtNB!RqEwG&%*jhwGPT{{d1HOsV+Io1nig0pzdwX+u^K!Vj*>G|T3k!2{ z@o@6+umdyL-F=-s%zfCM-Rb^gLC(tE(#_7*!_LK-`sKU1g^Q<$I4$je?%?FAs`@{M zo!$T2Q-IGneau}sxjDEvot!xTGuz!m!^-;qJ= zX$KY&;FlNR73LBU5|CBk=9ZI@7LezW6A}`Xm6w*4;o}ngkAeTa5O~7@2G$D7@X7JY zN%JZw@bPhT%gYM$@(2hCbIZuc^T-R!ax462t+KPbhq<$*)qmF7{ol1b|If7|vTj!9 z9xiTLE-sG$!HTk@qo=!v+skWe9sv$sY9?JfXRwR6JM&8@|NF33ZgyT)UpF+VUFm5fQPVEe;yP{=0wjDT;D3|0HVvy{H%# z=dzfkOqG&i+Fzv>;w`teSMUK=vCbR&kcgAVJuhrAM z?QhjCtTOc_^b}ar+ohqBTmI~SryweW@FC}5o{%u8gLAU7zE|rS!C8E9&~H~Ei`sWw zLZ};A2c9_-cqKC>p5xtz;eGCBh#Q+_Qws>>a1XtTkOCh>Y1X>(2X?%3M!fgrvVjU* zA{bni1hX&-^uvgz2Ay!H3(*SZR!gH&7~AJ4j#^+1 zq%mAB9f3F<{QM;bltn2JPxX@~J)(5zf--FrT}z>raul_K^~zre)CjLRizYyjGEt!r zHd1yErM42g2*1)GN}VAYYJj&s@}I!u1@( zr2I3~RU1#$TM9%Jre8-=XXM!QCJ$KPm1H)^Y5;ieE*vRU(qPRkNj}E9eMk9_b%#3E zgU(mcRk3h&e3T{Vs_=1IWeMZK=t6}8)ZbYBi?*bm*a`xy)O!AHl_qIHLgIdBM`nBh zPL)sALwFfML1qeSAo6XVp`X-hgnqjY3r7fUXDVRB>*)-xrPxhu>hrz^4#8n1q7Uk) z#V8-!!l^X?h5FOXc%@1%xerj{Y5`G?>guwz1cU}K4aEf18c4=|43wQ`WmSh48plb}q<{Q2dUIJTl| z&5jpCz8Ef*h^Jm{JDJJyJPW!~`k>JET!^@`j&SxPchrRxD5zd=h%FL3uNI*$1%eP;=q>@8}=3*bkO9~M zGPL%64#@C$CiRg!u3?R1XU#c7EHe{%gcf)8xb`bz#dB1XP>Dd>PU|9fgfwC4KQk8% zxlk%Aq-88NPRc~!aMn9(CUr{x)}Z6BxI-j)O2?3Le;7kYKLiG;LxsZ=a2h^ zkxSJ;p6B5CEeKC2qN(V#^i3X?s_>PTK%Y}D!}7V09BaqSf1xhD|2kDB)53#ilyeaz z_e?Ce>}=?vTTkL%+KxVa^j%{Kqkfo{h;@OH|3i<^r^;h|AboX^9&pMf3%-t8!)#fN zSVvipTH{J{wOJ%|!%)Jjr=pH6a% zR6js_#yG5CpWqQ4h2lR=?JmgggiQLA-Vdp=Avb+k;m*>0`o>2Ljo2yC5lNWaqMR^O z?gem5wZ`}a5nX?Z?k&U3QwsGd*N8ZL8HfQcIN~=H2-B&uAwzCrp#Uy|L~=PTX}1^L z@D(Q>N=u?Pz5itMN?_VLU0&p0K$w+9N2sRtV7Vh5BS@?ujw&DfX(cMHpf7o*Hv^X~ z#h$6cg)7Ks$Q@<`hFGtlIo2s9&!xVkjC0#KYODqhD1yT;yz$Fp?bNWp*hJv4Ccg0t z{go5i4Td2g+=LEZYyC1EZx8-MDw2g6f44dio+0)s8sShnGD6RMX>LcQ zRLKJNdaW$AOM4!3fiNhoevEDj7;+-B4mjZM7en%nMT%tI9Dy_|%mpQsCfi&@AIvwx z6bs5Jb)~`T5kGYx+D){*=S2bt&!c~vdw%2(B0I40md-1xj3A%w`F3xU17{UyfU$R8PQ=A#1jI{+qn3@Myki zYhk6NBAJ(@{Dlc5Wz--U4v>KlBuD6Bi#zRasS5ekU`g z;UO%7A#xq>q;@EJ#Idv40jq4zMaQ#lZ?3bkHRZa5$SC^BsD|yld}Zu*7`;DQpvn1ClMB+!i_fR zk`YNwh1~E3odEs{r-cRdi%?ksN?9?Fg|r8;1?1;Me*pr!cH}wy3hE!O3KXgD2t5?+ z?n=Mm_9j?@;x>g@3=;kkv3sgKTIkak?4GrnMu0~f9(gfdDo{Ex2vIuK>S6TdM9W%^ z(Qklo@2mbJJYR1d{UTj_r++YkThXTnij>?81&)*FFfX!xk!<>ZjhHO2&l`~g#yeV( zTX#x5-!o7ofcB}atL8G5`Zb?^@^`BB4!+P)LW8loSWzyfERi^HwfDa$0n$_kL+p3` zj(5aqMk&=(k2B!pU4MOiK~`ozhOMmuth&?SC`;^B7JD`zPrp`%Q!Ys|%?!G}1}1KwQtP`*POgfXxUxSBVs zkri z-XN?6F8IW@yaLFD0{z*e)NyL3$V@AUn!3jlwQ$&G6`3Yf^`%TCqB7yV;= zf9JT>okUyP;!P%?jwxF~UgSj6BTHzzxZMl?9d)Z80Tt0xp4H^j%Y1Ppb!hD!Kdoq1 z^qAUG961&qLVaVdjO!%4(XHb4 z;pB0P7jdTbjWE7$m{y+!p0>phWyw8N(gL_LYqIx?ynN3_s1LejLI1}mF%vcSbXi}8 zF-K^X*(|=k+<{FcT>F_=9=L(La6E7W0FEjwG04>Gc#P#JCk9C9K>mD;Jq0*cxd~*r z>(>*Ic8I&`UJNKeIaaXGzYyr40a_{<4EutcV#FALI%F9Bt-!`37Q=G1a!WG1rikCH zVr~HF+(0@T*oLQ>lR>uu|5EoQZag@$0L^Euarh>OAxOgC(AFu{{~NHE1}=%oKBVoW zrVnzMByV)yB{cFJI_*fs`6R=NEQlIY0=4 zJ{&v&e*NKvmf6n#4{K989Ik-Bi|U|yar%V&d^T=KJ>oN~wZl}(8SsvQ@zyRE6CAO8 zyY!PFLpB*J$lmHc%1Nd!&2~dAJ3bICl0*8*B4$os4SjGFAWpM2Uk*gG>6RSKD$S(O zWLhJj6cu@)$oG(PGfO{7tLl$%jlX1Mogyy+%`I*z_NJ*$K`^C9DeluixM|?Ud^Na7 zpacZ0A=FH|B13Hdna7Z-%LmlU6w7B0@WRa;tU)?}>xe=A>@T0{48T(YbuVv`r_opj$9I!Ua={R+`j=4adiDYVk{!*PsYx5;-3EGwVH`sJBDQuF`dEBG z85!lh`GsP^D$hJEB3*-T79H8=uCy|Zme^6gY^v@dLS9fD4=>4vq9I1f%<}pZb4n&c zjDm&&1|7}dHVycMfxqb>(X40_3GgNw@4Bkg;>Zs17*lNAP$AGEM6vx~d+8H0ro9+t z&E#cUeZ=B^eX~V;vp!51wfNPb$WK0&9K@wnb@NFMaNYBdoDrVFM@W3r=PygqWRMhX z!Z-o*(^xrLHI7UNN(9>2tG(=wa!*{qA`i+-U~Ap!%ubqGca))`mW@iGpu1Fg5%RJy zY1g+Oxd2&E8;3z^F#To;qjEOpdW)BgoZdZ2ey`H)!|PK<>K0B4tQQ;m$>U}afC`p4 zd}Y}1Vg`CukWm2=+Y9RmB0!>Pnb}JEG|{4sNGyx3z{h8pa`QRY&ooId{HvR|aQ7%Y z58J^L&Z;RX)^P(x`3uzg8gn>&v&Ibvw$&NnGKlm+j~W>u=Bo^VgIIz9HjHR;bm;Cf zJN~dW0<_WYYy!kO}z31d#qYt_Igv?|(u@!8hMpK)4q*&wb@c{H1*y>2q zzwg|+6-l@(har^R80uKmyY`3^415L>D&AC}uPs9?!;()}iK~Ro?T?`uBKpbLG}Vjc zI>u9-&Yjk>UFz5!1Jj<`vh}xY92}fuX}>(uljaJSdbjlJOs1!%>R{~~131DgU4{Df z*uZre(W2yV`_Ej zAqvgbHs>bGey=FcaY`BtF^PKc1f8BKR^3BD9cFfmujOTBSrdgNpT zQUAQY+kW$xlY^QMc;Hnw>>=eNRDe>Pnwq+hStn5U*#81&(2gPzDoRR9nq_`#Qp98; z9=z~{$stu#&Q(!XR+blQ9Z`CYY0oW3sz`2v|4$&G>w!lh`DUQ~yrO6gqVpWJnGg{X zkuC`DVO>=%6%gic_eT-I`(?-aByWw@ysrje>pQxCTNnfmxBJnK%*IV+?|Pd4o75nd z=?J(zu)WU)FtxytN5SzaC61U;LM)hQ~ht>pCdgeT13=ya#w;EgM@2Kk6H1b_At5=&V^27h~o60D|p8MWx$zkhGOMf?eQ!tuAU(vM8l zbBgAnXFLRwt{=LwDs`Uz>7hN!h9^{d+#I^h@Lh~4(XKx8j_hz#Q;r9AvmeSD(~zHb zw$40gA)5=QKe!CwtEB=n=+=f{ma!z>u`Q(s3v@_onp>VrTl zL`v%N;~;9F_o}V981oH>uZVf`!Qw-24xQXiGFF5E;0%UxLKl0u1+IbJk&+(RSL&G*u$`RiO*lY?8aHh!zf@7WBv^d-xnB@Dk_tVF>puS zq$`{@qs4u7BuHMs#EqfIMi@6_36SU11>7l&0*vxr zCUYokD+wfc=!!srCFjN05CHKW5!_b!h-Zv}v9KzZ#!U;05vu^NWk9!RD`bn`CqQ0P z_HHpDB7K8B2gc0ev))c}$&kY82lZ#@=bKoLJQI{_QY@lF*Kk1nf7#9Lc%?3zx(}YC zkBKpcK7>9(LN*m>P4}OUGz}w0s7N$icF6f#LB!!UpjW7W5<#`{p?}@FZ|ADr0<7Sk zs8+ufqwUY$setpw3%>rcKO=@|Yc*k(l|_sY3&D%P&w++yE{3X6U!JHL*^~ z%&wcSc%DTwS(lhv30Z_;Ize3vQM$iT=P7=)T6*DlO%PLD17}J@T`@+a4)O z7w=x{NqSFLPk@O~Y$nvPp8Vs-NK6drEem^P@LvxR@jA8=quMY9kvBo|_Ba&G#rdD7 za8Sl3##GeQ5`Z6-w~2^w?zMVjO!JykS7Kje{Kz9naXQM%3JNTY<>lE`Sc6knjuCV4 z5R`Fr^2h04l|765YO#@HA%?jJJJi@QGMaYhUtaRo_%l!a`q8=Wh{TBwucy0E=X;rV ze~)QWotXFb2TcJ!B@+#XyvynKP@)Um$S)N0OktrU?_|sAtQ_?|?_jgF6yBOf^C!pE z7XR(RiGu?qGyj^5jBr0JJlAmZQAvNVo27cuknWBGCY6tswto~MrOq*aN)h|=GUaX}rdDL0ROlS5T1y^TVTUBkX z=UUtS&!2dD6*Z~Dx*9H#*x5bOyzgP8r0z{RejgwD!%1NuQA!A~A&yP_k`dTK7t-?d z6hG>Wh!Bus()Z#R9tL@u+Oe22R-irdZHkWVZEjv>0?nVQQJn=C35xY8@Mux6WBrFM zU(WdF9-MBWu#|_Z8PoVg?B1IkFM%Tm7|{r0R4&B>n!UEx-@=&P_f^J<=5ER=HBCKT zNLeW7;~&vady=g4dqX#N-R>5(XtvH)M*>nCdngoo_OGnc?`N*iG2UxyJGr|9rH;3M zKU{w(`f>WxwrAR(?&nBs__*rtfAw`2)*z5Tl$+a+&0b#*xU#uE5D*+17&bRGIEJ`% z%f5Q16g*-;->NBdiP*%53ea8X3<;?+bET9(jsIH_dbHAXEa`VeHmh%c-&|MG-5zwi z^)@mh@3Pz5uL`Qe&O?YEDN`@Z!&nv{_-D?=d8v*F>9wZ1g2vS#Ki3(?Ao9=bqcd@4 z6f|LF>*^u_Yy@(V zW7VolmCVd7+*}R&ywqU# ztLwwi%1X0ur$p$V^A1zZVmpgl)BNg=Mo;tr;oOtVOy$iF*MyckJQkpS$nuIerSJt| zKa_xgpDSKUPi0DzUU|mPxw*NvwzKntqMhBquGCVzxhDIk% z{F~WXTc(`_d3Fafcs=B(qu-0Z0D40dd^_pE0+!b8RWfc*+>67FjpJu_Z{D!>n8T0E zXzJGszq!5ak9N`3p6j(9cETLmQr*G?PT`KCLmqx9LtG@xMy*JP8u9RM_EKA`Le%#{ z6k|M5C*iH?xL?$?omd9Vv%AO#72VeDb5>S}<^%`;l0=aC(f2 zf7MfT@eAZk(sNZ+kJ;G`H(jWqh;sGs4$@Pm=`_y*synYMb43$<#0KQt^0@vw_L_WT z$7G}Y{n+2H#AVA7iJ4u{=y{kYUM*iJVssZm^5vFaT)gU`;3GGD0BFa!J~$kOq!)u1 zC5TcIE3cYEaWdnzi84@rCiB*eeO;W-1>uzGPob_3%rM!sYd`8hEhQ0sjY8x*c&{6p zyu`cj_r`O>D;3$bm=Z!mM|WWZs;f6PZzkbl1!?wqxv&clE)&S_?w_ArQd3jIn(MXB zsXPT>_en_wVB&gjwzp;F!d&nobHzMTn0L(K=BPIbdEMZkKZN z##N<4C8R`%67{A`kn0s;-$O7vf-0R}UG>|eOI1Gq_8{bqR}mx6n>=$%NATt$@1noN z^=virzb0R5ztMJ0o)7(#_N8-D7prLUkez~z3Q>Vk@95)%WwL~d;@%ZXLZGSMb(AfymQ7S5 z!>kv{NDUGx{N11KF@1G=e>Z4v5+r+ijhj%rv$w&5B`XxN?n2))cZZ>pAVFGBJg2ixK*U^dWgCjxKux zHi%sE`S{>KWQ`~fGz>(P9#M^)6Inc_z$bGj5m&>Jdd6QLYjq9{4=kjG1t(^^P$m0t z^Bw6CiX>GNXWnW=DTRDjDXH9Q_<5mC=K&<>UL+_g1q z=>Dk2_Y;`qiKgnltvi0x5x=oqj7S7?HD%?y=|Y?3<)$C#zpwj5{l@FQn9CX)k3yl{ zkB@Q2ntU*aINnsk7xP^oO3eNozF|>K`&wF2VQ04_A}Gku&7G5hkE!fS!NkF_AU>!{ zZxiF^pMR$Jt%gF+3i2bXPU%zD#f3cz$sbiHoB4@`=>{N^s(+m1>?F=3dTT#3jZ&hl zFH~=w9h93^fBS}%+U#8nmu{@AjMzV2U#|&3_WIT9AN4acB|*)VPNFnNsD_i)tNR{3 z*}XTivTNXK(u{&H1Fu6`0To6W5CyU!rQzF3%GqLyEo%I%EK{7P2^airEnh_~fej0t z5$NzZol7fef(v5@Xbqc=0*@P;SDmYmA>xQZxFs`BgJc^Uoe$b>Mvhds@RDm&Q_t}( zk1fT;%;Z?hjqy)BE|}Clo89}b7}sfZi!BwNufJ-m`6f9;m0?rdlL~RI!q7U8ko5ES;f$nkN-P_m#E-J%GcB1t3N=&`k+U<{Jh4`OUS2g=3ZqSp zPSfj2ea&ZEwh)UDXz0Lpg1C8MU<5vZUkJK!us+dZJ3_{ z`qovvX)7xyWT;+X8p-RoerD2A#~nd((w5Mzlqp)o9lp47Js#reGjh>uv#)2co- zyX*-SE2COKdt;i5#uy9I*jGTLRaCT<{KDPuwD|gnQ3-cF!a9UaDVAGxDjhOzy?Xcu z_9GSf5iOoF53({kw>dLO++;DXd@`bBTIEtE15eQW+X@W z@`0Y7po3Qnh7~~n_BN-_sQn$i4KxaoxYv6*(xn#DjfG}=n_M(h@U@q>q>@!+j^lW> zWi1y3#UV;xWqj4nQUztb*?CQ|jHQr$`xLW=&)<&{kGmT23bL8k?6~B8^;=L0*Guj? z~~2h_hhy(iIBJ@hkkAf zVP`EDdjC_jfXR)4#NKi)mS}^Dj0^`MTJw+MVTfo=$9T4>aKNoLl2Zw@F z)-KkA-e)s&bAL+S{;dKre+&q1v*y#$*WdOcO0kX8m8N;U{%fyTsg(?QVdS=9Zw#um zF*X+7HH3Mc@>PBm8H`T@u#G!HIJBi%1L9-piDrhG)w+9CX zCP7c_tqY@^2o8>pWMpJHd6c}2UGR*a?VZ5Bfy&Ch(q)v53YMx;Gf2qox8n?(N3*k^ zMtALOZFh5XARrPk9o+@v4}G`>>>Bv|y0Hk3fXrI}a>!>i#VjerGgtJ+eX}%_S}1n_ zIs8 zG~U0U>9b`OJ|WEgM6M#BKR?IuKZL{k{I(ogzrGu}gOJ@4e0jI|bD;ienQ=j|fM~wk za{oDV!UtNkObU6d>B37-Y6j8c%N0rc#6{mH*|E+HuM&aKIZ4n z4ij}Q_i$zm+EWTI0_=7Xv!Osa_wss|6Ch;=L12kzJyobwcz^qSA%8bTO=(-(R)4g2 zj0Q4;HCN;2-_V|l=pqif02h%zG;w;y#+PTVr>m=}Br)In1S0gVW=l_rh%&MmJNNWH ziFpd_$Nkt>)t==^Yp%lncuSeq*Pa-6MV6Ao%vv69`o-IAP&zxmf;m=&9y~#ulGN&R zZhC*8H&Q24cRV;urHXb^L|MAMGkltwRypb-@1mW^^17RUGd4Vs8-*^k$M?Hwr|i+q zWJDj$vML-Q+8EX2Cu$`GisU;AsavkyT|jTPTS`^rVmfU!pQfVX@z_^`MY3yVV9+-& zVQi7$xW4d$1>n1DloB-~MGouW>feqi5$t}Daj?(2f5`7u-1SI>i$z%f;FZo|(<1ti zmGy_yFCQdG_BP-GZANm=H7ERvoftF^dNa|AOJqSGxyMS6gQ zAN)~`<&Z<}9GpW@YE>rSfCxfd&HqGvvhRx^iDZTrV)NmHgXYZO!2#0kTaC^iwyJ_` zrZ6{od6Al$zM2ZXEj`ui-~Zy{m~T6r%#_u@(5Ivn!o$CPcxY!}G(`vt%sMt!`o^x@ z3ySJ#>6z^Q{O3lpFbP>*4CmM0UOdn+X|-4dxS{5}bHCiXzRpdg+CX>>-EW2&>Ii(& z+|=IPoih@oemA|m{HJCd_SpkO1rn!t?OmV0ip6?*BqoP;e69-Zu0uBA=jTUrLC!~j z!iEjP&NIlRw6?p~7g?FDkV`x=P#5-Ws$HSJ%3JRa+$3XAqdz?=rKY#LMEpp#} zWnr;57qF}){_dcP!;#{kD(Y)fAVhCt`_pPJMVsyw&)OO~D#<4%GvdKfDplkYP`HaY zphX~KQEpDbe?+}GawPwr%#<{5`Qt%HOSk#Qu|bQU88%>d0Y3Hi-oXC&*d3u^JdP3S zb8DQI#&2mECS{EIji=?JxqWSYd+0mn&H_~Pz3Y#fKtNr6cz%@J==4Xe347<0S;@Wi z)3KLo%M~Z!y58m{7!uNlM2bQ-&lQPj`TH`hmlAfc`;S42`3Q*~v6gV*;c(J26pn#B z)Rl)nq2MFj@2!M{V>y~WaT?hwwx`fleq6ghL$ky2^R7;Lj^u{HqAG*6mM$4-+GI#4 zAjh{70Y1AkZ-WqLF#~>Z=Sc<-H!H)!CY7aIz}ngr6?T1fd}k{m))M|+CAMGc zvmg!P9sW1mKXCXhW=G3@ece=*d^pm*IWb0xM5|W@bKQ3cIjY}c)@&#!BIk>@AKpy% zIJ!BV*0_bikX~Pjp?wS$bBIyj9aE@Yu!(b1xXI!z5^Hg{ld&F7qwnGeN6GAc@S$7W zle^y!A7Mr5lWaGd0`rell+xU|=S5DvIO3J1M4cINh8dhc~tG zazo*Qja^O)zkH$mdVx~kQ zepl>LwD9i|%Vu3Rx?u2K{QTgG`-4aC@qYrEBKWFjXKt>so0Ff~6xPMS$Veuz^!*M+ z=!S87LSuuYK)1H0Bxb;MA9j#{&?4f)NH976$6Hq+1lWzEBdre~&_8#;z9AyS2yg1f zllcPkUH(lnFC7wt&Ag}Q+Ki~MEcygWH|fes^T}duzPAjL0IAAItGnb|dT_Gb^S7E| zs#nobCUs1;Wo6L~BC)%>NXRPP_kWJ1Rxp*I-FwJ*m^Fci^S;UP{94K~HWUQ{W;5~e zPIBDCL>pUo^z^&i+kLNIamEdI^toL5y-A>aLi+1ZD}N=k6i=4_;e0?6egm|X(bv7C z6(rst1P^a~IM1J+s&!`4&6Vfe%t#n}&T;DK>c&8sc=#bb3FPvHC%aH_vRmMnYLVHo zafeS+^(nV16s7@ZdE#d}E$;UOA-41k!6fI=J6yVyup{2hZr@W(V(%zH$=gk;;0KtR zz1>i3`VWBo1yD>zDv%@^5@W_2)z|OjKIP}{ANm{UE*xE+AE!%&6Yc_yX?^`FIv&hbw~S=KExRkD zQ7c|+2L9jMZBsE{H?lz8K+d1ZtS*p~6XbybE2*v4pQ1FU1Dk3Q5x4UvCBr0%Lx!d< za&8f;rSqD?viLlrUdJLM4@yB+G732O`qw-laIi@VsktAA^%vVO)@+V=!<(b?HS(e& zUU-d?3nvXDS69T;fV}=xeDFD^FRJur<8OO(v^n0$`*3(=4G|G966$fJ7@(P(5JjO= zx5j9Cg`+3s#XH78@FU=UUsB?OU+j${9Lv-?mf_&A=pmxm)pFxopsG)aUWpZUqSWL} zsI3JPrz_go&2@cXM0NTp{u(b1PpCJwdi?9X<-5P*b-zWxOHQsbgNJ_r@b4KYASrB| z=0BbF<-@uELMz0`9y-mBz(&f+I(&v5b8=qaxlB7B^wyKvL5mCfRsHUiD&YC9I$ts< z=?29yb2LN}AgBd#@x;3|I{Nz3`R9sqhY%N`?+_fcJ5J;KV3Qwv@u#`DiqJRmZVChh zOxWZ;@NmrV_&i)|*iI%z`1{R->YxRU6Q3qY#;qnVKF=|oF7<8vKA^^NnQ?-&s zdpZc29@F1S+zp+dn`_eeqiqnNFi1S#5*5+#ROg@`cdX9N_VC}Kr0D7BXq#^IUN(Qz z_{pj(;diltoC8p65Up;!PrQJ+ypxGbDQu}gve&JTg++hx4h9$e=4mU)MoFgF;_tJj0zFtF zFDC%<8Vcj&J=4&xLma0NHVh&PZO2~?=@`YBV_y1QG^wj&xH87+0mtTZe|7!zkf9QL zG#eZ);pahdiz_(>C-kkIH_L$7z+|Ly8cuL}xp;y-ufoGfL;TY;V?TclSgBCPu zZ{+pTYvKO(?YHgKG3DtjZx<1SRPu|8qQ?H%!WoZ4jLp9uNlPp)RuDd?Gn?Vah|IqT z*#qC&!P=#GNO{4K^77H>yZ;JnJ~?PBXX;4i<)T(q&5m=^odC2~1icLw#Xac}T*NIW z9=8Y$ylra~d)<+BnW>iCgt{+e-%2PC~VsuFfj8&TqrajPSP?XIq{cjeYuHSk3DEK1U=t_Z%Z!3?C<_ zlp#$=72_r?H|yDkVUoUAJX(sNuXvO(K>Ira?cc`Lpa>?yLSvOBTdz~{ORO!n7H2GA z9CZu&TVL|_R1X22@VANgO;}!@V$Q@*vv1|)hr(;Z2>I%!-$POau+QE*JKxV&a*oXq z3uM1@^z%EK$c@dJ;-Y+uPn4mfGk-B!U#T)-)DjK~0=2S~3ovFiD3qf%Xkaz>yNDmyTV5!rsa%_r#aWV6(ZbEWrqU zM>=)q;f8a3Ld}2{#juwv6@b5*YW&%`vOOohlvE$f=*-Soq2fZk>I8Uz2%7d)4Zcm7 zLmA2@(n`LY7!ccqQr?EI!JU@E#>0&Aqs{Yh81n*z%=Yg7VNT~Y!>mMAv;`gAlejoG z^~EOd5v|;twm#TWoVu!(PqF0@PQ1Ll=eOso8WF>vNvoettQ~z9o^1Ma3P`JGp1`!UabHw8Hfcx;n!oSxM)27s*LqE)+wen=X?jt1wUa zPUytTuhweq@weT4g$ZsyPKt?$aEYysXB)ZFpZ4{2ib+U*gwGf)JY^=Ym_0ZUp`5^P zQsolbnDex$6>Hvw`crc)%y}jvxAcM9)&}V5(-Z{uRhez#-b{cQFd<4UogZwvfR=E> z>T;N>9@d+(PKlUE9S1*gXf*nLH+i>7j6z}hXcOm0v(NrHpF}Z4nYGiWPBZOmYwGIk zDRJN_oy|Tw`1$o3EL~eUZr@!#K$Mhj-0_n|(^y#MNmi%Q1cC+Fgk@8Cw>(6@78N~q zhiFECz&m7u5{6kxG(mO>ca}PvYDOuatJyTt=N?lhbziqR96b7^{sx*C{+4&5*mBwV z`E$ndiisMp->`ZjdZ$VHb<&IR`JRO+N6Mg+NzrUl%UR~kgMED)dtBte?d};IAI!Ae z_Vk>bX3B;$!8~0-f0^^EdxPz_!ot=j^Tds^l5pkalgF5X#_(2&z>wfGzfI8=w9IeP zbF>X&>4=U+wY5DmO{lIs;>`o)J!-=YhCK* z(@6o-MyosGdTNeznVr4frIfEKYtSsr39~*p0nuhQIb}yrge)8#Q60Wz1~bRK zQ3*Cr>J582@)(C8va4YIxQZZln8eM>uEZQQ^Kjw2o+GaOVF7DQ zB4|m4C1@7uB({J`rw#edR2oYJ>L$mfZKoVZ>D!Mof&SgnAUxVglW@qMx2Z+cu zBWuo_i9&4nXcxpG9mH{yJ3CP)2t*1LX9>23m+s|ZMh+r^{QMK5r&RF@q@)5zi|?86 z(UQW)$dDMp=~XJlE4hQiRE?pcyC^t`T<@^8pOp$efu}<|G#H3tJ-zF(y5Yz`tED}$ zQuw_z?C_UNDGW}i#(7AGc}0jV{pK|(m`v~N@ z$#WJL`{puJg78AQDA0MkulJ@WU!jNwi*SMb)Ljx<-+wanJ?+{ zxEQsA@ot~jCh&WuQ~Nn;Cbh^_0X9z}@NlWZ@`UzB=BWqgOUipmn3|B(dW`otXF8fMTz1 zd7`uldr;7qIn61yqF}@BVVSl~G6?M}fxv}AW4Q4OLWs1!-|G}zKt^4UApkPEz1Pcs zT78VeA_HU;33Ct_!8_S<+=TEh?eYCm*KQ3Ad9>UQ0d+C{sGrI-=()MnacKz{u^WLC zUo(oCZFnd4rJxuY9ZGI{&)Y+Ss_Q>~Y=)~$hdy$ra{ee~mz4oS^tEFV{M2?6S^rW= za3=HBg(kyVmt4XNBv@yAZ@m^)k+B69)2Y#OVe%>`pRRY7>B=y0UMPCGzGc2!0D8|h z!~zI%|MfeaPfjz|6gZoX)&+7+f>}8<#<3KHn4j%r_NEJ0M3}K=+ev+kjxjd#;@Og~ z;k?P9;wf@tbSu%vz0Nf8U(osbYmKVY__seeV>*uWWCQ7d-q>s;BE99RG!%{@7M4@E z_X>hgJjtGvG6%lT-4|H&@2jS}lgJJhW&b)lg={wdp4R4uP$&l`xMmvmHIHYf?B72m zv6;VJ;fROl%70l~S7hYSpk}hRF@P`Vf{cvZ3cjK2NW?b1m8#wzkI#>`7RXf-q#Cz{6f6AbP{kj(yCbno<-O;Lets^wIW?U= zloCq37?PJ{9i^lp{li-+teBOeUFoL0Hg?F)UI!r{a`&Z(`su61G)sXgPMPm?;TSCk zXA+sJF#X|kOz&1wDZ{I?+VauY!z57xw2;lOZ>YliF54=aA4jM(Aq9Rr%u{F556-&P zbd^l?QvM;MgC&Y)IohJ8}>sgm=%H|HOdoZWzA$7oj} zv!+9e`PUOk9B=Xy<1u4ch>_gaNwK!eC94+q3fRj16+b)W6MLz@CS+#wWwPW}G2Vo^ zP88n^)_I^~Px_$OpnAm0)J8?e9N!Gv?VE0NWEMdpn?+j5nYix-9Q@u$as(|wyw{HTEvis&P zUUEDKCSt5ZySK^(@#{psb9x;rMdb=AV-R7?Pk&#E$Z@L0w- zTkO1@^+j(qtYe`39s@MzpEBH-+BuECg{Ce+g9{A}&I4O=X#9HBst;d?K%JCTI**H4 zxq2#j!?1^o<0cu@?)QTbyn=-TV*5GTV40MKTv}F_y8H%|R_wKWoE~mz6jF?AaO?H3 zHMWlwhxcz4@-A*y^o@kiV7Z1v0n3oroOruhCf=798A6W9j1(X2D4Q~`R<&s|Jdoc` zG7b!`2l%6WKM00;+5*-(P)Q6BenfB*!Xwh`9RIHDfN+s<@AcFo_)_2@_4UPS92T7K%XKyv$sbRWtwZbnV2?% z7Q>I*6~VxOLMAu7H6Vof)i_K`D_&R`l&Wqf?MdbTu$=q9Sl==pqQZN)3m$LdZ=^F* z5y{AKMqJLvW6ZFzRsW4E>$eem=Jv+r8APphTtQS9hP5W9AEPUx zA}zziLMRaJ2$T%C8rHk(H5>dY!&uuY=@`Sv<5ob)BjNjEkZR}4k@>gRG{>GTzQ_}{ zfRUYBdnL7b;48S?l6$l1C6h&QvVW(oH=&5Hn95n3=;agm$md0J%y64lpbm)&53T-t z()aCP7;dQ8_TAmR)t2-bnn=ql(Oqg#g%Yevfu|dz<2>zZk`pPSHHSt+P|gf#yb)I|zB__KSVh+2>ytKYKmh)AVMYJI)WD z0C@6k7mRpxKv=wExFrW`7#EN+C8geTFyL#rvC*X7Sl5=>1phWadN|z+TrP4;m;G0;-DI_+wS1Fn=(rjX7%%aPQxUB}1q*GYP9EmAd}Z1V+R zAq~^^T)wL5DQY>j*RW0_hII-VZW-g1;RiXN|CxxcTxZl4UyOZK_nwAfCImsg1?6&2nWC+$k+fV>Q4S4LP#=Y+T7?XZr5`N@z|Dk& z*e-!pf_JUl6vPo|OPZ`aBDWk*|q1O`)!mXKyVnz*}ECwnN!`mCzagQr7t!5Y1n{_UAl zEGHtK=Icj=^?79>A(6zd#0U}0$w6z12Jxuy(SLE;+plM$pQtM$yTt1?HF@k;y!+_V zG5>&s9UB542u$xNQxBUL8OJe9Ghum-y>m7Ae?|b7F3|3Bv#u5(o2f z(ldtPHh^3X8|5S{{q1VObgbg<-0M5L5{Z9aXO4-%UA<-(q7FAK_I&r-i7a5q#&v<( z<#i=sZT&YQ8D$qdbYE^r39JbfP1KsSpXx&o)k7fB+<@eKW90n&3m(*}9jyM*)zYFI z6!bJeHycd;{aqh!MJelY!t66aId8f1@uS*qqPZd7quU+Vs>t|e0Wf>jEh<|?+S(1@ zTt>}mRjzD>-vqG6&CaBFei|9W zmEJjjmy_yiCpE!H3N2Ei4|FE@=1i!crBkgjo72YcS6{W~H>j1EKXnETHC1T{CHlPa zrQq_p27M}m$XlCXQ)bZc^kZRxcorQbCWj{R^Zh*+g9?i$2#-dvcmhzT*SFR@si(Gv z_1mH4UXZe`cRCl>r8FvrR+#HtU?0Y{CD@;+@6plI|8h1I!fB>$>NKGnEIt>|;<9&%P3_X=o%UyWZv6V{sk;j!#R(xfF(&iu;z#2uK$%8)E%sA@ zyemE6Byc}_JQ&Xn@8Al1kUB+B`GE~9yDpw!2ou_(N+s0-Xwe`-V+u-;$ok)1kCgGVmCXKkN{EwwDeEt>e@T zP!LD0J6I@6i~LeHlP7;g;V+ed!C~xHS-Ofx!%LWU@asv7KZbGcp-`KuIGen4r3-J( z2U!qfS*T1nQc5zetL`vR*OuIVoU~5HkN83#kbVdJYz9Brg%g@UejPccMiJR)_1G?- z7Q{fNo0q1mZSRGfyXyJ*@k+laHQBRkV!}WgT<+LP5e`WufBa|1JgBBEEOGMpDyBXe zo`W3&GHVW%ThL@W(W7lK3aJcwai(FLqd;nfYD$6h#0~P5F;4DwufWqTCrW%xn~Q}9H`_c`jJC8$m^T{rrZ$U%GsOsrQs?M*ecA^>q-C2+(@fz zQCPC}<@Q;bruJU4AL9P8h(QiS!yaotaOp9Wp-$E3^i6xhetus%#HafX{7@J-OiMum zPT|l5tgDmS+eowzK)dVbLP5bJIYqg*VLIvPQNcEACx-*T#~l#6&-XEBzS*SX52vuy zs7VYlCW&|qR3i>zy?p}&H~vT93x?6qyz(R@IAor zyy7nn@02K-Lqz%1sj4UAdatc!c*Ltvp+svd_c`=k@n_bx6~7|m5cn(ca9&*eOA+CZ z^qsFxa3Wg*v3?j)?ZZDCbtPA~a%XHgKNTA&-`if|`M3spvM=1Ig zALg`jI-{-O84``YII~{ZZ?`p#bDg~7Qqibv*Nx`!9IW+YMk}htP@xCA8w3yy1y@RJ zR=WX((K5Dp$HU>f69?U`y?pm_0G9z*p~d)BrsolD5X$0xxUVb2P@dqO$;Hhtde_$) zv=)4xKOejZdKs!%yrJv$!+Fud&%?zfmO#RNt%->uYmlw3L`B46Y_s5`5e-!b;d_5@ z;XOTwp7?d8O$Xw-=I4{c!bX~!!dqSY#{(1Gxmb`iM&7c%+(AJS)=H!a3=VnnizOi; z>-1vdaHxB8QDbTCU5*f{c0*1zncSysw(Pi>gU~2rr zq)B->;N#s~xk;(tV|Ae!D?8Vmb2wRyw5L`rvIi^Zt78MLnM6b$BMJ^XWtJr1grN}!&>wUeXi2p{z4L{5FxTV}Vh-HA0(-uH^@6HE;^f_e%f?V+z6cQZ>_X<5 zd7}_3?Z+nsaP8_@kdzM)Mr9Z$uwXF~(iQ~k-yX#o((jWP!=)3zI}wJ>=#|0Hq;+Y- zml@t*T^-d(L~gBM5}8&O&xO&D`DcA4UvK;%OFk*P(69byf~c8fkFH)^S=?vLi*QSV zT1LY9i7DMxj4r&xVb)em6muM}p_o*r;d>!5G>X{hCNWuzCf@k&+O7%px>A>*+%Ui{ z-J?xP8}k6qCW^Q|4!ud;OUE=`twEY`ZZB?wboxxs=7N(4njTx7c0_+v9%qKIkULdM z&JR>hT!4{WC}D7;eZ(9sSnyPXAFPe`d-%8RMR~s6y84BSPnzsdS?W&&2&jmm3Lvo8 zI8w0Q07d~CwunFo>27`$3YT>2j!r_b{Mxtt{6Yf|8jF*sr%|)T1lF*`^q`eH-A`Cx zCNpnCzH+#|%Z^VF>0<$*F(T@*IfKpegw_5OA{ZKc?8~(#`xE;P7?)cg_5F!H@*f;o zXkXzcd)w;m7vufnY}HswQ>ln)adARWv8<#uVApp0T^XULsNh5fStFqg_X{qD#$Ya( z4N9Y;=pm4Kc)rXwD@@x*togdZN{ko&aD0w0!U!n3+!Fbtr&SM;|?BY zOz=AttFSB@Ortz##!%I6%$lMsgpSDCO#mh6KBERs9;4x2BVnb6LsXb{v>_o4{uOW= z|I2RkR%k#@Mk6m99L}aPG(LHK4f=7g9I8VM_@-%$V>6;3i>`pK~xD3L3|x6B|+M z0#d)m!cUV1*Uq3)b@lB#Qw;eYgZENg_b$ktvg}iLGTQLwp`OUiP+nOqVP!+4bJr=; zKP^H!@??&Eli2NM@nm<*GUgrv6Kv~?26ykPZ-S1G)tkKXNt`i7G&W8`1k_7G^9mu{ zJ3@Wdp5RXNVrV4asY-^*%T6Oul9Y^#6wQ}69%YG7rOS1up@_$G>Nqp%`VGrJtN3SU z*8R#PfJ&rAW&B5GafE{U%H6etgN3E}iE0ZXW##s7_3+yb-kSZ|pI85n5OBQML}T=y zB+)D|<9%UA(#}&+ZPI0rpt4nQu3|{*_AVC#$1=p?Pc9YY>C9nz3LS08K zC=T}ayyud^=}@zV;|$trU_{~GHMv~nw<;edf50$FeMS!kL68xK;m=+RrZD}E-DFLz zfqDYoBYqXs(;EQ-jEoo_KyK}Pz7=`$-8a?7I-9GgXQ2N)qNFPEBMX;mTw#sVR_xxT zqRB-wG!7l1#rU=$WUV3G7i^QMuKOFe;|M){P1&;##DrD7(GM@K<8!xV5h}AJ9R*tGDcFWHA)i_sY$Hu<4b4ASB+D)zt@ zOF688X0jC+o%7gKC+5jvvFX`PC9{ras~7qjaZoMeJn(5*_&d^ShQ8}&6*DL&%-HJ! zm7~ozw&&ykClEPezQ-mL|1^e=s2)nfoJ9DVV?D8dVl1AEemu>!U#JoPV5HKASHZTZ zBA-nKht%Lpckgg>EB`_~AI!+de=u^CTQ7OFd-%k3si-;VTIxDM9DfdtLlP5<>EMyE z9hMPmm3qvoQd>42b>6oCG^E)F>1#F=@cHRSE}ZIULylJYAFMYxbF9Ny>*NMBmE>Tg zEm;=(U~od+1AdCcqvc|^l9bu^td#M}4iIEW7Z+KO zut51J*png;wpdtTOMxRHae4NcVQFYp>TyGoaF&goY%{`8-h6J}cyg|oA4+jor`VYn?2`-KLqwJkolI&SJVbxWvsYJ)wn{B zrkUt_0ON@-_0S;R-uA1v+r55Za>V`xQ$|EA9#l!AKju1A%qPHA#_avsJd;OQ7Ct2M z3(eBKj+UZi7ES4FEuc;@r9jX$FsoPTCB-k#!9p1EMMCut_q-jx@f)?T!eC#0qqr+$&>7;F? zDnBjorsdz5mStll*6!P!IDLu(K_Y5Dof~@0(;Es2Jqizp$yLS5$+s(Et&+K#H`aXq zqHx`L7b>vn%*cxfWoQSML@dTU(_VMq$z*Kq;iti9?(28f{X~p@8|+$mPwmFM{@FQk z-Bwo$Tur;*vUd-D6|UuQo_P%ne0xy>qz7kaNJeI%q47-fmjm?U{_nzl0ZkU2OWrV2 z4o-=q=exH+=)(eKQr&hoEi^cZ0EPQz$9#0q9Qz>q@^Mg}S}7k0^}sEEi&ciGoxcQy zaSeG79|l)&)Y8N|aWPpw<%tnW>#_#eNnD)SKs1?AbboBfi2YtKCaS0Bx2W0PlO~tt zv-O(mJawoZP>MjS&#Se1T28-Pw@udwM(II z_70Q$N2sFq@*ZMiSuV*Ye57x6o#&7wSqSdNqdAW=+`spplcll z=4y(PMj8R*cg`TEyAREIlF8d#pP2g&u1_2+kA5tvcsbBh%PlKsZ?Id%Z%jP%u-~uP zvkJqDsi`R|q?d8WO{GIRqlT$E3iTz8+<>@Hv6Znj5T+fAVPFhV%JW0m+v=M6#;0rj zO-kmuzNH!-$qDa~zb2AW3_rQO0VDd#uLPYdtIP}y!Ri6DLkMR@{PdK!wTb>)%_iOP zoP^v|YG^|1+Q8LPuQfln#BKj=(uU^+8u>Hq>`cYEF@?|{nHURS*r#k#0WaAqEb}nM zY1k;$!9=TitHtENv0IO}D$PZ1ry{6=cgR?7wm)Qa(Lzef5uyD%o>W`Tz}LG5;)H!c zEeu(P<_nO2QsE_Sc4s{P<{_^3TV2b*7nv0BkQMaA&NPS(j*Q9Btku6s6zQT~4SoE2 zC@4aN!d`Bn2@Ty987V`^W^HZ#6zt7hBeoRoog$sPz+g7{8~`^krD8AtB4R{xQhFavkKs!66)iJA;EA zflQwNPEhb(%A5uLy~)B1Qt0&LF7FEoN;kG8CU(A{b83o=)T1u6eZnhad2upA(sMNy z3=?=?>?T(QpdJzY^Ar4&L~E0a3u%13sX(euZ16zByc5$veyDh%aKL+qe|qZV600O% zmKNyn34#r0r5I2OhGKBzo0M2@tQ~frEnWz?lRsJo{Z?aAPTO0b&EUROxdJU-PXXRQ zmFeZ%23lU6FrSBLY)ie)eHL76Y3}57;o}bAB@`Y2r}^8uD=SSBAcvh2@P zvq;)PpzEiO=VJvSC&Z7d&;99vzbe0a>5@Tg|g9^Jt)fY2=;TWJn97j4f2+pLw#AxgsL3>nGM@Ls*lkS|9gk~qC3Ow{WnaT zbq%|{>Sn8h-t;e@8Vy1!a84~HU8N#Nxd$Zmzi1Cg5bu8s$aG_IHKGrbUwjLTSV&&C$ToagYo#?LX_$WckGMhGlcRgM9Dp z5k9Y=ovOC#iWD^a-ET$;!ZAgthhtD3lQh97xVc%oT@7u*I*G~(hQc6VNQIz;y7!1J z+KMp+C*(63_q=w|vLQ&sgNu~qyz%Kt%$MjRO(~YL#HO@xf=}P;Vie1d&n@rMYT(6M zS;G34X5t}*@)6~yGb#h`9b0yPz@eg`tkDM27M*(&kSgKKLzR@20FL<`J96%jWsYKs z?ZN2o++mkH9$wLFEcyllfVMSQmZo^=@lNkG1}Jz-byVvB?G=kl)4LEHiWPP}HY1AT z)r9+B`VVn7itU|p&w3oxSdPv}?*(!bgYRZ$7EFYoGV!*p3U&xmME-O688w?V_%&CAhQl?;@=}_h_&6fpuQ8s8oaCIozAb2 zX*h)JxynQPisi~0)j=eXNy)NvTHU#O>b&gJU5X#cQBITD;T6`DDM>)wLv$JuvjQvf z%H&YTYR5@Z>iCJZ!dN0@rz5olr2RdRq_GQ+_fcSts zfRX`{b$!$DAGog)CA6I0;3LAK;j?$CtqJ2+3GwY1+ico5_T5wu$MrL!bu;ymQ?oxw zgt>bDrnIqDR@qqySh6rjXoBnH+?EjEb`>DY^*I& zYapai=A``UZCn3K-iMO!s7{e|CRXA}kiSu?6YPSR9b84Y{g3SCD^mY#QV;L|&J zKH_JlJ9nrHo+8df*zq@pu_AyyT&Td>VOvJ1uQ8tAuQs)(XNb42ak_4LveJ_wMcQQW zHo!^CY+Vu?%bv2-9kN12xWD+({B+nCHB9Gk9nH)Sj@5-GM1B1t-oeKjeZ3z1x_z!5 zgYz~7oO?DcN!MQ;z81Zv-L?1kXBmHj7U?(OUAD6YW8a+mLF%%j8spx-!8q^RKMFHx zXCB@z(6GZ=a3WKN*sKdWJ=CB)fC6mmjNE1-13q2$Jhft!{KQz)D&3$0|f$4Gj2QlURJjY|f0{ zJnd2F+rGQ!CVpy@HRuR;;CCL)6B84Epib1ACHEIBg@UV zLt;P|sTv&>^@O{0e3-Xav(9~wv49ccZOSr6+-_if*RUZM@Ak-vh`w~7JjxZz8Ch?D z$*yi|0d(Om&wYvodz7!u2H{=E?+%@Ga-S00LV{PO96B;NS|8k(TbFJ?*X1 z_0!dO#WYF$2?pA*>PcH$n9T)8A)|Ni}=PtTt!a4ol*ev1p|?`QoKv z$=Q9>Fc%rF_7k@d8FmptHoeV~TK)#gz*Wex=e@pfg|LOPM1I=#zcKd|2L!UeL zSp9LvhVUVs4ILdNf7juQMW1cUWAA!m%qfJnu-z@GYaVQsg!q@I((<_pj(Tbzo0}px zNi+mxg&H|IAYaYz=!f2S=fKGK^f)u!$z?If)?Q6bsxrr}P1K)gOKXCW#ZbWoAQa*! zqKFuOm{;&Uok1fIfrqs;A(!eV16)nIqGtgDDMNmcTr+L*Um<{_m4 zxJl#c@Xt>Vc%vkGwze+Vy=R*J6Dz$~A3!|nK@6*PS~!A%_o1NC;lTVLJJ@=CPmWGQ zR<#Y;1qiDt81&`!yXC)P@8(+h;<{a^(cFFvr4Zx73Qy{>l&MAQKw;xQRA1d8Z$4$= zlOr6I<9iNW#2SoRqeZ{p2Jc?0(&Q(tyUxy%uR^C*G)v*q9vU+T+JPCmH(NWnxvZD7 za&pMj3Q~zggTv4(n_W0;Tr6y%Su1KwOFsjet~__%-qf^FN>B9r zK<~d?MY`ZwTW@g*DTpx;6R^HamYtRPiB?;}U&Az&E(JO*-8s(c&YjT}CyeHvVI|U` zkvw_T_j_=T%}q&(k+Q#7+BOPC9m?ZHXbAlHbdBt}4P>p+C7S*XlJ@BR71#qoDVbDf zG{OtnmC^;w)^q2%qGk?QepJ_@!|5<5V+ooNY9vXXY!Xo}KF2y*dbNRagh?X{IF~da z(6b-OOyg1meMT=+jcivNn_7|=<|%GUOR!vbTy(MVV8aWa8mnl)FfcH`3A<>H2cJE< zGG=aBik-g+Gvo`S08W%0%(kO2-YeQ)RpY5WmmEoHlqA+^e{4z`Hva~4Z4$kEU7oM*W|A9V@ z6R`Oh`auj;aJFLv7`2vv$82mu!9(WHx?IcNtK4&mw86+Sio*Ma8+S~$TU&EOL&>Pd z4wg4ztKYXpN7qVBTA^sDla+OM9!Et;RNt$`K_VPf35!g3cAj4{G%+izq8!0;;Z|Xw z38*M_HNvH?iI8aX^@flrQ71|#CMoIRPc@f-B@WKkeD zLzzl6Ei*Ya<<*X-WDd&vgG=t}gl%+*+SViv>VgEyjf>j^bldV70t36+n}NtS+x~+^ z)A{SgzTcr4y_eul5Bl+Jz0>+VcVHSFl@v-xI|DGiWsQN(%0rA1dn9tJfP53J7X!Nr zID2Zdvhg@P5lk&1Lj#sf*@w>L>)Sj{IJS5Y+*S|{xymP!PvG^aiTWj^26!6%;ui_~ z2H=Mel6B!y*FV_FEcs$ov^TDuel#X1!|p*emxgh;ReoN?#x2^ppnpTD)ah8g{vC#5 zI#AQd$MRhZTd=N*$>>OIjE$h)FbL{wvZs^na7z|rr%(}l@@Yc|g|(%r9)0AmQ+rjr zs?vJIgSL7V*L%erUQqWJEeN=%8Y|D8u=jvwSd==;ZsdcB_1CW!ycSAmCGKg}%IGnJ zgi9J`QrO})akW!s%v5GN6>b9ja5ZtYy`95~^2njcRLcvSd;4G*dFWHGiMMuADXy}E zA|Bf=U-Yms1(83uR)l$$K3dsF*vqX03IH!O^(?iT}xi-@ap9B>Z#fha!!Pq zcE>tMP~}$Inc$CHt{W(bF4|M{sIK*U78`+X?P=azo^6TVF7FyKI+vGja54-pF(2xt zpV~OO`%c@fw9m=tz?mC{g8y8+QBIkH_0EfK@T7p| zVY|$KRDcRSLdlmdM%>~{PxN|wxOX`?^-WBT{aWjF>0Zg6z`?+XaGFymTtkC%d3LU$ zw1$}spmBM3Fk)8(of`i+%ySzP<9ES$b>Ab8*AD0+v;|R6$f4*2+rjoD83uu>w6yK^ zKn-PVDX6Fnu+@8*T3=ya+f0zPwp&e88X zb)>30K!tfGK!D7%?lBs;6i{FdqbiE@sg|7(b$~eso&q$f%|FC)?@q$J59CyL17yYjw}X zPh(h(_v&cx4*gB>e^{nLb=H_PHm)~}DReO$ziMv8fJ7>oFwLJYGA;6dLE877NL3t5z?)7hH zR?Bb4D4a(;;I~r^<4r)eH5!d_YDa}mYgByDWhvj)Qsyk`s8<#+E-w#D0K>Bd64S$N zCKh&oYP`;CVnFT1$Hzj-UPQ$E+tuKGIv2cD7F#FuPOpTl)u%yps#Gyda^KZOCXdYg^h zIlWjSbC=lH2t6WXOutlWqN8uOuhL2)rB46AcTsWu=G#HKx$8gW+q^B*3SYM9<)U&B%)pOC>JR22YMnWt*3%8dodr8?)7!@4sH0G&D zuDr)rf5$+l-tLtOFP>|P;*AX!W{oLqzWi&tAG ziFnO=^EY;|xIjBQIbZZU-EqW85z$mxc#D5t&5O%g`U$Pv+Ng$!&Wu<*ecK3rm<`Dh zk(`7=50%^lnG=$kA^jN?o1EjpjsZ2?+K>SWgOF`_f~RrP?VhP#7aIay0)ZWwQs3bS z0*Nu?Xyd~kCX7aOc#klKg3&EjQ&DJ^M@%XFlZ(X@tR;%71z!YXu#zbV!xxjh1Y@d2(DyGzn~_q!3V% zpV{F*_qb1sS8D+2qSx7wQb~X~%{%qYUa{4fMI`AQRMdbI?sGUXYNtz3`IIB5I<_bw z%UY!Q<@Jnk$nv$KFtR_@;hGLb+FGdz7!C~T^W$5d8@b*)SGOuBCy-1$u7@HVXLtqH z4AD(&T}VUe!!gsGg(-gZ$8EMHa%ju7uJWH{`ChuGLg`j~SrBTBsJG)1jN+%8+yV!q zGzNODeu;TM3Ni(<#kiIRPzJvbilUcg5bnv!FWG(ErmsEFVQ&Qkq;8%@N3|~#TE=>n z>Dx&?8iv9&@;W-S;8TNqL%KUv;u&Vj)4j(tjM{K=Vo!31#@dYcXhO`#+s#2irxKlC z4^-@k5d?GimMEy?NLnt*SfMUt%N=DpS?Kpt8_UAa(KZ_dCDxH{ROeKWWqjv%P`U$% z@C7!=^<{&$80!^sQDN!QDzC3ASh)nscWD`-7nQ70U+1#`+dqY*`35I?04p62@tMLh z!3MtVn0yJZIus&#DB}Q?1U@{9Rbr>v+QLbBz)j(0s4S{@v}Qb12Wus6gZ7F6lW{WXg%Q&Rmdf`53(nRSZ+M+>Q#g)BB5?!llVZ5II z0D}A6Wj6eCb`^OVL@#j|_IdrQ4@rb1Gu4G?wLp;H3b1*3cNDP+bNVdr;cDv?-w71% zg0IbYh@RpQysSM8my^4jd?k^^49Vp1emxP7vhE|bsvZf(+MZ&;@Vt(ilvCR8ZZT|` zshXECIKA(v&a;r>t0tL@RV~9t0s-cgytD7>wPa?438suwW?f_yT)R8 zvaSu2FjTT`wY@f{Nl9MNT;!Rketntz(9`&ecs0{mP{awD(N zUxi?pUd&KcwHI>BcR|-rTFPaMwRb&wOyC&j2Lm@#A{JO=N_Rb*qLWU)lVYvNa5dy= z(OD6XIHBReJl2#bkk+)K_cLM!(UI`P#eMY_QA31$G2tu{{>B8qb^q3oo?WP{|N00A z`0(e}!SLj=ozz6_xgFss^%+)u9T8z&@blIi1=JRw03XR$ zCT;K6?_;Ricg9G~J_{T^+>&AdR^#KNuFy6Xc`-?7jnP|L(}}+kKeZ20Bz_B?KICul zkJ4FwdD-XwgXvsb9El~#-7HKcW@GyTW13|s^XhJ&;yS?==ew5xsl+st8It3a0{65>;mYC5m z)n;`G+`SARS!0V`U)P8&Kl)jwE2k-(wKUu@W#vitcb>4h6r$i6k~1f&5HBHRe%~15 z<={*ey+VTre!v={q0CLn_ctti`SP5`=)jyCK5tBli4Xd8a&YN)&d%{nz6qHwL5_1! zKu#XM#K;f4iYBD$R@{Yj(W4Rl0X9IzKZicJ1}^NMcUv+nP{N@%JM4{PvQupVm145X z-a=)tYMCz4v+Qhi79Zqr{9E=nPfI-}C4zXuH~&^$Er9kCv2tF!Ddp145@;m>jYX@h zfdt2Au*Il2l5m6JwXbVsqmEmSF5-G4>LYDHqUAxMk~8>|+a2yTybfk0Uq&MA*qFxD z@H(xPm+0juoMBaI2ZX*J#Gy=27;K>!Zd?%NgxAJkheQUbWep&Yt#m$Y*oCc3Vq1ZI zCmDcY6@^s*&PYwX%T2#aJ+^6chBKp^U%Y$O(Ya}9q-UHp=$J!Ib0J3BvN#CPtjzp8 zs<0|Xp4bA*t8|jDtY29(wou)1@rUgX``Znez%@4r``-vfymYB3>}$t`NW8|B%>`mv}XW*+>O;Slh5K1ybrC)YK>P^0qz#=z7mcf$w+%U-E zDUjF0F+@w*+7QW3PUfFdb6tr0K{yP3X|XV>_WL)cFE#JJVyf7NZtO~x~s0@lr5nT^P$)>R^(&WNc^Ow0=;POuY98li05~ic8JQG5ozt^~bkuIBj!khY51DTTFHeYCT|}h4R@H!Sz)) zjEak(0*&FF>v>m%8oZ(#%^1l;^~kurHy-_4Q^m$?qQRVz1uvz~l*+vmV%=LO-$Z7F zw2{&rBB+F-fzseE}Hif zL?GQ%9wYDmAgesKLTj4wWa0NLcotYB8+r7dY#>EuD$OJM8IK(09&{JXF66#{Ld;u+ z{TCPz0SMfp<@u&TC@IBH=djw8}K)_#-EIyz#qoV|x?^GAQ>CA=BK%{K|?kQ-Z zsI*cauAz<%-0pU4|TWleJl52RA3=Eofu~%nLa=U1pnmoADP)ud}=}?VveN5h58EwDNZ2ok+WrHk`co7S9TpEzaB}F3?c(psh z#z}fE3%|DjUZQnzgR$2$`-+x$Lh|?T^m)ECe`g343GKubT=kmLcpS{U@h4;FFhPbbY`{*FrSvs{g zYquC!=eg4*N?BQ_9J!E}x@$S!+*p+q#{aFudTX^ihH4*j*Ku)RWtTh!z^Mf~6%voo zk;eeZ%r6<>cMsGJGv&!G9il85tbIEC&Mum{bN%2dU(m1r`X2mU#ZQg>=uOzcr9iqJ zT!Ia1NgACY-5F!PP@BJnZNaPuKYGk08n}~nOLm>&YZJS&RGB#G?ecp8o_?F(n7qwx z#@pZ&K(Ed`BeizgeOy5z!NV_$msxwp?0RT8-i#GkA5p5u0*quz{3=$^MUjAgjGt)~ zfW?FHg5S+%^XM)e{ zW&>6!eCJr))h7IDamTBTDHGQ%T!f~MbS=_@fS9k|xgJHWfwxe^`K@B|wMCQf!&{^l zFVD8~s$oYnAjBgJ8nL@tr>fKlHXpdd z08&qF(Ty2hHq;p^3j0B~dricE=StGg0%(B6yv345(7w&MxHS``k6AeSs6ZvJDmD*_ zJ@99}Jo(fiIlAhN2z1K#rWmx@Ca2OLIDHzp*CQ8-*TJY(_32sE>NXFV>r6RYV0qAw z;jS(YeaDO(K*uQw2CTn#?`QDGES01|<{0>Wds#emRGdgiJ|HFh1^=JyX#{K*xvziB z=^{V?0Qi5clAW!Yl8Lp6(ck|~#x{x5u^OO93Az17C0x}gJ2ySeLl?3~?@X(*Wg|km z)PNmHOn|S0`TbPfjA)4Df5^J)3Uy^6I%2~UJZmWT??JHC-m`mv=wc@q4pwoE*Mb$X zNd^}Up^Negbe_tYyWzWbdl}n_+HeX!$JJmRDl%ccN?&^g_=P8!C+h34`^`jAbzjrvEeU3WW zU4Kn*IUcH#JfQV(+&8@p>9RV@r^tn2*#D`X(JAASy2FY=WrbaHX<8^_f(KGtU*HNo z^&)vo-Q-{CEPOiu0slV()!oNF8}Tnl;}8G3_#- zJ6Yhr;x&5f6EWum`)`&2{*N($+`^#YjDC-BsRcVCoPa`YwCi22I&a$sr-S@C%=n4 zg}%NPhULt&CE%R)J=+m>b3h*kIef;-PKS$Wq!h|Ab-qne`7`+|3(^1=>Og@SRXd5D zBZIBqK)wMgFxP$6(qxD0xT|Z^A*!`}v+O?xL{Y7A0sp11R0o=u*3}+R)#Gd9=v20% zB2Qqq3Ug2K1%La%XcN{yAPz4@Z2zB9Y$20>_GnUoKqvtJ|H%4(pYZ=W{Qs-@zXA6D zsrpZB{QuDa0QfijAJzW@k^fKWe^TB5TPpjn8~snl`+sWxb5#DfcEa=jHAeqa{GUDb gzr~OL0Q3Lf{Z?KI6zsq51O0c>{(HQ7pZ~i0UkaAA#Q*>R literal 0 HcmV?d00001 diff --git a/osu.Game.Tests/Resources/Archives/modified-default-20220723.osk b/osu.Game.Tests/Resources/Archives/modified-default-20220723.osk new file mode 100644 index 0000000000000000000000000000000000000000..7547162165c84e5cd05b7c2cd259f17add5940a5 GIT binary patch literal 1383 zcmWIWW@Zs#U|`^2xXR2JdTQq0HlX?>Con{nEK zB&mxv{fasbEB*hd%(t;s0&#T`mGTx)X?bff(fQ z%)GRGy{zK=Jky@tT!$P4Tz}Ub-4V)^c<@@7vYouBy6Y*63#yWtT}c$q1W z$2a3+uk-r9M=~B2(VtWiz31D!f8`nX;`7~I+%s#kqpqP{Cc?1K8+viWzIr50Y*;9Atdw|EAd$%m6i=4uGKad~z7{8G`M zzvRLG1yL_dS4`Ae`sGh8=T5UjR$Sr7_Gz@gy`$vgB$alv^4a!`rx$*(^WM9*{_My5 zw>CYim|Mkv&T{gH#tppYArA#FFMXulTbgh~VY>!bps~4WMLFA(HzgPVzUMFZJYHx8#i|^NjzYSlO{8oLgY7|$h_4j6Z z)*p*^*Bd1bGQ)!tx9MH*e_ejz=gp08YVVjAukDg4ZA>_GQcHHZyp_${m50+0{d2ie z!Tc`Eu;k1K*`iHiEeAKUvOn3+TAzCG=5zbzC$}pmFzcD-&3H5+d*PawrRO|@rFN)$ z`d8eDZE>kX;^92~cjlj6(2hzd$dFjEaIjPAd;0TVLu-EUffr#68yVj{UBR1Zi&@`pd zrKz#8OKjSzW>5WDCP47{dG>}}rYw7TnQ&%{Z(o_<9=Qu=ev12jLJ zE9ks0&`T7UFj2k3C7r^>dQrbvgY{;6iEP54HerMkWyk+yx9UlpvrHM8S$1bY1AV8>$Bw y(mSEL;CUTgD|+5TXbl6#684;kZU%b1BFwO3#)#zrZ&o&t0u~_r1f=6xKs*59D<&=g literal 0 HcmV?d00001 From f9f9b65c86baf9d3a5f18dc422b720e925614d5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 Jul 2022 23:42:18 +0900 Subject: [PATCH 444/481] Add test coverage of deserialisation skin layouts --- .../Skins/SkinDeserialisationTest.cs | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 osu.Game.Tests/Skins/SkinDeserialisationTest.cs diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs new file mode 100644 index 0000000000..699c1bcc2c --- /dev/null +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics.OpenGL.Textures; +using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; +using osu.Game.Audio; +using osu.Game.IO; +using osu.Game.IO.Archives; +using osu.Game.Skinning; +using osu.Game.Tests.Resources; + +namespace osu.Game.Tests.Skins +{ + /// + /// Test that the main components (which are serialised based on namespace/class name) + /// remain compatible with any changes. + /// + /// + /// If this test breaks, check any naming or class structure changes. + /// Migration rules may need to be added to . + /// + [TestFixture] + public class SkinDeserialisationTest + { + [Test] + public void TestDeserialiseModifiedDefault() + { + using (var stream = TestResources.OpenResource("Archives/modified-default-20220723.osk")) + using (var storage = new ZipArchiveReader(stream)) + { + var skin = new TestSkin(new SkinInfo(), null, storage); + + Assert.That(skin.DrawableComponentInfo, Has.Count.EqualTo(2)); + Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents], Has.Length.EqualTo(9)); + } + } + + [Test] + public void TestDeserialiseModifiedClassic() + { + using (var stream = TestResources.OpenResource("Archives/modified-classic-20220723.osk")) + using (var storage = new ZipArchiveReader(stream)) + { + var skin = new TestSkin(new SkinInfo(), null, storage); + + Assert.That(skin.DrawableComponentInfo, Has.Count.EqualTo(2)); + Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents], Has.Length.EqualTo(6)); + Assert.That(skin.DrawableComponentInfo[SkinnableTarget.SongSelect], Has.Length.EqualTo(1)); + + var skinnableInfo = skin.DrawableComponentInfo[SkinnableTarget.SongSelect].First(); + + Assert.That(skinnableInfo.Type, Is.EqualTo(typeof(SkinnableSprite))); + Assert.That(skinnableInfo.Settings.First().Key, Is.EqualTo("sprite_name")); + Assert.That(skinnableInfo.Settings.First().Value, Is.EqualTo("ppy_logo-2.png")); + } + } + + private class TestSkin : Skin + { + public TestSkin(SkinInfo skin, IStorageResourceProvider? resources, IResourceStore? storage = null, string configurationFilename = "skin.ini") + : base(skin, resources, storage, configurationFilename) + { + } + + public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException(); + + public override IBindable GetConfig(TLookup lookup) => throw new NotImplementedException(); + + public override ISample GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException(); + } + } +} From 57b43e006583a1789f784c54600ecc9d4801cce7 Mon Sep 17 00:00:00 2001 From: notmyname <50967056+naipofo@users.noreply.github.com> Date: Sun, 31 Jul 2022 19:12:29 +0200 Subject: [PATCH 445/481] Stop capturing arrow keys on Playlist --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 5193fe5cbf..d3212b1b3b 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -204,6 +204,9 @@ namespace osu.Game.Screens.OnlinePlay public bool OnPressed(KeyBindingPressEvent e) { + if (!AllowSelection) + return false; + switch (e.Action) { case GlobalAction.SelectNext: From 98214beb6cd92b272872e6c8645aad185b7ee640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gutyina=20Gerg=C5=91?= Date: Sun, 31 Jul 2022 21:24:41 +0200 Subject: [PATCH 446/481] Prevent overflow on beatmap info using scrollable container --- osu.Game/Overlays/BeatmapSet/Info.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 666ceff6cb..5517e51515 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; @@ -62,7 +63,7 @@ namespace osu.Game.Overlays.BeatmapSet Child = new MetadataSection(MetadataType.Description), }, }, - new Container + new OsuScrollContainer { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, From cbabc4886cc7b94abca634ca618525fb32eb5cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Jul 2022 17:25:11 +0200 Subject: [PATCH 447/481] Convert `ModPreset` to realm object --- osu.Game/Rulesets/Mods/ModPreset.cs | 46 +++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModPreset.cs b/osu.Game/Rulesets/Mods/ModPreset.cs index 367acc8a91..2c3f574d47 100644 --- a/osu.Game/Rulesets/Mods/ModPreset.cs +++ b/osu.Game/Rulesets/Mods/ModPreset.cs @@ -3,6 +3,12 @@ using System; using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Game.Database; +using osu.Game.Online.API; +using Realms; namespace osu.Game.Rulesets.Mods { @@ -10,12 +16,18 @@ namespace osu.Game.Rulesets.Mods /// A mod preset is a named collection of configured mods. /// Presets are presented to the user in the mod select overlay for convenience. /// - public class ModPreset + public class ModPreset : RealmObject, IHasGuidPrimaryKey, ISoftDelete { + /// + /// The internal database ID of the preset. + /// + [PrimaryKey] + public Guid ID { get; set; } = Guid.NewGuid(); + /// /// The ruleset that the preset is valid for. /// - public RulesetInfo RulesetInfo { get; set; } = null!; + public RulesetInfo Ruleset { get; set; } = null!; /// /// The name of the mod preset. @@ -30,6 +42,34 @@ namespace osu.Game.Rulesets.Mods /// /// The set of configured mods that are part of the preset. /// - public ICollection Mods { get; set; } = Array.Empty(); + [Ignored] + public ICollection Mods + { + get + { + if (string.IsNullOrEmpty(ModsJson)) + return Array.Empty(); + + var apiMods = JsonConvert.DeserializeObject>(ModsJson); + var ruleset = Ruleset.CreateInstance(); + return apiMods.AsNonNull().Select(mod => mod.ToMod(ruleset)).ToArray(); + } + set + { + var apiMods = value.Select(mod => new APIMod(mod)).ToArray(); + ModsJson = JsonConvert.SerializeObject(apiMods); + } + } + + /// + /// The set of configured mods that are part of the preset, serialised as a JSON blob. + /// + [MapTo("Mods")] + public string ModsJson { get; set; } = string.Empty; + + /// + /// Whether the preset has been soft-deleted by the user. + /// + public bool DeletePending { get; set; } } } From fa3b9ee32ffa18764cd96246f5bef092a69421b9 Mon Sep 17 00:00:00 2001 From: notmyname <50967056+naipofo@users.noreply.github.com> Date: Sun, 31 Jul 2022 23:42:20 +0200 Subject: [PATCH 448/481] remove unneded guard --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index d3212b1b3b..ed554ebd34 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -227,9 +227,6 @@ namespace osu.Game.Screens.OnlinePlay private void selectNext(int direction) { - if (!AllowSelection) - return; - var visibleItems = ListContainer.AsEnumerable().Where(r => r.IsPresent); PlaylistItem item; From 345f10311935da5f49e7f230acb82e3348af8fc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Jul 2022 18:01:11 +0200 Subject: [PATCH 449/481] Migrate mod preset column to use realm --- .../UserInterface/TestSceneModPresetColumn.cs | 61 ++++++++++++++++--- .../UserInterface/TestSceneModPresetPanel.cs | 3 +- osu.Game/Database/RealmAccess.cs | 3 +- osu.Game/Overlays/Mods/ModPresetColumn.cs | 55 +++++++++++------ osu.Game/Overlays/Mods/ModPresetPanel.cs | 11 ++-- 5 files changed, 99 insertions(+), 34 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs index f6209e1b42..f86072dbc0 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs @@ -5,10 +5,14 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; using osu.Game.Overlays; using osu.Game.Overlays.Mods; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; @@ -16,29 +20,54 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestSceneModPresetColumn : OsuTestScene { + protected override bool UseFreshStoragePerRun => true; + + [Resolved] + private RulesetStore rulesets { get; set; } = null!; + [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + [BackgroundDependencyLoader] + private void load() + { + Dependencies.Cache(Realm); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("reset storage", () => + { + Realm.Write(realm => + { + realm.RemoveAll(); + realm.Add(createTestPresets()); + }); + }); + } + [Test] public void TestBasicAppearance() { - ModPresetColumn modPresetColumn = null!; - + AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0)); AddStep("create content", () => Child = new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(30), - Child = modPresetColumn = new ModPresetColumn + Child = new ModPresetColumn { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Presets = createTestPresets().ToArray() } }); - AddStep("change presets", () => modPresetColumn.Presets = createTestPresets().Skip(1).ToArray()); + AddUntilStep("3 panels visible", () => this.ChildrenOfType().Count() == 3); + + AddStep("change ruleset to mania", () => Ruleset.Value = rulesets.GetRuleset(3)); + AddUntilStep("1 panel visible", () => this.ChildrenOfType().Count() == 1); } - private static IEnumerable createTestPresets() => new[] + private IEnumerable createTestPresets() => new[] { new ModPreset { @@ -48,7 +77,8 @@ namespace osu.Game.Tests.Visual.UserInterface { new OsuModHardRock(), new OsuModDoubleTime() - } + }, + Ruleset = rulesets.GetRuleset(0).AsNonNull() }, new ModPreset { @@ -60,7 +90,8 @@ namespace osu.Game.Tests.Visual.UserInterface { ApproachRate = { Value = 0 } } - } + }, + Ruleset = rulesets.GetRuleset(0).AsNonNull() }, new ModPreset { @@ -70,7 +101,19 @@ namespace osu.Game.Tests.Visual.UserInterface { new OsuModFlashlight(), new OsuModSpinIn() - } + }, + Ruleset = rulesets.GetRuleset(0).AsNonNull() + }, + new ModPreset + { + Name = "Different ruleset", + Description = "Just to shake things up", + Mods = new Mod[] + { + new ManiaModKey4(), + new ManiaModFadeIn() + }, + Ruleset = rulesets.GetRuleset(3).AsNonNull() } }; } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs index 62e63d47bc..dd3c7000a2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Database; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; @@ -31,7 +32,7 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.Centre, Origin = Anchor.Centre, Spacing = new Vector2(0, 5), - ChildrenEnumerable = createTestPresets().Select(preset => new ModPresetPanel(preset)) + ChildrenEnumerable = createTestPresets().Select(preset => new ModPresetPanel(preset.ToLiveUnmanaged())) }); } diff --git a/osu.Game/Database/RealmAccess.cs b/osu.Game/Database/RealmAccess.cs index 1a0c03af7d..5f0ec67c71 100644 --- a/osu.Game/Database/RealmAccess.cs +++ b/osu.Game/Database/RealmAccess.cs @@ -66,8 +66,9 @@ namespace osu.Game.Database /// 19 2022-07-19 Added DateSubmitted and DateRanked to BeatmapSetInfo. /// 20 2022-07-21 Added LastAppliedDifficultyVersion to RulesetInfo, changed default value of BeatmapInfo.StarRating to -1. /// 21 2022-07-27 Migrate collections to realm (BeatmapCollection). + /// 22 2022-07-31 Added ModPreset. /// - private const int schema_version = 21; + private const int schema_version = 22; /// /// Lock object which is held during sections, blocking realm retrieval during blocking periods. diff --git a/osu.Game/Overlays/Mods/ModPresetColumn.cs b/osu.Game/Overlays/Mods/ModPresetColumn.cs index 1eea8383f8..bed4cff0ea 100644 --- a/osu.Game/Overlays/Mods/ModPresetColumn.cs +++ b/osu.Game/Overlays/Mods/ModPresetColumn.cs @@ -7,31 +7,24 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Localisation; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osuTK; +using Realms; namespace osu.Game.Overlays.Mods { public class ModPresetColumn : ModSelectColumn { - private IReadOnlyList presets = Array.Empty(); + [Resolved] + private RealmAccess realm { get; set; } = null!; - /// - /// Sets the collection of available mod presets. - /// - public IReadOnlyList Presets - { - get => presets; - set - { - presets = value; - - if (IsLoaded) - asyncLoadPanels(); - } - } + [Resolved] + private IBindable ruleset { get; set; } = null!; [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -44,7 +37,20 @@ namespace osu.Game.Overlays.Mods { base.LoadComplete(); - asyncLoadPanels(); + ruleset.BindValueChanged(_ => rulesetChanged(), true); + } + + private IDisposable? presetSubscription; + + private void rulesetChanged() + { + presetSubscription?.Dispose(); + presetSubscription = realm.RegisterForNotifications(r => + r.All() + .Filter($"{nameof(ModPreset.Ruleset)}.{nameof(RulesetInfo.ShortName)} == $0" + + $" && {nameof(ModPreset.DeletePending)} == false", ruleset.Value.ShortName) + .OrderBy(preset => preset.Name), + (presets, _, _) => asyncLoadPanels(presets)); } private CancellationTokenSource? cancellationTokenSource; @@ -52,11 +58,17 @@ namespace osu.Game.Overlays.Mods private Task? latestLoadTask; internal bool ItemsLoaded => latestLoadTask == null; - private void asyncLoadPanels() + private void asyncLoadPanels(IReadOnlyList presets) { cancellationTokenSource?.Cancel(); - var panels = presets.Select(preset => new ModPresetPanel(preset) + if (!presets.Any()) + { + ItemsFlow.Clear(); + return; + } + + var panels = presets.Select(preset => new ModPresetPanel(preset.ToLive(realm)) { Shear = Vector2.Zero }); @@ -73,5 +85,12 @@ namespace osu.Game.Overlays.Mods latestLoadTask = null; }); } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + presetSubscription?.Dispose(); + } } } diff --git a/osu.Game/Overlays/Mods/ModPresetPanel.cs b/osu.Game/Overlays/Mods/ModPresetPanel.cs index 47e2f25538..a00729d9fd 100644 --- a/osu.Game/Overlays/Mods/ModPresetPanel.cs +++ b/osu.Game/Overlays/Mods/ModPresetPanel.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Cursor; +using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; @@ -11,16 +12,16 @@ namespace osu.Game.Overlays.Mods { public class ModPresetPanel : ModSelectPanel, IHasCustomTooltip { - public readonly ModPreset Preset; + public readonly Live Preset; public override BindableBool Active { get; } = new BindableBool(); - public ModPresetPanel(ModPreset preset) + public ModPresetPanel(Live preset) { Preset = preset; - Title = preset.Name; - Description = preset.Description; + Title = preset.Value.Name; + Description = preset.Value.Description; } [BackgroundDependencyLoader] @@ -29,7 +30,7 @@ namespace osu.Game.Overlays.Mods AccentColour = colours.Orange1; } - public ModPreset TooltipContent => Preset; + public ModPreset TooltipContent => Preset.Value; public ITooltip GetCustomTooltip() => new ModPresetTooltip(ColourProvider); } } From c837848238f3b75b9a567b412916f4117b656571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Jul 2022 18:09:27 +0200 Subject: [PATCH 450/481] Add extended test coverage of preset realm subscription --- .../UserInterface/TestSceneModPresetColumn.cs | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs index f86072dbc0..f927d83722 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestBasicAppearance() + public void TestBasicOperation() { AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0)); AddStep("create content", () => Child = new Container @@ -65,6 +65,41 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("change ruleset to mania", () => Ruleset.Value = rulesets.GetRuleset(3)); AddUntilStep("1 panel visible", () => this.ChildrenOfType().Count() == 1); + + AddStep("add another mania preset", () => Realm.Write(r => r.Add(new ModPreset + { + Name = "and another one", + Mods = new Mod[] + { + new ManiaModMirror(), + new ManiaModNightcore(), + new ManiaModHardRock() + }, + Ruleset = rulesets.GetRuleset(3).AsNonNull() + }))); + AddUntilStep("2 panels visible", () => this.ChildrenOfType().Count() == 2); + + AddStep("add another osu! preset", () => Realm.Write(r => r.Add(new ModPreset + { + Name = "hdhr", + Mods = new Mod[] + { + new OsuModHidden(), + new OsuModHardRock() + }, + Ruleset = rulesets.GetRuleset(0).AsNonNull() + }))); + AddUntilStep("2 panels visible", () => this.ChildrenOfType().Count() == 2); + + AddStep("remove mania preset", () => Realm.Write(r => + { + var toRemove = r.All().Single(preset => preset.Name == "Different ruleset"); + r.Remove(toRemove); + })); + AddUntilStep("1 panel visible", () => this.ChildrenOfType().Count() == 1); + + AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0)); + AddUntilStep("4 panels visible", () => this.ChildrenOfType().Count() == 4); } private IEnumerable createTestPresets() => new[] From 9dea8e3d12ef84ca23b4d36a2e114dc5e3501218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Jul 2022 18:12:55 +0200 Subject: [PATCH 451/481] Add test coverage of preset soft deletion --- .../UserInterface/TestSceneModPresetColumn.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs index f927d83722..2ef6d2ab70 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs @@ -102,6 +102,44 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("4 panels visible", () => this.ChildrenOfType().Count() == 4); } + [Test] + public void TestSoftDeleteSupport() + { + AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0)); + AddStep("create content", () => Child = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(30), + Child = new ModPresetColumn + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }); + AddUntilStep("3 panels visible", () => this.ChildrenOfType().Count() == 3); + + AddStep("soft delete preset", () => Realm.Write(r => + { + var toSoftDelete = r.All().Single(preset => preset.Name == "AR0"); + toSoftDelete.DeletePending = true; + })); + AddUntilStep("2 panels visible", () => this.ChildrenOfType().Count() == 2); + + AddStep("soft delete all presets", () => Realm.Write(r => + { + foreach (var preset in r.All()) + preset.DeletePending = true; + })); + AddUntilStep("no panels visible", () => this.ChildrenOfType().Count() == 0); + + AddStep("undelete preset", () => Realm.Write(r => + { + foreach (var preset in r.All()) + preset.DeletePending = false; + })); + AddUntilStep("3 panels visible", () => this.ChildrenOfType().Count() == 3); + } + private IEnumerable createTestPresets() => new[] { new ModPreset From 9d3cdae4bba5cd15eaaecea1b9ad32107541103c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Jul 2022 21:56:10 +0200 Subject: [PATCH 452/481] Fix test scene to handle restarts properly --- .../UserInterface/TestSceneModPresetColumn.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs index 2ef6d2ab70..593c8abac4 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetColumn.cs @@ -22,8 +22,7 @@ namespace osu.Game.Tests.Visual.UserInterface { protected override bool UseFreshStoragePerRun => true; - [Resolved] - private RulesetStore rulesets { get; set; } = null!; + private RulesetStore rulesets = null!; [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); @@ -31,18 +30,25 @@ namespace osu.Game.Tests.Visual.UserInterface [BackgroundDependencyLoader] private void load() { + Dependencies.Cache(rulesets = new RealmRulesetStore(Realm)); Dependencies.Cache(Realm); } [SetUpSteps] public void SetUpSteps() { + AddStep("clear contents", Clear); AddStep("reset storage", () => { Realm.Write(realm => { realm.RemoveAll(); - realm.Add(createTestPresets()); + + var testPresets = createTestPresets(); + foreach (var preset in testPresets) + preset.Ruleset = realm.Find(preset.Ruleset.ShortName); + + realm.Add(testPresets); }); }); } @@ -75,7 +81,7 @@ namespace osu.Game.Tests.Visual.UserInterface new ManiaModNightcore(), new ManiaModHardRock() }, - Ruleset = rulesets.GetRuleset(3).AsNonNull() + Ruleset = r.Find("mania") }))); AddUntilStep("2 panels visible", () => this.ChildrenOfType().Count() == 2); @@ -87,7 +93,7 @@ namespace osu.Game.Tests.Visual.UserInterface new OsuModHidden(), new OsuModHardRock() }, - Ruleset = rulesets.GetRuleset(0).AsNonNull() + Ruleset = r.Find("osu") }))); AddUntilStep("2 panels visible", () => this.ChildrenOfType().Count() == 2); @@ -140,7 +146,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("3 panels visible", () => this.ChildrenOfType().Count() == 3); } - private IEnumerable createTestPresets() => new[] + private ICollection createTestPresets() => new[] { new ModPreset { From 5a34122a85c746e5eb1d954c6df898519ef9ef2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 23 Jul 2022 21:29:30 +0200 Subject: [PATCH 453/481] Fix test breakage after realm migration --- .../Visual/UserInterface/TestSceneModPresetPanel.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs index dd3c7000a2..92d1cba2c2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModPresetPanel.cs @@ -11,6 +11,7 @@ using osu.Game.Database; using osu.Game.Overlays; using osu.Game.Overlays.Mods; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osuTK; @@ -46,7 +47,8 @@ namespace osu.Game.Tests.Visual.UserInterface { new OsuModHardRock(), new OsuModDoubleTime() - } + }, + Ruleset = new OsuRuleset().RulesetInfo }, new ModPreset { @@ -58,7 +60,8 @@ namespace osu.Game.Tests.Visual.UserInterface { ApproachRate = { Value = 0 } } - } + }, + Ruleset = new OsuRuleset().RulesetInfo }, new ModPreset { @@ -68,7 +71,8 @@ namespace osu.Game.Tests.Visual.UserInterface { new OsuModFlashlight(), new OsuModSpinIn() - } + }, + Ruleset = new OsuRuleset().RulesetInfo } }; } From 85f77abee113342d224c03353f9aa2a7e597e1a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 31 Jul 2022 22:59:33 +0200 Subject: [PATCH 454/481] Fix code quality inspection about ambiguous equality --- osu.Game/Overlays/Mods/ModPresetTooltip.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModPresetTooltip.cs b/osu.Game/Overlays/Mods/ModPresetTooltip.cs index 68da649e81..97d118fbfd 100644 --- a/osu.Game/Overlays/Mods/ModPresetTooltip.cs +++ b/osu.Game/Overlays/Mods/ModPresetTooltip.cs @@ -50,7 +50,7 @@ namespace osu.Game.Overlays.Mods public void SetContent(ModPreset preset) { - if (preset == lastPreset) + if (ReferenceEquals(preset, lastPreset)) return; lastPreset = preset; From 415d6def2d345903a51634ec544c693c46bd3166 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 1 Aug 2022 13:22:58 +0900 Subject: [PATCH 455/481] Remove unnecessary AsNonNull() --- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index c273da2462..623157a427 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; @@ -274,7 +273,7 @@ namespace osu.Game.Rulesets.Osu.Mods { if (hitObjects.Count == 0) return; - float nextSingle(float max = 1f) => (float)(rng.AsNonNull().NextDouble() * max); + float nextSingle(float max = 1f) => (float)(rng.NextDouble() * max); const float two_pi = MathF.PI * 2; From 5b98a73edcb2dc061e0898251098a5de405d5325 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Aug 2022 14:03:57 +0900 Subject: [PATCH 456/481] Apply nullability to `SkinComponentToolbox` and split out reflection method to get all skinnable components --- osu.Game/Screens/Play/HUD/SkinnableInfo.cs | 9 +++++ .../Skinning/Editor/SkinComponentToolbox.cs | 35 ++++++++----------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs index ee29241321..6d63776dbb 100644 --- a/osu.Game/Screens/Play/HUD/SkinnableInfo.cs +++ b/osu.Game/Screens/Play/HUD/SkinnableInfo.cs @@ -98,5 +98,14 @@ namespace osu.Game.Screens.Play.HUD return Drawable.Empty(); } } + + public static Type[] GetAllAvailableDrawables() + { + return typeof(OsuGame).Assembly.GetTypes() + .Where(t => !t.IsInterface && !t.IsAbstract) + .Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)) + .OrderBy(t => t.Name) + .ToArray(); + } } } diff --git a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs index 344a659627..980dee8601 100644 --- a/osu.Game/Skinning/Editor/SkinComponentToolbox.cs +++ b/osu.Game/Skinning/Editor/SkinComponentToolbox.cs @@ -1,11 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Diagnostics; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -16,24 +13,25 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Screens.Edit.Components; +using osu.Game.Screens.Play.HUD; using osuTK; namespace osu.Game.Skinning.Editor { public class SkinComponentToolbox : EditorSidebarSection { - public Action RequestPlacement; + public Action? RequestPlacement; - private readonly CompositeDrawable target; + private readonly CompositeDrawable? target; - public SkinComponentToolbox(CompositeDrawable target = null) + private FillFlowContainer fill = null!; + + public SkinComponentToolbox(CompositeDrawable? target = null) : base("Components") { this.target = target; } - private FillFlowContainer fill; - [BackgroundDependencyLoader] private void load() { @@ -52,12 +50,7 @@ namespace osu.Game.Skinning.Editor { fill.Clear(); - var skinnableTypes = typeof(OsuGame).Assembly.GetTypes() - .Where(t => !t.IsInterface && !t.IsAbstract) - .Where(t => typeof(ISkinnableDrawable).IsAssignableFrom(t)) - .OrderBy(t => t.Name) - .ToArray(); - + var skinnableTypes = SkinnableInfo.GetAllAvailableDrawables(); foreach (var type in skinnableTypes) attemptAddComponent(type); } @@ -90,21 +83,21 @@ namespace osu.Game.Skinning.Editor public class ToolboxComponentButton : OsuButton { + public Action? RequestPlacement; + protected override bool ShouldBeConsideredForInput(Drawable child) => false; public override bool PropagateNonPositionalInputSubTree => false; private readonly Drawable component; - private readonly CompositeDrawable dependencySource; + private readonly CompositeDrawable? dependencySource; - public Action RequestPlacement; - - private Container innerContainer; + private Container innerContainer = null!; private const float contracted_size = 60; private const float expanded_size = 120; - public ToolboxComponentButton(Drawable component, CompositeDrawable dependencySource) + public ToolboxComponentButton(Drawable component, CompositeDrawable? dependencySource) { this.component = component; this.dependencySource = dependencySource; @@ -184,9 +177,9 @@ namespace osu.Game.Skinning.Editor public class DependencyBorrowingContainer : Container { - private readonly CompositeDrawable donor; + private readonly CompositeDrawable? donor; - public DependencyBorrowingContainer(CompositeDrawable donor) + public DependencyBorrowingContainer(CompositeDrawable? donor) { this.donor = donor; } From d112743cea7d959a1f1d8e22dc3b58f04aa356aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Aug 2022 14:04:06 +0900 Subject: [PATCH 457/481] Improve test coverage of skin serialisation to ensure full coverage Will fail when new skinnable components are added until they have coverage in resources. --- .../Skins/SkinDeserialisationTest.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index 699c1bcc2c..2ef8de387b 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Audio.Sample; @@ -12,6 +13,7 @@ using osu.Framework.IO.Stores; using osu.Game.Audio; using osu.Game.IO; using osu.Game.IO.Archives; +using osu.Game.Screens.Play.HUD; using osu.Game.Skinning; using osu.Game.Tests.Resources; @@ -28,6 +30,39 @@ namespace osu.Game.Tests.Skins [TestFixture] public class SkinDeserialisationTest { + private static readonly string[] available_skins = + { + // Covers song progress before namespace changes, and most other components. + "Archives/modified-default-20220723.osk", + "Archives/modified-classic-20220723.osk" + }; + + /// + /// If this test fails, new test resources should be added to include new components. + /// + [Test] + public void TestSkinnableComponentsCoveredByDeserialisationTests() + { + HashSet instantiatedTypes = new HashSet(); + + foreach (string oskFile in available_skins) + { + using (var stream = TestResources.OpenResource(oskFile)) + using (var storage = new ZipArchiveReader(stream)) + { + var skin = new TestSkin(new SkinInfo(), null, storage); + + foreach (var target in skin.DrawableComponentInfo) + { + foreach (var info in target.Value) + instantiatedTypes.Add(info.Type); + } + } + } + + Assert.That(SkinnableInfo.GetAllAvailableDrawables(), Is.EquivalentTo(instantiatedTypes)); + } + [Test] public void TestDeserialiseModifiedDefault() { From 3b6349a14522c7bc7293ead59b47d508fdfa0061 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Aug 2022 14:16:26 +0900 Subject: [PATCH 458/481] Add test coverage of remaining components which weren't already included --- .../Archives/modified-classic-20220801.osk | Bin 0 -> 1182 bytes .../Skins/SkinDeserialisationTest.cs | 20 ++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Resources/Archives/modified-classic-20220801.osk diff --git a/osu.Game.Tests/Resources/Archives/modified-classic-20220801.osk b/osu.Game.Tests/Resources/Archives/modified-classic-20220801.osk new file mode 100644 index 0000000000000000000000000000000000000000..9236e1d77f181e680171e0ffb4374f4eb291944d GIT binary patch literal 1182 zcmWIWW@Zs#U|`^2cvrv}`s~Z3;}d|q)nE|@hT`nZJiW}kOxZJre1{Bp94^`}H;C3& z)Vh}EljCEc$Yx%qsI)Qg(TZ0$B`k!(7o`64|Nr}6iR>JCp2f#j1W&N85KT6mv4J7% za*LSFF$-oP!-EFR({?egs(h-r*W|Vsn~rx#qoIqK56@jI)pHYWOt?_+e4V@4If?vt zTRQ&Pe-iZOl$*6g~tTM0A1V##JoTZa&=~2 zTE1RZaekiZ8Aq-|4nU{Z`U`H(TFA4bbhQHOOE(*b8EIY_vmMl3UH0ted-d(}kMmp% z)lm}s4b!&A&e2To4A3|85R#Kx@tt?uQQ2^{?OQL*_RA{&`^|6jO9TeKD;bIXE6aWKM)7!=cNay=A{_}ow0%nVOW!QCnJX)!{_qdIf z`=zrdKL0aY|NQ6mugX8>+r;1Is(sAg@a(&j=0dM8%wBON?+M);XG|_ z?r*Crns(oPU>_58LyhgZRKT{gdrQxTJKdAah|SA$kKJazXrB131IL|Zm8S8niTGud z<TY55_N7#|D5n+C-Z!!>3OA^Zx%oK%C41XQ*elRxvY&D z%UQoemFq$+jdz(Oygg{xUnrw|=*EKonN1OgHmTT|d=_72Z71~g5Q9c{ny=~Nmyd*e zTYo3@nAtD9>8JDSjIY1t@-F$!-yg}hI{&k<5&Yk}sZU=urm%dAvPiq(p-7)abw8I& zsH%8oZc6{D-XSzAHSqs|8y#PIXY((6evy6Wv|Uz3+6i5oM2<)P|7Xr8sA$5)dHP$- z^s?32*BXx5-uFvge@B=9mg3^u{{y@knM4?H=SyIsfPh921T0?;e6D @@ -60,7 +64,9 @@ namespace osu.Game.Tests.Skins } } - Assert.That(SkinnableInfo.GetAllAvailableDrawables(), Is.EquivalentTo(instantiatedTypes)); + var editableTypes = SkinnableInfo.GetAllAvailableDrawables().Where(t => (Activator.CreateInstance(t) as ISkinnableDrawable)?.IsEditable == true); + + Assert.That(instantiatedTypes, Is.EquivalentTo(editableTypes)); } [Test] @@ -94,6 +100,16 @@ namespace osu.Game.Tests.Skins Assert.That(skinnableInfo.Settings.First().Key, Is.EqualTo("sprite_name")); Assert.That(skinnableInfo.Settings.First().Value, Is.EqualTo("ppy_logo-2.png")); } + + using (var stream = TestResources.OpenResource("Archives/modified-classic-20220801.osk")) + using (var storage = new ZipArchiveReader(stream)) + { + var skin = new TestSkin(new SkinInfo(), null, storage); + Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents], Has.Length.EqualTo(8)); + Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter))); + Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter))); + Assert.That(skin.DrawableComponentInfo[SkinnableTarget.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(LegacySongProgress))); + } } private class TestSkin : Skin From a5f48e336a2021efd96096ea3b9215b1da0ef004 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Aug 2022 14:38:02 +0900 Subject: [PATCH 459/481] Isolate development builds' storage from release builds --- osu.Desktop/Program.cs | 4 ++++ osu.Game/OsuGameBase.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 19cf7f5d46..c7505e624c 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -21,7 +21,11 @@ namespace osu.Desktop { public static class Program { +#if DEBUG + private const string base_game_name = @"osu-development"; +#else private const string base_game_name = @"osu"; +#endif private static LegacyTcpIpcProvider legacyIpc; diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index c7820d395d..97d15ae6ad 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -212,6 +212,10 @@ namespace osu.Game { Name = @"osu!"; +#if DEBUG + Name += " (development)"; +#endif + allowableExceptions = UnhandledExceptionsBeforeCrash; } From fc8835d43a71b6c1f43eb434eeb60bb850f31913 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Aug 2022 14:58:09 +0900 Subject: [PATCH 460/481] Fix migration failing on single file copy failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No longer throw if copying of single files fails during data migration. Aiming to fix https://github.com/ppy/osu/runs/7601653833?check_suite_focus=true, which could also affect end users. I've left a limit before an exception is still thrown, to handle cases like the user running out of disk space (where we probably *do* want to bail, so they can continue to use their still-intact original storage location). If this isn't seen as a good direction, an alternative will be to make the migration code aware of the structure of the temporary files created by `Storage` (but doesn't guarantee this will cover all cases of such temporary files – there could for isntance be metadata files created by the host operating system). Another option would be to mark those temporary files as hidden and skip any hidden files during iteration. --- osu.Game/IO/MigratableStorage.cs | 34 ++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 30e74adca4..4bc729f429 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -7,6 +7,7 @@ using System; using System.IO; using System.Linq; using System.Threading; +using osu.Framework.Logging; using osu.Framework.Platform; namespace osu.Game.IO @@ -16,6 +17,12 @@ namespace osu.Game.IO /// public abstract class MigratableStorage : WrappedStorage { + /// + /// The number of file copy failures before actually bailing on migration. + /// This allows some lenience to cover things like temporary files which could not be copied but are also not too important. + /// + private const int allowed_failures = 10; + /// /// A relative list of directory paths which should not be migrated. /// @@ -73,7 +80,7 @@ namespace osu.Game.IO if (topLevelExcludes && IgnoreFiles.Contains(fi.Name)) continue; - allFilesDeleted &= AttemptOperation(() => fi.Delete(), throwOnFailure: false); + allFilesDeleted &= AttemptOperation(() => fi.Delete()); } foreach (DirectoryInfo dir in target.GetDirectories()) @@ -81,16 +88,16 @@ namespace osu.Game.IO if (topLevelExcludes && IgnoreDirectories.Contains(dir.Name)) continue; - allFilesDeleted &= AttemptOperation(() => dir.Delete(true), throwOnFailure: false); + allFilesDeleted &= AttemptOperation(() => dir.Delete(true)); } if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0) - allFilesDeleted &= AttemptOperation(target.Delete, throwOnFailure: false); + allFilesDeleted &= AttemptOperation(target.Delete); return allFilesDeleted; } - protected void CopyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) + protected void CopyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true, int totalFailedOperations = 0) { // based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo if (!destination.Exists) @@ -101,7 +108,14 @@ namespace osu.Game.IO if (topLevelExcludes && IgnoreFiles.Contains(fi.Name)) continue; - AttemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true)); + if (!AttemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), false))) + { + Logger.Log($"Failed to copy file {fi.Name} during folder migration"); + totalFailedOperations++; + + if (totalFailedOperations > allowed_failures) + throw new Exception("Aborting due to too many file copy failures during data migration"); + } } foreach (DirectoryInfo dir in source.GetDirectories()) @@ -109,7 +123,7 @@ namespace osu.Game.IO if (topLevelExcludes && IgnoreDirectories.Contains(dir.Name)) continue; - CopyRecursive(dir, destination.CreateSubdirectory(dir.Name), false); + CopyRecursive(dir, destination.CreateSubdirectory(dir.Name), false, totalFailedOperations); } } @@ -118,8 +132,7 @@ namespace osu.Game.IO /// /// The action to perform. /// The number of attempts (250ms wait between each). - /// Whether to throw an exception on failure. If false, will silently fail. - protected static bool AttemptOperation(Action action, int attempts = 10, bool throwOnFailure = true) + protected static bool AttemptOperation(Action action, int attempts = 10) { while (true) { @@ -131,12 +144,7 @@ namespace osu.Game.IO catch (Exception) { if (attempts-- == 0) - { - if (throwOnFailure) - throw; - return false; - } } Thread.Sleep(250); From 47860bb96606644cfadd5d80eaad7a2245647583 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 1 Aug 2022 16:33:59 +0900 Subject: [PATCH 461/481] Remove unused using --- osu.Game.Tests/Skins/SkinDeserialisationTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs index 9181dc80f3..53639deac3 100644 --- a/osu.Game.Tests/Skins/SkinDeserialisationTest.cs +++ b/osu.Game.Tests/Skins/SkinDeserialisationTest.cs @@ -7,7 +7,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; From 2519706ad612e0297be4b5c8f742150784cf018a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Aug 2022 16:53:47 +0900 Subject: [PATCH 462/481] Add test coverage of editor crash --- .../Editing/TestSceneEditorBeatmapCreation.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 6ad6f0b299..4baa4af8dd 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -136,6 +136,20 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("track is not virtual", () => Beatmap.Value.Track is not TrackVirtual); AddAssert("track length changed", () => Beatmap.Value.Track.Length > 60000); + + AddStep("test play", () => Editor.TestGameplay()); + + AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog != null); + AddStep("confirm save", () => InputManager.Key(Key.Number1)); + + AddUntilStep("wait for return to editor", () => Editor.IsCurrentScreen()); + + AddAssert("track is still not virtual", () => Beatmap.Value.Track is not TrackVirtual); + AddAssert("track length correct", () => Beatmap.Value.Track.Length > 60000); + + AddUntilStep("track not playing", () => !EditorClock.IsRunning); + AddStep("play track", () => InputManager.Key(Key.Space)); + AddUntilStep("wait for track playing", () => EditorClock.IsRunning); } [Test] From 2f60f91a0ed2173282e1b97c68c76d76a476597d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Aug 2022 16:30:45 +0900 Subject: [PATCH 463/481] Fix editor potentially using a track post-disposal This changes the editor to track the current track as it is *loaded* by `MusicController`, rather than haphazardly following the current global `WorkingBeatmap` (with a potentially unloaded track) or relying on local immediate-load behaviour (as implemented in `ResourcesSection`). --- osu.Game/Overlays/MusicController.cs | 6 +++++- osu.Game/Screens/Edit/Editor.cs | 17 +++++++++++++---- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 2 -- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 8af295dfe8..da87336039 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -70,7 +70,11 @@ namespace osu.Game.Overlays /// /// Forcefully reload the current 's track from disk. /// - public void ReloadCurrentTrack() => changeTrack(); + public void ReloadCurrentTrack() + { + changeTrack(); + TrackChanged?.Invoke(current, TrackChangeDirection.None); + } /// /// Returns whether the beatmap track is playing. diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 3e3940c5ba..9e9cd8e3b7 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -329,6 +329,9 @@ namespace osu.Game.Screens.Edit changeHandler?.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true); } + [Resolved] + private MusicController musicController { get; set; } + protected override void LoadComplete() { base.LoadComplete(); @@ -336,12 +339,18 @@ namespace osu.Game.Screens.Edit Mode.Value = isNewBeatmap ? EditorScreenMode.SongSetup : EditorScreenMode.Compose; Mode.BindValueChanged(onModeChanged, true); + + musicController.TrackChanged += onTrackChanged; } - /// - /// If the beatmap's track has changed, this method must be called to keep the editor in a valid state. - /// - public void UpdateClockSource() => clock.ChangeSource(Beatmap.Value.Track); + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + musicController.TrackChanged -= onTrackChanged; + } + + private void onTrackChanged(WorkingBeatmap working, TrackChangeDirection direction) => clock.ChangeSource(working.Track); /// /// Creates an instance representing the current state of the editor. diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 1f8381e1ed..44bc2126fb 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -118,8 +118,6 @@ namespace osu.Game.Screens.Edit.Setup working.Value.Metadata.AudioFile = destination.Name; music.ReloadCurrentTrack(); - - editor?.UpdateClockSource(); return true; } From 6e7c298aaf6380032d344474863b73cabaf9342e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Aug 2022 16:36:12 +0900 Subject: [PATCH 464/481] Fix changes to audio / background not triggering an editor state change --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 44bc2126fb..8c14feebbc 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -30,8 +30,8 @@ namespace osu.Game.Screens.Edit.Setup [Resolved] private IBindable working { get; set; } - [Resolved(canBeNull: true)] - private Editor editor { get; set; } + [Resolved] + private EditorBeatmap editorBeatmap { get; set; } [Resolved] private SetupScreenHeader header { get; set; } @@ -88,6 +88,8 @@ namespace osu.Game.Screens.Edit.Setup beatmaps.AddFile(set, stream, destination.Name); } + editorBeatmap.SaveState(); + working.Value.Metadata.BackgroundFile = destination.Name; header.Background.UpdateBackground(); @@ -117,7 +119,9 @@ namespace osu.Game.Screens.Edit.Setup working.Value.Metadata.AudioFile = destination.Name; + editorBeatmap.SaveState(); music.ReloadCurrentTrack(); + return true; } From 59210ecc9df96119f70004f68c06f96e9d734e8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Aug 2022 16:57:50 +0900 Subject: [PATCH 465/481] Revert "Fix migration failing on single file copy failure" This reverts commit fc8835d43a71b6c1f43eb434eeb60bb850f31913. --- osu.Game/IO/MigratableStorage.cs | 34 ++++++++++++-------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 4bc729f429..30e74adca4 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -7,7 +7,6 @@ using System; using System.IO; using System.Linq; using System.Threading; -using osu.Framework.Logging; using osu.Framework.Platform; namespace osu.Game.IO @@ -17,12 +16,6 @@ namespace osu.Game.IO /// public abstract class MigratableStorage : WrappedStorage { - /// - /// The number of file copy failures before actually bailing on migration. - /// This allows some lenience to cover things like temporary files which could not be copied but are also not too important. - /// - private const int allowed_failures = 10; - /// /// A relative list of directory paths which should not be migrated. /// @@ -80,7 +73,7 @@ namespace osu.Game.IO if (topLevelExcludes && IgnoreFiles.Contains(fi.Name)) continue; - allFilesDeleted &= AttemptOperation(() => fi.Delete()); + allFilesDeleted &= AttemptOperation(() => fi.Delete(), throwOnFailure: false); } foreach (DirectoryInfo dir in target.GetDirectories()) @@ -88,16 +81,16 @@ namespace osu.Game.IO if (topLevelExcludes && IgnoreDirectories.Contains(dir.Name)) continue; - allFilesDeleted &= AttemptOperation(() => dir.Delete(true)); + allFilesDeleted &= AttemptOperation(() => dir.Delete(true), throwOnFailure: false); } if (target.GetFiles().Length == 0 && target.GetDirectories().Length == 0) - allFilesDeleted &= AttemptOperation(target.Delete); + allFilesDeleted &= AttemptOperation(target.Delete, throwOnFailure: false); return allFilesDeleted; } - protected void CopyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true, int totalFailedOperations = 0) + protected void CopyRecursive(DirectoryInfo source, DirectoryInfo destination, bool topLevelExcludes = true) { // based off example code https://docs.microsoft.com/en-us/dotnet/api/system.io.directoryinfo if (!destination.Exists) @@ -108,14 +101,7 @@ namespace osu.Game.IO if (topLevelExcludes && IgnoreFiles.Contains(fi.Name)) continue; - if (!AttemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), false))) - { - Logger.Log($"Failed to copy file {fi.Name} during folder migration"); - totalFailedOperations++; - - if (totalFailedOperations > allowed_failures) - throw new Exception("Aborting due to too many file copy failures during data migration"); - } + AttemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true)); } foreach (DirectoryInfo dir in source.GetDirectories()) @@ -123,7 +109,7 @@ namespace osu.Game.IO if (topLevelExcludes && IgnoreDirectories.Contains(dir.Name)) continue; - CopyRecursive(dir, destination.CreateSubdirectory(dir.Name), false, totalFailedOperations); + CopyRecursive(dir, destination.CreateSubdirectory(dir.Name), false); } } @@ -132,7 +118,8 @@ namespace osu.Game.IO /// /// The action to perform. /// The number of attempts (250ms wait between each). - protected static bool AttemptOperation(Action action, int attempts = 10) + /// Whether to throw an exception on failure. If false, will silently fail. + protected static bool AttemptOperation(Action action, int attempts = 10, bool throwOnFailure = true) { while (true) { @@ -144,7 +131,12 @@ namespace osu.Game.IO catch (Exception) { if (attempts-- == 0) + { + if (throwOnFailure) + throw; + return false; + } } Thread.Sleep(250); From cc8a71b65d52d9fe849fc02434fce58c3be4326d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Aug 2022 17:01:19 +0900 Subject: [PATCH 466/481] Re-query file existence before failing a recursive copy operation during migration --- osu.Game/IO/MigratableStorage.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game/IO/MigratableStorage.cs b/osu.Game/IO/MigratableStorage.cs index 30e74adca4..7cd409c04c 100644 --- a/osu.Game/IO/MigratableStorage.cs +++ b/osu.Game/IO/MigratableStorage.cs @@ -96,12 +96,22 @@ namespace osu.Game.IO if (!destination.Exists) Directory.CreateDirectory(destination.FullName); - foreach (System.IO.FileInfo fi in source.GetFiles()) + foreach (System.IO.FileInfo fileInfo in source.GetFiles()) { - if (topLevelExcludes && IgnoreFiles.Contains(fi.Name)) + if (topLevelExcludes && IgnoreFiles.Contains(fileInfo.Name)) continue; - AttemptOperation(() => fi.CopyTo(Path.Combine(destination.FullName, fi.Name), true)); + AttemptOperation(() => + { + fileInfo.Refresh(); + + // A temporary file may have been deleted since the initial GetFiles operation. + // We don't want the whole migration process to fail in such a case. + if (!fileInfo.Exists) + return; + + fileInfo.CopyTo(Path.Combine(destination.FullName, fileInfo.Name), true); + }); } foreach (DirectoryInfo dir in source.GetDirectories()) From ee681139131b1690772e2801b243baeaa8ba8e87 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Aug 2022 17:06:45 +0900 Subject: [PATCH 467/481] Add more missing realm `Refresh()` calls to new beatmap import tests As noticed at https://github.com/ppy/osu/runs/7605101313?check_suite_focus=true --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 0546d3e912..56964aa8b2 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -702,6 +702,8 @@ namespace osu.Game.Tests.Database var firstImport = await importer.Import(new ImportTask(pathMissingOneBeatmap)); Assert.That(firstImport, Is.Not.Null); + realm.Run(r => r.Refresh()); + Assert.That(realm.Realm.All().Where(s => !s.DeletePending), Has.Count.EqualTo(1)); Assert.That(realm.Realm.All().First(s => !s.DeletePending).Beatmaps, Has.Count.EqualTo(11)); @@ -709,6 +711,8 @@ namespace osu.Game.Tests.Database var secondImport = await importer.Import(new ImportTask(pathOriginal)); Assert.That(secondImport, Is.Not.Null); + realm.Run(r => r.Refresh()); + Assert.That(realm.Realm.All(), Has.Count.EqualTo(23)); Assert.That(realm.Realm.All(), Has.Count.EqualTo(2)); From c65747d1b88ee677b6f04e3bb0d36b7cc798539c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gutyina=20Gerg=C5=91?= Date: Mon, 1 Aug 2022 10:36:06 +0200 Subject: [PATCH 468/481] Use masking instead of scrollable container to prevent tags overflow --- osu.Game/Overlays/BeatmapSet/Info.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 5517e51515..08423f2aa7 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; @@ -63,7 +62,7 @@ namespace osu.Game.Overlays.BeatmapSet Child = new MetadataSection(MetadataType.Description), }, }, - new OsuScrollContainer + new Container { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -71,6 +70,7 @@ namespace osu.Game.Overlays.BeatmapSet Width = metadata_width, Padding = new MarginPadding { Horizontal = 10 }, Margin = new MarginPadding { Right = BeatmapSetOverlay.RIGHT_WIDTH + spacing }, + Masking = true, Child = new FillFlowContainer { RelativeSizeAxes = Axes.X, From e0940c6c22e28f24d120d764e1c63eb024016f56 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Aug 2022 18:03:32 +0900 Subject: [PATCH 469/481] Update animations to final versions --- .../Skinning/Default/DefaultFollowCircle.cs | 8 +++----- .../Skinning/Legacy/LegacyFollowCircle.cs | 5 +---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs index 3b087245e9..aaace89cd5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Default/DefaultFollowCircle.cs @@ -59,11 +59,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Default protected override void OnSliderTick() { - // TODO: Follow circle should bounce on each slider tick. - - // TEMP DUMMY ANIMS - this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.1f) - .ScaleTo(DrawableSliderBall.FOLLOW_AREA, 175f); + this.ScaleTo(DrawableSliderBall.FOLLOW_AREA * 1.08f, 40, Easing.OutQuint) + .Then() + .ScaleTo(DrawableSliderBall.FOLLOW_AREA, 200f, Easing.OutQuint); } protected override void OnSliderBreak() diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs index 6d16596ed2..0d12fb01f5 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyFollowCircle.cs @@ -44,11 +44,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected override void OnSliderTick() { - // TODO: Follow circle should bounce on each slider tick. - - // TEMP DUMMY ANIMS this.ScaleTo(2.2f) - .ScaleTo(2f, 175f); + .ScaleTo(2f, 200); } protected override void OnSliderBreak() From 6cccb6b84825dd4f58ddb5d5e8ea679f0a57f4bb Mon Sep 17 00:00:00 2001 From: andy840119 Date: Mon, 1 Aug 2022 19:45:15 +0800 Subject: [PATCH 470/481] Remove `canBeNull: true`. --- osu.Game/Audio/PreviewTrackManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index a0537d7a4e..b8662b6a4b 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -87,7 +87,7 @@ namespace osu.Game.Audio public class TrackManagerPreviewTrack : PreviewTrack { - [Resolved(canBeNull: true)] + [Resolved] public IPreviewTrackOwner? Owner { get; private set; } private readonly IBeatmapSetInfo beatmapSetInfo; From a5fac70c3b468b9788d5e783f65fb94ca80fce4e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 1 Aug 2022 23:30:00 +0900 Subject: [PATCH 471/481] Rename variable to not include mode name itself --- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index f3d944d55c..3f1c3aa812 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Osu.Mods private const int wiggle_duration = 100; // (ms) Higher = fewer wiggles [SettingSource("Strength", "Multiplier applied to the wiggling strength.")] - public BindableDouble WiggleStrength { get; } = new BindableDouble(1) + public BindableDouble Strength { get; } = new BindableDouble(1) { MinValue = 0.1f, MaxValue = 2f, @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Mods void wiggle() { float nextAngle = (float)(objRand.NextDouble() * 2 * Math.PI); - float nextDist = (float)(objRand.NextDouble() * WiggleStrength.Value * 7); + float nextDist = (float)(objRand.NextDouble() * Strength.Value * 7); drawable.MoveTo(new Vector2((float)(nextDist * Math.Cos(nextAngle) + origin.X), (float)(nextDist * Math.Sin(nextAngle) + origin.Y)), wiggle_duration); } From 682192dbd7fc53d8e642d84a3f2c9cf780ec83e4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Aug 2022 18:43:01 +0300 Subject: [PATCH 472/481] Add failing test case --- .../SongSelect/TestSceneBeatmapCarousel.cs | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 59932f8781..bb9e83a21c 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -486,9 +486,6 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Something is selected", () => carousel.SelectedBeatmapInfo != null); } - /// - /// Test sorting - /// [Test] public void TestSorting() { @@ -517,6 +514,9 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert($"Check {zzz_string} is at bottom", () => carousel.BeatmapSets.Last().Metadata.Artist == zzz_string); } + /// + /// Ensures stability is maintained on different sort modes for items with equal properties. + /// [Test] public void TestSortingStability() { @@ -549,6 +549,53 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("Items reset to original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b)); } + /// + /// Ensures stability is maintained on different sort modes while a new item is added to the carousel. + /// + [Test] + public void TestSortingStabilityWithNewItems() + { + List sets = new List(); + + for (int i = 0; i < 3; i++) + { + var set = TestResources.CreateTestBeatmapSetInfo(3); + + // only need to set the first as they are a shared reference. + var beatmap = set.Beatmaps.First(); + + beatmap.Metadata.Artist = "same artist"; + beatmap.Metadata.Title = "same title"; + + sets.Add(set); + } + + int idOffset = sets.First().OnlineID; + + loadBeatmaps(sets); + + AddStep("Sort by artist", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Artist }, false)); + AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b)); + + AddStep("Add new item", () => + { + var set = TestResources.CreateTestBeatmapSetInfo(); + + // only need to set the first as they are a shared reference. + var beatmap = set.Beatmaps.First(); + + beatmap.Metadata.Artist = "same artist"; + beatmap.Metadata.Title = "same title"; + + carousel.UpdateBeatmapSet(set); + }); + + AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b)); + + AddStep("Sort by title", () => carousel.Filter(new FilterCriteria { Sort = SortMode.Title }, false)); + AddAssert("Items remain in original order", () => carousel.BeatmapSets.Select((set, index) => set.OnlineID == idOffset + index).All(b => b)); + } + [Test] public void TestSortingWithFiltered() { From fc7fc3d673cf0a843d4eeea0225bcde76b987c36 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Aug 2022 19:13:57 +0300 Subject: [PATCH 473/481] Fix newly imported beatmaps not using correct comparer for sorting --- .../Screens/Select/Carousel/CarouselGroup.cs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs index 8d141b6f72..9302578038 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselGroup.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselGroup.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; namespace osu.Game.Screens.Select.Carousel { @@ -15,7 +14,7 @@ namespace osu.Game.Screens.Select.Carousel public IReadOnlyList Items => items; - private List items = new List(); + private readonly List items = new List(); /// /// Used to assign a monotonically increasing ID to items as they are added. This member is @@ -24,9 +23,6 @@ namespace osu.Game.Screens.Select.Carousel private ulong currentItemID; private Comparer? criteriaComparer; - - private static readonly Comparer item_id_comparer = Comparer.Create((x, y) => x.ItemID.CompareTo(y.ItemID)); - private FilterCriteria? lastCriteria; protected int GetIndexOfItem(CarouselItem lastSelected) => items.IndexOf(lastSelected); @@ -90,9 +86,16 @@ namespace osu.Game.Screens.Select.Carousel items.ForEach(c => c.Filter(criteria)); - // IEnumerable.OrderBy() is used instead of List.Sort() to ensure sorting stability - criteriaComparer = Comparer.Create((x, y) => x.CompareTo(criteria, y)); - items = items.OrderBy(c => c, criteriaComparer).ThenBy(c => c, item_id_comparer).ToList(); + criteriaComparer = Comparer.Create((x, y) => + { + int comparison = x.CompareTo(criteria, y); + if (comparison != 0) + return comparison; + + return x.ItemID.CompareTo(y.ItemID); + }); + + items.Sort(criteriaComparer); lastCriteria = criteria; } From 1fe7e4d19a51c8cd8a15122ccb8d5b20c0bfb4bd Mon Sep 17 00:00:00 2001 From: andy840119 Date: Tue, 2 Aug 2022 00:45:47 +0800 Subject: [PATCH 474/481] Use non-nullable instead in the catch ruleset. --- osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs | 5 ++--- osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs | 5 +---- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs index 4824106c55..abe391ba4e 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModFlashlight.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Game.Configuration; using osu.Game.Rulesets.Catch.Objects; @@ -36,9 +35,9 @@ namespace osu.Game.Rulesets.Catch.Mods public override float DefaultFlashlightSize => 350; - protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield.AsNonNull()); + protected override Flashlight CreateFlashlight() => new CatchFlashlight(this, playfield); - private CatchPlayfield? playfield; + private CatchPlayfield playfield = null!; public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs index 0ab6da0363..60f1614d98 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModRelax.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Diagnostics; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Bindings; @@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public override string Description => @"Use the mouse to control the catcher."; - private DrawableRuleset? drawableRuleset; + private DrawableRuleset drawableRuleset = null!; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { @@ -28,8 +27,6 @@ namespace osu.Game.Rulesets.Catch.Mods public void ApplyToPlayer(Player player) { - Debug.Assert(drawableRuleset != null); - if (!drawableRuleset.HasReplayLoaded.Value) drawableRuleset.Cursor.Add(new MouseInputHelper((CatchPlayfield)drawableRuleset.Playfield)); } From b1d320bf6700285957102206ade738b3a6b1d318 Mon Sep 17 00:00:00 2001 From: andy840119 Date: Tue, 2 Aug 2022 00:48:23 +0800 Subject: [PATCH 475/481] Use non-nullable instead in the taiko ruleset. --- osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs | 5 ++--- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 4 +--- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs index 8872de4d7a..66616486df 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModFlashlight.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; -using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Layout; using osu.Game.Configuration; @@ -37,9 +36,9 @@ namespace osu.Game.Rulesets.Taiko.Mods public override float DefaultFlashlightSize => 250; - protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield.AsNonNull()); + protected override Flashlight CreateFlashlight() => new TaikoFlashlight(this, playfield); - private TaikoPlayfield? playfield; + private TaikoPlayfield playfield = null!; public override void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index dab2279351..ec39a2b2e5 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Mods /// private const float fade_out_duration = 0.375f; - private DrawableTaikoRuleset? drawableRuleset; + private DrawableTaikoRuleset drawableRuleset = null!; public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { @@ -45,8 +45,6 @@ namespace osu.Game.Rulesets.Taiko.Mods protected override void ApplyNormalVisibilityState(DrawableHitObject hitObject, ArmedState state) { - Debug.Assert(drawableRuleset != null); - switch (hitObject) { case DrawableDrumRollTick: From 6686b095492abdd1eb0909eea6b57a6c33513b93 Mon Sep 17 00:00:00 2001 From: notmyname <50967056+naipofo@users.noreply.github.com> Date: Mon, 1 Aug 2022 18:54:00 +0200 Subject: [PATCH 476/481] Hide F rank from beatmap overlay --- osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs index 118ddfb060..09b44be6c9 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchScoreFilterRow.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.BeatmapListing { protected override MultipleSelectionFilterTabItem CreateTabItem(ScoreRank value) => new RankItem(value); - protected override IEnumerable GetValues() => base.GetValues().Reverse(); + protected override IEnumerable GetValues() => base.GetValues().Where(r => r > ScoreRank.F).Reverse(); } private class RankItem : MultipleSelectionFilterTabItem From c851e3d8f3150dc965be7e0c5d20a25a1da44d8e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Aug 2022 20:08:18 +0300 Subject: [PATCH 477/481] Fix playlist settings reference leak due to unsafe callback binding --- .../OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 9c6a2a5e0b..a59d37dc8b 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -78,6 +78,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [Resolved] private IAPIProvider api { get; set; } + private IBindable localUser; + private readonly Room room; public MatchSettings(Room room) @@ -304,7 +306,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true); Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue ?? TimeSpan.FromMinutes(30), true); - api.LocalUser.BindValueChanged(populateDurations, true); + localUser = api.LocalUser.GetBoundCopy(); + localUser.BindValueChanged(populateDurations, true); playlist.Items.BindTo(Playlist); Playlist.BindCollectionChanged(onPlaylistChanged, true); From 7e9d11ee2486e04b06f92d3edeb19088ffcf3e68 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Aug 2022 20:15:08 +0300 Subject: [PATCH 478/481] Enable NRT on playlists settings overlay --- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index a59d37dc8b..cd52981528 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.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.Specialized; using System.Linq; @@ -29,9 +27,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { public class PlaylistsRoomSettingsOverlay : RoomSettingsOverlay { - public Action EditPlaylist; + public Action? EditPlaylist; - private MatchSettings settings; + private MatchSettings settings = null!; protected override OsuButton SubmitButton => settings.ApplyButton; @@ -55,30 +53,30 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { private const float disabled_alpha = 0.2f; - public Action EditPlaylist; + public Action? EditPlaylist; - public OsuTextBox NameField, MaxParticipantsField, MaxAttemptsField; - public OsuDropdown DurationField; - public RoomAvailabilityPicker AvailabilityPicker; - public TriangleButton ApplyButton; + public OsuTextBox NameField = null!, MaxParticipantsField = null!, MaxAttemptsField = null!; + public OsuDropdown DurationField = null!; + public RoomAvailabilityPicker AvailabilityPicker = null!; + public TriangleButton ApplyButton = null!; public bool IsLoading => loadingLayer.State.Value == Visibility.Visible; - public OsuSpriteText ErrorText; + public OsuSpriteText ErrorText = null!; - private LoadingLayer loadingLayer; - private DrawableRoomPlaylist playlist; - private OsuSpriteText playlistLength; + private LoadingLayer loadingLayer = null!; + private DrawableRoomPlaylist playlist = null!; + private OsuSpriteText playlistLength = null!; - private PurpleTriangleButton editPlaylistButton; - - [Resolved(CanBeNull = true)] - private IRoomManager manager { get; set; } + private PurpleTriangleButton editPlaylistButton = null!; [Resolved] - private IAPIProvider api { get; set; } + private IRoomManager? manager { get; set; } - private IBindable localUser; + [Resolved] + private IAPIProvider api { get; set; } = null!; + + private IBindable localUser = null!; private readonly Room room; From 923d9a4e5f19aa10273f99a058b51fcecf5b4757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 1 Aug 2022 22:04:14 +0200 Subject: [PATCH 479/481] Add failing assertions to demonstrate autosize failure --- .../UserInterface/TestSceneShearedButtons.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs index ba9e1c6366..6c485aff34 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneShearedButtons.cs @@ -3,9 +3,13 @@ #nullable disable +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osuTK.Input; @@ -99,7 +103,10 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Text = "Fixed width" }); + AddAssert("draw width is 200", () => toggleButton.DrawWidth, () => Is.EqualTo(200).Within(Precision.FLOAT_EPSILON)); + AddStep("change text", () => toggleButton.Text = "New text"); + AddAssert("draw width is 200", () => toggleButton.DrawWidth, () => Is.EqualTo(200).Within(Precision.FLOAT_EPSILON)); AddStep("create auto-sizing button", () => Child = toggleButton = new ShearedToggleButton { @@ -107,7 +114,14 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Text = "This button autosizes to its text!" }); + AddAssert("button is wider than text", () => toggleButton.DrawWidth, () => Is.GreaterThan(toggleButton.ChildrenOfType().Single().DrawWidth)); + + float originalDrawWidth = 0; + AddStep("store button width", () => originalDrawWidth = toggleButton.DrawWidth); + AddStep("change text", () => toggleButton.Text = "New text"); + AddAssert("button is wider than text", () => toggleButton.DrawWidth, () => Is.GreaterThan(toggleButton.ChildrenOfType().Single().DrawWidth)); + AddAssert("button width decreased", () => toggleButton.DrawWidth, () => Is.LessThan(originalDrawWidth)); } [Test] From 298efa5391734206c8c8d896beb02a940b2b28d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 22 Jul 2022 21:40:24 +0200 Subject: [PATCH 480/481] Fix broken `ShearedButton` autosizing logic --- osu.Game/Graphics/UserInterface/ShearedButton.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/ShearedButton.cs b/osu.Game/Graphics/UserInterface/ShearedButton.cs index 259c0646f3..0c25d06cd4 100644 --- a/osu.Game/Graphics/UserInterface/ShearedButton.cs +++ b/osu.Game/Graphics/UserInterface/ShearedButton.cs @@ -97,7 +97,7 @@ namespace osu.Game.Graphics.UserInterface { backgroundLayer = new Container { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.Y, CornerRadius = corner_radius, Masking = true, BorderThickness = 2, @@ -128,10 +128,12 @@ namespace osu.Game.Graphics.UserInterface if (width != null) { Width = width.Value; + backgroundLayer.RelativeSizeAxes = Axes.Both; } else { AutoSizeAxes = Axes.X; + backgroundLayer.AutoSizeAxes = Axes.X; text.Margin = new MarginPadding { Horizontal = 15 }; } } From eb73f9e88c2616bc42ff81a2b5f0e7729a798e9f Mon Sep 17 00:00:00 2001 From: andy840119 Date: Tue, 2 Aug 2022 10:23:52 +0800 Subject: [PATCH 481/481] Remove un-need using. --- osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs index ec39a2b2e5..4c802978e3 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModHidden.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Diagnostics; using osu.Framework.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects;