From 7b620392fe6430e8d8c948ef097839da219860ed Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Thu, 24 Nov 2022 13:13:45 +0100 Subject: [PATCH 001/384] Provide DrawableRulesetDependencies from HitObjectComposer --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 520fcb0290..d19ab2239e 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Edit } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => - dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies = new DrawableRulesetDependencies(Ruleset, base.CreateChildDependencies(parent)); [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) From 65e5c9a3ae32c0786171d2a09d949e075569a5f7 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Fri, 25 Nov 2022 08:27:22 +0100 Subject: [PATCH 002/384] Dispose DrawableRulesetDependencies in HitObjectComposer --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index d19ab2239e..d0164d0f2e 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Edit protected readonly Ruleset Ruleset; // Provides `Playfield` - private DependencyContainer dependencies; + private DrawableRulesetDependencies dependencies; [Resolved] protected EditorClock EditorClock { get; private set; } @@ -410,6 +410,13 @@ namespace osu.Game.Rulesets.Edit } #endregion + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + dependencies.Dispose(); + } } /// From cff3e06ad6cc68b97e2e80baa5526a7c10ba4746 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 27 Nov 2022 20:57:00 +0100 Subject: [PATCH 003/384] Update state based on result during LoadComplete --- .../Objects/Drawables/DrawableHitObject.cs | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index d6c151028e..4b074f00aa 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -199,7 +199,7 @@ namespace osu.Game.Rulesets.Objects.Drawables comboColourBrightness.BindValueChanged(_ => UpdateComboColour()); // Apply transforms - updateState(State.Value, true); + updateStateBasedOnResults(); } /// @@ -266,12 +266,7 @@ namespace osu.Game.Rulesets.Objects.Drawables // If not loaded, the state update happens in LoadComplete(). if (IsLoaded) { - if (Result.IsHit) - updateState(ArmedState.Hit, true); - else if (Result.HasResult) - updateState(ArmedState.Miss, true); - else - updateState(ArmedState.Idle, true); + updateStateBasedOnResults(); // Combo colour may have been applied via a bindable flow while no object entry was attached. // Update here to ensure we're in a good state. @@ -279,6 +274,16 @@ namespace osu.Game.Rulesets.Objects.Drawables } } + private void updateStateBasedOnResults() + { + if (Result.IsHit) + updateState(ArmedState.Hit, true); + else if (Result.HasResult) + updateState(ArmedState.Miss, true); + else + updateState(ArmedState.Idle, true); + } + protected sealed override void OnFree(HitObjectLifetimeEntry entry) { StartTimeBindable.UnbindFrom(HitObject.StartTimeBindable); From 70c320b2e8efef9ea3775513a547310b2f022440 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 27 Nov 2022 21:43:33 +0100 Subject: [PATCH 004/384] Provide ruleset dependencies only to Compose Placement Blueprints --- .../Edit/CatchBlueprintContainer.cs | 4 +-- .../Edit/CatchHitObjectComposer.cs | 2 +- .../Edit/ManiaBlueprintContainer.cs | 4 +-- .../Edit/ManiaHitObjectComposer.cs | 2 +- .../Edit/OsuBlueprintContainer.cs | 4 +-- .../Edit/OsuHitObjectComposer.cs | 2 +- .../Edit/TaikoBlueprintContainer.cs | 4 +-- .../Edit/TaikoHitObjectComposer.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 13 ++----- ...leRulesetDependenciesProvidingContainer.cs | 34 +++++++++++++++++++ .../Components/ComposeBlueprintContainer.cs | 13 +++++-- 11 files changed, 60 insertions(+), 24 deletions(-) create mode 100644 osu.Game/Rulesets/UI/DrawableRulesetDependenciesProvidingContainer.cs diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs index a0a11424d0..19642c521e 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs @@ -13,8 +13,8 @@ namespace osu.Game.Rulesets.Catch.Edit { public class CatchBlueprintContainer : ComposeBlueprintContainer { - public CatchBlueprintContainer(CatchHitObjectComposer composer) - : base(composer) + public CatchBlueprintContainer(CatchHitObjectComposer composer, Ruleset ruleset) + : base(composer, ruleset) { } diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index 220bc49203..7cc65a834a 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Catch.Edit return result; } - protected override ComposeBlueprintContainer CreateBlueprintContainer() => new CatchBlueprintContainer(this); + protected override ComposeBlueprintContainer CreateBlueprintContainer() => new CatchBlueprintContainer(this, Ruleset); [CanBeNull] private PalpableCatchHitObject getLastSnappableHitObject(double time) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs index f438d6497c..597e94396a 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs @@ -13,8 +13,8 @@ namespace osu.Game.Rulesets.Mania.Edit { public class ManiaBlueprintContainer : ComposeBlueprintContainer { - public ManiaBlueprintContainer(HitObjectComposer composer) - : base(composer) + public ManiaBlueprintContainer(HitObjectComposer composer, Ruleset ruleset) + : base(composer, ruleset) { } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 3585fd4e8c..529cf77078 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Mania.Edit } protected override ComposeBlueprintContainer CreateBlueprintContainer() - => new ManiaBlueprintContainer(this); + => new ManiaBlueprintContainer(this, Ruleset); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs index c0fb0ae7db..7330ed6bb9 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs @@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuBlueprintContainer : ComposeBlueprintContainer { - public OsuBlueprintContainer(HitObjectComposer composer) - : base(composer) + public OsuBlueprintContainer(HitObjectComposer composer, Ruleset ruleset) + : base(composer, ruleset) { } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 1460fae4d7..c044b2cc79 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Edit } protected override ComposeBlueprintContainer CreateBlueprintContainer() - => new OsuBlueprintContainer(this); + => new OsuBlueprintContainer(this, Ruleset); public override string ConvertSelectionToString() => string.Join(',', selectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString())); diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs index 6107c8009a..b149fbcd4b 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs @@ -12,8 +12,8 @@ namespace osu.Game.Rulesets.Taiko.Edit { public class TaikoBlueprintContainer : ComposeBlueprintContainer { - public TaikoBlueprintContainer(HitObjectComposer composer) - : base(composer) + public TaikoBlueprintContainer(HitObjectComposer composer, Ruleset ruleset) + : base(composer, ruleset) { } diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs index 161799c980..1b6a4a1cdf 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs @@ -24,6 +24,6 @@ namespace osu.Game.Rulesets.Taiko.Edit }; protected override ComposeBlueprintContainer CreateBlueprintContainer() - => new TaikoBlueprintContainer(this); + => new TaikoBlueprintContainer(this, Ruleset); } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index d0164d0f2e..dea28667c6 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Edit protected readonly Ruleset Ruleset; // Provides `Playfield` - private DrawableRulesetDependencies dependencies; + private DependencyContainer dependencies; [Resolved] protected EditorClock EditorClock { get; private set; } @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Edit } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => - dependencies = new DrawableRulesetDependencies(Ruleset, base.CreateChildDependencies(parent)); + dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -218,7 +218,7 @@ namespace osu.Game.Rulesets.Edit /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. /// - protected virtual ComposeBlueprintContainer CreateBlueprintContainer() => new ComposeBlueprintContainer(this); + protected virtual ComposeBlueprintContainer CreateBlueprintContainer() => new ComposeBlueprintContainer( this, Ruleset); /// /// Construct a drawable ruleset for the provided ruleset. @@ -410,13 +410,6 @@ namespace osu.Game.Rulesets.Edit } #endregion - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - dependencies.Dispose(); - } } /// diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependenciesProvidingContainer.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependenciesProvidingContainer.cs new file mode 100644 index 0000000000..414cfe3a97 --- /dev/null +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependenciesProvidingContainer.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Rulesets.UI +{ + public class DrawableRulesetDependenciesProvidingContainer : Container + { + private Ruleset ruleset; + + private DrawableRulesetDependencies rulesetDependencies = null!; + + public DrawableRulesetDependenciesProvidingContainer(Ruleset ruleset) + { + this.ruleset = ruleset; + RelativeSizeAxes = Axes.Both; + } + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + return rulesetDependencies = new DrawableRulesetDependencies(ruleset, base.CreateChildDependencies(parent)); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + rulesetDependencies?.Dispose(); + } + } +} diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index ec07da43a0..c1100c3d7f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -15,11 +15,13 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Components.TernaryButtons; using osuTK; using osuTK.Input; @@ -37,15 +39,19 @@ namespace osu.Game.Screens.Edit.Compose.Components private PlacementBlueprint currentPlacement; + private readonly Ruleset ruleset; + /// /// Positional input must be received outside the container's bounds, /// in order to handle composer blueprints which are partially offscreen. /// public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - public ComposeBlueprintContainer(HitObjectComposer composer) + public ComposeBlueprintContainer(HitObjectComposer composer, Ruleset ruleset) : base(composer) { + this.ruleset = ruleset; + placementBlueprintContainer = new Container { RelativeSizeAxes = Axes.Both @@ -57,7 +63,10 @@ namespace osu.Game.Screens.Edit.Compose.Components { TernaryStates = CreateTernaryButtons().ToArray(); - AddInternal(placementBlueprintContainer); + AddInternal(new DrawableRulesetDependenciesProvidingContainer(ruleset) + { + Child = placementBlueprintContainer + }); } protected override void LoadComplete() From 93ba84e5ff187db13f5a368101722f9226ebfed3 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Sun, 27 Nov 2022 21:48:44 +0100 Subject: [PATCH 005/384] Fix code style --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- .../UI/DrawableRulesetDependenciesProvidingContainer.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index dea28667c6..cf10c9deca 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -218,7 +218,7 @@ namespace osu.Game.Rulesets.Edit /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. /// - protected virtual ComposeBlueprintContainer CreateBlueprintContainer() => new ComposeBlueprintContainer( this, Ruleset); + protected virtual ComposeBlueprintContainer CreateBlueprintContainer() => new ComposeBlueprintContainer(this, Ruleset); /// /// Construct a drawable ruleset for the provided ruleset. diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependenciesProvidingContainer.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependenciesProvidingContainer.cs index 414cfe3a97..1efe72fdf3 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependenciesProvidingContainer.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependenciesProvidingContainer.cs @@ -7,9 +7,9 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Rulesets.UI { - public class DrawableRulesetDependenciesProvidingContainer : Container + public partial class DrawableRulesetDependenciesProvidingContainer : Container { - private Ruleset ruleset; + private readonly Ruleset ruleset; private DrawableRulesetDependencies rulesetDependencies = null!; From f3fa39f2541c640398b2d312d167acd26b647386 Mon Sep 17 00:00:00 2001 From: Joppe27 Date: Wed, 30 Nov 2022 00:44:20 +0100 Subject: [PATCH 006/384] Add legacy taiko kiai glow element --- .../Skinning/Legacy/LegacyKiaiGlow.cs | 67 +++++++++++++++++++ .../Legacy/TaikoLegacySkinTransformer.cs | 6 ++ .../TaikoSkinComponents.cs | 1 + osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 4 ++ 4 files changed, 78 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs new file mode 100644 index 0000000000..07eb052b20 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs @@ -0,0 +1,67 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.Skinning.Legacy +{ + internal partial class LegacyKiaiGlow : Container + { + public LegacyKiaiGlow() + { + AlwaysPresent = true; + Alpha = 0; + } + + [BackgroundDependencyLoader] + private void load(ISkinSource skin) + { + Child = new Sprite + { + Texture = skin.GetTexture("taiko-glow"), + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Scale = new Vector2(0.75f), + }; + } + + [Resolved(CanBeNull = true)] + private IBeatSyncProvider? beatSyncProvider { get; set; } + + [Resolved(CanBeNull = true)] + private HealthProcessor? healthProcessor { get; set; } + + protected override void Update() + { + base.Update(); + + if (healthProcessor != null) + healthProcessor.NewJudgement += onNewJudgement; + + if (beatSyncProvider != null) + { + if (beatSyncProvider.CheckIsKiaiTime()) + this.FadeIn(180); + else + this.FadeOut(180); + } + } + + private void onNewJudgement(JudgementResult result) + { + if (!result.IsHit) + return; + + this.ScaleTo(1.1f, 50) + .Then().ScaleTo(1f, 50); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs index 7bf99306f0..d61f9ac35d 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacySkinTransformer.cs @@ -129,6 +129,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy case TaikoSkinComponents.Mascot: return new DrawableTaikoMascot(); + case TaikoSkinComponents.KiaiGlow: + if (GetTexture("taiko-glow") != null) + return new LegacyKiaiGlow(); + + return null; + default: throw new UnsupportedSkinComponentException(lookup); } diff --git a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs index bf48898dd2..b8e3313e1b 100644 --- a/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs +++ b/osu.Game.Rulesets.Taiko/TaikoSkinComponents.cs @@ -21,5 +21,6 @@ namespace osu.Game.Rulesets.Taiko TaikoExplosionKiai, Scroller, Mascot, + KiaiGlow } } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 6ce0be5868..9493de624a 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -112,6 +112,10 @@ namespace osu.Game.Rulesets.Taiko.UI FillMode = FillMode.Fit, Children = new[] { + new SkinnableDrawable(new TaikoSkinComponentLookup(TaikoSkinComponents.KiaiGlow), _ => Empty()) + { + RelativeSizeAxes = Axes.Both, + }, hitExplosionContainer = new Container { RelativeSizeAxes = Axes.Both, From dc1dfacbb17b8be5106830f5ed456b6c03e427c8 Mon Sep 17 00:00:00 2001 From: Joppe27 Date: Wed, 30 Nov 2022 00:44:39 +0100 Subject: [PATCH 007/384] Add very basic test coverage --- .../Skinning/TestSceneTaikoKiaiGlow.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoKiaiGlow.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoKiaiGlow.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoKiaiGlow.cs new file mode 100644 index 0000000000..26e2202124 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoKiaiGlow.cs @@ -0,0 +1,33 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Taiko.Skinning.Legacy; + +namespace osu.Game.Rulesets.Taiko.Tests.Skinning +{ + public partial class TestSceneTaikoKiaiGlow : TaikoSkinnableTestScene + { + [Test] + public void TestKiaiGlow() + { + AddStep("Create kiai glow", () => SetContents(_ => new LegacyKiaiGlow())); + AddToggleStep("Toggle kiai mode", setUpBeatmap); + } + + private void setUpBeatmap(bool withKiai) + { + var controlPointInfo = new ControlPointInfo(); + + if (withKiai) + controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true }); + + Beatmap.Value = CreateWorkingBeatmap(new Beatmap + { + ControlPointInfo = controlPointInfo + }); + } + } +} From b0039c9b1b4898d6bd0211f8391f62fc38a02a5a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Nov 2022 15:27:12 +0900 Subject: [PATCH 008/384] Add test coverage of incorrect system message ordering --- .../Online/TestSceneStandAloneChatDisplay.cs | 45 ++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 7d2ac90939..b858b4e355 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -73,6 +73,11 @@ namespace osu.Game.Tests.Visual.Online messageIdSequence = 0; channelManager.CurrentChannel.Value = testChannel = new Channel(); + reinitialiseDrawableDisplay(); + }); + + private void reinitialiseDrawableDisplay() + { Children = new[] { chatDisplay = new TestStandAloneChatDisplay @@ -92,7 +97,7 @@ namespace osu.Game.Tests.Visual.Online Channel = { Value = testChannel }, } }; - }); + } [Test] public void TestSystemMessageOrdering() @@ -106,14 +111,44 @@ namespace osu.Game.Tests.Visual.Online var infoMessage1 = new InfoMessage($"the system is calling {messageIdSequence++}"); var infoMessage2 = new InfoMessage($"the system is calling {messageIdSequence++}"); + var standardMessage2 = new Message(messageIdSequence++) + { + Sender = admin, + Content = "I am a wang!" + }; + AddStep("message from admin", () => testChannel.AddNewMessages(standardMessage)); AddStep("message from system", () => testChannel.AddNewMessages(infoMessage1)); AddStep("message from system", () => testChannel.AddNewMessages(infoMessage2)); + AddStep("message from admin", () => testChannel.AddNewMessages(standardMessage2)); - AddAssert("message order is correct", () => testChannel.Messages.Count == 3 - && testChannel.Messages[0] == standardMessage - && testChannel.Messages[1] == infoMessage1 - && testChannel.Messages[2] == infoMessage2); + AddAssert("count is correct", () => testChannel.Messages.Count, () => Is.EqualTo(4)); + + AddAssert("message order is correct", () => testChannel.Messages, () => Is.EqualTo(new[] + { + standardMessage, + infoMessage1, + infoMessage2, + standardMessage2 + })); + + AddAssert("displayed order is correct", () => chatDisplay.DrawableChannel.ChildrenOfType().Select(c => c.Message), () => Is.EqualTo(new[] + { + standardMessage, + infoMessage1, + infoMessage2, + standardMessage2 + })); + + AddStep("reinit drawable channel", reinitialiseDrawableDisplay); + + AddAssert("displayed order is still correct", () => chatDisplay.DrawableChannel.ChildrenOfType().Select(c => c.Message), () => Is.EqualTo(new[] + { + standardMessage, + infoMessage1, + infoMessage2, + standardMessage2 + })); } [Test] From c84f011bb7d4566fdc6a7a558a55be35687af365 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Nov 2022 15:36:22 +0900 Subject: [PATCH 009/384] Improve `ToString` output of `Message` --- osu.Game/Online/Chat/Message.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index 9f6f9c8d6b..2569434df7 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -85,6 +85,6 @@ namespace osu.Game.Online.Chat // ReSharper disable once ImpureMethodCallOnReadonlyValueField public override int GetHashCode() => Id.GetHashCode(); - public override string ToString() => $"[{ChannelId}] ({Id}) {Sender}: {Content}"; + public override string ToString() => $"({(Id?.ToString() ?? "null")}) {Timestamp} {Sender}: {Content}"; } } From 9e34fe360a081853fbaf61e0021bf63ed6ba3375 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Nov 2022 15:46:36 +0900 Subject: [PATCH 010/384] Ensure all `LocalMessage`s get a timestamp --- .../Visual/Online/TestSceneStandAloneChatDisplay.cs | 2 ++ osu.Game/Online/Chat/InfoMessage.cs | 4 ---- osu.Game/Online/Chat/LocalEchoMessage.cs | 2 -- osu.Game/Online/Chat/LocalMessage.cs | 3 ++- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index b858b4e355..ebd5e12acb 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -104,6 +104,7 @@ namespace osu.Game.Tests.Visual.Online { var standardMessage = new Message(messageIdSequence++) { + Timestamp = DateTimeOffset.Now, Sender = admin, Content = "I am a wang!" }; @@ -113,6 +114,7 @@ namespace osu.Game.Tests.Visual.Online var standardMessage2 = new Message(messageIdSequence++) { + Timestamp = DateTimeOffset.Now, Sender = admin, Content = "I am a wang!" }; diff --git a/osu.Game/Online/Chat/InfoMessage.cs b/osu.Game/Online/Chat/InfoMessage.cs index d98c67de34..2ade99dcb2 100644 --- a/osu.Game/Online/Chat/InfoMessage.cs +++ b/osu.Game/Online/Chat/InfoMessage.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 osu.Game.Online.API.Requests.Responses; namespace osu.Game.Online.Chat @@ -13,7 +10,6 @@ namespace osu.Game.Online.Chat public InfoMessage(string message) : base(null) { - Timestamp = DateTimeOffset.Now; Content = message; Sender = APIUser.SYSTEM_USER; diff --git a/osu.Game/Online/Chat/LocalEchoMessage.cs b/osu.Game/Online/Chat/LocalEchoMessage.cs index b226fe6cad..8a39515575 100644 --- a/osu.Game/Online/Chat/LocalEchoMessage.cs +++ b/osu.Game/Online/Chat/LocalEchoMessage.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Online.Chat { public class LocalEchoMessage : LocalMessage diff --git a/osu.Game/Online/Chat/LocalMessage.cs b/osu.Game/Online/Chat/LocalMessage.cs index 5736f5cabf..57caca2287 100644 --- a/osu.Game/Online/Chat/LocalMessage.cs +++ b/osu.Game/Online/Chat/LocalMessage.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable +using System; namespace osu.Game.Online.Chat { @@ -13,6 +13,7 @@ namespace osu.Game.Online.Chat protected LocalMessage(long? id) : base(id) { + Timestamp = DateTimeOffset.Now; } } } From 2d8854cbac6dbb5f86e965282321e0b8d1270197 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Nov 2022 15:46:58 +0900 Subject: [PATCH 011/384] Change message comparator to always use timestamp comparison if either message has a missing `Id` --- osu.Game/Online/Chat/Message.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index 2569434df7..4c86c963b7 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -66,12 +66,10 @@ namespace osu.Game.Online.Chat public int CompareTo(Message other) { - if (!Id.HasValue) - return other.Id.HasValue ? 1 : Timestamp.CompareTo(other.Timestamp); - if (!other.Id.HasValue) - return -1; + if (Id.HasValue && other.Id.HasValue) + return Id.Value.CompareTo(other.Id.Value); - return Id.Value.CompareTo(other.Id.Value); + return Timestamp.CompareTo(other.Timestamp); } public virtual bool Equals(Message other) From b1ce93e7bff368d6852d4cc914b79ee43b574ed9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Nov 2022 16:55:39 +0900 Subject: [PATCH 012/384] Add fallback stable sort to avoid any change of misordered local messages --- osu.Game/Online/Chat/Message.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index 4c86c963b7..8ea3ca0fc7 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Threading; using Newtonsoft.Json; using osu.Game.Online.API.Requests.Responses; @@ -59,9 +60,14 @@ namespace osu.Game.Online.Chat /// The s' and s are according to public List Links; + private static long constructionOrderStatic; + private readonly long constructionOrder; + public Message(long? id) { Id = id; + + constructionOrder = Interlocked.Increment(ref constructionOrderStatic); } public int CompareTo(Message other) @@ -69,7 +75,13 @@ namespace osu.Game.Online.Chat if (Id.HasValue && other.Id.HasValue) return Id.Value.CompareTo(other.Id.Value); - return Timestamp.CompareTo(other.Timestamp); + int timestampComparison = Timestamp.CompareTo(other.Timestamp); + + if (timestampComparison != 0) + return timestampComparison; + + // Timestamp might not be accurate enough to make a stable sorting decision. + return constructionOrder.CompareTo(other.constructionOrder); } public virtual bool Equals(Message other) From bb0237d4a96a75e17034c5309b0e25269977896e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Nov 2022 17:49:08 +0900 Subject: [PATCH 013/384] Fix nullability inspection on disposal path --- .../UI/DrawableRulesetDependenciesProvidingContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependenciesProvidingContainer.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependenciesProvidingContainer.cs index 1efe72fdf3..6c213497dd 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependenciesProvidingContainer.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependenciesProvidingContainer.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -28,7 +29,8 @@ namespace osu.Game.Rulesets.UI { base.Dispose(isDisposing); - rulesetDependencies?.Dispose(); + if (rulesetDependencies.IsNotNull()) + rulesetDependencies.Dispose(); } } } From 5ce2d6f54a78235bff651cfabce5152207724d81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Nov 2022 18:13:53 +0900 Subject: [PATCH 014/384] Hide the game mouse cursor when playing osu!catch with relax mod --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 12 ++++++++++++ .../UI/CatchRelaxCursorContainer.cs | 15 +++++++++++++++ osu.Game/Rulesets/UI/Playfield.cs | 10 +++++----- 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Rulesets.Catch/UI/CatchRelaxCursorContainer.cs diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 4df297565e..cd8caa43b8 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -3,15 +3,19 @@ #nullable disable +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.UI { @@ -49,6 +53,14 @@ namespace osu.Game.Rulesets.Catch.UI this.difficulty = difficulty; } + protected override GameplayCursorContainer CreateCursor() + { + if (Mods.Any(m => m is ModRelax)) + return new CatchRelaxCursorContainer(); + + return base.CreateCursor(); + } + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game.Rulesets.Catch/UI/CatchRelaxCursorContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchRelaxCursorContainer.cs new file mode 100644 index 0000000000..f30b8f0f36 --- /dev/null +++ b/osu.Game.Rulesets.Catch/UI/CatchRelaxCursorContainer.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Rulesets.UI; + +namespace osu.Game.Rulesets.Catch.UI +{ + public partial class CatchRelaxCursorContainer : GameplayCursorContainer + { + // Just hide the cursor in relax. + // The main goal here is to show that we have a cursor so the game never shows the global one. + protected override Drawable CreateCursor() => Empty(); + } +} diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index 859be6e210..abb057f9af 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -93,7 +93,7 @@ namespace osu.Game.Rulesets.UI public readonly BindableBool DisplayJudgements = new BindableBool(true); [Resolved(CanBeNull = true)] - private IReadOnlyList mods { get; set; } + protected IReadOnlyList Mods { get; private set; } private readonly HitObjectEntryManager entryManager = new HitObjectEntryManager(); @@ -243,9 +243,9 @@ namespace osu.Game.Rulesets.UI { base.Update(); - if (!IsNested && mods != null) + if (!IsNested && Mods != null) { - foreach (var mod in mods) + foreach (var mod in Mods) { if (mod is IUpdatableByPlayfield updatable) updatable.Update(this); @@ -374,9 +374,9 @@ namespace osu.Game.Rulesets.UI // If this is the first time this DHO is being used, then apply the DHO mods. // This is done before Apply() so that the state is updated once when the hitobject is applied. - if (mods != null) + if (Mods != null) { - foreach (var m in mods.OfType()) + foreach (var m in Mods.OfType()) m.ApplyToDrawableHitObject(dho); } } From f5d85f5774a8de446420434a0d931c5c4f724dd8 Mon Sep 17 00:00:00 2001 From: Piggey Date: Wed, 30 Nov 2022 10:51:42 +0100 Subject: [PATCH 015/384] make `ExportStorage` protected --- osu.Game/Database/LegacyExporter.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/LegacyExporter.cs b/osu.Game/Database/LegacyExporter.cs index 16d7441dde..9f440f3728 100644 --- a/osu.Game/Database/LegacyExporter.cs +++ b/osu.Game/Database/LegacyExporter.cs @@ -23,11 +23,11 @@ namespace osu.Game.Database protected readonly Storage UserFileStorage; - private readonly Storage exportStorage; + protected readonly Storage ExportStorage; protected LegacyExporter(Storage storage) { - exportStorage = storage.GetStorageForDirectory(@"exports"); + ExportStorage = storage.GetStorageForDirectory(@"exports"); UserFileStorage = storage.GetStorageForDirectory(@"files"); } @@ -35,14 +35,14 @@ namespace osu.Game.Database /// Exports an item to a legacy (.zip based) package. /// /// The item to export. - public void Export(TModel item) + public virtual void Export(TModel item) { string filename = $"{item.GetDisplayString().GetValidFilename()}{FileExtension}"; - using (var stream = exportStorage.CreateFileSafely(filename)) + using (var stream = ExportStorage.CreateFileSafely(filename)) ExportModelTo(item, stream); - exportStorage.PresentFileExternally(filename); + ExportStorage.PresentFileExternally(filename); } /// From 5e74c4e3b7cbf96e7c635bd05739166835f71ce3 Mon Sep 17 00:00:00 2001 From: Piggey Date: Wed, 30 Nov 2022 10:52:41 +0100 Subject: [PATCH 016/384] override `LegacyScoreExporter.Export()` to not overwrite files --- osu.Game/Database/LegacyScoreExporter.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game/Database/LegacyScoreExporter.cs b/osu.Game/Database/LegacyScoreExporter.cs index 6fa02b957d..76828ad102 100644 --- a/osu.Game/Database/LegacyScoreExporter.cs +++ b/osu.Game/Database/LegacyScoreExporter.cs @@ -8,6 +8,7 @@ using System.Linq; using osu.Framework.Platform; using osu.Game.Extensions; using osu.Game.Scoring; +using osu.Game.Utils; namespace osu.Game.Database { @@ -29,5 +30,22 @@ namespace osu.Game.Database using (var inputStream = UserFileStorage.GetStream(file.File.GetStoragePath())) inputStream.CopyTo(outputStream); } + + public override void Export(ScoreInfo item) + { + var itemFilename = item.GetDisplayString().GetValidFilename(); + + var existingExports = ExportStorage.GetFiles("", $"{itemFilename}*{FileExtension}").ToArray(); + + // trim the file extension + for (int i = 0; i < existingExports.Length; i++) + existingExports[i] = existingExports[i].TrimEnd(FileExtension.ToCharArray()); + + string filename = $"{NamingUtils.GetNextBestName(existingExports, itemFilename)}{FileExtension}"; + using (var stream = ExportStorage.CreateFileSafely(filename)) + ExportModelTo(item, stream); + + ExportStorage.PresentFileExternally(filename); + } } } From 660ad913ec54db3f6192e0e10bd69dca6dc7f049 Mon Sep 17 00:00:00 2001 From: Piggey Date: Wed, 30 Nov 2022 11:06:44 +0100 Subject: [PATCH 017/384] oh wait this affects all of the legacy exporters --- osu.Game/Database/LegacyExporter.cs | 21 +++++++++++++++------ osu.Game/Database/LegacyScoreExporter.cs | 18 ------------------ 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/osu.Game/Database/LegacyExporter.cs b/osu.Game/Database/LegacyExporter.cs index 9f440f3728..430505e855 100644 --- a/osu.Game/Database/LegacyExporter.cs +++ b/osu.Game/Database/LegacyExporter.cs @@ -4,8 +4,10 @@ #nullable disable using System.IO; +using System.Linq; using osu.Framework.Platform; using osu.Game.Extensions; +using osu.Game.Utils; using SharpCompress.Archives.Zip; namespace osu.Game.Database @@ -23,11 +25,11 @@ namespace osu.Game.Database protected readonly Storage UserFileStorage; - protected readonly Storage ExportStorage; + private readonly Storage exportStorage; protected LegacyExporter(Storage storage) { - ExportStorage = storage.GetStorageForDirectory(@"exports"); + exportStorage = storage.GetStorageForDirectory(@"exports"); UserFileStorage = storage.GetStorageForDirectory(@"files"); } @@ -35,14 +37,21 @@ namespace osu.Game.Database /// Exports an item to a legacy (.zip based) package. /// /// The item to export. - public virtual void Export(TModel item) + public void Export(TModel item) { - string filename = $"{item.GetDisplayString().GetValidFilename()}{FileExtension}"; + var itemFilename = item.GetDisplayString().GetValidFilename(); - using (var stream = ExportStorage.CreateFileSafely(filename)) + var existingExports = exportStorage.GetFiles("", $"{itemFilename}*{FileExtension}").ToArray(); + + // trim the file extension + for (int i = 0; i < existingExports.Length; i++) + existingExports[i] = existingExports[i].TrimEnd(FileExtension.ToCharArray()); + + string filename = $"{NamingUtils.GetNextBestName(existingExports, itemFilename)}{FileExtension}"; + using (var stream = exportStorage.CreateFileSafely(filename)) ExportModelTo(item, stream); - ExportStorage.PresentFileExternally(filename); + exportStorage.PresentFileExternally(filename); } /// diff --git a/osu.Game/Database/LegacyScoreExporter.cs b/osu.Game/Database/LegacyScoreExporter.cs index 76828ad102..6fa02b957d 100644 --- a/osu.Game/Database/LegacyScoreExporter.cs +++ b/osu.Game/Database/LegacyScoreExporter.cs @@ -8,7 +8,6 @@ using System.Linq; using osu.Framework.Platform; using osu.Game.Extensions; using osu.Game.Scoring; -using osu.Game.Utils; namespace osu.Game.Database { @@ -30,22 +29,5 @@ namespace osu.Game.Database using (var inputStream = UserFileStorage.GetStream(file.File.GetStoragePath())) inputStream.CopyTo(outputStream); } - - public override void Export(ScoreInfo item) - { - var itemFilename = item.GetDisplayString().GetValidFilename(); - - var existingExports = ExportStorage.GetFiles("", $"{itemFilename}*{FileExtension}").ToArray(); - - // trim the file extension - for (int i = 0; i < existingExports.Length; i++) - existingExports[i] = existingExports[i].TrimEnd(FileExtension.ToCharArray()); - - string filename = $"{NamingUtils.GetNextBestName(existingExports, itemFilename)}{FileExtension}"; - using (var stream = ExportStorage.CreateFileSafely(filename)) - ExportModelTo(item, stream); - - ExportStorage.PresentFileExternally(filename); - } } } From 6a61e7076628ac7ffddc860c75c0ff352e6d14ee Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 30 Nov 2022 12:17:58 +0100 Subject: [PATCH 018/384] Expose Ruleset from HitObjectComposer instead --- .../Edit/CatchBlueprintContainer.cs | 4 ++-- .../Edit/CatchHitObjectComposer.cs | 2 +- .../Editor/TestSceneManiaBeatSnapGrid.cs | 1 + .../Edit/ManiaBlueprintContainer.cs | 4 ++-- .../Edit/ManiaHitObjectComposer.cs | 2 +- osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs | 4 ++-- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 2 +- .../Edit/TaikoBlueprintContainer.cs | 4 ++-- .../Edit/TaikoHitObjectComposer.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 11 ++++++----- .../Compose/Components/ComposeBlueprintContainer.cs | 8 ++------ 11 files changed, 21 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs index da45e952ab..9408a9f95c 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchBlueprintContainer.cs @@ -13,8 +13,8 @@ namespace osu.Game.Rulesets.Catch.Edit { public partial class CatchBlueprintContainer : ComposeBlueprintContainer { - public CatchBlueprintContainer(CatchHitObjectComposer composer, Ruleset ruleset) - : base(composer, ruleset) + public CatchBlueprintContainer(CatchHitObjectComposer composer) + : base(composer) { } diff --git a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs index fec678a4b6..bbdffbf39c 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchHitObjectComposer.cs @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Catch.Edit return result; } - protected override ComposeBlueprintContainer CreateBlueprintContainer() => new CatchBlueprintContainer(this, Ruleset); + protected override ComposeBlueprintContainer CreateBlueprintContainer() => new CatchBlueprintContainer(this); [CanBeNull] private PalpableCatchHitObject getLastSnappableHitObject(double time) diff --git a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs index 653c75baac..aca555552f 100644 --- a/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania.Tests/Editor/TestSceneManiaBeatSnapGrid.cs @@ -90,6 +90,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor public override bool CursorInPlacementArea => false; public TestHitObjectComposer(Playfield playfield) + : base(new ManiaRuleset()) { Playfield = playfield; } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs index 060073889d..05d8ccc73f 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBlueprintContainer.cs @@ -13,8 +13,8 @@ namespace osu.Game.Rulesets.Mania.Edit { public partial class ManiaBlueprintContainer : ComposeBlueprintContainer { - public ManiaBlueprintContainer(HitObjectComposer composer, Ruleset ruleset) - : base(composer, ruleset) + public ManiaBlueprintContainer(HitObjectComposer composer) + : base(composer) { } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index ff59131c8a..5e577a2964 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -68,7 +68,7 @@ namespace osu.Game.Rulesets.Mania.Edit } protected override ComposeBlueprintContainer CreateBlueprintContainer() - => new ManiaBlueprintContainer(this, Ruleset); + => new ManiaBlueprintContainer(this); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs index 58bae7e2b2..173a664902 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuBlueprintContainer.cs @@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Osu.Edit { public partial class OsuBlueprintContainer : ComposeBlueprintContainer { - public OsuBlueprintContainer(HitObjectComposer composer, Ruleset ruleset) - : base(composer, ruleset) + public OsuBlueprintContainer(HitObjectComposer composer) + : base(composer) { } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index cf22059133..09ddc420a7 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Edit } protected override ComposeBlueprintContainer CreateBlueprintContainer() - => new OsuBlueprintContainer(this, Ruleset); + => new OsuBlueprintContainer(this); public override string ConvertSelectionToString() => string.Join(',', selectedHitObjects.Cast().OrderBy(h => h.StartTime).Select(h => (h.IndexInCurrentCombo + 1).ToString())); diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs index 0081f89b7c..6be22f3af0 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs @@ -12,8 +12,8 @@ namespace osu.Game.Rulesets.Taiko.Edit { public partial class TaikoBlueprintContainer : ComposeBlueprintContainer { - public TaikoBlueprintContainer(HitObjectComposer composer, Ruleset ruleset) - : base(composer, ruleset) + public TaikoBlueprintContainer(HitObjectComposer composer) + : base(composer) { } diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs index 4109bc4887..cff5731181 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs @@ -24,6 +24,6 @@ namespace osu.Game.Rulesets.Taiko.Edit }; protected override ComposeBlueprintContainer CreateBlueprintContainer() - => new TaikoBlueprintContainer(this, Ruleset); + => new TaikoBlueprintContainer(this); } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 8279150aa8..b5b7400f64 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -45,8 +45,6 @@ namespace osu.Game.Rulesets.Edit { protected IRulesetConfigManager Config { get; private set; } - protected readonly Ruleset Ruleset; - // Provides `Playfield` private DependencyContainer dependencies; @@ -74,8 +72,8 @@ namespace osu.Game.Rulesets.Edit private IBindable hasTiming; protected HitObjectComposer(Ruleset ruleset) + : base(ruleset) { - Ruleset = ruleset; } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => @@ -218,7 +216,7 @@ namespace osu.Game.Rulesets.Edit /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. /// - protected virtual ComposeBlueprintContainer CreateBlueprintContainer() => new ComposeBlueprintContainer(this, Ruleset); + protected virtual ComposeBlueprintContainer CreateBlueprintContainer() => new ComposeBlueprintContainer(this); /// /// Construct a drawable ruleset for the provided ruleset. @@ -419,8 +417,11 @@ namespace osu.Game.Rulesets.Edit [Cached] public abstract partial class HitObjectComposer : CompositeDrawable, IPositionSnapProvider { - protected HitObjectComposer() + public readonly Ruleset Ruleset; + + protected HitObjectComposer(Ruleset ruleset) { + Ruleset = ruleset; RelativeSizeAxes = Axes.Both; } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index fc23f1d1b4..cf38cd7261 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Audio; using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; @@ -39,7 +38,6 @@ namespace osu.Game.Screens.Edit.Compose.Components private PlacementBlueprint currentPlacement; - private readonly Ruleset ruleset; /// /// Positional input must be received outside the container's bounds, @@ -47,11 +45,9 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - public ComposeBlueprintContainer(HitObjectComposer composer, Ruleset ruleset) + public ComposeBlueprintContainer(HitObjectComposer composer) : base(composer) { - this.ruleset = ruleset; - placementBlueprintContainer = new Container { RelativeSizeAxes = Axes.Both @@ -63,7 +59,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { TernaryStates = CreateTernaryButtons().ToArray(); - AddInternal(new DrawableRulesetDependenciesProvidingContainer(ruleset) + AddInternal(new DrawableRulesetDependenciesProvidingContainer(Composer.Ruleset) { Child = placementBlueprintContainer }); From 5c8fce1c087cf7ecc8cf642dd8011b3708d15cb5 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Wed, 30 Nov 2022 13:05:14 +0100 Subject: [PATCH 019/384] Remove extra blank line --- .../Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index cf38cd7261..713625c15f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -38,7 +38,6 @@ namespace osu.Game.Screens.Edit.Compose.Components private PlacementBlueprint currentPlacement; - /// /// Positional input must be received outside the container's bounds, /// in order to handle composer blueprints which are partially offscreen. From b99ddc2acf47f65e2c0c25290dbff44d0d06ec13 Mon Sep 17 00:00:00 2001 From: Piggey Date: Wed, 30 Nov 2022 15:36:08 +0100 Subject: [PATCH 020/384] use `.Select()` to trim the file extension from filename --- osu.Game/Database/LegacyExporter.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Database/LegacyExporter.cs b/osu.Game/Database/LegacyExporter.cs index 430505e855..4b65e26145 100644 --- a/osu.Game/Database/LegacyExporter.cs +++ b/osu.Game/Database/LegacyExporter.cs @@ -3,6 +3,7 @@ #nullable disable +using System.Collections.Generic; using System.IO; using System.Linq; using osu.Framework.Platform; @@ -39,13 +40,11 @@ namespace osu.Game.Database /// The item to export. public void Export(TModel item) { - var itemFilename = item.GetDisplayString().GetValidFilename(); + string itemFilename = item.GetDisplayString().GetValidFilename(); - var existingExports = exportStorage.GetFiles("", $"{itemFilename}*{FileExtension}").ToArray(); - - // trim the file extension - for (int i = 0; i < existingExports.Length; i++) - existingExports[i] = existingExports[i].TrimEnd(FileExtension.ToCharArray()); + IEnumerable existingExports = exportStorage + .GetFiles("", $"{itemFilename}*{FileExtension}") + .Select(export => export.Substring(0, export.Length - FileExtension.Length)); string filename = $"{NamingUtils.GetNextBestName(existingExports, itemFilename)}{FileExtension}"; using (var stream = exportStorage.CreateFileSafely(filename)) From 8412a441796b5d8bbea63b9a45b5a727084ab725 Mon Sep 17 00:00:00 2001 From: Piggey Date: Wed, 30 Nov 2022 18:32:14 +0100 Subject: [PATCH 021/384] create `NamingUtils.GetNextBestFilename()` --- osu.Game/Utils/NamingUtils.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Utils/NamingUtils.cs b/osu.Game/Utils/NamingUtils.cs index 482e3d0954..ca4667b82a 100644 --- a/osu.Game/Utils/NamingUtils.cs +++ b/osu.Game/Utils/NamingUtils.cs @@ -1,7 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; namespace osu.Game.Utils @@ -57,5 +59,19 @@ namespace osu.Game.Utils ? desiredName : $"{desiredName} ({bestNumber})"; } + + /// + /// Given a set of and a desired target + /// finds a filename closest to that is not in + /// + /// SHOULD NOT CONTAIN the file extension. + /// + /// + public static string GetNextBestFilename(IEnumerable existingFilenames, string desiredName, string fileExtension) + { + var stripped = existingFilenames.Select(filename => filename.Substring(0, filename.Length - fileExtension.Length)); + + return $"{GetNextBestName(stripped, desiredName)}{fileExtension}"; + } } } From 8b856f1c89d9f45d566984e0b4c5319f18ddd346 Mon Sep 17 00:00:00 2001 From: Piggey Date: Wed, 30 Nov 2022 18:32:53 +0100 Subject: [PATCH 022/384] make `LegacyExporter` use `NamingUtils.GetNextBestFilename()` --- osu.Game/Database/LegacyExporter.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/LegacyExporter.cs b/osu.Game/Database/LegacyExporter.cs index 4b65e26145..e9789ca777 100644 --- a/osu.Game/Database/LegacyExporter.cs +++ b/osu.Game/Database/LegacyExporter.cs @@ -3,9 +3,9 @@ #nullable disable +using System; using System.Collections.Generic; using System.IO; -using System.Linq; using osu.Framework.Platform; using osu.Game.Extensions; using osu.Game.Utils; @@ -42,11 +42,9 @@ namespace osu.Game.Database { string itemFilename = item.GetDisplayString().GetValidFilename(); - IEnumerable existingExports = exportStorage - .GetFiles("", $"{itemFilename}*{FileExtension}") - .Select(export => export.Substring(0, export.Length - FileExtension.Length)); + IEnumerable existingExports = exportStorage.GetFiles("", $"{itemFilename}*{FileExtension}"); - string filename = $"{NamingUtils.GetNextBestName(existingExports, itemFilename)}{FileExtension}"; + string filename = NamingUtils.GetNextBestFilename(existingExports, itemFilename, FileExtension); using (var stream = exportStorage.CreateFileSafely(filename)) ExportModelTo(item, stream); From 8f59aad91c77982d7f99dbf81036bfd2601efa9b Mon Sep 17 00:00:00 2001 From: Piggey Date: Wed, 30 Nov 2022 18:37:50 +0100 Subject: [PATCH 023/384] unnecessary includes --- osu.Game/Database/LegacyExporter.cs | 1 - osu.Game/Utils/NamingUtils.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/osu.Game/Database/LegacyExporter.cs b/osu.Game/Database/LegacyExporter.cs index e9789ca777..0a5f787469 100644 --- a/osu.Game/Database/LegacyExporter.cs +++ b/osu.Game/Database/LegacyExporter.cs @@ -3,7 +3,6 @@ #nullable disable -using System; using System.Collections.Generic; using System.IO; using osu.Framework.Platform; diff --git a/osu.Game/Utils/NamingUtils.cs b/osu.Game/Utils/NamingUtils.cs index ca4667b82a..2439d4ba22 100644 --- a/osu.Game/Utils/NamingUtils.cs +++ b/osu.Game/Utils/NamingUtils.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; From f9a9ab1d9be3f74a2f90cea0ba8176260d775d7a Mon Sep 17 00:00:00 2001 From: Joppe27 Date: Wed, 30 Nov 2022 19:50:47 +0100 Subject: [PATCH 024/384] Use BeatSyncedContainer and adjust values to be in line with stable --- .../Skinning/Legacy/LegacyKiaiGlow.cs | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs index 07eb052b20..955deb7f3a 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs @@ -2,10 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; @@ -13,8 +14,10 @@ using osuTK; namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { - internal partial class LegacyKiaiGlow : Container + internal partial class LegacyKiaiGlow : BeatSyncedContainer { + private const float colour_compensation = 1.58f; + public LegacyKiaiGlow() { AlwaysPresent = true; @@ -29,13 +32,11 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy Texture = skin.GetTexture("taiko-glow"), Origin = Anchor.Centre, Anchor = Anchor.Centre, - Scale = new Vector2(0.75f), + Scale = new Vector2(0.7f), + Colour = new Colour4(255, 228, 0, 255), }; } - [Resolved(CanBeNull = true)] - private IBeatSyncProvider? beatSyncProvider { get; set; } - [Resolved(CanBeNull = true)] private HealthProcessor? healthProcessor { get; set; } @@ -45,14 +46,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy if (healthProcessor != null) healthProcessor.NewJudgement += onNewJudgement; - - if (beatSyncProvider != null) - { - if (beatSyncProvider.CheckIsKiaiTime()) - this.FadeIn(180); - else - this.FadeOut(180); - } } private void onNewJudgement(JudgementResult result) @@ -60,8 +53,16 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy if (!result.IsHit) return; - this.ScaleTo(1.1f, 50) - .Then().ScaleTo(1f, 50); + this.ScaleTo(1.2f) + .Then().ScaleTo(1f, 80, Easing.OutQuad); + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + if (effectPoint.KiaiMode) + this.FadeTo(colour_compensation, 180); + else + this.FadeOut(180); } } } From 6ebde9a7473c3e93a3eee67bb844cbad1f5c0eda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Nov 2022 20:00:25 +0100 Subject: [PATCH 025/384] Annotate `Playfield.Mods` as maybe-null --- osu.Game/Rulesets/UI/Playfield.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index abb057f9af..a7881678f1 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -93,6 +93,7 @@ namespace osu.Game.Rulesets.UI public readonly BindableBool DisplayJudgements = new BindableBool(true); [Resolved(CanBeNull = true)] + [CanBeNull] protected IReadOnlyList Mods { get; private set; } private readonly HitObjectEntryManager entryManager = new HitObjectEntryManager(); From 285248d55412c9c26ff3648499c64d455dffc7e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Nov 2022 20:01:11 +0100 Subject: [PATCH 026/384] Fix potential null dereference in `CatchPlayfield` --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index cd8caa43b8..3df63bbe30 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Catch.UI protected override GameplayCursorContainer CreateCursor() { - if (Mods.Any(m => m is ModRelax)) + if (Mods != null && Mods.Any(m => m is ModRelax)) return new CatchRelaxCursorContainer(); return base.CreateCursor(); From 7f68fe429f7f3fb5c1d533d9f8731cada420d42f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Nov 2022 20:01:59 +0100 Subject: [PATCH 027/384] Remove unused using directive --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 3df63bbe30..184ff38cc6 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -15,7 +15,6 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.UI { From 77e282ada9522547b887ee18d386dadebbfa2bf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Nov 2022 20:14:35 +0100 Subject: [PATCH 028/384] Add test coverage for catch relax mod hiding cursor --- .../Mods/TestSceneCatchModRelax.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs index 8472b995e8..5835ccaf78 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModRelax.cs @@ -4,8 +4,10 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps; +using osu.Game.Graphics.Cursor; using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; @@ -55,6 +57,21 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods } }); + [Test] + public void TestGameCursorHidden() + { + CreateModTest(new ModTestData + { + Mod = new CatchModRelax(), + Autoplay = false, + PassCondition = () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + return this.ChildrenOfType().Single().State.Value == Visibility.Hidden; + } + }); + } + private bool passCondition() { var playfield = this.ChildrenOfType().Single(); From 830e243d12719f2887ab1b67a3601aaed5186275 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Nov 2022 22:53:56 +0100 Subject: [PATCH 029/384] Add test coverage for failure case --- .../Gameplay/TestSceneDrawableHitObject.cs | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs index 62863524fe..04fc4cafbd 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrawableHitObject.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; @@ -137,6 +138,31 @@ namespace osu.Game.Tests.Gameplay AddAssert("DHO state is correct", () => dho.State.Value == ArmedState.Miss); } + [Test] + public void TestResultSetBeforeLoadComplete() + { + TestDrawableHitObject dho = null; + HitObjectLifetimeEntry lifetimeEntry = null; + AddStep("Create lifetime entry", () => + { + var hitObject = new HitObject { StartTime = Time.Current }; + lifetimeEntry = new HitObjectLifetimeEntry(hitObject) + { + Result = new JudgementResult(hitObject, hitObject.CreateJudgement()) + { + Type = HitResult.Great + } + }; + }); + AddStep("Create DHO and apply entry", () => + { + dho = new TestDrawableHitObject(); + dho.Apply(lifetimeEntry); + Child = dho; + }); + AddAssert("DHO state is correct", () => dho.State.Value, () => Is.EqualTo(ArmedState.Hit)); + } + private partial class TestDrawableHitObject : DrawableHitObject { public const double INITIAL_LIFETIME_OFFSET = 100; From 2e19e93036baf5ccd2ee73fa5cf824fa08ad36a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 30 Nov 2022 22:54:14 +0100 Subject: [PATCH 030/384] Rename method --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 4b074f00aa..096132d024 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -199,7 +199,7 @@ namespace osu.Game.Rulesets.Objects.Drawables comboColourBrightness.BindValueChanged(_ => UpdateComboColour()); // Apply transforms - updateStateBasedOnResults(); + updateStateFromResult(); } /// @@ -266,7 +266,7 @@ namespace osu.Game.Rulesets.Objects.Drawables // If not loaded, the state update happens in LoadComplete(). if (IsLoaded) { - updateStateBasedOnResults(); + updateStateFromResult(); // Combo colour may have been applied via a bindable flow while no object entry was attached. // Update here to ensure we're in a good state. @@ -274,7 +274,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } } - private void updateStateBasedOnResults() + private void updateStateFromResult() { if (Result.IsHit) updateState(ArmedState.Hit, true); From bbf023cf682b95fb6bd00bff8bc5800163b78a3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Dec 2022 16:08:14 +0900 Subject: [PATCH 031/384] Fix incorrect culture used when parsing timeline popup textbox content --- .../Timing/IndeterminateSliderWithTextBoxInput.cs | 15 ++++++++++++++- .../Screens/Edit/Timing/SliderWithTextBoxInput.cs | 15 ++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs index 36186353f8..64713c7714 100644 --- a/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/IndeterminateSliderWithTextBoxInput.cs @@ -94,7 +94,20 @@ namespace osu.Game.Screens.Edit.Timing try { - slider.Current.Parse(t.Text); + switch (slider.Current) + { + case Bindable bindableInt: + bindableInt.Value = int.Parse(t.Text); + break; + + case Bindable bindableDouble: + bindableDouble.Value = double.Parse(t.Text); + break; + + default: + slider.Current.Parse(t.Text); + break; + } } catch { diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs index e1a5c3b23c..65c5128438 100644 --- a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs @@ -58,7 +58,20 @@ namespace osu.Game.Screens.Edit.Timing try { - slider.Current.Parse(t.Text); + switch (slider.Current) + { + case Bindable bindableInt: + bindableInt.Value = int.Parse(t.Text); + break; + + case Bindable bindableDouble: + bindableDouble.Value = double.Parse(t.Text); + break; + + default: + slider.Current.Parse(t.Text); + break; + } } catch { From f34a79b1a9a986615f691130f5ed3c057e98d1e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Dec 2022 17:03:53 +0900 Subject: [PATCH 032/384] Set a better default colour for timeline blueprints when no colour information is available --- .../Compose/Components/Timeline/TimelineHitObjectBlueprint.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 3e49c31b1e..d089b1e744 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -165,7 +165,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline break; default: - return; + colour = Color4.Gray; + break; } if (IsSelected) From 896f2d8f74041d279dca91bb9dbe8b47e017d2b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Dec 2022 17:43:54 +0900 Subject: [PATCH 033/384] Fix multiple instances of last hitobject time being calculated incorrectly --- osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs | 5 ++--- osu.Game/Beatmaps/Beatmap.cs | 5 ++++- osu.Game/Beatmaps/IBeatmap.cs | 12 ++++++++++++ osu.Game/Rulesets/Objects/BarLineGenerator.cs | 7 ++----- .../UI/Scrolling/DrawableScrollingRuleset.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 7 ++----- osu.Game/Screens/Play/ReplayPlayer.cs | 4 +--- 7 files changed, 24 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs index 55c20eebe9..77cf340b95 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTargetPractice.cs @@ -17,7 +17,6 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Overlays.Settings; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Beatmaps; @@ -196,8 +195,8 @@ namespace osu.Game.Rulesets.Osu.Mods private IEnumerable generateBeats(IBeatmap beatmap, IReadOnlyCollection originalHitObjects) { - double startTime = originalHitObjects.First().StartTime; - double endTime = originalHitObjects.Last().GetEndTime(); + double startTime = beatmap.HitObjects.First().StartTime; + double endTime = beatmap.GetLastObjectTime(); var beats = beatmap.ControlPointInfo.TimingPoints // Ignore timing points after endTime diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 2d02fb6200..2ab4fe0c79 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -81,9 +81,12 @@ namespace osu.Game.Beatmaps public double GetMostCommonBeatLength() { + if (!HitObjects.Any()) + return ControlPointInfo.TimingPoints.LastOrDefault()?.Time ?? 0; + // The last playable time in the beatmap - the last timing point extends to this time. // Note: This is more accurate and may present different results because osu-stable didn't have the ability to calculate slider durations in this context. - double lastTime = HitObjects.LastOrDefault()?.GetEndTime() ?? ControlPointInfo.TimingPoints.LastOrDefault()?.Time ?? 0; + double lastTime = this.GetLastObjectTime(); var mostCommon = // Construct a set of (beatLength, duration) tuples for each individual timing point. diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index 0e892b6581..f6771f7adf 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -4,6 +4,7 @@ #nullable disable using System.Collections.Generic; +using System.Linq; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Objects; @@ -102,5 +103,16 @@ namespace osu.Game.Beatmaps addCombo(nested, ref combo); } } + + /// + /// Find the absolute end time of the latest in a beatmap. Will throw if beatmap contains no objects. + /// + /// + /// This correctly accounts for rulesets which have concurrent hitobjects which may have durations, causing the .Last() object + /// to not necessarily have the latest end time. + /// + /// It's not super efficient so calls should be kept to a minimum. + /// + public static double GetLastObjectTime(this IBeatmap beatmap) => beatmap.HitObjects.Max(h => h.GetEndTime()); } } diff --git a/osu.Game/Rulesets/Objects/BarLineGenerator.cs b/osu.Game/Rulesets/Objects/BarLineGenerator.cs index 5c76c43f20..af32c7def3 100644 --- a/osu.Game/Rulesets/Objects/BarLineGenerator.cs +++ b/osu.Game/Rulesets/Objects/BarLineGenerator.cs @@ -27,11 +27,8 @@ namespace osu.Game.Rulesets.Objects if (beatmap.HitObjects.Count == 0) return; - HitObject firstObject = beatmap.HitObjects.First(); - HitObject lastObject = beatmap.HitObjects.Last(); - - double firstHitTime = firstObject.StartTime; - double lastHitTime = 1 + lastObject.GetEndTime(); + double firstHitTime = beatmap.HitObjects.First().StartTime; + double lastHitTime = 1 + beatmap.GetLastObjectTime(); var timingPoints = beatmap.ControlPointInfo.TimingPoints; diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 52853d3979..123be0f117 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.UI.Scrolling break; } - double lastObjectTime = Objects.LastOrDefault()?.GetEndTime() ?? double.MaxValue; + double lastObjectTime = Beatmap.GetLastObjectTime(); double baseBeatLength = TimingControlPoint.DEFAULT_BEAT_LENGTH; if (RelativeScaleBeatLengths) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 02130b9662..b8d308cc7c 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -40,7 +40,6 @@ using osu.Game.Overlays.Notifications; using osu.Game.Overlays.OSD; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit.Components.Menus; using osu.Game.Screens.Edit.Compose; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -538,12 +537,10 @@ namespace osu.Game.Screens.Edit // Seek to last object time, or track end if already there. // Note that in osu-stable subsequent presses when at track end won't return to last object. // This has intentionally been changed to make it more useful. - double? lastObjectTime = editorBeatmap.HitObjects.LastOrDefault()?.GetEndTime(); - - if (lastObjectTime == null || clock.CurrentTime == lastObjectTime) + if (!editorBeatmap.HitObjects.Any() || clock.CurrentTime == editorBeatmap.GetLastObjectTime()) clock.Seek(clock.TrackLength); else - clock.Seek(lastObjectTime.Value); + clock.Seek(editorBeatmap.GetLastObjectTime()); return true; } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 4395b96139..c5ef6b1585 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using osu.Framework.Bindables; using osu.Framework.Input.Bindings; @@ -13,7 +12,6 @@ using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Objects; using osu.Game.Scoring; using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Ranking; @@ -94,7 +92,7 @@ namespace osu.Game.Screens.Play void keyboardSeek(int direction) { - double target = Math.Clamp(GameplayClockContainer.CurrentTime + direction * keyboard_seek_amount, 0, GameplayState.Beatmap.HitObjects.Last().GetEndTime()); + double target = Math.Clamp(GameplayClockContainer.CurrentTime + direction * keyboard_seek_amount, 0, GameplayState.Beatmap.GetLastObjectTime()); Seek(target); } From d9d48516fce4a8cfda3d8b91fd3f1aa55270ce8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Dec 2022 18:04:04 +0900 Subject: [PATCH 034/384] Add test coverage of getting last object time --- .../Formats/LegacyBeatmapDecoderTest.cs | 18 +++++++++ .../mania-last-object-not-latest.osu | 39 +++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 osu.Game.Tests/Resources/mania-last-object-not-latest.osu diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index c6bdd25e8b..5787bd6066 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -314,6 +314,24 @@ namespace osu.Game.Tests.Beatmaps.Formats } } + [Test] + public void TestGetLastObjectTime() + { + var decoder = new LegacyBeatmapDecoder(); + + using (var resStream = TestResources.OpenResource("mania-last-object-not-latest.osu")) + using (var stream = new LineBufferedReader(resStream)) + { + var beatmap = decoder.Decode(stream); + + Assert.That(beatmap.HitObjects.Last().StartTime, Is.EqualTo(2494)); + Assert.That(beatmap.HitObjects.Last().GetEndTime(), Is.EqualTo(2494)); + + Assert.That(beatmap.HitObjects.Max(h => h.GetEndTime()), Is.EqualTo(2582)); + Assert.That(beatmap.GetLastObjectTime(), Is.EqualTo(2582)); + } + } + [Test] public void TestDecodeBeatmapComboOffsetsOsu() { diff --git a/osu.Game.Tests/Resources/mania-last-object-not-latest.osu b/osu.Game.Tests/Resources/mania-last-object-not-latest.osu new file mode 100644 index 0000000000..51893383d8 --- /dev/null +++ b/osu.Game.Tests/Resources/mania-last-object-not-latest.osu @@ -0,0 +1,39 @@ +osu file format v14 + +[General] +SampleSet: Normal +StackLeniency: 0.7 +Mode: 3 + +[Difficulty] +HPDrainRate:3 +CircleSize:5 +OverallDifficulty:8 +ApproachRate:8 +SliderMultiplier:3.59999990463257 +SliderTickRate:2 + +[TimingPoints] +24,352.941176470588,4,1,1,100,1,0 +6376,-50,4,1,1,100,0,0 + +[HitObjects] +51,192,24,1,0,0:0:0:0: +153,192,200,1,0,0:0:0:0: +358,192,376,1,0,0:0:0:0: +460,192,553,1,0,0:0:0:0: +460,192,729,128,0,1435:0:0:0:0: +358,192,906,128,0,1612:0:0:0:0: +256,192,1082,128,0,1788:0:0:0:0: +153,192,1259,128,0,1965:0:0:0:0: +51,192,1435,128,0,2141:0:0:0:0: +51,192,2318,1,12,0:0:0:0: +153,192,2318,1,4,0:0:0:0: +256,192,2318,1,6,0:0:0:0: +358,192,2318,1,14,0:0:0:0: +460,192,2318,1,0,0:0:0:0: +51,192,2494,128,0,2582:0:0:0:0: +153,192,2494,128,14,2582:0:0:0:0: +256,192,2494,128,6,2582:0:0:0:0: +358,192,2494,128,4,2582:0:0:0:0: +460,192,2494,1,12,0:0:0:0:0: From a7f4325d3c473bb616b7b82cc23ceefe7044d115 Mon Sep 17 00:00:00 2001 From: Joppe27 Date: Thu, 1 Dec 2022 12:16:58 +0100 Subject: [PATCH 035/384] Revert hacky fade value and fix test --- .../Skinning/TestSceneTaikoKiaiGlow.cs | 4 ++++ osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoKiaiGlow.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoKiaiGlow.cs index 26e2202124..a5e2eb0dbb 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoKiaiGlow.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneTaikoKiaiGlow.cs @@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning { var controlPointInfo = new ControlPointInfo(); + controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 }); + if (withKiai) controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true }); @@ -28,6 +30,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning { ControlPointInfo = controlPointInfo }); + + Beatmap.Value.Track.Start(); } } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs index 955deb7f3a..80b8bc9b11 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs @@ -16,8 +16,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { internal partial class LegacyKiaiGlow : BeatSyncedContainer { - private const float colour_compensation = 1.58f; - public LegacyKiaiGlow() { AlwaysPresent = true; @@ -60,7 +58,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { if (effectPoint.KiaiMode) - this.FadeTo(colour_compensation, 180); + this.FadeIn(180); else this.FadeOut(180); } From d8dd7e7e0ff8a9476949d3ab73e27777fa638e83 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 1 Dec 2022 21:42:03 +0900 Subject: [PATCH 036/384] Fix test failures when no hitobjects are present in a scrolling ruleset --- osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 123be0f117..5218ddfe15 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.UI.Scrolling break; } - double lastObjectTime = Beatmap.GetLastObjectTime(); + double lastObjectTime = Beatmap.HitObjects.Any() ? Beatmap.GetLastObjectTime() : 0; double baseBeatLength = TimingControlPoint.DEFAULT_BEAT_LENGTH; if (RelativeScaleBeatLengths) From 5de01686a9d60c80c0f4afb5d6a3a57a9693fce1 Mon Sep 17 00:00:00 2001 From: Piggey Date: Thu, 1 Dec 2022 18:42:52 +0100 Subject: [PATCH 037/384] extract `findBestNumber()` from `GetNextBestName()` into private method --- osu.Game/Utils/NamingUtils.cs | 42 +++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/osu.Game/Utils/NamingUtils.cs b/osu.Game/Utils/NamingUtils.cs index 2439d4ba22..0f55c79e84 100644 --- a/osu.Game/Utils/NamingUtils.cs +++ b/osu.Game/Utils/NamingUtils.cs @@ -31,6 +31,30 @@ namespace osu.Game.Utils { string pattern = $@"^(?i){Regex.Escape(desiredName)}(?-i)( \((?[1-9][0-9]*)\))?$"; var regex = new Regex(pattern, RegexOptions.Compiled); + + int bestNumber = findBestNumber(existingNames, regex); + + return bestNumber == 0 + ? desiredName + : $"{desiredName} ({bestNumber.ToString()})"; + } + + /// + /// Given a set of and a desired target + /// finds a filename closest to that is not in + /// + /// SHOULD NOT CONTAIN the file extension. + /// + /// + public static string GetNextBestFilename(IEnumerable existingFilenames, string desiredName, string fileExtension) + { + var stripped = existingFilenames.Select(filename => filename.Substring(0, filename.Length - fileExtension.Length)); + + return $"{GetNextBestName(stripped, desiredName)}{fileExtension}"; + } + + private static int findBestNumber(IEnumerable existingNames, Regex regex) + { var takenNumbers = new HashSet(); foreach (string name in existingNames) @@ -54,23 +78,7 @@ namespace osu.Game.Utils while (takenNumbers.Contains(bestNumber)) bestNumber += 1; - return bestNumber == 0 - ? desiredName - : $"{desiredName} ({bestNumber})"; - } - - /// - /// Given a set of and a desired target - /// finds a filename closest to that is not in - /// - /// SHOULD NOT CONTAIN the file extension. - /// - /// - public static string GetNextBestFilename(IEnumerable existingFilenames, string desiredName, string fileExtension) - { - var stripped = existingFilenames.Select(filename => filename.Substring(0, filename.Length - fileExtension.Length)); - - return $"{GetNextBestName(stripped, desiredName)}{fileExtension}"; + return bestNumber; } } } From 75cf7bd1d2262fd09633f64182e2d5acbe4758bd Mon Sep 17 00:00:00 2001 From: Piggey Date: Thu, 1 Dec 2022 18:43:34 +0100 Subject: [PATCH 038/384] change `GetNextBestFilename()`'s parameters --- osu.Game/Database/LegacyExporter.cs | 2 +- osu.Game/Utils/NamingUtils.cs | 23 ++++++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game/Database/LegacyExporter.cs b/osu.Game/Database/LegacyExporter.cs index 0a5f787469..374f9f557a 100644 --- a/osu.Game/Database/LegacyExporter.cs +++ b/osu.Game/Database/LegacyExporter.cs @@ -43,7 +43,7 @@ namespace osu.Game.Database IEnumerable existingExports = exportStorage.GetFiles("", $"{itemFilename}*{FileExtension}"); - string filename = NamingUtils.GetNextBestFilename(existingExports, itemFilename, FileExtension); + string filename = NamingUtils.GetNextBestFilename(existingExports, $"{itemFilename}{FileExtension}"); using (var stream = exportStorage.CreateFileSafely(filename)) ExportModelTo(item, stream); diff --git a/osu.Game/Utils/NamingUtils.cs b/osu.Game/Utils/NamingUtils.cs index 0f55c79e84..fa102ff56f 100644 --- a/osu.Game/Utils/NamingUtils.cs +++ b/osu.Game/Utils/NamingUtils.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; +using System.IO; using System.Text.RegularExpressions; namespace osu.Game.Utils @@ -40,17 +40,22 @@ namespace osu.Game.Utils } /// - /// Given a set of and a desired target - /// finds a filename closest to that is not in - /// - /// SHOULD NOT CONTAIN the file extension. - /// + /// Given a set of and a desired target + /// finds a filename closest to that is not in /// - public static string GetNextBestFilename(IEnumerable existingFilenames, string desiredName, string fileExtension) + public static string GetNextBestFilename(IEnumerable existingFilenames, string desiredFilename) { - var stripped = existingFilenames.Select(filename => filename.Substring(0, filename.Length - fileExtension.Length)); + string name = Path.GetFileNameWithoutExtension(desiredFilename); + string extension = Path.GetExtension(desiredFilename); - return $"{GetNextBestName(stripped, desiredName)}{fileExtension}"; + string pattern = $@"^(?i){Regex.Escape(name)}(?-i)( \((?[1-9][0-9]*)\))?(?i){Regex.Escape(extension)}(?-i)$"; + var regex = new Regex(pattern, RegexOptions.Compiled); + + int bestNumber = findBestNumber(existingFilenames, regex); + + return bestNumber == 0 + ? desiredFilename + : $"{name} ({bestNumber.ToString()}){extension}"; } private static int findBestNumber(IEnumerable existingNames, Regex regex) From 4308120912e995db07dd165b368d039ff3830310 Mon Sep 17 00:00:00 2001 From: Piggey Date: Thu, 1 Dec 2022 18:44:26 +0100 Subject: [PATCH 039/384] add tests for `GetNextBestFilename()` --- osu.Game.Tests/Utils/NamingUtilsTest.cs | 166 ++++++++++++++++++++++-- 1 file changed, 157 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Utils/NamingUtilsTest.cs b/osu.Game.Tests/Utils/NamingUtilsTest.cs index 62e688db90..1f7e06f996 100644 --- a/osu.Game.Tests/Utils/NamingUtilsTest.cs +++ b/osu.Game.Tests/Utils/NamingUtilsTest.cs @@ -11,7 +11,7 @@ namespace osu.Game.Tests.Utils public class NamingUtilsTest { [Test] - public void TestEmptySet() + public void TestNextBestNameEmptySet() { string nextBestName = NamingUtils.GetNextBestName(Enumerable.Empty(), "New Difficulty"); @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Utils } [Test] - public void TestNotTaken() + public void TestNextBestNameNotTaken() { string[] existingNames = { @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Utils } [Test] - public void TestNotTakenButClose() + public void TestNextBestNameNotTakenButClose() { string[] existingNames = { @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Utils } [Test] - public void TestAlreadyTaken() + public void TestNextBestNameAlreadyTaken() { string[] existingNames = { @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Utils } [Test] - public void TestAlreadyTakenWithDifferentCase() + public void TestNextBestNameAlreadyTakenWithDifferentCase() { string[] existingNames = { @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Utils } [Test] - public void TestAlreadyTakenWithBrackets() + public void TestNextBestNameAlreadyTakenWithBrackets() { string[] existingNames = { @@ -88,7 +88,7 @@ namespace osu.Game.Tests.Utils } [Test] - public void TestMultipleAlreadyTaken() + public void TestNextBestNameMultipleAlreadyTaken() { string[] existingNames = { @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Utils } [Test] - public void TestEvenMoreAlreadyTaken() + public void TestNextBestNameEvenMoreAlreadyTaken() { string[] existingNames = Enumerable.Range(1, 30).Select(i => $"New Difficulty ({i})").Append("New Difficulty").ToArray(); @@ -114,7 +114,7 @@ namespace osu.Game.Tests.Utils } [Test] - public void TestMultipleAlreadyTakenWithGaps() + public void TestNextBestNameMultipleAlreadyTakenWithGaps() { string[] existingNames = { @@ -128,5 +128,153 @@ namespace osu.Game.Tests.Utils Assert.AreEqual("New Difficulty (2)", nextBestName); } + + [Test] + public void TestNextBestFilenameEmptySet() + { + string nextBestFilename = NamingUtils.GetNextBestFilename(Enumerable.Empty(), "test_file.osr"); + + Assert.AreEqual("test_file.osr", nextBestFilename); + } + + [Test] + public void TestNextBestFilenameNotTaken() + { + string[] existingFiles = + { + "this file exists.zip", + "that file exists.too", + "three.4", + }; + + string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "test_file.osr"); + + Assert.AreEqual("test_file.osr", nextBestFilename); + } + + [Test] + public void TestNextBestFilenameNotTakenButClose() + { + string[] existingFiles = + { + "replay_file(1).osr", + "replay_file (not a number).zip", + "replay_file (1 <- now THAT is a number right here).lol", + }; + + string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr"); + + Assert.AreEqual("replay_file.osr", nextBestFilename); + } + + [Test] + public void TestNextBestFilenameAlreadyTaken() + { + string[] existingFiles = + { + "replay_file.osr", + }; + + string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr"); + + Assert.AreEqual("replay_file (1).osr", nextBestFilename); + } + + [Test] + public void TestNextBestFilenameAlreadyTakenDifferentCase() + { + string[] existingFiles = + { + "replay_file.osr", + "RePlAy_FiLe (1).OsR", + "REPLAY_FILE (2).OSR", + }; + + string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr"); + Assert.AreEqual("replay_file (3).osr", nextBestFilename); + } + + [Test] + public void TestNextBestFilenameAlreadyTakenWithBrackets() + { + string[] existingFiles = + { + "replay_file.osr", + "replay_file (copy).osr", + }; + + string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr"); + Assert.AreEqual("replay_file (1).osr", nextBestFilename); + + nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file (copy).osr"); + Assert.AreEqual("replay_file (copy) (1).osr", nextBestFilename); + } + + [Test] + public void TestNextBestFilenameMultipleAlreadyTaken() + { + string[] existingFiles = + { + "replay_file.osr", + "replay_file (1).osr", + "replay_file (2).osr", + "replay_file (3).osr", + }; + + string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr"); + + Assert.AreEqual("replay_file (4).osr", nextBestFilename); + } + + [Test] + public void TestNextBestFilenameMultipleAlreadyTakenWithGaps() + { + string[] existingFiles = + { + "replay_file.osr", + "replay_file (1).osr", + "replay_file (2).osr", + "replay_file (4).osr", + "replay_file (5).osr", + }; + + string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr"); + + Assert.AreEqual("replay_file (3).osr", nextBestFilename); + } + + [Test] + public void TestNextBestFilenameNoExtensions() + { + string[] existingFiles = + { + "those", + "are definitely", + "files", + }; + + string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "surely"); + Assert.AreEqual("surely", nextBestFilename); + + nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "those"); + Assert.AreEqual("those (1)", nextBestFilename); + } + + [Test] + public void TestNextBestFilenameDifferentExtensions() + { + string[] existingFiles = + { + "replay_file.osr", + "replay_file (1).osr", + "replay_file.txt", + }; + + string nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.osr"); + Assert.AreEqual("replay_file (2).osr", nextBestFilename); + + nextBestFilename = NamingUtils.GetNextBestFilename(existingFiles, "replay_file.txt"); + Assert.AreEqual("replay_file (1).txt", nextBestFilename); + } } } From 8cf89fcb81c663fbaee4b6576e231028bda73cd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Dec 2022 15:30:47 +0900 Subject: [PATCH 040/384] Use `Highlight1` instead of dull grey --- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index d089b1e744..a7569daf93 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -18,6 +18,7 @@ using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -54,6 +55,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private ISkinSource skin { get; set; } = null!; + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + public TimelineHitObjectBlueprint(HitObject item) : base(item) { @@ -165,7 +169,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline break; default: - colour = Color4.Gray; + colour = colourProvider.Highlight1; break; } From 809d02cda0156032360471aa5bf287c829529ea0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Dec 2022 16:05:14 +0900 Subject: [PATCH 041/384] Fix two implementation oversights --- osu.Game/Beatmaps/Beatmap.cs | 8 +++++--- .../Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 2ab4fe0c79..416d655cc3 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -81,12 +81,14 @@ namespace osu.Game.Beatmaps public double GetMostCommonBeatLength() { - if (!HitObjects.Any()) - return ControlPointInfo.TimingPoints.LastOrDefault()?.Time ?? 0; + double lastTime; // The last playable time in the beatmap - the last timing point extends to this time. // Note: This is more accurate and may present different results because osu-stable didn't have the ability to calculate slider durations in this context. - double lastTime = this.GetLastObjectTime(); + if (!HitObjects.Any()) + lastTime = ControlPointInfo.TimingPoints.LastOrDefault()?.Time ?? 0; + else + lastTime = this.GetLastObjectTime(); var mostCommon = // Construct a set of (beatLength, duration) tuples for each individual timing point. diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 5218ddfe15..4c7564b791 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.UI.Scrolling break; } - double lastObjectTime = Beatmap.HitObjects.Any() ? Beatmap.GetLastObjectTime() : 0; + double lastObjectTime = Beatmap.HitObjects.Any() ? Beatmap.GetLastObjectTime() : double.MaxValue; double baseBeatLength = TimingControlPoint.DEFAULT_BEAT_LENGTH; if (RelativeScaleBeatLengths) From a2db0afe1922cb1e6a99247b222c4800b9bea36f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Dec 2022 16:07:24 +0900 Subject: [PATCH 042/384] Avoid querying `GetLastObjectTime` twice in editor seek flow --- osu.Game/Screens/Edit/Editor.cs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b8d308cc7c..f3f2b8ad6b 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -537,10 +537,14 @@ namespace osu.Game.Screens.Edit // Seek to last object time, or track end if already there. // Note that in osu-stable subsequent presses when at track end won't return to last object. // This has intentionally been changed to make it more useful. - if (!editorBeatmap.HitObjects.Any() || clock.CurrentTime == editorBeatmap.GetLastObjectTime()) + if (!editorBeatmap.HitObjects.Any()) + { clock.Seek(clock.TrackLength); - else - clock.Seek(editorBeatmap.GetLastObjectTime()); + return true; + } + + double lastObjectTime = editorBeatmap.GetLastObjectTime(); + clock.Seek(clock.CurrentTime == lastObjectTime ? clock.TrackLength : lastObjectTime); return true; } From 25120060aa16f05d32d80119c1bf1bb0379bd020 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Dec 2022 16:09:26 +0900 Subject: [PATCH 043/384] Use new helper method in `ModTimeRamp` --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index c4cb41fb6a..7285315c3b 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -7,7 +7,6 @@ using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Mods @@ -71,7 +70,7 @@ namespace osu.Game.Rulesets.Mods SpeedChange.SetDefault(); double firstObjectStart = beatmap.HitObjects.FirstOrDefault()?.StartTime ?? 0; - double lastObjectEnd = beatmap.HitObjects.LastOrDefault()?.GetEndTime() ?? 0; + double lastObjectEnd = beatmap.HitObjects.Any() ? beatmap.GetLastObjectTime() : 0; beginRampTime = firstObjectStart; finalRateTime = firstObjectStart + FINAL_RATE_PROGRESS * (lastObjectEnd - firstObjectStart); From 47855de6ab69536bce3fde959db249283acbe069 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Dec 2022 16:54:58 +0900 Subject: [PATCH 044/384] Fix multiple issues with transform handling --- .../Skinning/Legacy/LegacyKiaiGlow.cs | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs index 80b8bc9b11..d06571eba2 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs @@ -16,15 +16,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { internal partial class LegacyKiaiGlow : BeatSyncedContainer { - public LegacyKiaiGlow() - { - AlwaysPresent = true; - Alpha = 0; - } + private bool isKiaiActive; - [BackgroundDependencyLoader] - private void load(ISkinSource skin) + [BackgroundDependencyLoader(true)] + private void load(ISkinSource skin, HealthProcessor? healthProcessor) { + Alpha = 0; + Child = new Sprite { Texture = skin.GetTexture("taiko-glow"), @@ -33,14 +31,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy Scale = new Vector2(0.7f), Colour = new Colour4(255, 228, 0, 255), }; - } - - [Resolved(CanBeNull = true)] - private HealthProcessor? healthProcessor { get; set; } - - protected override void Update() - { - base.Update(); if (healthProcessor != null) healthProcessor.NewJudgement += onNewJudgement; @@ -48,16 +38,21 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy private void onNewJudgement(JudgementResult result) { - if (!result.IsHit) + if (!result.IsHit || !isKiaiActive) return; - this.ScaleTo(1.2f) - .Then().ScaleTo(1f, 80, Easing.OutQuad); + this.ScaleTo(1.2f).Then() + .ScaleTo(1f, 80, Easing.OutQuad); } protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { - if (effectPoint.KiaiMode) + if (effectPoint.KiaiMode == isKiaiActive) + return; + + isKiaiActive = effectPoint.KiaiMode; + + if (isKiaiActive) this.FadeIn(180); else this.FadeOut(180); From ebc75d40d2a7478950ba3e5544ef5dcb19346bdf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Dec 2022 17:32:57 +0900 Subject: [PATCH 045/384] Switch to using immediate transforms to make rewind handle better --- .../Skinning/Legacy/LegacyKiaiGlow.cs | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs index d06571eba2..623243e9e1 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyKiaiGlow.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.Audio.Track; using osu.Framework.Graphics; @@ -18,16 +19,17 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { private bool isKiaiActive; + private Sprite sprite = null!; + [BackgroundDependencyLoader(true)] private void load(ISkinSource skin, HealthProcessor? healthProcessor) { - Alpha = 0; - - Child = new Sprite + Child = sprite = new Sprite { Texture = skin.GetTexture("taiko-glow"), Origin = Anchor.Centre, Anchor = Anchor.Centre, + Alpha = 0, Scale = new Vector2(0.7f), Colour = new Colour4(255, 228, 0, 255), }; @@ -36,26 +38,28 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy healthProcessor.NewJudgement += onNewJudgement; } + protected override void Update() + { + base.Update(); + + if (isKiaiActive) + sprite.Alpha = (float)Math.Min(1, sprite.Alpha + Math.Abs(Clock.ElapsedFrameTime) / 100f); + else + sprite.Alpha = (float)Math.Max(0, sprite.Alpha - Math.Abs(Clock.ElapsedFrameTime) / 600f); + } + + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) + { + isKiaiActive = effectPoint.KiaiMode; + } + private void onNewJudgement(JudgementResult result) { if (!result.IsHit || !isKiaiActive) return; - this.ScaleTo(1.2f).Then() - .ScaleTo(1f, 80, Easing.OutQuad); - } - - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) - { - if (effectPoint.KiaiMode == isKiaiActive) - return; - - isKiaiActive = effectPoint.KiaiMode; - - if (isKiaiActive) - this.FadeIn(180); - else - this.FadeOut(180); + sprite.ScaleTo(0.85f).Then() + .ScaleTo(0.7f, 80, Easing.OutQuad); } } } From 2fd535ea2066251a44f07cc4019a5b816067650a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Dec 2022 17:33:18 +0900 Subject: [PATCH 046/384] Apply same immediate transform logic to `TaikoLegacyPlayfieldBackgroundRight` to fix it sticking during rewind --- .../TaikoLegacyPlayfieldBackgroundRight.cs | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyPlayfieldBackgroundRight.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyPlayfieldBackgroundRight.cs index 86175d3bca..85870d0fd6 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyPlayfieldBackgroundRight.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/TaikoLegacyPlayfieldBackgroundRight.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.Audio.Track; using osu.Framework.Graphics; @@ -16,7 +17,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy { private Sprite kiai = null!; - private bool kiaiDisplayed; + private bool isKiaiActive; [BackgroundDependencyLoader] private void load(ISkinSource skin) @@ -41,17 +42,19 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy }; } + protected override void Update() + { + base.Update(); + + if (isKiaiActive) + kiai.Alpha = (float)Math.Min(1, kiai.Alpha + Math.Abs(Clock.ElapsedFrameTime) / 200f); + else + kiai.Alpha = (float)Math.Max(0, kiai.Alpha - Math.Abs(Clock.ElapsedFrameTime) / 200f); + } + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { - base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); - - if (effectPoint.KiaiMode != kiaiDisplayed) - { - kiaiDisplayed = effectPoint.KiaiMode; - - kiai.ClearTransforms(); - kiai.FadeTo(kiaiDisplayed ? 1 : 0, 200); - } + isKiaiActive = effectPoint.KiaiMode; } } } From ba99f1288c0be9a727cbcdfcbec90812298dc325 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Dec 2022 17:47:29 +0900 Subject: [PATCH 047/384] Update `IBeatSnapProvider` documentation to mention the pre-divided `BeatSnap` --- osu.Game/Rulesets/Edit/IBeatSnapProvider.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Edit/IBeatSnapProvider.cs b/osu.Game/Rulesets/Edit/IBeatSnapProvider.cs index dbad407b75..5e45cefe8c 100644 --- a/osu.Game/Rulesets/Edit/IBeatSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IBeatSnapProvider.cs @@ -1,14 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - namespace osu.Game.Rulesets.Edit { public interface IBeatSnapProvider { /// - /// Snaps a duration to the closest beat of a timing point applicable at the reference time. + /// Snaps a duration to the closest beat of a timing point applicable at the reference time, factoring in the current . /// /// The time to snap. /// An optional reference point to use for timing point lookup. @@ -16,10 +14,10 @@ namespace osu.Game.Rulesets.Edit double SnapTime(double time, double? referenceTime = null); /// - /// Get the most appropriate beat length at a given time. + /// Get the most appropriate beat length at a given time, pre-divided by . /// /// A reference time used for lookup. - /// The most appropriate beat length. + /// The most appropriate beat length, divided by . double GetBeatLengthAtTime(double referenceTime); /// From 45ea183cc36bcee3050daec63af63e6f059695df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Dec 2022 17:56:34 +0900 Subject: [PATCH 048/384] Fix being able to place zero-length spinners Also always snap a spinner's end time using beat snap (matches stable). --- .../Blueprints/Spinners/SpinnerPlacementBlueprint.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs index e412c47c09..52f845f358 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; @@ -22,6 +23,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners private bool isPlacingEnd; + [Resolved] + private IBeatSnapProvider beatSnapProvider { get; set; } + public SpinnerPlacementBlueprint() : base(new Spinner { Position = OsuPlayfield.BASE_SIZE / 2 }) { @@ -33,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners base.Update(); if (isPlacingEnd) - HitObject.EndTime = Math.Max(HitObject.StartTime, EditorClock.CurrentTime); + updateEndTimeFromCurrent(); piece.UpdateFrom(HitObject); } @@ -45,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners if (e.Button != MouseButton.Right) return false; - HitObject.EndTime = EditorClock.CurrentTime; + updateEndTimeFromCurrent(); EndPlacement(true); } else @@ -61,5 +65,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners return true; } + + private void updateEndTimeFromCurrent() => + HitObject.EndTime = Math.Max(HitObject.StartTime + beatSnapProvider.GetBeatLengthAtTime(HitObject.StartTime), beatSnapProvider.SnapTime(EditorClock.CurrentTime)); } } From d1296a22ce0e9d61beb39feb02ff8c7adafebdef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 2 Dec 2022 17:57:13 +0900 Subject: [PATCH 049/384] Update timeline blueprint's end time logic to better match new blueprint implementation --- .../Compose/Components/Timeline/TimelineHitObjectBlueprint.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index a7569daf93..03e67306df 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -424,9 +424,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline break; case IHasDuration endTimeHitObject: - double snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time)); + double snappedTime = Math.Max(hitObject.StartTime + beatSnapProvider.GetBeatLengthAtTime(hitObject.StartTime), beatSnapProvider.SnapTime(time)); - if (endTimeHitObject.EndTime == snappedTime || Precision.AlmostEquals(snappedTime, hitObject.StartTime, beatmap.GetBeatLengthAtTime(snappedTime))) + if (endTimeHitObject.EndTime == snappedTime) return; endTimeHitObject.Duration = snappedTime - hitObject.StartTime; From ee62f3c0d3f061762cf2370b93f9901eab81b171 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 2 Dec 2022 18:32:40 +0900 Subject: [PATCH 050/384] Fix test failure Similar to SliderPlacementBlueprint. --- .../Blueprints/Spinners/SpinnerPlacementBlueprint.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs index 52f845f358..73ee5df9dc 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; @@ -23,7 +24,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners private bool isPlacingEnd; - [Resolved] + [Resolved(CanBeNull = true)] + [CanBeNull] private IBeatSnapProvider beatSnapProvider { get; set; } public SpinnerPlacementBlueprint() @@ -66,7 +68,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners return true; } - private void updateEndTimeFromCurrent() => - HitObject.EndTime = Math.Max(HitObject.StartTime + beatSnapProvider.GetBeatLengthAtTime(HitObject.StartTime), beatSnapProvider.SnapTime(EditorClock.CurrentTime)); + private void updateEndTimeFromCurrent() + { + HitObject.EndTime = beatSnapProvider == null + ? Math.Max(HitObject.StartTime, EditorClock.CurrentTime) + : Math.Max(HitObject.StartTime + beatSnapProvider.GetBeatLengthAtTime(HitObject.StartTime), beatSnapProvider.SnapTime(EditorClock.CurrentTime)); + } } } From 49f5ac57b8ab109f7a4b62f20f05a02f94900ead Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 3 Dec 2022 18:48:53 +0900 Subject: [PATCH 051/384] Fix first run beatmap screen button colours looking incorrect with new triangle effect Addresses #21500. --- osu.Game/Overlays/FirstRunSetup/ProgressRoundedButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ProgressRoundedButton.cs b/osu.Game/Overlays/FirstRunSetup/ProgressRoundedButton.cs index a4762fdaed..a1e61e66f8 100644 --- a/osu.Game/Overlays/FirstRunSetup/ProgressRoundedButton.cs +++ b/osu.Game/Overlays/FirstRunSetup/ProgressRoundedButton.cs @@ -81,7 +81,7 @@ namespace osu.Game.Overlays.FirstRunSetup loading.Hide(); tick.FadeIn(500, Easing.OutQuint); - Background.FadeColour(colours.Green, 500, Easing.OutQuint); + this.TransformTo(nameof(BackgroundColour), colours.Green, 500, Easing.OutQuint); progressBar.FillColour = colours.Green; this.TransformBindableTo(progressBar.Current, 1, 500, Easing.OutQuint); From 94cfcdb338690f2cdd174bcdf8b789a44929c5be Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 3 Dec 2022 16:16:21 +0300 Subject: [PATCH 052/384] Remove SmoothCircularProgress usage in BeatmapCardThumbnail --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs index 781133aac7..c99d1f0c76 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs @@ -11,7 +11,7 @@ using osu.Game.Beatmaps.Drawables.Cards.Buttons; using osu.Game.Graphics; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; -using osu.Game.Screens.Ranking.Expanded.Accuracy; +using osu.Framework.Graphics.UserInterface; using osuTK; using osuTK.Graphics; @@ -30,7 +30,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly UpdateableOnlineBeatmapSetCover cover; private readonly Container foreground; private readonly PlayButton playButton; - private readonly SmoothCircularProgress progress; + private readonly CircularProgress progress; private readonly Container content; protected override Container Content => content; @@ -53,7 +53,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { RelativeSizeAxes = Axes.Both }, - progress = new SmoothCircularProgress + progress = new CircularProgress { Anchor = Anchor.Centre, Origin = Anchor.Centre, From 97b6d88f003ffa1230ac03da372c0508c467adb6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 3 Dec 2022 16:39:44 +0300 Subject: [PATCH 053/384] Remove SmoothCircularProgress usage in AccuracyCircle --- .../Expanded/Accuracy/AccuracyCircle.cs | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 3285ebc914..8e04bb68fb 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -12,6 +12,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Graphics; @@ -79,8 +80,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy private readonly ScoreInfo score; - private SmoothCircularProgress accuracyCircle; - private SmoothCircularProgress innerMask; + private CircularProgress accuracyCircle; + private CircularProgress innerMask; private Container badges; private RankText rankText; @@ -109,7 +110,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy { InternalChildren = new Drawable[] { - new SmoothCircularProgress + new CircularProgress { Name = "Background circle", Anchor = Anchor.Centre, @@ -120,7 +121,7 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy InnerRadius = accuracy_circle_radius + 0.01f, // Extends a little bit into the circle Current = { Value = 1 }, }, - accuracyCircle = new SmoothCircularProgress + accuracyCircle = new CircularProgress { Name = "Accuracy circle", Anchor = Anchor.Centre, @@ -139,42 +140,42 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Padding = new MarginPadding(2), Children = new Drawable[] { - new SmoothCircularProgress + new CircularProgress { RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.X), InnerRadius = RANK_CIRCLE_RADIUS, Current = { Value = 1 } }, - new SmoothCircularProgress + new CircularProgress { RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.S), InnerRadius = RANK_CIRCLE_RADIUS, Current = { Value = 1 - virtual_ss_percentage } }, - new SmoothCircularProgress + new CircularProgress { RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.A), InnerRadius = RANK_CIRCLE_RADIUS, Current = { Value = 0.95f } }, - new SmoothCircularProgress + new CircularProgress { RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.B), InnerRadius = RANK_CIRCLE_RADIUS, Current = { Value = 0.9f } }, - new SmoothCircularProgress + new CircularProgress { RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.C), InnerRadius = RANK_CIRCLE_RADIUS, Current = { Value = 0.8f } }, - new SmoothCircularProgress + new CircularProgress { RelativeSizeAxes = Axes.Both, Colour = OsuColour.ForRank(ScoreRank.D), @@ -195,14 +196,14 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Blending = new BlendingParameters { Source = BlendingType.DstColor, - Destination = BlendingType.OneMinusSrcAlpha, + Destination = BlendingType.OneMinusSrcColor, SourceAlpha = BlendingType.One, DestinationAlpha = BlendingType.SrcAlpha }, - Child = innerMask = new SmoothCircularProgress + Child = innerMask = new CircularProgress { RelativeSizeAxes = Axes.Both, - InnerRadius = RANK_CIRCLE_RADIUS - 0.01f, + InnerRadius = RANK_CIRCLE_RADIUS - 0.02f, } } } From 41f7dad8e96c3b8135277d4151eb5d0d553c1fd7 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 3 Dec 2022 16:40:41 +0300 Subject: [PATCH 054/384] Delete SmoothCircularProgress.cs --- .../Accuracy/SmoothCircularProgress.cs | 128 ------------------ 1 file changed, 128 deletions(-) delete mode 100644 osu.Game/Screens/Ranking/Expanded/Accuracy/SmoothCircularProgress.cs diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/SmoothCircularProgress.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/SmoothCircularProgress.cs deleted file mode 100644 index 601c47ea55..0000000000 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/SmoothCircularProgress.cs +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -#nullable disable - -using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Transforms; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics; -using osuTK; - -namespace osu.Game.Screens.Ranking.Expanded.Accuracy -{ - /// - /// Contains a with smoothened edges. - /// - public partial class SmoothCircularProgress : CompositeDrawable - { - public Bindable Current - { - get => progress.Current; - set => progress.Current = value; - } - - public float InnerRadius - { - get => progress.InnerRadius; - set - { - progress.InnerRadius = value; - innerSmoothingContainer.Size = new Vector2(1 - value); - smoothingWedge.Height = value / 2; - } - } - - private readonly CircularProgress progress; - private readonly Container innerSmoothingContainer; - private readonly Drawable smoothingWedge; - - public SmoothCircularProgress() - { - Container smoothingWedgeContainer; - - InternalChild = new BufferedContainer - { - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - progress = new CircularProgress { RelativeSizeAxes = Axes.Both }, - smoothingWedgeContainer = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Child = smoothingWedge = new Box - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = 1f, - EdgeSmoothness = new Vector2(2, 0), - } - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(-1), - Child = new CircularContainer - { - RelativeSizeAxes = Axes.Both, - BorderThickness = 2, - Masking = true, - BorderColour = OsuColour.Gray(0.5f).Opacity(0.75f), - Blending = new BlendingParameters - { - AlphaEquation = BlendingEquation.ReverseSubtract, - }, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - } - }, - innerSmoothingContainer = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = Vector2.Zero, - Padding = new MarginPadding(-1), - Child = new CircularContainer - { - RelativeSizeAxes = Axes.Both, - BorderThickness = 2, - BorderColour = OsuColour.Gray(0.5f).Opacity(0.75f), - Masking = true, - Blending = new BlendingParameters - { - AlphaEquation = BlendingEquation.ReverseSubtract, - }, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true - } - } - }, - } - }; - - Current.BindValueChanged(c => - { - smoothingWedgeContainer.Alpha = c.NewValue > 0 ? 1 : 0; - smoothingWedgeContainer.Rotation = (float)(360 * c.NewValue); - }, true); - } - - public TransformSequence FillTo(double newValue, double duration = 0, Easing easing = Easing.None) - => progress.FillTo(newValue, duration, easing); - } -} From 7f0d366d0169521beb33335dadfbd306375096d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Dec 2022 16:59:43 +0100 Subject: [PATCH 055/384] Extract common part of regex to separate method --- osu.Game/Utils/NamingUtils.cs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game/Utils/NamingUtils.cs b/osu.Game/Utils/NamingUtils.cs index fa102ff56f..97220f4201 100644 --- a/osu.Game/Utils/NamingUtils.cs +++ b/osu.Game/Utils/NamingUtils.cs @@ -29,7 +29,7 @@ namespace osu.Game.Utils /// public static string GetNextBestName(IEnumerable existingNames, string desiredName) { - string pattern = $@"^(?i){Regex.Escape(desiredName)}(?-i)( \((?[1-9][0-9]*)\))?$"; + string pattern = $@"^{getBaselineNameDetectingPattern(desiredName)}$"; var regex = new Regex(pattern, RegexOptions.Compiled); int bestNumber = findBestNumber(existingNames, regex); @@ -48,7 +48,7 @@ namespace osu.Game.Utils string name = Path.GetFileNameWithoutExtension(desiredFilename); string extension = Path.GetExtension(desiredFilename); - string pattern = $@"^(?i){Regex.Escape(name)}(?-i)( \((?[1-9][0-9]*)\))?(?i){Regex.Escape(extension)}(?-i)$"; + string pattern = $@"^{getBaselineNameDetectingPattern(name)}(?i){Regex.Escape(extension)}(?-i)$"; var regex = new Regex(pattern, RegexOptions.Compiled); int bestNumber = findBestNumber(existingFilenames, regex); @@ -58,6 +58,22 @@ namespace osu.Game.Utils : $"{name} ({bestNumber.ToString()}){extension}"; } + /// + /// Generates a basic regex pattern that will match all possible conflicting filenames when picking the best available name, given the . + /// The generated pattern can be composed into more complicated regexes for particular uses, such as picking filenames, which need additional file extension handling. + /// + /// + /// The regex shall detect: + /// + /// all strings that are equal to , + /// all strings of the format desiredName (number), where number is a number written using Arabic numerals. + /// + /// All comparisons are made in a case-insensitive manner. + /// If a number is detected in the matches, it will be output to the copyNumber named group. + /// + private static string getBaselineNameDetectingPattern(string desiredName) + => $@"(?i){Regex.Escape(desiredName)}(?-i)( \((?[1-9][0-9]*)\))?"; + private static int findBestNumber(IEnumerable existingNames, Regex regex) { var takenNumbers = new HashSet(); From 36ab6bc64e6559a88b7d3f3ee1944903f40219b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 3 Dec 2022 23:03:43 +0900 Subject: [PATCH 056/384] Update framework 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 75828147a5..0bf415e764 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 6e75450594..a176d73854 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index bb20b0474d..e192467247 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + @@ -82,7 +82,7 @@ - + From 6c95b1d90dbf1e6aa0dfb2039aa2747bd786b3ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 3 Dec 2022 21:59:57 +0100 Subject: [PATCH 057/384] Bump `dorny/test-reporter` action to 1.6.0 The only reason I'm bothering to do this is that I mistakenly clicked on one of the execution logs of the "Annotate CI with test results" workflow and noticed a bunch of deprecation warnings: https://github.com/ppy/osu/actions/runs/3610166871 Judging from the release notes [[1]] and diffstat [[2]] of version 1.6.0 of the action affected, a bump should silence these. [1]: https://github.com/dorny/test-reporter/blob/main/CHANGELOG.md [2]: https://github.com/dorny/test-reporter/compare/v1.4.2...v1.6.0 --- .github/workflows/report-nunit.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/report-nunit.yml b/.github/workflows/report-nunit.yml index bfc9620174..99e39f6f56 100644 --- a/.github/workflows/report-nunit.yml +++ b/.github/workflows/report-nunit.yml @@ -28,7 +28,7 @@ jobs: timeout-minutes: 5 steps: - name: Annotate CI run with test results - uses: dorny/test-reporter@v1.4.2 + uses: dorny/test-reporter@v1.6.0 with: artifact: osu-test-results-${{matrix.os.prettyname}}-${{matrix.threadingMode}} name: Test Results (${{matrix.os.prettyname}}, ${{matrix.threadingMode}}) From af000c6e9205161e48bc988ed80eb65430401ca4 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 4 Dec 2022 21:58:00 +0300 Subject: [PATCH 058/384] Fix `RangeSlider` potentially throwing exception on mobile platforms --- osu.Game/Graphics/UserInterface/RangeSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/RangeSlider.cs b/osu.Game/Graphics/UserInterface/RangeSlider.cs index 483119cd58..4e23b06c2b 100644 --- a/osu.Game/Graphics/UserInterface/RangeSlider.cs +++ b/osu.Game/Graphics/UserInterface/RangeSlider.cs @@ -197,7 +197,7 @@ namespace osu.Game.Graphics.UserInterface }, true); } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(OverlayColourProvider? colourProvider) { if (colourProvider == null) return; From 486ea153fe752d6b1dc94aabc73073d76c668023 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 5 Dec 2022 19:00:04 +0900 Subject: [PATCH 059/384] Add failing test --- .../Rulesets/Scoring/ScoreProcessorTest.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs index e9e94aa897..826c610f56 100644 --- a/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs +++ b/osu.Game.Tests/Rulesets/Scoring/ScoreProcessorTest.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; @@ -355,6 +356,28 @@ namespace osu.Game.Tests.Rulesets.Scoring } #pragma warning restore CS0618 + [Test] + public void TestAccuracyWhenNearPerfect() + { + const int count_judgements = 1000; + const int count_misses = 1; + + double actual = new TestScoreProcessor().ComputeAccuracy(new ScoreInfo + { + Statistics = new Dictionary + { + { HitResult.Great, count_judgements - count_misses }, + { HitResult.Miss, count_misses } + } + }); + + const double expected = (count_judgements - count_misses) / (double)count_judgements; + + Assert.That(actual, Is.Not.EqualTo(0.0)); + Assert.That(actual, Is.Not.EqualTo(1.0)); + Assert.That(actual, Is.EqualTo(expected).Within(Precision.FLOAT_EPSILON)); + } + private class TestRuleset : Ruleset { public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException(); From 9e64f8dab1ea4ec5aa847ab838d359a03e8a8755 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 5 Dec 2022 19:08:50 +0900 Subject: [PATCH 060/384] Fix loss of precision when computing accuracy --- 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 29c37c31d5..18c88dff2a 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -285,7 +285,7 @@ namespace osu.Game.Rulesets.Scoring // We only extract scoring values from the score's statistics. This is because accuracy is always relative to the point of pass or fail rather than relative to the whole beatmap. extractScoringValues(scoreInfo.Statistics, out var current, out var maximum); - return maximum.BaseScore > 0 ? current.BaseScore / maximum.BaseScore : 1; + return maximum.BaseScore > 0 ? (double)current.BaseScore / maximum.BaseScore : 1; } /// From a457a2fbbf5edd1c297cb367467b5b85974fea61 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 5 Dec 2022 14:45:29 +0300 Subject: [PATCH 061/384] Fix disposal of playing preview track not unmuting game track --- osu.Game/Audio/PreviewTrack.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs index ea226ab650..2c63c16274 100644 --- a/osu.Game/Audio/PreviewTrack.cs +++ b/osu.Game/Audio/PreviewTrack.cs @@ -109,6 +109,8 @@ namespace osu.Game.Audio protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + + Stop(); Track?.Dispose(); } } From b66409bd37e519d40584fcb1d06a3690595f39ce Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Mon, 5 Dec 2022 23:38:45 +0300 Subject: [PATCH 062/384] Perform rewind with right click only if cursor didn't leave the button --- osu.Game/Screens/Select/FooterButtonRandom.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/FooterButtonRandom.cs b/osu.Game/Screens/Select/FooterButtonRandom.cs index 8d7463067e..f413126e87 100644 --- a/osu.Game/Screens/Select/FooterButtonRandom.cs +++ b/osu.Game/Screens/Select/FooterButtonRandom.cs @@ -119,7 +119,7 @@ namespace osu.Game.Screens.Select protected override void OnMouseUp(MouseUpEvent e) { - if (e.Button == MouseButton.Right) + if (e.Button == MouseButton.Right && IsHovered) { rewindSearch = true; TriggerClick(); From 0bfc46963b7ddd37ee2096b8bb1623f507d24988 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 6 Dec 2022 22:46:39 +0100 Subject: [PATCH 063/384] Add failing test case --- .../TestSceneModDifficultyAdjustSettings.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs index 0d02a72d87..f45f5b9f59 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModDifficultyAdjustSettings.cs @@ -126,6 +126,21 @@ namespace osu.Game.Tests.Visual.UserInterface checkBindableAtValue("Circle Size", 9); } + [Test] + public void TestExtendedLimitsRetainedAfterBoundCopyCreation() + { + setExtendedLimits(true); + setSliderValue("Circle Size", 11); + + checkSliderAtValue("Circle Size", 11); + checkBindableAtValue("Circle Size", 11); + + AddStep("create bound copy", () => _ = modDifficultyAdjust.CircleSize.GetBoundCopy()); + + checkSliderAtValue("Circle Size", 11); + checkBindableAtValue("Circle Size", 11); + } + [Test] public void TestResetToDefault() { From 81a42dc05d53d714b128b1704730d45fac85d454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 6 Dec 2022 22:50:14 +0100 Subject: [PATCH 064/384] Fix extended values in difficulty adjust being truncated to 10 on beatmap change --- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index cca72cf3ac..c21ce756c9 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -118,11 +118,18 @@ namespace osu.Game.Rulesets.Mods if (!(them is DifficultyBindable otherDifficultyBindable)) throw new InvalidOperationException($"Cannot bind to a non-{nameof(DifficultyBindable)}."); + // ensure that MaxValue and ExtendedMaxValue are copied across first before continuing. + // not doing so may cause the value of CurrentNumber to be truncated to 10. + otherDifficultyBindable.CopyTo(this); + + // set up mutual binding for ExtendedLimits to correctly set the upper bound of CurrentNumber. ExtendedLimits.BindTarget = otherDifficultyBindable.ExtendedLimits; - // the actual values need to be copied after the max value constraints. + // set up mutual binding for CurrentNumber. this must happen after all of the above. CurrentNumber.BindTarget = otherDifficultyBindable.CurrentNumber; + // finish up the binding by setting up weak references via the base call. + // unfortunately this will call `.CopyTo()` again, but fixing that is problematic and messy. base.BindTo(them); } From 30952199b8b4da83878f59fbeec88103e641a9fa Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Wed, 7 Dec 2022 00:40:18 +0100 Subject: [PATCH 065/384] Allow random mod to flip sliders --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 5 ++++ .../Utils/OsuHitObjectGenerationUtils.cs | 23 ++++++++++++++++--- 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 1621bb50b1..fc92696e9a 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -65,6 +65,11 @@ namespace osu.Game.Rulesets.Osu.Mods flowDirection = !flowDirection; } + if (positionInfos[i].HitObject is Slider slider && random.NextDouble() < 0.5) + { + OsuHitObjectGenerationUtils.FlipSlider(slider); + } + if (i == 0) { positionInfos[i].DistanceFromPrevious = (float)(random.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2); diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 3a8b3f67d0..b081978803 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -160,14 +160,31 @@ namespace osu.Game.Rulesets.Osu.Utils public static void RotateSlider(Slider slider, float rotation) { void rotateNestedObject(OsuHitObject nested) => nested.Position = rotateVector(nested.Position - slider.Position, rotation) + slider.Position; + void rotateControlPoint(PathControlPoint point) => point.Position = rotateVector(point.Position, rotation); + modifySlider(slider, rotateNestedObject, rotateControlPoint); + } + + /// + /// Flips the slider about its start position. + /// + public static void FlipSlider(Slider slider) + { + void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(slider.Position.X - (nested.X - slider.Position.X), nested.Y); + static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y); + + modifySlider(slider, flipNestedObject, flipControlPoint); + } + + private static void modifySlider(Slider slider, Action modifyNestedObject, Action modifyControlPoint) + { // No need to update the head and tail circles, since slider handles that when the new slider path is set - slider.NestedHitObjects.OfType().ForEach(rotateNestedObject); - slider.NestedHitObjects.OfType().ForEach(rotateNestedObject); + slider.NestedHitObjects.OfType().ForEach(modifyNestedObject); + slider.NestedHitObjects.OfType().ForEach(modifyNestedObject); var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray(); foreach (var point in controlPoints) - point.Position = rotateVector(point.Position, rotation); + modifyControlPoint(point); slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value); } From 91e9c275078d7ec098cacdf14fb5cb766899a65d Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Wed, 7 Dec 2022 00:48:25 +0100 Subject: [PATCH 066/384] Rename `FlipSlider()` to `FlipSliderHorizontally` --- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 2 +- osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index fc92696e9a..58f5b2fa8d 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (positionInfos[i].HitObject is Slider slider && random.NextDouble() < 0.5) { - OsuHitObjectGenerationUtils.FlipSlider(slider); + OsuHitObjectGenerationUtils.FlipSliderHorizontally(slider); } if (i == 0) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index b081978803..42c6946414 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -166,9 +166,9 @@ namespace osu.Game.Rulesets.Osu.Utils } /// - /// Flips the slider about its start position. + /// Flips the slider about its start position horizontally. /// - public static void FlipSlider(Slider slider) + public static void FlipSliderHorizontally(Slider slider) { void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(slider.Position.X - (nested.X - slider.Position.X), nested.Y); static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y); From 8f3023ffd9c64c91258e3d7642d57f8431c12cd4 Mon Sep 17 00:00:00 2001 From: Pasi4K5 Date: Wed, 7 Dec 2022 01:05:15 +0100 Subject: [PATCH 067/384] Update `ReflectHorizontally()` and `ReflectVertically()` to use `FlipSliderHorizontally()` and `modifySlider()`. --- .../Utils/OsuHitObjectGenerationUtils.cs | 27 +++++-------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 42c6946414..f565456911 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -116,18 +116,10 @@ namespace osu.Game.Rulesets.Osu.Utils { osuObject.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - osuObject.X, osuObject.Position.Y); - if (!(osuObject is Slider slider)) + if (osuObject is not Slider slider) return; - // No need to update the head and tail circles, since slider handles that when the new slider path is set - slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y)); - slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y)); - - var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray(); - foreach (var point in controlPoints) - point.Position = new Vector2(-point.Position.X, point.Position.Y); - - slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value); + FlipSliderHorizontally(slider); } /// @@ -138,18 +130,13 @@ namespace osu.Game.Rulesets.Osu.Utils { osuObject.Position = new Vector2(osuObject.Position.X, OsuPlayfield.BASE_SIZE.Y - osuObject.Y); - if (!(osuObject is Slider slider)) + if (osuObject is not Slider slider) return; - // No need to update the head and tail circles, since slider handles that when the new slider path is set - slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); - slider.NestedHitObjects.OfType().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y)); + void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(nested.X, slider.Y - (nested.Y - slider.Y)); + static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(point.Position.X, -point.Position.Y); - var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray(); - foreach (var point in controlPoints) - point.Position = new Vector2(point.Position.X, -point.Position.Y); - - slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value); + modifySlider(slider, flipNestedObject, flipControlPoint); } /// @@ -170,7 +157,7 @@ namespace osu.Game.Rulesets.Osu.Utils /// public static void FlipSliderHorizontally(Slider slider) { - void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(slider.Position.X - (nested.X - slider.Position.X), nested.Y); + void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(slider.X - (nested.X - slider.X), nested.Y); static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y); modifySlider(slider, flipNestedObject, flipControlPoint); From df181acffe1869cba648a75d9dc13b1f453cb98f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 6 Dec 2022 20:10:51 +0900 Subject: [PATCH 068/384] Append lazer score data to .osr files --- .../Legacy/LegacyReplaySoloScoreInfo.cs | 38 +++++++++++ osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 64 ++++++++++++------- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 12 +++- 3 files changed, 89 insertions(+), 25 deletions(-) create mode 100644 osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs diff --git a/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs b/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs new file mode 100644 index 0000000000..f2e8cf141b --- /dev/null +++ b/osu.Game/Scoring/Legacy/LegacyReplaySoloScoreInfo.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Scoring.Legacy +{ + /// + /// A minified version of retrofit onto the end of legacy replay files (.osr), + /// containing the minimum data required to support storage of non-legacy replays. + /// + [Serializable] + [JsonObject(MemberSerialization.OptIn)] + public class LegacyReplaySoloScoreInfo + { + [JsonProperty("mods")] + public APIMod[] Mods { get; set; } = Array.Empty(); + + [JsonProperty("statistics")] + public Dictionary Statistics { get; set; } = new Dictionary(); + + [JsonProperty("maximum_statistics")] + public Dictionary MaximumStatistics { get; set; } = new Dictionary(); + + public static LegacyReplaySoloScoreInfo FromScore(ScoreInfo score) => new LegacyReplaySoloScoreInfo + { + Mods = score.APIMods, + Statistics = score.Statistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), + MaximumStatistics = score.MaximumStatistics.Where(kvp => kvp.Value != 0).ToDictionary(kvp => kvp.Key, kvp => kvp.Value), + }; + } +} diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index f64e730c06..2f7727ac49 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -6,6 +6,7 @@ using System; using System.IO; using System.Linq; +using Newtonsoft.Json; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Legacy; @@ -91,31 +92,23 @@ namespace osu.Game.Scoring.Legacy else if (version >= 20121008) scoreInfo.OnlineID = sr.ReadInt32(); + byte[] compressedScoreInfo = null; + + if (version >= 30000001) + compressedScoreInfo = sr.ReadByteArray(); + if (compressedReplay?.Length > 0) + readCompressedData(compressedReplay, reader => readLegacyReplay(score.Replay, reader)); + + if (compressedScoreInfo?.Length > 0) { - using (var replayInStream = new MemoryStream(compressedReplay)) + readCompressedData(compressedScoreInfo, reader => { - byte[] properties = new byte[5]; - if (replayInStream.Read(properties, 0, 5) != 5) - throw new IOException("input .lzma is too short"); - - long outSize = 0; - - for (int i = 0; i < 8; i++) - { - int v = replayInStream.ReadByte(); - if (v < 0) - throw new IOException("Can't Read 1"); - - outSize |= (long)(byte)v << (8 * i); - } - - long compressedSize = replayInStream.Length - replayInStream.Position; - - using (var lzma = new LzmaStream(properties, replayInStream, compressedSize, outSize)) - using (var reader = new StreamReader(lzma)) - readLegacyReplay(score.Replay, reader); - } + LegacyReplaySoloScoreInfo readScore = JsonConvert.DeserializeObject(reader.ReadToEnd()); + score.ScoreInfo.Statistics = readScore.Statistics; + score.ScoreInfo.MaximumStatistics = readScore.MaximumStatistics; + score.ScoreInfo.Mods = readScore.Mods.Select(m => m.ToMod(currentRuleset)).ToArray(); + }); } } @@ -128,6 +121,33 @@ namespace osu.Game.Scoring.Legacy return score; } + private void readCompressedData(byte[] data, Action readFunc) + { + using (var replayInStream = new MemoryStream(data)) + { + byte[] properties = new byte[5]; + if (replayInStream.Read(properties, 0, 5) != 5) + throw new IOException("input .lzma is too short"); + + long outSize = 0; + + for (int i = 0; i < 8; i++) + { + int v = replayInStream.ReadByte(); + if (v < 0) + throw new IOException("Can't Read 1"); + + outSize |= (long)(byte)v << (8 * i); + } + + long compressedSize = replayInStream.Length - replayInStream.Position; + + using (var lzma = new LzmaStream(properties, replayInStream, compressedSize, outSize)) + using (var reader = new StreamReader(lzma)) + readFunc(reader); + } + } + /// /// Populates the accuracy of a given from its contained statistics. /// diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 750bb50be3..024b691de2 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -11,6 +11,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.Extensions; using osu.Game.IO.Legacy; +using osu.Game.IO.Serialization; using osu.Game.Replays.Legacy; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; @@ -29,7 +30,7 @@ namespace osu.Game.Scoring.Legacy /// /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays. /// - public const int FIRST_LAZER_VERSION = 30000000; + public const int FIRST_LAZER_VERSION = 30000001; private readonly Score score; private readonly IBeatmap? beatmap; @@ -77,6 +78,7 @@ namespace osu.Game.Scoring.Legacy sw.WriteByteArray(createReplayData()); sw.Write((long)0); writeModSpecificData(score.ScoreInfo, sw); + sw.WriteByteArray(createScoreInfoData()); } } @@ -84,9 +86,13 @@ namespace osu.Game.Scoring.Legacy { } - private byte[] createReplayData() + private byte[] createReplayData() => compress(replayStringContent); + + private byte[] createScoreInfoData() => compress(LegacyReplaySoloScoreInfo.FromScore(score.ScoreInfo).Serialize()); + + private byte[] compress(string data) { - byte[] content = new ASCIIEncoding().GetBytes(replayStringContent); + byte[] content = new ASCIIEncoding().GetBytes(data); using (var outStream = new MemoryStream()) { From 8709b1f2eaa2ef158149b1aaa08bfafe6109d7fc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Dec 2022 14:23:52 +0900 Subject: [PATCH 069/384] Immediately send ack request when joining new channels --- osu.Game/Online/Chat/ChannelManager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index eaef940d5f..a4661dcbd7 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -529,6 +529,10 @@ namespace osu.Game.Online.Chat { Logger.Log($"Joined public channel {channel}"); joinChannel(channel, fetchInitialMessages); + + // Required after joining public channels to mark the user as online in them. + // Todo: Temporary workaround for https://github.com/ppy/osu-web/issues/9602 + SendAck(); }; req.Failure += e => { From eea7984ba936b5896b9afb26479150dafb1358cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Dec 2022 14:53:56 +0900 Subject: [PATCH 070/384] Remove triangle effect from latency certifier buttons --- osu.Game/Screens/Utility/ButtonWithKeyBind.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Utility/ButtonWithKeyBind.cs b/osu.Game/Screens/Utility/ButtonWithKeyBind.cs index 29e9cd2515..7c78836b12 100644 --- a/osu.Game/Screens/Utility/ButtonWithKeyBind.cs +++ b/osu.Game/Screens/Utility/ButtonWithKeyBind.cs @@ -47,6 +47,8 @@ namespace osu.Game.Screens.Utility Height = 100; SpriteText.Colour = overlayColourProvider.Background6; SpriteText.Font = OsuFont.TorusAlternate.With(size: 34); + + Triangles?.Hide(); } } } From 49df05dd07a162955788a4cfde863272d18c1420 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 7 Dec 2022 15:07:39 +0900 Subject: [PATCH 071/384] Add test --- .../Formats/LegacyScoreDecoderTest.cs | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index cd6e5e7919..93cda34ef7 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -16,7 +16,9 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Mania; using osu.Game.Rulesets.Mania.Mods; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Replays; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Replays; @@ -179,6 +181,40 @@ namespace osu.Game.Tests.Beatmaps.Formats }); } + [Test] + public void TestSoloScoreData() + { + var ruleset = new OsuRuleset().RulesetInfo; + + var scoreInfo = TestResources.CreateTestScoreInfo(ruleset); + scoreInfo.Mods = new Mod[] + { + new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } + }; + + var beatmap = new TestBeatmap(ruleset); + var score = new Score + { + ScoreInfo = scoreInfo, + Replay = new Replay + { + Frames = new List + { + new OsuReplayFrame(2000, OsuPlayfield.BASE_SIZE / 2, OsuAction.LeftButton) + } + } + }; + + var decodedAfterEncode = encodeThenDecode(LegacyBeatmapDecoder.LATEST_VERSION, score, beatmap); + + Assert.Multiple(() => + { + Assert.That(decodedAfterEncode.ScoreInfo.Statistics, Is.EqualTo(scoreInfo.Statistics)); + Assert.That(decodedAfterEncode.ScoreInfo.MaximumStatistics, Is.EqualTo(scoreInfo.MaximumStatistics)); + Assert.That(decodedAfterEncode.ScoreInfo.Mods, Is.EqualTo(scoreInfo.Mods)); + }); + } + private static Score encodeThenDecode(int beatmapVersion, Score score, TestBeatmap beatmap) { var encodeStream = new MemoryStream(); From e8766570c518cf35421db0a5a8ade5bb3caca0f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Dec 2022 16:06:53 +0900 Subject: [PATCH 072/384] 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 0bf415e764..5494460de2 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 a176d73854..725d1d6209 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index e192467247..29777c6584 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + From c5e461e734f47364333432af118d398b78f6f4c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Dec 2022 16:28:51 +0900 Subject: [PATCH 073/384] 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 5494460de2..61c968ebc9 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 725d1d6209..fbac6f38b0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 29777c6584..4683437173 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + @@ -82,7 +82,7 @@ - + From 0497e433b1de18dd7c176fd6ab63e24d74581e1c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Dec 2022 16:30:15 +0900 Subject: [PATCH 074/384] Change `SectionsContainer` to use flowing children for section update logic --- osu.Game/Graphics/Containers/SectionsContainer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/SectionsContainer.cs b/osu.Game/Graphics/Containers/SectionsContainer.cs index 123589c552..8dd6eac7bb 100644 --- a/osu.Game/Graphics/Containers/SectionsContainer.cs +++ b/osu.Game/Graphics/Containers/SectionsContainer.cs @@ -240,7 +240,9 @@ namespace osu.Game.Graphics.Containers headerBackgroundContainer.Height = expandableHeaderSize + fixedHeaderSize; headerBackgroundContainer.Y = ExpandableHeader?.Y ?? 0; - float smallestSectionHeight = Children.Count > 0 ? Children.Min(d => d.Height) : 0; + var flowChildren = scrollContentContainer.FlowingChildren.OfType(); + + float smallestSectionHeight = flowChildren.Any() ? flowChildren.Min(d => d.Height) : 0; // scroll offset is our fixed header height if we have it plus 10% of content height // plus 5% to fix floating point errors and to not have a section instantly unselect when scrolling upwards @@ -249,7 +251,7 @@ namespace osu.Game.Graphics.Containers float scrollCentre = fixedHeaderSize + scrollContainer.DisplayableContent * scroll_y_centre + selectionLenienceAboveSection; - var presentChildren = Children.Where(c => c.IsPresent); + var presentChildren = flowChildren.Where(c => c.IsPresent); if (lastClickedSection != null) SelectedSection.Value = lastClickedSection; From 56e94e49a36473178712f3e865e06ea68cabb5e0 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Tue, 6 Dec 2022 23:36:11 -0800 Subject: [PATCH 075/384] Display nominated ranked beatmaps in user profile --- osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs | 3 ++- osu.Game/Online/API/Requests/Responses/APIUser.cs | 3 +++ .../Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs | 3 +++ osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs | 3 ++- 4 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs index d723786f23..e4134980b1 100644 --- a/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserBeatmapsRequest.cs @@ -32,6 +32,7 @@ namespace osu.Game.Online.API.Requests Loved, Pending, Guest, - Graveyard + Graveyard, + Nominated, } } diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index d3ddcffaf5..2b6193f661 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -164,6 +164,9 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"guest_beatmapset_count")] public int GuestBeatmapsetCount; + [JsonProperty(@"nominated_beatmapset_count")] + public int NominatedBeatmapsetCount; + [JsonProperty(@"scores_best_count")] public int ScoresBestCount; diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 8c1eea6520..56d9fb9ec6 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -58,6 +58,9 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps case BeatmapSetType.Guest: return user.GuestBeatmapsetCount; + case BeatmapSetType.Nominated: + return user.NominatedBeatmapsetCount; + default: return 0; } diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs index 9f2e79b371..cf80ebd66f 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs @@ -25,7 +25,8 @@ namespace osu.Game.Overlays.Profile.Sections new PaginatedBeatmapContainer(BeatmapSetType.Loved, User, UsersStrings.ShowExtraBeatmapsLovedTitle), new PaginatedBeatmapContainer(BeatmapSetType.Guest, User, UsersStrings.ShowExtraBeatmapsGuestTitle), new PaginatedBeatmapContainer(BeatmapSetType.Pending, User, UsersStrings.ShowExtraBeatmapsPendingTitle), - new PaginatedBeatmapContainer(BeatmapSetType.Graveyard, User, UsersStrings.ShowExtraBeatmapsGraveyardTitle) + new PaginatedBeatmapContainer(BeatmapSetType.Graveyard, User, UsersStrings.ShowExtraBeatmapsGraveyardTitle), + new PaginatedBeatmapContainer(BeatmapSetType.Nominated, User, UsersStrings.ShowExtraBeatmapsNominatedTitle), }; } } From 62b0436bcf81d7274dd0c3a92c3301dc45699bc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Dec 2022 16:31:24 +0900 Subject: [PATCH 076/384] Reverse depth of profile sections to allow for overflowing expanded beatmap cards Closes #21554. --- osu.Game/Overlays/UserProfileOverlay.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index b1a9b2096e..386f95cf0a 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -205,7 +205,9 @@ namespace osu.Game.Overlays protected override UserTrackingScrollContainer CreateScrollContainer() => new OverlayScrollContainer(); - protected override FlowContainer CreateScrollContentContainer() => new FillFlowContainer + // Reverse child ID is required so expanding beatmap panels can appear above sections below them. + // This can also be done by setting Depth when adding new sections above if using ReverseChildID turns out to have any issues. + protected override FlowContainer CreateScrollContentContainer() => new ReverseChildIDFillFlowContainer { Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Y, From 819fd5f950f82a196ed8d4ce1b1b617a05e2f4e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Dec 2022 17:52:57 +0900 Subject: [PATCH 077/384] Fix incorrect resolution of `GameHost` in `LegacyImportManager` --- osu.Game/Database/LegacyImportManager.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/LegacyImportManager.cs b/osu.Game/Database/LegacyImportManager.cs index 901b953bf2..b5338fbe1f 100644 --- a/osu.Game/Database/LegacyImportManager.cs +++ b/osu.Game/Database/LegacyImportManager.cs @@ -42,8 +42,8 @@ namespace osu.Game.Database [Resolved] private RealmAccess realmAccess { get; set; } = null!; - [Resolved(canBeNull: true)] // canBeNull required while we remain on mono for mobile platforms. - private DesktopGameHost? desktopGameHost { get; set; } + [Resolved] + private GameHost gameHost { get; set; } = null!; [Resolved] private INotificationOverlay? notifications { get; set; } @@ -52,7 +52,7 @@ namespace osu.Game.Database public bool SupportsImportFromStable => RuntimeInfo.IsDesktop; - public void UpdateStorage(string stablePath) => cachedStorage = new StableStorage(stablePath, desktopGameHost); + public void UpdateStorage(string stablePath) => cachedStorage = new StableStorage(stablePath, gameHost as DesktopGameHost); public virtual async Task GetImportCount(StableContent content, CancellationToken cancellationToken) { From 0aeee8d6ab6ef72a33b09444cfff5546683df091 Mon Sep 17 00:00:00 2001 From: Loreos7 <86934170+Loreos7@users.noreply.github.com> Date: Wed, 7 Dec 2022 17:47:37 +0300 Subject: [PATCH 078/384] fix typo in today's tip --- osu.Game/Screens/Menu/Disclaimer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index e30be72704..539d58d2d7 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -239,7 +239,7 @@ namespace osu.Game.Screens.Menu "New features are coming online every update. Make sure to stay up-to-date!", "If you find the UI too large or small, try adjusting UI scale in settings!", "Try adjusting the \"Screen Scaling\" mode to change your gameplay or UI area, even in fullscreen!", - "What used to be \"osu!direct\" is available to all users just like on the website. You can access it anywhere using Ctrl-D!", + "What used to be \"osu!direct\" is available to all users just like on the website. You can access it anywhere using Ctrl-B!", "Seeking in replays is available by dragging on the difficulty bar at the bottom of the screen!", "Multithreading support means that even with low \"FPS\" your input and judgements will be accurate!", "Try scrolling down in the mod select panel to find a bunch of new fun mods!", From 1776485b93eeb2ff0b7e5ccf1657aa5704dac997 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 7 Dec 2022 20:20:11 +0000 Subject: [PATCH 079/384] Reflect nested objects vertically in the playfield correctly --- osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index f565456911..5b327fc3b0 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.Osu.Utils if (osuObject is not Slider slider) return; - void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(nested.X, slider.Y - (nested.Y - slider.Y)); + void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(nested.Position.X, OsuPlayfield.BASE_SIZE.Y - nested.Position.Y); static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(point.Position.X, -point.Position.Y); modifySlider(slider, flipNestedObject, flipControlPoint); From 684b16cef5dc7338ef3333106e3433de8085153f Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 7 Dec 2022 21:09:53 +0000 Subject: [PATCH 080/384] Disambiguate object flipping and reflection methods --- osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs | 8 ++--- osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs | 2 +- .../Utils/OsuHitObjectGenerationUtils.cs | 32 +++++++++++-------- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs index 5430929143..19d4a1bf83 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModHardRock.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Mods { var osuObject = (OsuHitObject)hitObject; - OsuHitObjectGenerationUtils.ReflectVertically(osuObject); + OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(osuObject); } } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs b/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs index 0a54d58718..6d01808fb5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModMirror.cs @@ -27,16 +27,16 @@ namespace osu.Game.Rulesets.Osu.Mods switch (Reflection.Value) { case MirrorType.Horizontal: - OsuHitObjectGenerationUtils.ReflectHorizontally(osuObject); + OsuHitObjectGenerationUtils.ReflectHorizontallyAlongPlayfield(osuObject); break; case MirrorType.Vertical: - OsuHitObjectGenerationUtils.ReflectVertically(osuObject); + OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(osuObject); break; case MirrorType.Both: - OsuHitObjectGenerationUtils.ReflectHorizontally(osuObject); - OsuHitObjectGenerationUtils.ReflectVertically(osuObject); + OsuHitObjectGenerationUtils.ReflectHorizontallyAlongPlayfield(osuObject); + OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(osuObject); break; } } diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs index 58f5b2fa8d..307d731fd4 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRandom.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Mods if (positionInfos[i].HitObject is Slider slider && random.NextDouble() < 0.5) { - OsuHitObjectGenerationUtils.FlipSliderHorizontally(slider); + OsuHitObjectGenerationUtils.FlipSliderInPlaceHorizontally(slider); } if (i == 0) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 5b327fc3b0..15bc03261f 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -112,21 +112,24 @@ namespace osu.Game.Rulesets.Osu.Utils /// Reflects the position of the in the playfield horizontally. /// /// The object to reflect. - public static void ReflectHorizontally(OsuHitObject osuObject) + public static void ReflectHorizontallyAlongPlayfield(OsuHitObject osuObject) { osuObject.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - osuObject.X, osuObject.Position.Y); if (osuObject is not Slider slider) return; - FlipSliderHorizontally(slider); + void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - nested.Position.X, nested.Position.Y); + static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y); + + modifySlider(slider, flipNestedObject, flipControlPoint); } /// /// Reflects the position of the in the playfield vertically. /// /// The object to reflect. - public static void ReflectVertically(OsuHitObject osuObject) + public static void ReflectVerticallyAlongPlayfield(OsuHitObject osuObject) { osuObject.Position = new Vector2(osuObject.Position.X, OsuPlayfield.BASE_SIZE.Y - osuObject.Y); @@ -139,6 +142,18 @@ namespace osu.Game.Rulesets.Osu.Utils modifySlider(slider, flipNestedObject, flipControlPoint); } + /// + /// Flips the position of the around its start position horizontally. + /// + /// The slider to be flipped. + public static void FlipSliderInPlaceHorizontally(Slider slider) + { + void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(slider.X - (nested.X - slider.X), nested.Y); + static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y); + + modifySlider(slider, flipNestedObject, flipControlPoint); + } + /// /// Rotate a slider about its start position by the specified angle. /// @@ -152,17 +167,6 @@ namespace osu.Game.Rulesets.Osu.Utils modifySlider(slider, rotateNestedObject, rotateControlPoint); } - /// - /// Flips the slider about its start position horizontally. - /// - public static void FlipSliderHorizontally(Slider slider) - { - void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(slider.X - (nested.X - slider.X), nested.Y); - static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y); - - modifySlider(slider, flipNestedObject, flipControlPoint); - } - private static void modifySlider(Slider slider, Action modifyNestedObject, Action modifyControlPoint) { // No need to update the head and tail circles, since slider handles that when the new slider path is set From 7676838cc04d162c37d87749d275b582cbed8e50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Dec 2022 23:27:02 +0100 Subject: [PATCH 081/384] Apply "reflect" vernacular in nested methods --- .../Utils/OsuHitObjectGenerationUtils.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs index 15bc03261f..aa4cd0af14 100644 --- a/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs +++ b/osu.Game.Rulesets.Osu/Utils/OsuHitObjectGenerationUtils.cs @@ -119,10 +119,10 @@ namespace osu.Game.Rulesets.Osu.Utils if (osuObject is not Slider slider) return; - void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - nested.Position.X, nested.Position.Y); - static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y); + void reflectNestedObject(OsuHitObject nested) => nested.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - nested.Position.X, nested.Position.Y); + static void reflectControlPoint(PathControlPoint point) => point.Position = new Vector2(-point.Position.X, point.Position.Y); - modifySlider(slider, flipNestedObject, flipControlPoint); + modifySlider(slider, reflectNestedObject, reflectControlPoint); } /// @@ -136,10 +136,10 @@ namespace osu.Game.Rulesets.Osu.Utils if (osuObject is not Slider slider) return; - void flipNestedObject(OsuHitObject nested) => nested.Position = new Vector2(nested.Position.X, OsuPlayfield.BASE_SIZE.Y - nested.Position.Y); - static void flipControlPoint(PathControlPoint point) => point.Position = new Vector2(point.Position.X, -point.Position.Y); + void reflectNestedObject(OsuHitObject nested) => nested.Position = new Vector2(nested.Position.X, OsuPlayfield.BASE_SIZE.Y - nested.Position.Y); + static void reflectControlPoint(PathControlPoint point) => point.Position = new Vector2(point.Position.X, -point.Position.Y); - modifySlider(slider, flipNestedObject, flipControlPoint); + modifySlider(slider, reflectNestedObject, reflectControlPoint); } /// From 5807d4c43dcc2dfd7f8b9b61e277b2dc35c862da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 7 Dec 2022 23:50:01 +0100 Subject: [PATCH 082/384] Add test coverage for slider transformations --- .../OsuHitObjectGenerationUtilsTest.cs | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs diff --git a/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs new file mode 100644 index 0000000000..daa914cac2 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/OsuHitObjectGenerationUtilsTest.cs @@ -0,0 +1,91 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Osu.Utils; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class OsuHitObjectGenerationUtilsTest + { + private static Slider createTestSlider() + { + var slider = new Slider + { + Position = new Vector2(128, 128), + Path = new SliderPath + { + ControlPoints = + { + new PathControlPoint(new Vector2(), PathType.Linear), + new PathControlPoint(new Vector2(-64, -128), PathType.Linear), // absolute position: (64, 0) + new PathControlPoint(new Vector2(-128, 0), PathType.Linear) // absolute position: (0, 128) + } + }, + RepeatCount = 1 + }; + slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + return slider; + } + + [Test] + public void TestReflectSliderHorizontallyAlongPlayfield() + { + var slider = createTestSlider(); + + OsuHitObjectGenerationUtils.ReflectHorizontallyAlongPlayfield(slider); + + Assert.That(slider.Position, Is.EqualTo(new Vector2(OsuPlayfield.BASE_SIZE.X - 128, 128))); + Assert.That(slider.NestedHitObjects.OfType().Single().Position, Is.EqualTo(new Vector2(OsuPlayfield.BASE_SIZE.X - 0, 128))); + Assert.That(slider.Path.ControlPoints.Select(point => point.Position), Is.EquivalentTo(new[] + { + new Vector2(), + new Vector2(64, -128), + new Vector2(128, 0) + })); + } + + [Test] + public void TestReflectSliderVerticallyAlongPlayfield() + { + var slider = createTestSlider(); + + OsuHitObjectGenerationUtils.ReflectVerticallyAlongPlayfield(slider); + + Assert.That(slider.Position, Is.EqualTo(new Vector2(128, OsuPlayfield.BASE_SIZE.Y - 128))); + Assert.That(slider.NestedHitObjects.OfType().Single().Position, Is.EqualTo(new Vector2(0, OsuPlayfield.BASE_SIZE.Y - 128))); + Assert.That(slider.Path.ControlPoints.Select(point => point.Position), Is.EquivalentTo(new[] + { + new Vector2(), + new Vector2(-64, 128), + new Vector2(-128, 0) + })); + } + + [Test] + public void TestFlipSliderInPlaceHorizontally() + { + var slider = createTestSlider(); + + OsuHitObjectGenerationUtils.FlipSliderInPlaceHorizontally(slider); + + Assert.That(slider.Position, Is.EqualTo(new Vector2(128, 128))); + Assert.That(slider.NestedHitObjects.OfType().Single().Position, Is.EqualTo(new Vector2(256, 128))); + Assert.That(slider.Path.ControlPoints.Select(point => point.Position), Is.EquivalentTo(new[] + { + new Vector2(), + new Vector2(64, -128), + new Vector2(128, 0) + })); + } + } +} From e00c075482a0be3f9ac52284328e95c56ac7d377 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 8 Dec 2022 11:30:18 +0900 Subject: [PATCH 083/384] Fix incorrectly modified first lazer version --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 024b691de2..63094c5548 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -25,12 +25,12 @@ namespace osu.Game.Scoring.Legacy /// Database version in stable-compatible YYYYMMDD format. /// Should be incremented if any changes are made to the format/usage. /// - public const int LATEST_VERSION = FIRST_LAZER_VERSION; + public const int LATEST_VERSION = 30000001; /// /// The first stable-compatible YYYYMMDD format version given to lazer usage of replays. /// - public const int FIRST_LAZER_VERSION = 30000001; + public const int FIRST_LAZER_VERSION = 30000000; private readonly Score score; private readonly IBeatmap? beatmap; From f21e903a94609400f2af8d24587c17dac6e1969d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 8 Dec 2022 14:06:38 +0900 Subject: [PATCH 084/384] 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 61c968ebc9..a4900132b1 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 fbac6f38b0..3cb569bdd4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 4683437173..184e3303c8 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + @@ -82,7 +82,7 @@ - + From cdf76077b290db2eb583161f71782607c1d8d6af Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 Dec 2022 05:38:06 +0000 Subject: [PATCH 085/384] Bump Newtonsoft.Json from 13.0.1 to 13.0.2 in /osu.Game Bumps [Newtonsoft.Json](https://github.com/JamesNK/Newtonsoft.Json) from 13.0.1 to 13.0.2. - [Release notes](https://github.com/JamesNK/Newtonsoft.Json/releases) - [Commits](https://github.com/JamesNK/Newtonsoft.Json/compare/13.0.1...13.0.2) --- updated-dependencies: - dependency-name: Newtonsoft.Json dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3cb569bdd4..8639ce6361 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -29,7 +29,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 34ad1e1d61223d289a59879d34d44f6504178c74 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 8 Dec 2022 22:50:29 +0100 Subject: [PATCH 086/384] Fix Android linker removing `System.Globalisation.*Calendar` --- osu.Android.props | 3 +++ osu.Android/Linker.xml | 7 +++++++ 2 files changed, 10 insertions(+) create mode 100644 osu.Android/Linker.xml diff --git a/osu.Android.props b/osu.Android.props index a4900132b1..147d236d98 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -58,4 +58,7 @@ + + + diff --git a/osu.Android/Linker.xml b/osu.Android/Linker.xml new file mode 100644 index 0000000000..c7d31fe087 --- /dev/null +++ b/osu.Android/Linker.xml @@ -0,0 +1,7 @@ + + + + + + + From 27a96b8e6d92c93b5fa4a9323898d1c05be60569 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Thu, 8 Dec 2022 22:52:10 +0100 Subject: [PATCH 087/384] Add linker exception to iOS out of an abundance of caution --- osu.iOS/Linker.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.iOS/Linker.xml b/osu.iOS/Linker.xml index 04591c55d0..b55be3be39 100644 --- a/osu.iOS/Linker.xml +++ b/osu.iOS/Linker.xml @@ -24,4 +24,8 @@ + + + + From 3570fa8d930abc35d6ec5990161dba9f0e733055 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 9 Dec 2022 17:12:20 +0900 Subject: [PATCH 088/384] Fix hub clients not reconnecting after connection error --- osu.Game/Online/PersistentEndpointClientConnector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/PersistentEndpointClientConnector.cs b/osu.Game/Online/PersistentEndpointClientConnector.cs index be76644745..55e9190f99 100644 --- a/osu.Game/Online/PersistentEndpointClientConnector.cs +++ b/osu.Game/Online/PersistentEndpointClientConnector.cs @@ -150,7 +150,7 @@ namespace osu.Game.Online await disconnect(true); if (ex != null) - await handleErrorAndDelay(ex, cancellationToken).ConfigureAwait(false); + await handleErrorAndDelay(ex, CancellationToken.None).ConfigureAwait(false); else Logger.Log($"{ClientName} disconnected", LoggingTarget.Network); From 4a65f5c8647ec402904c9736c865f980e7c5c1ae Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 9 Dec 2022 16:05:36 +0900 Subject: [PATCH 089/384] Add score token to spectator state --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../Visual/Gameplay/TestSceneSpectatorPlayback.cs | 2 +- osu.Game/Online/Spectator/SpectatorClient.cs | 3 ++- osu.Game/Online/Spectator/SpectatorState.cs | 7 +++++-- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 8f1eb98c79..4f319a7c34 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -261,7 +261,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestFinalFramesPurgedBeforeEndingPlay() { - AddStep("begin playing", () => spectatorClient.BeginPlaying(TestGameplayState.Create(new OsuRuleset()), new Score())); + AddStep("begin playing", () => spectatorClient.BeginPlaying(TestGameplayState.Create(new OsuRuleset()), new Score(), 0)); AddStep("send frames and finish play", () => { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 1ad1da0994..a949c0d79d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -147,7 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay } }; - spectatorClient.BeginPlaying(TestGameplayState.Create(new OsuRuleset()), recordingScore); + spectatorClient.BeginPlaying(TestGameplayState.Create(new OsuRuleset()), recordingScore, 0); spectatorClient.OnNewFrames += onNewFrames; }); } diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index b0ee0bc37b..c532cb266d 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -159,7 +159,7 @@ namespace osu.Game.Online.Spectator return Task.CompletedTask; } - public void BeginPlaying(GameplayState state, Score score) + public void BeginPlaying(GameplayState state, Score score, long? token) { // This schedule is only here to match the one below in `EndPlaying`. Schedule(() => @@ -175,6 +175,7 @@ namespace osu.Game.Online.Spectator currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); currentState.State = SpectatedUserState.Playing; currentState.MaximumScoringValues = state.ScoreProcessor.MaximumScoringValues; + currentState.ScoreToken = token; currentBeatmap = state.Beatmap; currentScore = score; diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index 766b274e63..34dbefd2a5 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -33,14 +33,17 @@ namespace osu.Game.Online.Spectator [Key(4)] public ScoringValues MaximumScoringValues { get; set; } + [Key(5)] + public long? ScoreToken { get; set; } + public bool Equals(SpectatorState other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return BeatmapID == other.BeatmapID && Mods.SequenceEqual(other.Mods) && RulesetID == other.RulesetID && State == other.State; + return BeatmapID == other.BeatmapID && Mods.SequenceEqual(other.Mods) && RulesetID == other.RulesetID && State == other.State && ScoreToken == other.ScoreToken; } - public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)} Ruleset:{RulesetID} State:{State}"; + public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)} Ruleset:{RulesetID} State:{State} Token:{ScoreToken}"; } } diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 14453c8cbe..81c4e9ab2a 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -148,7 +148,7 @@ namespace osu.Game.Screens.Play realmBeatmap.LastPlayed = DateTimeOffset.Now; }); - spectatorClient.BeginPlaying(GameplayState, Score); + spectatorClient.BeginPlaying(GameplayState, Score, token); } public override bool OnExiting(ScreenExitEvent e) From e9998f1690003675f8b8642b98f34e72c18584c2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 9 Dec 2022 20:15:07 +0900 Subject: [PATCH 090/384] Store maximum statistics to spectator state --- .../Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs | 6 ++---- osu.Game/Online/Spectator/SpectatorClient.cs | 2 +- osu.Game/Online/Spectator/SpectatorScoreProcessor.cs | 4 ++-- osu.Game/Online/Spectator/SpectatorState.cs | 4 ++-- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 7 ++----- 5 files changed, 9 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs index fa7d2c04f4..649c662e41 100644 --- a/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/MultiplayerGameplayLeaderboardTestScene.cs @@ -117,11 +117,9 @@ namespace osu.Game.Tests.Visual.Multiplayer BeatmapID = 0, RulesetID = 0, Mods = user.Mods, - MaximumScoringValues = new ScoringValues + MaximumStatistics = new Dictionary { - BaseScore = 10000, - MaxCombo = 1000, - CountBasicHitObjects = 1000 + { HitResult.Perfect, 100 } } }; } diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index b0ee0bc37b..0a499a202c 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -174,7 +174,7 @@ namespace osu.Game.Online.Spectator currentState.RulesetID = score.ScoreInfo.RulesetID; currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); currentState.State = SpectatedUserState.Playing; - currentState.MaximumScoringValues = state.ScoreProcessor.MaximumScoringValues; + currentState.MaximumStatistics = state.ScoreProcessor.MaximumStatistics; currentBeatmap = state.Beatmap; currentScore = score; diff --git a/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs b/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs index c97871c3aa..75b6a6e83b 100644 --- a/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs +++ b/osu.Game/Online/Spectator/SpectatorScoreProcessor.cs @@ -152,12 +152,12 @@ namespace osu.Game.Online.Spectator scoreInfo.MaxCombo = frame.Header.MaxCombo; scoreInfo.Statistics = frame.Header.Statistics; + scoreInfo.MaximumStatistics = spectatorState.MaximumStatistics; Accuracy.Value = frame.Header.Accuracy; Combo.Value = frame.Header.Combo; - scoreProcessor.ExtractScoringValues(frame.Header, out var currentScoringValues, out _); - TotalScore.Value = scoreProcessor.ComputeScore(Mode.Value, currentScoringValues, spectatorState.MaximumScoringValues); + TotalScore.Value = scoreProcessor.ComputeScore(Mode.Value, scoreInfo); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index 766b274e63..91df05bf96 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -9,7 +9,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using MessagePack; using osu.Game.Online.API; -using osu.Game.Scoring; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Online.Spectator { @@ -31,7 +31,7 @@ namespace osu.Game.Online.Spectator public SpectatedUserState State { get; set; } [Key(4)] - public ScoringValues MaximumScoringValues { get; set; } + public Dictionary MaximumStatistics { get; set; } = new Dictionary(); public bool Equals(SpectatorState other) { diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 18c88dff2a..b2b31c5394 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -90,17 +90,14 @@ namespace osu.Game.Rulesets.Scoring private readonly double accuracyPortion; private readonly double comboPortion; - /// - /// Scoring values for a perfect play. - /// - public ScoringValues MaximumScoringValues + public Dictionary MaximumStatistics { get { if (!beatmapApplied) throw new InvalidOperationException($"Cannot access maximum scoring values before calling {nameof(ApplyBeatmap)}."); - return maximumScoringValues; + return maximumResultCounts.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); } } From 0372e38f577e7ef82a5ca5e6a0907125823df671 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 11 Dec 2022 13:00:12 +0900 Subject: [PATCH 091/384] Add nullability assertion to appease CI --- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 2f7727ac49..15ed992c3e 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Diagnostics; using System.IO; using System.Linq; using Newtonsoft.Json; @@ -105,6 +106,9 @@ namespace osu.Game.Scoring.Legacy readCompressedData(compressedScoreInfo, reader => { LegacyReplaySoloScoreInfo readScore = JsonConvert.DeserializeObject(reader.ReadToEnd()); + + Debug.Assert(readScore != null); + score.ScoreInfo.Statistics = readScore.Statistics; score.ScoreInfo.MaximumStatistics = readScore.MaximumStatistics; score.ScoreInfo.Mods = readScore.Mods.Select(m => m.ToMod(currentRuleset)).ToArray(); From 85039209f80347462f9d25d0026dfdaa966015b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Dec 2022 01:56:32 +0900 Subject: [PATCH 092/384] Fix parsing of ruleset configuration values being incorrect on some locales Closes #21611. --- osu.Game/Rulesets/Configuration/RulesetConfigManager.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs index 4ff4f66665..0eea1ff215 100644 --- a/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs +++ b/osu.Game/Rulesets/Configuration/RulesetConfigManager.cs @@ -5,9 +5,11 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Configuration; +using osu.Framework.Extensions; using osu.Game.Configuration; using osu.Game.Database; @@ -67,7 +69,7 @@ namespace osu.Game.Rulesets.Configuration { var setting = r.All().First(s => s.RulesetName == rulesetName && s.Variant == variant && s.Key == c.ToString()); - setting.Value = ConfigStore[c].ToString(); + setting.Value = ConfigStore[c].ToString(CultureInfo.InvariantCulture); } }); @@ -89,7 +91,7 @@ namespace osu.Game.Rulesets.Configuration setting = new RealmRulesetSetting { Key = lookup.ToString(), - Value = bindable.Value.ToString(), + Value = bindable.ToString(CultureInfo.InvariantCulture), RulesetName = rulesetName, Variant = variant, }; From 133256879685caf181defefe128ddd9e752e51cd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Dec 2022 03:10:13 +0300 Subject: [PATCH 093/384] Fix SettingsButton receiving input at padded area --- .../UserInterface/TestSceneSettingsButton.cs | 41 +++++++++++++++++++ osu.Game/Overlays/Settings/SettingsButton.cs | 4 ++ 2 files changed, 45 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneSettingsButton.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsButton.cs new file mode 100644 index 0000000000..4353e7ffef --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsButton.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Overlays.Settings; +using NUnit.Framework; +using osuTK; +using osu.Game.Overlays; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public partial class TestSceneSettingsButton : OsuManualInputManagerTestScene + { + private readonly SettingsButton settingsButton; + + public TestSceneSettingsButton() + { + Add(new Container + { + AutoSizeAxes = Axes.Y, + Width = 500, + Child = settingsButton = new SettingsButton + { + Enabled = { Value = true }, + Text = "Test settings button" + } + }); + } + + [Test] + public void TestInputAtPaddedArea() + { + AddStep("Move cursor to button", () => InputManager.MoveMouseTo(settingsButton)); + AddAssert("Button is hovered", () => settingsButton.IsHovered); + AddStep("Move cursor to padded area", () => InputManager.MoveMouseTo(settingsButton.ScreenSpaceDrawQuad.TopLeft + new Vector2(SettingsPanel.CONTENT_MARGINS / 2f, 10))); + AddAssert("Cursor within a button", () => settingsButton.ScreenSpaceDrawQuad.Contains(InputManager.CurrentState.Mouse.Position)); + AddAssert("Button is not hovered", () => !settingsButton.IsHovered); + } + } +} diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs index 5091ddc2d0..68cb95a2d8 100644 --- a/osu.Game/Overlays/Settings/SettingsButton.cs +++ b/osu.Game/Overlays/Settings/SettingsButton.cs @@ -9,11 +9,15 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; +using osuTK; namespace osu.Game.Overlays.Settings { public partial class SettingsButton : RoundedButton, IHasTooltip, IConditionalFilterable { + // We don't want to receive input at the padded area + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Content.ReceivePositionalInputAt(screenSpacePos); + public SettingsButton() { RelativeSizeAxes = Axes.X; From 5e8d75bbbfdf02e10eba7a7b95cd07920a9baaa6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 12 Dec 2022 11:39:03 +0900 Subject: [PATCH 094/384] Remove unused overload --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 25 --------------------- 1 file changed, 25 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index b2b31c5394..cdef3e5afb 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -487,31 +487,6 @@ namespace osu.Game.Rulesets.Scoring extractScoringValues(scoreInfo.MaximumStatistics, out _, out maximum); } - /// - /// Applies a best-effort extraction of hit statistics into . - /// - /// - /// This method is useful in a variety of situations, with a few drawbacks that need to be considered: - /// - /// The maximum will always be 0. - /// The current and maximum will always be the same value. - /// - /// Consumers are expected to more accurately fill in the above values through external means. - /// - /// Ensure to fill in the maximum for use in - /// . - /// - /// - /// The replay frame header to extract scoring values from. - /// The "current" scoring values, representing the hit statistics as they appear. - /// The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time. - [Pure] - internal void ExtractScoringValues(FrameHeader header, out ScoringValues current, out ScoringValues maximum) - { - extractScoringValues(header.Statistics, out current, out maximum); - current.MaxCombo = header.MaxCombo; - } - /// /// Applies a best-effort extraction of hit statistics into . /// From 6e41efede88ba6bdabd2e54a645f7303c256e898 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 12 Dec 2022 11:40:15 +0900 Subject: [PATCH 095/384] Simplify dictionary construction --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index cdef3e5afb..7e94ea2b36 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -95,9 +95,9 @@ namespace osu.Game.Rulesets.Scoring get { if (!beatmapApplied) - throw new InvalidOperationException($"Cannot access maximum scoring values before calling {nameof(ApplyBeatmap)}."); + throw new InvalidOperationException($"Cannot access maximum statistics before calling {nameof(ApplyBeatmap)}."); - return maximumResultCounts.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + return new Dictionary(maximumResultCounts); } } From 7a54bcac57b193a9b2c1f268fe4751a9da691703 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 12 Dec 2022 11:41:07 +0900 Subject: [PATCH 096/384] Remove unused using --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 7e94ea2b36..5e05c7e2a6 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -10,7 +10,6 @@ using osu.Framework.Bindables; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Extensions; -using osu.Game.Online.Spectator; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; From e76c56b03b1039b75010528a20cd9284b0833949 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 11 Dec 2022 20:10:06 -0800 Subject: [PATCH 097/384] Fix changelog single build dates disappearing before being off screen --- osu.Game/Overlays/Changelog/ChangelogBuild.cs | 10 ++++-- .../Changelog/ChangelogSingleBuild.cs | 32 ++++++++++--------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index fd7a3f8791..96d5203d14 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -68,11 +68,15 @@ namespace osu.Game.Overlays.Changelog Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, + Direction = FillDirection.Vertical, Margin = new MarginPadding { Top = 20 }, - Children = new Drawable[] + Child = new FillFlowContainer { - new OsuHoverContainer + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Child = new OsuHoverContainer { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs index ddee6ff8bb..902aa72178 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs @@ -104,27 +104,29 @@ namespace osu.Game.Overlays.Changelog { var fill = base.CreateHeader(); - foreach (var existing in fill.Children.OfType()) + var nestedFill = fill.Children.OfType().First(); + + var buildDisplay = nestedFill.Children.OfType().First(); + + buildDisplay.Scale = new Vector2(1.25f); + buildDisplay.Action = null; + + fill.Add(date = new OsuSpriteText { - existing.Scale = new Vector2(1.25f); - existing.Action = null; + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = Build.CreatedAt.Date.ToString("dd MMMM yyyy"), + Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 14), + Margin = new MarginPadding { Top = 5 }, + Scale = new Vector2(1.25f), + }); - existing.Add(date = new OsuSpriteText - { - Text = Build.CreatedAt.Date.ToString("dd MMMM yyyy"), - Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 14), - Anchor = Anchor.BottomCentre, - Origin = Anchor.TopCentre, - Margin = new MarginPadding { Top = 5 }, - }); - } - - fill.Insert(-1, new NavigationIconButton(Build.Versions?.Previous) + nestedFill.Insert(-1, new NavigationIconButton(Build.Versions?.Previous) { Icon = FontAwesome.Solid.ChevronLeft, SelectBuild = b => SelectBuild(b) }); - fill.Insert(1, new NavigationIconButton(Build.Versions?.Next) + nestedFill.Insert(1, new NavigationIconButton(Build.Versions?.Next) { Icon = FontAwesome.Solid.ChevronRight, SelectBuild = b => SelectBuild(b) From 731184eb39b7ad0f6d11c4aeaf32eaef52a11316 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Dec 2022 13:43:01 +0900 Subject: [PATCH 098/384] Revert "Merge pull request #21614 from EVAST9919/button-fix" This reverts commit 4bd196684fe26b146986b04f8af5c75735774817, reversing changes made to a1d22ef77a0dd71aff97b17fd656ac68d347d4d7. --- .../UserInterface/TestSceneSettingsButton.cs | 41 ------------------- osu.Game/Overlays/Settings/SettingsButton.cs | 4 -- 2 files changed, 45 deletions(-) delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneSettingsButton.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsButton.cs deleted file mode 100644 index 4353e7ffef..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsButton.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics; -using osu.Game.Overlays.Settings; -using NUnit.Framework; -using osuTK; -using osu.Game.Overlays; - -namespace osu.Game.Tests.Visual.UserInterface -{ - public partial class TestSceneSettingsButton : OsuManualInputManagerTestScene - { - private readonly SettingsButton settingsButton; - - public TestSceneSettingsButton() - { - Add(new Container - { - AutoSizeAxes = Axes.Y, - Width = 500, - Child = settingsButton = new SettingsButton - { - Enabled = { Value = true }, - Text = "Test settings button" - } - }); - } - - [Test] - public void TestInputAtPaddedArea() - { - AddStep("Move cursor to button", () => InputManager.MoveMouseTo(settingsButton)); - AddAssert("Button is hovered", () => settingsButton.IsHovered); - AddStep("Move cursor to padded area", () => InputManager.MoveMouseTo(settingsButton.ScreenSpaceDrawQuad.TopLeft + new Vector2(SettingsPanel.CONTENT_MARGINS / 2f, 10))); - AddAssert("Cursor within a button", () => settingsButton.ScreenSpaceDrawQuad.Contains(InputManager.CurrentState.Mouse.Position)); - AddAssert("Button is not hovered", () => !settingsButton.IsHovered); - } - } -} diff --git a/osu.Game/Overlays/Settings/SettingsButton.cs b/osu.Game/Overlays/Settings/SettingsButton.cs index 68cb95a2d8..5091ddc2d0 100644 --- a/osu.Game/Overlays/Settings/SettingsButton.cs +++ b/osu.Game/Overlays/Settings/SettingsButton.cs @@ -9,15 +9,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; -using osuTK; namespace osu.Game.Overlays.Settings { public partial class SettingsButton : RoundedButton, IHasTooltip, IConditionalFilterable { - // We don't want to receive input at the padded area - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Content.ReceivePositionalInputAt(screenSpacePos); - public SettingsButton() { RelativeSizeAxes = Axes.X; From f27603dd6de7521a8fb0bd98334325de5dffe6ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Oct 2022 17:46:01 +0900 Subject: [PATCH 099/384] Use hard links instead of file copy when available --- osu.Game/Database/RealmFileStore.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index 036b15ea17..56ea8f23bc 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -4,6 +4,8 @@ using System; using System.IO; using System.Linq; +using System.Runtime.InteropServices; +using osu.Framework; using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Framework.Logging; @@ -60,6 +62,13 @@ namespace osu.Game.Database private void copyToStore(RealmFile file, Stream data) { + if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows && data is FileStream fs) + { + // attempt to do a fast hard link rather than copy. + if (CreateHardLink(Storage.GetFullPath(file.GetStoragePath()), fs.Name, IntPtr.Zero)) + return; + } + data.Seek(0, SeekOrigin.Begin); using (var output = Storage.CreateFileSafely(file.GetStoragePath())) @@ -68,6 +77,13 @@ namespace osu.Game.Database data.Seek(0, SeekOrigin.Begin); } + [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)] + private static extern bool CreateHardLink( + string lpFileName, + string lpExistingFileName, + IntPtr lpSecurityAttributes + ); + private bool checkFileExistsAndMatchesHash(RealmFile file) { string path = file.GetStoragePath(); From 3b1920c060b9352654b3f2d429548bffcbbbc4a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Oct 2022 18:18:29 +0900 Subject: [PATCH 100/384] Add code to check whether a file is a hard link --- osu.Game/Database/RealmFileStore.cs | 48 +++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index 56ea8f23bc..4aa65dd50a 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -5,6 +5,8 @@ using System; using System.IO; using System.Linq; using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using Microsoft.Win32.SafeHandles; using osu.Framework; using osu.Framework.Extensions; using osu.Framework.IO.Stores; @@ -77,6 +79,20 @@ namespace osu.Game.Database data.Seek(0, SeekOrigin.Begin); } + public static int GetFileLinkCount(string filePath) + { + int result = 0; + SafeFileHandle handle = CreateFile(filePath, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Archive, IntPtr.Zero); + + ByHandleFileInformation fileInfo; + + if (GetFileInformationByHandle(handle, out fileInfo)) + result = (int)fileInfo.NumberOfLinks; + CloseHandle(handle); + + return result; + } + [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)] private static extern bool CreateHardLink( string lpFileName, @@ -84,6 +100,38 @@ namespace osu.Game.Database IntPtr lpSecurityAttributes ); + [StructLayout(LayoutKind.Sequential)] + private struct ByHandleFileInformation + { + public readonly uint FileAttributes; + public readonly FILETIME CreationTime; + public readonly FILETIME LastAccessTime; + public readonly FILETIME LastWriteTime; + public readonly uint VolumeSerialNumber; + public readonly uint FileSizeHigh; + public readonly uint FileSizeLow; + public readonly uint NumberOfLinks; + public readonly uint FileIndexHigh; + public readonly uint FileIndexLow; + } + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] + private static extern SafeFileHandle CreateFile( + string lpFileName, + [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess, + [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode, + IntPtr lpSecurityAttributes, + [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition, + [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes, + IntPtr hTemplateFile); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool GetFileInformationByHandle(SafeFileHandle handle, out ByHandleFileInformation lpFileInformation); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool CloseHandle(SafeHandle hObject); + private bool checkFileExistsAndMatchesHash(RealmFile file) { string path = file.GetStoragePath(); From 902dff15e38be4259e79d022bec5b39c3a466861 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Oct 2022 18:20:49 +0900 Subject: [PATCH 101/384] Add todo regarding validity check --- osu.Game/Beatmaps/WorkingBeatmapCache.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index adb5f8c433..e6f96330e7 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -141,6 +141,9 @@ namespace osu.Game.Beatmaps try { string fileStorePath = BeatmapSetInfo.GetPathForFile(BeatmapInfo.Path); + + // TODO: check validity of file + var stream = GetStream(fileStorePath); if (stream == null) From d8de99bbe452d000896d4ac75e036e854038dda9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 13 Oct 2022 18:39:52 +0900 Subject: [PATCH 102/384] Check for hard link support in first run overlay --- osu.Game/Database/LegacyImportManager.cs | 21 +++++++++++++++++++ osu.Game/Database/RealmFileStore.cs | 2 +- .../FirstRunSetup/ScreenImportFromStable.cs | 4 ++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/LegacyImportManager.cs b/osu.Game/Database/LegacyImportManager.cs index b5338fbe1f..80c8e9c19e 100644 --- a/osu.Game/Database/LegacyImportManager.cs +++ b/osu.Game/Database/LegacyImportManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework; @@ -54,6 +55,26 @@ namespace osu.Game.Database public void UpdateStorage(string stablePath) => cachedStorage = new StableStorage(stablePath, gameHost as DesktopGameHost); + public bool CheckHardLinkAvailability() + { + if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) + return false; + + var stableStorage = GetCurrentStableStorage(); + + if (stableStorage == null || gameHost is not DesktopGameHost desktopGameHost) + return false; + + const string test_filename = "_hard_link_test"; + + desktopGameHost.Storage.Delete(test_filename); + + string testExistingPath = stableStorage.GetFullPath(stableStorage.GetFiles(string.Empty).First()); + string testDestinationPath = desktopGameHost.Storage.GetFullPath(test_filename); + + return RealmFileStore.CreateHardLink(testDestinationPath, testExistingPath, IntPtr.Zero); + } + public virtual async Task GetImportCount(StableContent content, CancellationToken cancellationToken) { var stableStorage = GetCurrentStableStorage(); diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index 4aa65dd50a..99033e2c88 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -94,7 +94,7 @@ namespace osu.Game.Database } [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)] - private static extern bool CreateHardLink( + public static extern bool CreateHardLink( string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 29cf3824fd..8227106ac2 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -14,6 +14,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; +using osu.Framework.Logging; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -105,6 +106,9 @@ namespace osu.Game.Overlays.FirstRunSetup toggleInteraction(true); stableLocatorTextBox.Current.Value = storage.GetFullPath(string.Empty); importButton.Enabled.Value = true; + + bool available = legacyImportManager.CheckHardLinkAvailability(); + Logger.Log($"Hard link support is {available}"); } private void runImport() From 726943cb14150f8673b6ef0b71a2805bd6e43e9a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 7 Dec 2022 17:01:28 +0900 Subject: [PATCH 103/384] Add information attempting to explain hard links to the end user --- ...RunOverlayImportFromStableScreenStrings.cs | 4 ++-- .../FirstRunSetup/ScreenImportFromStable.cs | 21 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs b/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs index deac7d8628..f0620245c3 100644 --- a/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs +++ b/osu.Game/Localisation/FirstRunOverlayImportFromStableScreenStrings.cs @@ -15,10 +15,10 @@ namespace osu.Game.Localisation public static LocalisableString Header => new TranslatableString(getKey(@"header"), @"Import"); /// - /// "If you have an installation of a previous osu! version, you can choose to migrate your existing content. Note that this will create a copy, and not affect your existing installation." + /// "If you have an installation of a previous osu! version, you can choose to migrate your existing content. Note that this will not affect your existing installation's files in any way." /// public static LocalisableString Description => new TranslatableString(getKey(@"description"), - @"If you have an installation of a previous osu! version, you can choose to migrate your existing content. Note that this will create a copy, and not affect your existing installation."); + @"If you have an installation of a previous osu! version, you can choose to migrate your existing content. Note that this will not affect your existing installation's files in any way."); /// /// "previous osu! install" diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 8227106ac2..d1221d4174 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -7,6 +7,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; @@ -40,6 +41,8 @@ namespace osu.Game.Overlays.FirstRunSetup private StableLocatorLabelledTextBox stableLocatorTextBox = null!; + private OsuTextFlowContainer copyInformation = null!; + private IEnumerable contentCheckboxes => Content.Children.OfType(); [BackgroundDependencyLoader(permitNulls: true)] @@ -63,6 +66,12 @@ namespace osu.Game.Overlays.FirstRunSetup new ImportCheckbox(CommonStrings.Scores, StableContent.Scores), new ImportCheckbox(CommonStrings.Skins, StableContent.Skins), new ImportCheckbox(CommonStrings.Collections, StableContent.Collections), + copyInformation = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) + { + Colour = OverlayColourProvider.Content1, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }, importButton = new ProgressRoundedButton { Size = button_size, @@ -109,6 +118,18 @@ namespace osu.Game.Overlays.FirstRunSetup bool available = legacyImportManager.CheckHardLinkAvailability(); Logger.Log($"Hard link support is {available}"); + + if (available) + { + copyInformation.Text = "Data migration will use \"hard links\". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation."; + } + else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) + copyInformation.Text = "Hard links are not supported on this operating system, so a copy of all files will be made during import."; + else + { + copyInformation.Text = + "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install and the file system is NTFS."; + } } private void runImport() From e2d8909e7355d5773d69cb1245a3536d61973a09 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 11 Dec 2022 20:17:04 +0900 Subject: [PATCH 104/384] Add link to change folder location if it isn't on the same drive as import path --- .../FirstRunSetup/ScreenImportFromStable.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index d1221d4174..4af80c6fbb 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -16,12 +16,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Framework.Logging; +using osu.Framework.Screens; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; using osu.Game.Overlays.Settings; +using osu.Game.Overlays.Settings.Sections.Maintenance; using osu.Game.Screens.Edit.Setup; using osuTK; @@ -41,7 +43,7 @@ namespace osu.Game.Overlays.FirstRunSetup private StableLocatorLabelledTextBox stableLocatorTextBox = null!; - private OsuTextFlowContainer copyInformation = null!; + private LinkFlowContainer copyInformation = null!; private IEnumerable contentCheckboxes => Content.Children.OfType(); @@ -50,7 +52,7 @@ namespace osu.Game.Overlays.FirstRunSetup { Content.Children = new Drawable[] { - new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) + new LinkFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) { Colour = OverlayColourProvider.Content1, Text = FirstRunOverlayImportFromStableScreenStrings.Description, @@ -66,7 +68,7 @@ namespace osu.Game.Overlays.FirstRunSetup new ImportCheckbox(CommonStrings.Scores, StableContent.Scores), new ImportCheckbox(CommonStrings.Skins, StableContent.Skins), new ImportCheckbox(CommonStrings.Collections, StableContent.Collections), - copyInformation = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) + copyInformation = new LinkFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE)) { Colour = OverlayColourProvider.Content1, RelativeSizeAxes = Axes.X, @@ -93,6 +95,9 @@ namespace osu.Game.Overlays.FirstRunSetup stableLocatorTextBox.Current.BindValueChanged(_ => updateStablePath(), true); } + [Resolved(canBeNull: true)] + private OsuGame? game { get; set; } + private void updateStablePath() { var storage = legacyImportManager.GetCurrentStableStorage(); @@ -124,11 +129,15 @@ namespace osu.Game.Overlays.FirstRunSetup copyInformation.Text = "Data migration will use \"hard links\". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation."; } else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) - copyInformation.Text = "Hard links are not supported on this operating system, so a copy of all files will be made during import."; + copyInformation.Text = "Lightweight linking of files are not supported on your operating system yet, so a copy of all files will be made during import."; else { copyInformation.Text = - "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install and the file system is NTFS."; + "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS). "; + copyInformation.AddLink(GeneralSettingsStrings.ChangeFolderLocation, () => + { + game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())); + }); } } From caa0b7c290acdc70a61440d6ff209b77236c1325 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 12 Dec 2022 13:59:27 +0900 Subject: [PATCH 105/384] Move score token to BeginPlaying --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../Visual/Gameplay/TestSceneSpectatorPlayback.cs | 2 +- osu.Game/Online/Spectator/ISpectatorServer.cs | 3 ++- osu.Game/Online/Spectator/OnlineSpectatorClient.cs | 6 +++--- osu.Game/Online/Spectator/SpectatorClient.cs | 11 ++++++----- osu.Game/Online/Spectator/SpectatorState.cs | 7 ++----- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- .../Tests/Visual/Spectator/TestSpectatorClient.cs | 2 +- 8 files changed, 17 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 4f319a7c34..ffd034e4d2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -261,7 +261,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestFinalFramesPurgedBeforeEndingPlay() { - AddStep("begin playing", () => spectatorClient.BeginPlaying(TestGameplayState.Create(new OsuRuleset()), new Score(), 0)); + AddStep("begin playing", () => spectatorClient.BeginPlaying(0, TestGameplayState.Create(new OsuRuleset()), new Score())); AddStep("send frames and finish play", () => { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index a949c0d79d..794860b9ec 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -147,7 +147,7 @@ namespace osu.Game.Tests.Visual.Gameplay } }; - spectatorClient.BeginPlaying(TestGameplayState.Create(new OsuRuleset()), recordingScore, 0); + spectatorClient.BeginPlaying(0, TestGameplayState.Create(new OsuRuleset()), recordingScore); spectatorClient.OnNewFrames += onNewFrames; }); } diff --git a/osu.Game/Online/Spectator/ISpectatorServer.cs b/osu.Game/Online/Spectator/ISpectatorServer.cs index 25785f60a4..fa9d04792a 100644 --- a/osu.Game/Online/Spectator/ISpectatorServer.cs +++ b/osu.Game/Online/Spectator/ISpectatorServer.cs @@ -15,8 +15,9 @@ namespace osu.Game.Online.Spectator /// /// Signal the start of a new play session. /// + /// The score submission token. /// The state of gameplay. - Task BeginPlaySession(SpectatorState state); + Task BeginPlaySession(long? scoreToken, SpectatorState state); /// /// Send a bundle of frame data for the current play session. diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index d69bd81b57..d5c1e56761 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -47,7 +47,7 @@ namespace osu.Game.Online.Spectator } } - protected override async Task BeginPlayingInternal(SpectatorState state) + protected override async Task BeginPlayingInternal(long? scoreToken, SpectatorState state) { if (!IsConnected.Value) return; @@ -56,7 +56,7 @@ namespace osu.Game.Online.Spectator try { - await connection.InvokeAsync(nameof(ISpectatorServer.BeginPlaySession), state); + await connection.InvokeAsync(nameof(ISpectatorServer.BeginPlaySession), scoreToken, state); } catch (Exception exception) { @@ -65,7 +65,7 @@ namespace osu.Game.Online.Spectator Debug.Assert(connector != null); await connector.Reconnect(); - await BeginPlayingInternal(state); + await BeginPlayingInternal(scoreToken, state); } // Exceptions can occur if, for instance, the locally played beatmap doesn't have a server-side counterpart. diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index c532cb266d..71b1896922 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -76,6 +76,7 @@ namespace osu.Game.Online.Spectator private IBeatmap? currentBeatmap; private Score? currentScore; + private long? currentScoreToken; private readonly Queue pendingFrameBundles = new Queue(); @@ -108,7 +109,7 @@ namespace osu.Game.Online.Spectator // re-send state in case it wasn't received if (IsPlaying) // TODO: this is likely sent out of order after a reconnect scenario. needs further consideration. - BeginPlayingInternal(currentState); + BeginPlayingInternal(currentScoreToken, currentState); } else { @@ -159,7 +160,7 @@ namespace osu.Game.Online.Spectator return Task.CompletedTask; } - public void BeginPlaying(GameplayState state, Score score, long? token) + public void BeginPlaying(long? scoreToken, GameplayState state, Score score) { // This schedule is only here to match the one below in `EndPlaying`. Schedule(() => @@ -175,12 +176,12 @@ namespace osu.Game.Online.Spectator currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); currentState.State = SpectatedUserState.Playing; currentState.MaximumScoringValues = state.ScoreProcessor.MaximumScoringValues; - currentState.ScoreToken = token; currentBeatmap = state.Beatmap; currentScore = score; + currentScoreToken = scoreToken; - BeginPlayingInternal(currentState); + BeginPlayingInternal(currentScoreToken, currentState); }); } @@ -265,7 +266,7 @@ namespace osu.Game.Online.Spectator }); } - protected abstract Task BeginPlayingInternal(SpectatorState state); + protected abstract Task BeginPlayingInternal(long? scoreToken, SpectatorState state); protected abstract Task SendFramesInternal(FrameDataBundle bundle); diff --git a/osu.Game/Online/Spectator/SpectatorState.cs b/osu.Game/Online/Spectator/SpectatorState.cs index 34dbefd2a5..766b274e63 100644 --- a/osu.Game/Online/Spectator/SpectatorState.cs +++ b/osu.Game/Online/Spectator/SpectatorState.cs @@ -33,17 +33,14 @@ namespace osu.Game.Online.Spectator [Key(4)] public ScoringValues MaximumScoringValues { get; set; } - [Key(5)] - public long? ScoreToken { get; set; } - public bool Equals(SpectatorState other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return BeatmapID == other.BeatmapID && Mods.SequenceEqual(other.Mods) && RulesetID == other.RulesetID && State == other.State && ScoreToken == other.ScoreToken; + return BeatmapID == other.BeatmapID && Mods.SequenceEqual(other.Mods) && RulesetID == other.RulesetID && State == other.State; } - public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)} Ruleset:{RulesetID} State:{State} Token:{ScoreToken}"; + public override string ToString() => $"Beatmap:{BeatmapID} Mods:{string.Join(',', Mods)} Ruleset:{RulesetID} State:{State}"; } } diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 81c4e9ab2a..4c507f8c67 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -148,7 +148,7 @@ namespace osu.Game.Screens.Play realmBeatmap.LastPlayed = DateTimeOffset.Now; }); - spectatorClient.BeginPlaying(GameplayState, Score, token); + spectatorClient.BeginPlaying(token, GameplayState, Score); } public override bool OnExiting(ScreenExitEvent e) diff --git a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs index a76f6c7052..1db35b3aaa 100644 --- a/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs +++ b/osu.Game/Tests/Visual/Spectator/TestSpectatorClient.cs @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.Spectator } } - protected override Task BeginPlayingInternal(SpectatorState state) + protected override Task BeginPlayingInternal(long? scoreToken, SpectatorState state) { // Track the local user's playing beatmap ID. Debug.Assert(state.BeatmapID != null); From 05b59498108925db6d2934c5ba96d29870728361 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Dec 2022 17:38:39 +0900 Subject: [PATCH 106/384] Use explicit casts --- osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs index 902aa72178..a729ef2cc8 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using System.Linq; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -104,9 +103,9 @@ namespace osu.Game.Overlays.Changelog { var fill = base.CreateHeader(); - var nestedFill = fill.Children.OfType().First(); + var nestedFill = (FillFlowContainer)fill.Child; - var buildDisplay = nestedFill.Children.OfType().First(); + var buildDisplay = (OsuHoverContainer)nestedFill.Child; buildDisplay.Scale = new Vector2(1.25f); buildDisplay.Action = null; From 7b48c91fe255c838784939dc9295307ab9690a90 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 12 Dec 2022 18:56:43 +0900 Subject: [PATCH 107/384] Only show local results in multi-spectator results screen --- .../Spectate/MultiSpectatorPlayer.cs | 3 +++ .../Spectate/MultiSpectatorResultsScreen.cs | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorResultsScreen.cs diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs index 5a686ffa72..930bea4497 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorPlayer.cs @@ -7,6 +7,7 @@ using osu.Framework.Audio; using osu.Game.Beatmaps; using osu.Game.Scoring; using osu.Game.Screens.Play; +using osu.Game.Screens.Ranking; namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { @@ -70,5 +71,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate clockAdjustmentsFromMods.BindAdjustments(gameplayClockContainer.AdjustmentsFromMods); return gameplayClockContainer; } + + protected override ResultsScreen CreateResults(ScoreInfo score) => new MultiSpectatorResultsScreen(score); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorResultsScreen.cs new file mode 100644 index 0000000000..fe3f02466d --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorResultsScreen.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using System; +using System.Collections.Generic; +using osu.Game.Online.API; +using osu.Game.Scoring; +using osu.Game.Screens.Play; + +namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate +{ + public partial class MultiSpectatorResultsScreen : SpectatorResultsScreen + { + public MultiSpectatorResultsScreen(ScoreInfo score) + : base(score) + { + } + + protected override APIRequest FetchScores(Action> scoresCallback) => null; + + protected override APIRequest FetchNextPage(int direction, Action> scoresCallback) => null; + } +} From df94af44956e06f9c7cce10daef7794262f1b5ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Dec 2022 19:22:09 +0900 Subject: [PATCH 108/384] Inline `ScoringValues` and make some more methods `private` inside `ScoreProcessor` --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 38 +++++++++++++++--- osu.Game/Scoring/ScoringValues.cs | 43 --------------------- 2 files changed, 32 insertions(+), 49 deletions(-) delete mode 100644 osu.Game/Scoring/ScoringValues.cs diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 5e05c7e2a6..b048566c8a 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -264,7 +264,7 @@ namespace osu.Game.Rulesets.Scoring private void updateScore() { Accuracy.Value = currentMaximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / currentMaximumScoringValues.BaseScore : 1; - TotalScore.Value = ComputeScore(Mode.Value, currentScoringValues, maximumScoringValues); + TotalScore.Value = computeScore(Mode.Value, currentScoringValues, maximumScoringValues); } /// @@ -299,9 +299,9 @@ namespace osu.Game.Rulesets.Scoring if (!ruleset.RulesetInfo.Equals(scoreInfo.Ruleset)) throw new ArgumentException($"Unexpected score ruleset. Expected \"{ruleset.RulesetInfo.ShortName}\" but was \"{scoreInfo.Ruleset.ShortName}\"."); - ExtractScoringValues(scoreInfo, out var current, out var maximum); + extractScoringValues(scoreInfo, out var current, out var maximum); - return ComputeScore(mode, current, maximum); + return computeScore(mode, current, maximum); } /// @@ -312,7 +312,7 @@ namespace osu.Game.Rulesets.Scoring /// The maximum scoring values. /// The total score computed from the given scoring values. [Pure] - public long ComputeScore(ScoringMode mode, ScoringValues current, ScoringValues maximum) + private long computeScore(ScoringMode mode, ScoringValues current, ScoringValues maximum) { double accuracyRatio = maximum.BaseScore > 0 ? (double)current.BaseScore / maximum.BaseScore : 1; double comboRatio = maximum.MaxCombo > 0 ? (double)current.MaxCombo / maximum.MaxCombo : 1; @@ -470,14 +470,14 @@ namespace osu.Game.Rulesets.Scoring /// Consumers are expected to more accurately fill in the above values through external means. /// /// Ensure to fill in the maximum for use in - /// . + /// . /// /// /// The score to extract scoring values from. /// The "current" scoring values, representing the hit statistics as they appear. /// The "maximum" scoring values, representing the hit statistics as if the maximum hit result was attained each time. [Pure] - internal void ExtractScoringValues(ScoreInfo scoreInfo, out ScoringValues current, out ScoringValues maximum) + private void extractScoringValues(ScoreInfo scoreInfo, out ScoringValues current, out ScoringValues maximum) { extractScoringValues(scoreInfo.Statistics, out current, out maximum); current.MaxCombo = scoreInfo.MaxCombo; @@ -560,6 +560,32 @@ namespace osu.Game.Rulesets.Scoring base.Dispose(isDisposing); hitEvents.Clear(); } + + /// + /// Stores the required scoring data that fulfils the minimum requirements for a to calculate score. + /// + private struct ScoringValues + { + /// + /// The sum of all "basic" scoring values. See: and . + /// + public long BaseScore; + + /// + /// The sum of all "bonus" scoring values. See: and . + /// + public long BonusScore; + + /// + /// The highest achieved combo. + /// + public int MaxCombo; + + /// + /// The count of "basic" s. See: . + /// + public int CountBasicHitObjects; + } } public enum ScoringMode diff --git a/osu.Game/Scoring/ScoringValues.cs b/osu.Game/Scoring/ScoringValues.cs deleted file mode 100644 index 471067c9db..0000000000 --- a/osu.Game/Scoring/ScoringValues.cs +++ /dev/null @@ -1,43 +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 MessagePack; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Scoring; - -namespace osu.Game.Scoring -{ - /// - /// Stores the required scoring data that fulfils the minimum requirements for a to calculate score. - /// - [MessagePackObject] - public struct ScoringValues - { - /// - /// The sum of all "basic" scoring values. See: and . - /// - [Key(0)] - public long BaseScore; - - /// - /// The sum of all "bonus" scoring values. See: and . - /// - [Key(1)] - public long BonusScore; - - /// - /// The highest achieved combo. - /// - [Key(2)] - public int MaxCombo; - - /// - /// The count of "basic" s. See: . - /// - [Key(3)] - public int CountBasicHitObjects; - } -} From 37a075632163e6acea5aa7125a8bceaadebbd0f8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Dec 2022 14:38:24 +0300 Subject: [PATCH 109/384] Add tests --- .../UserInterface/TestSceneButtonsInput.cs | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneButtonsInput.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneButtonsInput.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneButtonsInput.cs new file mode 100644 index 0000000000..b8bcf583bf --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneButtonsInput.cs @@ -0,0 +1,92 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Overlays.Settings; +using NUnit.Framework; +using osuTK; +using osu.Game.Overlays; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Graphics.UserInterface; +using osu.Framework.Allocation; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public partial class TestSceneButtonsInput : OsuManualInputManagerTestScene + { + private const int width = 500; + + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + + private readonly SettingsButton settingsButton; + private readonly RoundedButton roundedButton; + private readonly ShearedButton shearedButton; + + public TestSceneButtonsInput() + { + Add(new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + Width = 500, + Spacing = new Vector2(0, 5), + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + settingsButton = new SettingsButton + { + Enabled = { Value = true }, + Text = "Settings button" + }, + roundedButton = new RoundedButton + { + RelativeSizeAxes = Axes.X, + Enabled = { Value = true }, + Text = "Rounded button" + }, + shearedButton = new ShearedButton(width) + { + Text = "Sheared button", + LighterColour = Colour4.FromHex("#FFFFFF"), + DarkerColour = Colour4.FromHex("#FFCC22"), + TextColour = Colour4.Black, + Height = 40, + Enabled = { Value = true }, + Padding = new MarginPadding(0) + } + } + }); + } + + [Test] + public void TestSettingsButtonInput() + { + AddStep("Move cursor to button", () => InputManager.MoveMouseTo(settingsButton)); + AddAssert("Button is hovered", () => settingsButton.IsHovered); + AddStep("Move cursor to padded area", () => InputManager.MoveMouseTo(settingsButton.ScreenSpaceDrawQuad.TopLeft + new Vector2(SettingsPanel.CONTENT_MARGINS / 2f, 10))); + AddAssert("Cursor within a button", () => settingsButton.ScreenSpaceDrawQuad.Contains(InputManager.CurrentState.Mouse.Position)); + AddAssert("Button is not hovered", () => !settingsButton.IsHovered); + } + + [Test] + public void TestRoundedButtonInput() + { + AddStep("Move cursor to button", () => InputManager.MoveMouseTo(roundedButton)); + AddAssert("Button is hovered", () => roundedButton.IsHovered); + AddStep("Move cursor to corner", () => InputManager.MoveMouseTo(roundedButton.ScreenSpaceDrawQuad.TopLeft + Vector2.One)); + AddAssert("Cursor within a button", () => roundedButton.ScreenSpaceDrawQuad.Contains(InputManager.CurrentState.Mouse.Position)); + AddAssert("Button is not hovered", () => !roundedButton.IsHovered); + } + + [Test] + public void TestShearedButtonInput() + { + AddStep("Move cursor to button", () => InputManager.MoveMouseTo(shearedButton)); + AddAssert("Button is hovered", () => shearedButton.IsHovered); + AddStep("Move cursor to corner", () => InputManager.MoveMouseTo(shearedButton.ScreenSpaceDrawQuad.TopLeft + Vector2.One)); + AddAssert("Cursor within a button", () => shearedButton.ScreenSpaceDrawQuad.Contains(InputManager.CurrentState.Mouse.Position)); + AddAssert("Button is not hovered", () => !shearedButton.IsHovered); + } + } +} From 7e39f171fbd787339e59ed0ceee38f6e51be4654 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Dec 2022 14:40:38 +0300 Subject: [PATCH 110/384] Fix OsuButton input receiving --- osu.Game/Graphics/UserInterface/OsuButton.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index fa61b06cff..70f76fb453 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -13,6 +11,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; +using osuTK; using osuTK.Graphics; namespace osu.Game.Graphics.UserInterface @@ -66,6 +65,8 @@ namespace osu.Game.Graphics.UserInterface protected override Container Content { get; } + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Content.ReceivePositionalInputAt(screenSpacePos); + protected Box Hover; protected Box Background; protected SpriteText SpriteText; @@ -115,7 +116,7 @@ namespace osu.Game.Graphics.UserInterface }); if (hoverSounds.HasValue) - AddInternal(new HoverClickSounds(hoverSounds.Value) { Enabled = { BindTarget = Enabled } }); + Add(new HoverClickSounds(hoverSounds.Value) { Enabled = { BindTarget = Enabled } }); } [BackgroundDependencyLoader] From d2b3533356d22d3ac0350e3c8f3eb08a561413de Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Dec 2022 14:42:50 +0300 Subject: [PATCH 111/384] Fix OsuClickableContainer input receiving --- .../Graphics/Containers/OsuClickableContainer.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index 4729ddf1a8..945ebbb02d 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.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 osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Localisation; using osu.Game.Graphics.UserInterface; +using osuTK; namespace osu.Game.Graphics.Containers { @@ -18,6 +17,8 @@ namespace osu.Game.Graphics.Containers private readonly Container content = new Container { RelativeSizeAxes = Axes.Both }; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Content.ReceivePositionalInputAt(screenSpacePos); + protected override Container Content => content; protected virtual HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverClickSounds(sampleSet) { Enabled = { BindTarget = Enabled } }; @@ -38,11 +39,8 @@ namespace osu.Game.Graphics.Containers content.AutoSizeAxes = AutoSizeAxes; } - InternalChildren = new Drawable[] - { - content, - CreateHoverSounds(sampleSet) - }; + AddInternal(content); + Add(CreateHoverSounds(sampleSet)); } } } From b0d475cd8b2f641d5cece506ad05a46b4552ae71 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Dec 2022 14:57:07 +0300 Subject: [PATCH 112/384] CI fix --- osu.Game/Graphics/UserInterface/OsuButton.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 70f76fb453..9216b41170 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -23,12 +23,8 @@ namespace osu.Game.Graphics.UserInterface { public LocalisableString Text { - get => SpriteText?.Text ?? default; - set - { - if (SpriteText != null) - SpriteText.Text = value; - } + get => SpriteText.Text; + set => SpriteText.Text = value; } private Color4? backgroundColour; From 890dd9cd061d34eaf693ad81c219937324f7192f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Dec 2022 15:10:03 +0300 Subject: [PATCH 113/384] Fix input doesn't take original drawable modifications into account --- .../UserInterface/TestSceneButtonsInput.cs | 37 +++++++++++++++++++ .../Containers/OsuClickableContainer.cs | 2 +- osu.Game/Graphics/UserInterface/OsuButton.cs | 2 +- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneButtonsInput.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneButtonsInput.cs index b8bcf583bf..985f613b63 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneButtonsInput.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneButtonsInput.cs @@ -10,6 +10,10 @@ using osu.Game.Overlays; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterface; using osu.Framework.Allocation; +using osu.Game.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; +using osu.Game.Graphics.Sprites; namespace osu.Game.Tests.Visual.UserInterface { @@ -21,6 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); private readonly SettingsButton settingsButton; + private readonly OsuClickableContainer clickableContainer; private readonly RoundedButton roundedButton; private readonly ShearedButton shearedButton; @@ -34,6 +39,28 @@ namespace osu.Game.Tests.Visual.UserInterface Direction = FillDirection.Vertical, Children = new Drawable[] { + clickableContainer = new OsuClickableContainer + { + RelativeSizeAxes = Axes.X, + Height = 40, + Enabled = { Value = true }, + Masking = true, + CornerRadius = 20, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Red + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Rounded clickable container" + } + } + }, settingsButton = new SettingsButton { Enabled = { Value = true }, @@ -88,5 +115,15 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("Cursor within a button", () => shearedButton.ScreenSpaceDrawQuad.Contains(InputManager.CurrentState.Mouse.Position)); AddAssert("Button is not hovered", () => !shearedButton.IsHovered); } + + [Test] + public void TestRoundedClickableContainerInput() + { + AddStep("Move cursor to button", () => InputManager.MoveMouseTo(clickableContainer)); + AddAssert("Button is hovered", () => clickableContainer.IsHovered); + AddStep("Move cursor to corner", () => InputManager.MoveMouseTo(clickableContainer.ScreenSpaceDrawQuad.TopLeft + Vector2.One)); + AddAssert("Cursor within a button", () => clickableContainer.ScreenSpaceDrawQuad.Contains(InputManager.CurrentState.Mouse.Position)); + AddAssert("Button is not hovered", () => !clickableContainer.IsHovered); + } } } diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index 945ebbb02d..ecae456e88 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs @@ -17,7 +17,7 @@ namespace osu.Game.Graphics.Containers private readonly Container content = new Container { RelativeSizeAxes = Axes.Both }; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Content.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && Content.ReceivePositionalInputAt(screenSpacePos); protected override Container Content => content; diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 9216b41170..00cd3b13e5 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -61,7 +61,7 @@ namespace osu.Game.Graphics.UserInterface protected override Container Content { get; } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Content.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && Content.ReceivePositionalInputAt(screenSpacePos); protected Box Hover; protected Box Background; From 15bd82add819fb7ee61a2b2c785b8153094493de Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 12 Dec 2022 18:24:49 +0300 Subject: [PATCH 114/384] Fix DrawableUsername being affected --- osu.Game/Overlays/Chat/DrawableUsername.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableUsername.cs b/osu.Game/Overlays/Chat/DrawableUsername.cs index 7026d519a5..6bae498a6c 100644 --- a/osu.Game/Overlays/Chat/DrawableUsername.cs +++ b/osu.Game/Overlays/Chat/DrawableUsername.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Chat public Color4 AccentColour { get; } public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => - Child.ReceivePositionalInputAt(screenSpacePos); + colouredDrawable.ReceivePositionalInputAt(screenSpacePos); public float FontSize { @@ -87,13 +87,13 @@ namespace osu.Game.Overlays.Chat { AccentColour = default_colours[user.Id % default_colours.Length]; - Child = colouredDrawable = drawableText; + Add(colouredDrawable = drawableText); } else { AccentColour = Color4Extensions.FromHex(user.Colour); - Child = new Container + Add(new Container { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -127,7 +127,7 @@ namespace osu.Game.Overlays.Chat } } } - }; + }); } } From 0dd4e0cf940a5650f013bc0845aee59303b9e238 Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Mon, 12 Dec 2022 21:18:01 -0500 Subject: [PATCH 115/384] hide cursor in catch --- ...tchRelaxCursorContainer.cs => CatchCursorContainer.cs} | 4 ++-- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) rename osu.Game.Rulesets.Catch/UI/{CatchRelaxCursorContainer.cs => CatchCursorContainer.cs} (78%) diff --git a/osu.Game.Rulesets.Catch/UI/CatchRelaxCursorContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs similarity index 78% rename from osu.Game.Rulesets.Catch/UI/CatchRelaxCursorContainer.cs rename to osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs index f30b8f0f36..4ae61ef8c7 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchRelaxCursorContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchCursorContainer.cs @@ -6,9 +6,9 @@ using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Catch.UI { - public partial class CatchRelaxCursorContainer : GameplayCursorContainer + public partial class CatchCursorContainer : GameplayCursorContainer { - // Just hide the cursor in relax. + // Just hide the cursor. // The main goal here is to show that we have a cursor so the game never shows the global one. protected override Drawable CreateCursor() => Empty(); } diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 184ff38cc6..2c7b52b1b5 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -52,13 +52,7 @@ namespace osu.Game.Rulesets.Catch.UI this.difficulty = difficulty; } - protected override GameplayCursorContainer CreateCursor() - { - if (Mods != null && Mods.Any(m => m is ModRelax)) - return new CatchRelaxCursorContainer(); - - return base.CreateCursor(); - } + protected override GameplayCursorContainer CreateCursor() => new CatchCursorContainer(); [BackgroundDependencyLoader] private void load() From ad7554cc7d60b3449433abd1df84e07b75d0a6cb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 13 Dec 2022 16:15:14 +0900 Subject: [PATCH 116/384] Allow keeping stream open after encoding scores --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 63094c5548..5452e4b9eb 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -53,9 +53,9 @@ namespace osu.Game.Scoring.Legacy throw new ArgumentException(@"Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score)); } - public void Encode(Stream stream) + public void Encode(Stream stream, bool leaveOpen = false) { - using (SerializationWriter sw = new SerializationWriter(stream)) + using (SerializationWriter sw = new SerializationWriter(stream, leaveOpen)) { sw.Write((byte)(score.ScoreInfo.Ruleset.OnlineID)); sw.Write(LATEST_VERSION); From 775952380fe2927ed901b5248220598bd6fb3be2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Dec 2022 18:57:17 +0900 Subject: [PATCH 117/384] Remove unused using statements --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 2c7b52b1b5..6167ee53f6 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -3,14 +3,12 @@ #nullable disable -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; From cf2719d4c0661bf2f0b34e668069ec8f5bf660ee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Dec 2022 23:56:11 +0900 Subject: [PATCH 118/384] Convert `batchImport` parameter to parameters class to allow further import configuration --- .../Database/BeatmapImporterTests.cs | 2 +- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 4 +-- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 3 +- osu.Game/Beatmaps/BeatmapImporter.cs | 6 ++-- osu.Game/Beatmaps/BeatmapManager.cs | 26 +++++++++++++++-- .../Database/RealmArchiveModelImporter.cs | 29 ++++++++++--------- osu.Game/Scoring/ScoreImporter.cs | 4 +-- osu.Game/Scoring/ScoreManager.cs | 4 +-- osu.Game/Skinning/SkinManager.cs | 10 +++++-- 9 files changed, 57 insertions(+), 31 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index 56964aa8b2..cbaa7bf972 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -1052,7 +1052,7 @@ namespace osu.Game.Tests.Database { string? temp = path ?? TestResources.GetTestBeatmapForImport(virtualTrack); - var importedSet = await importer.Import(new ImportTask(temp), batchImport); + var importedSet = await importer.Import(new ImportTask(temp), new ImportParameters { Batch = batchImport }); Assert.NotNull(importedSet); Debug.Assert(importedSet != null); diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index e1d8e08c5e..585fd516bd 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -226,12 +226,12 @@ namespace osu.Game.Tests.Online this.testBeatmapManager = testBeatmapManager; } - public override Live ImportModel(BeatmapSetInfo item, ArchiveReader archive = null, bool batchImport = false, CancellationToken cancellationToken = default) + public override Live ImportModel(BeatmapSetInfo item, ArchiveReader archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default) { if (!testBeatmapManager.AllowImport.Wait(TimeSpan.FromSeconds(10), cancellationToken)) throw new TimeoutException("Timeout waiting for import to be allowed."); - return (testBeatmapManager.CurrentImport = base.ImportModel(item, archive, batchImport, cancellationToken)); + return (testBeatmapManager.CurrentImport = base.ImportModel(item, archive, parameters, cancellationToken)); } } } diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index 5c20f46787..f90c983627 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -12,6 +12,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Platform; +using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Extensions; using osu.Game.IO; @@ -360,7 +361,7 @@ namespace osu.Game.Tests.Skins.IO private async Task> loadSkinIntoOsu(OsuGameBase osu, ImportTask import, bool batchImport = false) { var skinManager = osu.Dependencies.Get(); - return await skinManager.Import(import, batchImport); + return await skinManager.Import(import, new ImportParameters { Batch = batchImport }); } } } diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index bcb1d7f961..303e85b83a 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -203,10 +203,10 @@ namespace osu.Game.Beatmaps } } - protected override void PostImport(BeatmapSetInfo model, Realm realm, bool batchImport) + protected override void PostImport(BeatmapSetInfo model, Realm realm, ImportParameters parameters) { - base.PostImport(model, realm, batchImport); - ProcessBeatmap?.Invoke((model, batchImport)); + base.PostImport(model, realm, parameters); + ProcessBeatmap?.Invoke((model, parameters.Batch)); } private void validateOnlineIds(BeatmapSetInfo beatmapSet, Realm realm) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 965cc43815..e288239d74 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -460,11 +460,11 @@ namespace osu.Game.Beatmaps public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) => beatmapImporter.Import(notification, tasks); - public Task?> Import(ImportTask task, bool batchImport = false, CancellationToken cancellationToken = default) => - beatmapImporter.Import(task, batchImport, cancellationToken); + public Task?> Import(ImportTask task, ImportParameters parameters = default, CancellationToken cancellationToken = default) => + beatmapImporter.Import(task, parameters, cancellationToken); public Live? Import(BeatmapSetInfo item, ArchiveReader? archive = null, CancellationToken cancellationToken = default) => - beatmapImporter.ImportModel(item, archive, false, cancellationToken); + beatmapImporter.ImportModel(item, archive, default, cancellationToken); public IEnumerable HandledExtensions => beatmapImporter.HandledExtensions; @@ -526,4 +526,24 @@ namespace osu.Game.Beatmaps public override string HumanisedModelName => "beatmap"; } + + public struct ImportParameters + { + /// + /// Whether this import is part of a larger batch. + /// + /// + /// May skip intensive pre-import checks in favour of faster processing. + /// + /// More specifically, imports will be skipped before they begin, given an existing model matches on hash and filenames. Should generally only be used for large batch imports, as it may defy user expectations when updating an existing model. + /// + /// Will also change scheduling behaviour to run at a lower priority. + /// + public bool Batch { get; set; } + + /// + /// Whether this import should use hard links rather than file copy operations if available. + /// + public bool PreferHardLinks { get; set; } + } } diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 0286815569..41e491bce6 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -13,6 +13,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Threading; +using osu.Game.Beatmaps; using osu.Game.Extensions; using osu.Game.IO.Archives; using osu.Game.Models; @@ -115,7 +116,7 @@ namespace osu.Game.Database try { - var model = await Import(task, isBatchImport, notification.CancellationToken).ConfigureAwait(false); + var model = await Import(task, new ImportParameters { Batch = isBatchImport }, notification.CancellationToken).ConfigureAwait(false); lock (imported) { @@ -176,16 +177,16 @@ namespace osu.Game.Database /// Note that this bypasses the UI flow and should only be used for special cases or testing. /// /// The containing data about the to import. - /// Whether this import is part of a larger batch. + /// Parameters to further configure the import process. /// An optional cancellation token. /// The imported model, if successful. - public async Task?> Import(ImportTask task, bool batchImport = false, CancellationToken cancellationToken = default) + public async Task?> Import(ImportTask task, ImportParameters parameters = default, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); Live? import; using (ArchiveReader reader = task.GetReader()) - import = await importFromArchive(reader, batchImport, cancellationToken).ConfigureAwait(false); + import = await importFromArchive(reader, parameters, cancellationToken).ConfigureAwait(false); // We may or may not want to delete the file depending on where it is stored. // e.g. reconstructing/repairing database with items from default storage. @@ -211,9 +212,9 @@ namespace osu.Game.Database /// This method also handled queueing the import task on a relevant import thread pool. /// /// The archive to be imported. - /// Whether this import is part of a larger batch. + /// Parameters to further configure the import process. /// An optional cancellation token. - private async Task?> importFromArchive(ArchiveReader archive, bool batchImport = false, CancellationToken cancellationToken = default) + private async Task?> importFromArchive(ArchiveReader archive, ImportParameters parameters = default, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -236,10 +237,10 @@ namespace osu.Game.Database return null; } - var scheduledImport = Task.Factory.StartNew(() => ImportModel(model, archive, batchImport, cancellationToken), + var scheduledImport = Task.Factory.StartNew(() => ImportModel(model, archive, parameters, cancellationToken), cancellationToken, TaskCreationOptions.HideScheduler, - batchImport ? import_scheduler_batch : import_scheduler); + parameters.Batch ? import_scheduler_batch : import_scheduler); return await scheduledImport.ConfigureAwait(false); } @@ -249,15 +250,15 @@ namespace osu.Game.Database /// /// The model to be imported. /// An optional archive to use for model population. - /// If true, imports will be skipped before they begin, given an existing model matches on hash and filenames. Should generally only be used for large batch imports, as it may defy user expectations when updating an existing model. + /// Parameters to further configure the import process. /// An optional cancellation token. - public virtual Live? ImportModel(TModel item, ArchiveReader? archive = null, bool batchImport = false, CancellationToken cancellationToken = default) => Realm.Run(realm => + public virtual Live? ImportModel(TModel item, ArchiveReader? archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default) => Realm.Run(realm => { cancellationToken.ThrowIfCancellationRequested(); TModel? existing; - if (batchImport && archive != null) + if (parameters.Batch && archive != null) { // this is a fast bail condition to improve large import performance. item.Hash = computeHashFast(archive); @@ -358,7 +359,7 @@ namespace osu.Game.Database // import to store realm.Add(item); - PostImport(item, realm, batchImport); + PostImport(item, realm, parameters); transaction.Commit(); } @@ -493,8 +494,8 @@ namespace osu.Game.Database /// /// The model prepared for import. /// The current realm context. - /// Whether the import was part of a batch. - protected virtual void PostImport(TModel model, Realm realm, bool batchImport) + /// Parameters to further configure the import process. + protected virtual void PostImport(TModel model, Realm realm, ImportParameters parameters) { } diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 5c8e21014c..4656f02897 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -145,9 +145,9 @@ namespace osu.Game.Scoring #pragma warning restore CS0618 } - protected override void PostImport(ScoreInfo model, Realm realm, bool batchImport) + protected override void PostImport(ScoreInfo model, Realm realm, ImportParameters parameters) { - base.PostImport(model, realm, batchImport); + base.PostImport(model, realm, parameters); var userRequest = new GetUserRequest(model.RealmUser.Username); diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index b2944ad219..a57db0eccf 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -179,8 +179,8 @@ namespace osu.Game.Scoring 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); + public Live Import(ScoreInfo item, ArchiveReader archive = null, ImportParameters parameters = default, CancellationToken cancellationToken = default) => + scoreImporter.ImportModel(item, archive, parameters, cancellationToken); /// /// Populates the for a given . diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 4a5277f3bf..40c43563b2 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -22,6 +22,7 @@ using osu.Framework.Testing; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Audio; +using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO; using osu.Game.Overlays.Notifications; @@ -274,11 +275,14 @@ namespace osu.Game.Skinning public IEnumerable HandledExtensions => skinImporter.HandledExtensions; - public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) => skinImporter.Import(notification, tasks); + 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> 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); + public Task> Import(ImportTask task, ImportParameters parameters = default, CancellationToken cancellationToken = default) => + skinImporter.Import(task, parameters, cancellationToken); #endregion From 1d4230993dfceca084d0195318a336622d386a94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Dec 2022 00:56:27 +0900 Subject: [PATCH 119/384] Hook up parameter with `RealmFileStore` to complete the chain --- .../Database/RealmArchiveModelImporter.cs | 2 +- osu.Game/Database/RealmFileStore.cs | 110 +++++++++--------- 2 files changed, 59 insertions(+), 53 deletions(-) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 41e491bce6..1f9debc46f 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -304,7 +304,7 @@ namespace osu.Game.Database foreach (var filenames in getShortenedFilenames(archive)) { using (Stream s = archive.GetStream(filenames.original)) - files.Add(new RealmNamedFileUsage(Files.Add(s, realm, false), filenames.shortened)); + files.Add(new RealmNamedFileUsage(Files.Add(s, realm, false, parameters.PreferHardLinks), filenames.shortened)); } } diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index 99033e2c88..87d347fbfa 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -45,7 +45,8 @@ namespace osu.Game.Database /// The file data stream. /// The realm instance to add to. Should already be in a transaction. /// Whether the should immediately be added to the underlying realm. If false is provided here, the instance must be manually added. - public RealmFile Add(Stream data, Realm realm, bool addToRealm = true) + /// Whether this import should use hard links rather than file copy operations if available. + public RealmFile Add(Stream data, Realm realm, bool addToRealm = true, bool preferHardLinks = false) { string hash = data.ComputeSHA2Hash(); @@ -54,7 +55,7 @@ namespace osu.Game.Database var file = existing ?? new RealmFile { Hash = hash }; if (!checkFileExistsAndMatchesHash(file)) - copyToStore(file, data); + copyToStore(file, data, preferHardLinks); if (addToRealm && !file.IsManaged) realm.Add(file); @@ -62,9 +63,9 @@ namespace osu.Game.Database return file; } - private void copyToStore(RealmFile file, Stream data) + private void copyToStore(RealmFile file, Stream data, bool preferHardLinks) { - if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows && data is FileStream fs) + if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows && data is FileStream fs && preferHardLinks) { // attempt to do a fast hard link rather than copy. if (CreateHardLink(Storage.GetFullPath(file.GetStoragePath()), fs.Name, IntPtr.Zero)) @@ -79,6 +80,58 @@ namespace osu.Game.Database data.Seek(0, SeekOrigin.Begin); } + private bool checkFileExistsAndMatchesHash(RealmFile file) + { + string path = file.GetStoragePath(); + + // we may be re-adding a file to fix missing store entries. + if (!Storage.Exists(path)) + return false; + + // even if the file already exists, check the existing checksum for safety. + using (var stream = Storage.GetStream(path)) + return stream.ComputeSHA2Hash() == file.Hash; + } + + public void Cleanup() + { + Logger.Log(@"Beginning realm file store cleanup"); + + int totalFiles = 0; + int removedFiles = 0; + + // can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal. + realm.Write(r => + { + // TODO: consider using a realm native query to avoid iterating all files (https://github.com/realm/realm-dotnet/issues/2659#issuecomment-927823707) + var files = r.All().ToList(); + + foreach (var file in files) + { + totalFiles++; + + if (file.BacklinksCount > 0) + continue; + + try + { + removedFiles++; + Storage.Delete(file.GetStoragePath()); + r.Remove(file); + } + catch (Exception e) + { + Logger.Error(e, $@"Could not delete databased file {file.Hash}"); + } + } + }); + + Logger.Log($@"Finished realm file store cleanup ({removedFiles} of {totalFiles} deleted)"); + } + + #region Windows hard link support + + // For future use (to detect if a file is a hard link with other references existing on disk). public static int GetFileLinkCount(string filePath) { int result = 0; @@ -132,53 +185,6 @@ namespace osu.Game.Database [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CloseHandle(SafeHandle hObject); - private bool checkFileExistsAndMatchesHash(RealmFile file) - { - string path = file.GetStoragePath(); - - // we may be re-adding a file to fix missing store entries. - if (!Storage.Exists(path)) - return false; - - // even if the file already exists, check the existing checksum for safety. - using (var stream = Storage.GetStream(path)) - return stream.ComputeSHA2Hash() == file.Hash; - } - - public void Cleanup() - { - Logger.Log(@"Beginning realm file store cleanup"); - - int totalFiles = 0; - int removedFiles = 0; - - // can potentially be run asynchronously, although we will need to consider operation order for disk deletion vs realm removal. - realm.Write(r => - { - // TODO: consider using a realm native query to avoid iterating all files (https://github.com/realm/realm-dotnet/issues/2659#issuecomment-927823707) - var files = r.All().ToList(); - - foreach (var file in files) - { - totalFiles++; - - if (file.BacklinksCount > 0) - continue; - - try - { - removedFiles++; - Storage.Delete(file.GetStoragePath()); - r.Remove(file); - } - catch (Exception e) - { - Logger.Error(e, $@"Could not delete databased file {file.Hash}"); - } - } - }); - - Logger.Log($@"Finished realm file store cleanup ({removedFiles} of {totalFiles} deleted)"); - } + #endregion } } From bbf931c746341c1e80239670dbcc7e3ce4ccdf01 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Dec 2022 19:53:12 +0900 Subject: [PATCH 120/384] Move hard link helper functions to their own class --- osu.Game/Database/LegacyImportManager.cs | 2 +- osu.Game/Database/RealmFileStore.cs | 64 +--------------------- osu.Game/IO/HardLinkHelper.cs | 68 ++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 63 deletions(-) create mode 100644 osu.Game/IO/HardLinkHelper.cs diff --git a/osu.Game/Database/LegacyImportManager.cs b/osu.Game/Database/LegacyImportManager.cs index 80c8e9c19e..ec8eac611c 100644 --- a/osu.Game/Database/LegacyImportManager.cs +++ b/osu.Game/Database/LegacyImportManager.cs @@ -72,7 +72,7 @@ namespace osu.Game.Database string testExistingPath = stableStorage.GetFullPath(stableStorage.GetFiles(string.Empty).First()); string testDestinationPath = desktopGameHost.Storage.GetFullPath(test_filename); - return RealmFileStore.CreateHardLink(testDestinationPath, testExistingPath, IntPtr.Zero); + return HardLinkHelper.CreateHardLink(testDestinationPath, testExistingPath, IntPtr.Zero); } public virtual async Task GetImportCount(StableContent content, CancellationToken cancellationToken) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index 87d347fbfa..71ea6cba58 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -4,9 +4,6 @@ using System; using System.IO; using System.Linq; -using System.Runtime.InteropServices; -using System.Runtime.InteropServices.ComTypes; -using Microsoft.Win32.SafeHandles; using osu.Framework; using osu.Framework.Extensions; using osu.Framework.IO.Stores; @@ -14,6 +11,7 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Extensions; +using osu.Game.IO; using osu.Game.Models; using Realms; @@ -68,7 +66,7 @@ namespace osu.Game.Database if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows && data is FileStream fs && preferHardLinks) { // attempt to do a fast hard link rather than copy. - if (CreateHardLink(Storage.GetFullPath(file.GetStoragePath()), fs.Name, IntPtr.Zero)) + if (HardLinkHelper.CreateHardLink(Storage.GetFullPath(file.GetStoragePath()), fs.Name, IntPtr.Zero)) return; } @@ -128,63 +126,5 @@ namespace osu.Game.Database Logger.Log($@"Finished realm file store cleanup ({removedFiles} of {totalFiles} deleted)"); } - - #region Windows hard link support - - // For future use (to detect if a file is a hard link with other references existing on disk). - public static int GetFileLinkCount(string filePath) - { - int result = 0; - SafeFileHandle handle = CreateFile(filePath, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Archive, IntPtr.Zero); - - ByHandleFileInformation fileInfo; - - if (GetFileInformationByHandle(handle, out fileInfo)) - result = (int)fileInfo.NumberOfLinks; - CloseHandle(handle); - - return result; - } - - [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)] - public static extern bool CreateHardLink( - string lpFileName, - string lpExistingFileName, - IntPtr lpSecurityAttributes - ); - - [StructLayout(LayoutKind.Sequential)] - private struct ByHandleFileInformation - { - public readonly uint FileAttributes; - public readonly FILETIME CreationTime; - public readonly FILETIME LastAccessTime; - public readonly FILETIME LastWriteTime; - public readonly uint VolumeSerialNumber; - public readonly uint FileSizeHigh; - public readonly uint FileSizeLow; - public readonly uint NumberOfLinks; - public readonly uint FileIndexHigh; - public readonly uint FileIndexLow; - } - - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] - private static extern SafeFileHandle CreateFile( - string lpFileName, - [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess, - [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode, - IntPtr lpSecurityAttributes, - [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition, - [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes, - IntPtr hTemplateFile); - - [DllImport("kernel32.dll", SetLastError = true)] - private static extern bool GetFileInformationByHandle(SafeFileHandle handle, out ByHandleFileInformation lpFileInformation); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool CloseHandle(SafeHandle hObject); - - #endregion } } diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs new file mode 100644 index 0000000000..4b6f871719 --- /dev/null +++ b/osu.Game/IO/HardLinkHelper.cs @@ -0,0 +1,68 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; +using Microsoft.Win32.SafeHandles; + +namespace osu.Game.IO +{ + internal static class HardLinkHelper + { + // For future use (to detect if a file is a hard link with other references existing on disk). + public static int GetFileLinkCount(string filePath) + { + int result = 0; + SafeFileHandle handle = CreateFile(filePath, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Archive, IntPtr.Zero); + + ByHandleFileInformation fileInfo; + + if (GetFileInformationByHandle(handle, out fileInfo)) + result = (int)fileInfo.NumberOfLinks; + CloseHandle(handle); + + return result; + } + + [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)] + public static extern bool CreateHardLink( + string lpFileName, + string lpExistingFileName, + IntPtr lpSecurityAttributes + ); + + [StructLayout(LayoutKind.Sequential)] + private struct ByHandleFileInformation + { + public readonly uint FileAttributes; + public readonly FILETIME CreationTime; + public readonly FILETIME LastAccessTime; + public readonly FILETIME LastWriteTime; + public readonly uint VolumeSerialNumber; + public readonly uint FileSizeHigh; + public readonly uint FileSizeLow; + public readonly uint NumberOfLinks; + public readonly uint FileIndexHigh; + public readonly uint FileIndexLow; + } + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] + private static extern SafeFileHandle CreateFile( + string lpFileName, + [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess, + [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode, + IntPtr lpSecurityAttributes, + [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition, + [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes, + IntPtr hTemplateFile); + + [DllImport("kernel32.dll", SetLastError = true)] + private static extern bool GetFileInformationByHandle(SafeFileHandle handle, out ByHandleFileInformation lpFileInformation); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool CloseHandle(SafeHandle hObject); + } +} From 6bb612ce697a8d7bd09bb4dee2ff1572f542ef91 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Dec 2022 20:02:57 +0900 Subject: [PATCH 121/384] Move hard link availability check to helper class --- osu.Game/Database/LegacyImportManager.cs | 14 +---- osu.Game/IO/HardLinkHelper.cs | 80 ++++++++++++++++++------ 2 files changed, 63 insertions(+), 31 deletions(-) diff --git a/osu.Game/Database/LegacyImportManager.cs b/osu.Game/Database/LegacyImportManager.cs index ec8eac611c..3f1ddb1eda 100644 --- a/osu.Game/Database/LegacyImportManager.cs +++ b/osu.Game/Database/LegacyImportManager.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using osu.Framework; @@ -57,22 +56,15 @@ namespace osu.Game.Database public bool CheckHardLinkAvailability() { - if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) - return false; - var stableStorage = GetCurrentStableStorage(); if (stableStorage == null || gameHost is not DesktopGameHost desktopGameHost) return false; - const string test_filename = "_hard_link_test"; + string testExistingPath = stableStorage.GetFullPath(string.Empty); + string testDestinationPath = desktopGameHost.Storage.GetFullPath(string.Empty); - desktopGameHost.Storage.Delete(test_filename); - - string testExistingPath = stableStorage.GetFullPath(stableStorage.GetFiles(string.Empty).First()); - string testDestinationPath = desktopGameHost.Storage.GetFullPath(test_filename); - - return HardLinkHelper.CreateHardLink(testDestinationPath, testExistingPath, IntPtr.Zero); + return HardLinkHelper.CheckAvailability(testDestinationPath, testExistingPath); } public virtual async Task GetImportCount(StableContent content, CancellationToken cancellationToken) diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index 4b6f871719..7e1f92c0ad 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -6,11 +6,55 @@ using System.IO; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using Microsoft.Win32.SafeHandles; +using osu.Framework; namespace osu.Game.IO { internal static class HardLinkHelper { + public static bool CheckAvailability(string testDestinationPath, string testSourcePath) + { + // We can support other operating systems quite easily in the future. + // Let's handle the most common one for now, though. + if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) + return false; + + const string test_filename = "_hard_link_test"; + + testDestinationPath = Path.Combine(testDestinationPath, test_filename); + testSourcePath = Path.Combine(testSourcePath, test_filename); + + cleanupFiles(); + + try + { + File.WriteAllText(testSourcePath, string.Empty); + + // Test availability by creating an arbitrary hard link between the source and destination paths. + return CreateHardLink(testDestinationPath, testSourcePath, IntPtr.Zero); + } + catch + { + return false; + } + finally + { + cleanupFiles(); + } + + void cleanupFiles() + { + try + { + File.Delete(testDestinationPath); + File.Delete(testSourcePath); + } + catch + { + } + } + } + // For future use (to detect if a file is a hard link with other references existing on disk). public static int GetFileLinkCount(string filePath) { @@ -27,26 +71,7 @@ namespace osu.Game.IO } [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)] - public static extern bool CreateHardLink( - string lpFileName, - string lpExistingFileName, - IntPtr lpSecurityAttributes - ); - - [StructLayout(LayoutKind.Sequential)] - private struct ByHandleFileInformation - { - public readonly uint FileAttributes; - public readonly FILETIME CreationTime; - public readonly FILETIME LastAccessTime; - public readonly FILETIME LastWriteTime; - public readonly uint VolumeSerialNumber; - public readonly uint FileSizeHigh; - public readonly uint FileSizeLow; - public readonly uint NumberOfLinks; - public readonly uint FileIndexHigh; - public readonly uint FileIndexLow; - } + public static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] private static extern SafeFileHandle CreateFile( @@ -64,5 +89,20 @@ namespace osu.Game.IO [DllImport("kernel32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CloseHandle(SafeHandle hObject); + + [StructLayout(LayoutKind.Sequential)] + private struct ByHandleFileInformation + { + public readonly uint FileAttributes; + public readonly FILETIME CreationTime; + public readonly FILETIME LastAccessTime; + public readonly FILETIME LastWriteTime; + public readonly uint VolumeSerialNumber; + public readonly uint FileSizeHigh; + public readonly uint FileSizeLow; + public readonly uint NumberOfLinks; + public readonly uint FileIndexHigh; + public readonly uint FileIndexLow; + } } } From cb16d62700f3c107588c96fef4537501a9de4cc7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Dec 2022 21:03:25 +0900 Subject: [PATCH 122/384] Hook up `ImportParameter` flow with `IModelImporter` caller methods --- osu.Game.Tests/Database/BeatmapImporterTests.cs | 2 +- osu.Game/Beatmaps/BeatmapImporter.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++-- osu.Game/Database/ICanAcceptFiles.cs | 4 +++- osu.Game/Database/IModelImporter.cs | 4 +++- osu.Game/Database/LegacyModelImporter.cs | 8 +++++++- osu.Game/Database/ModelDownloader.cs | 2 +- osu.Game/Database/RealmArchiveModelImporter.cs | 10 +++++----- osu.Game/OsuGame.cs | 4 ++-- osu.Game/OsuGameBase_Importing.cs | 5 +++-- .../Overlays/FirstRunSetup/ScreenImportFromStable.cs | 3 ++- osu.Game/Scoring/ScoreManager.cs | 4 ++-- osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs | 3 ++- osu.Game/Skinning/Editor/SkinEditor.cs | 3 ++- osu.Game/Skinning/SkinManager.cs | 6 +++--- 15 files changed, 39 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index cbaa7bf972..446eb72b04 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -564,7 +564,7 @@ namespace osu.Game.Tests.Database var imported = await importer.Import( progressNotification, - new ImportTask(zipStream, string.Empty) + new[] { new ImportTask(zipStream, string.Empty) } ); realm.Run(r => r.Refresh()); diff --git a/osu.Game/Beatmaps/BeatmapImporter.cs b/osu.Game/Beatmaps/BeatmapImporter.cs index 303e85b83a..8a6315fc65 100644 --- a/osu.Game/Beatmaps/BeatmapImporter.cs +++ b/osu.Game/Beatmaps/BeatmapImporter.cs @@ -44,7 +44,7 @@ namespace osu.Game.Beatmaps public override async Task?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original) { - var imported = await Import(notification, importTask); + var imported = await Import(notification, new[] { importTask }); if (!imported.Any()) return null; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index e288239d74..f0fd8bd2e0 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -456,9 +456,9 @@ namespace osu.Game.Beatmaps public Task Import(params string[] paths) => beatmapImporter.Import(paths); - public Task Import(params ImportTask[] tasks) => beatmapImporter.Import(tasks); + public Task Import(ImportTask[] tasks, ImportParameters parameters = default) => beatmapImporter.Import(tasks, parameters); - public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) => beatmapImporter.Import(notification, tasks); + public Task>> Import(ProgressNotification notification, ImportTask[] tasks, ImportParameters parameters = default) => beatmapImporter.Import(notification, tasks, parameters); public Task?> Import(ImportTask task, ImportParameters parameters = default, CancellationToken cancellationToken = default) => beatmapImporter.Import(task, parameters, cancellationToken); diff --git a/osu.Game/Database/ICanAcceptFiles.cs b/osu.Game/Database/ICanAcceptFiles.cs index 3ce343249b..4fdedd4688 100644 --- a/osu.Game/Database/ICanAcceptFiles.cs +++ b/osu.Game/Database/ICanAcceptFiles.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Threading.Tasks; +using osu.Game.Beatmaps; namespace osu.Game.Database { @@ -31,7 +32,8 @@ namespace osu.Game.Database /// This will post notifications tracking progress. /// /// The import tasks from which the files should be imported. - Task Import(params ImportTask[] tasks); + /// Parameters to further configure the import process. + Task Import(ImportTask[] tasks, ImportParameters parameters = default); /// /// An array of accepted file extensions (in the standard format of ".abc"). diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index 4085f122d0..5896715a8f 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using osu.Game.Beatmaps; using osu.Game.Overlays.Notifications; namespace osu.Game.Database @@ -20,8 +21,9 @@ namespace osu.Game.Database /// /// The notification to update. /// The import tasks. + /// Parameters to further configure the import process. /// The imported models. - Task>> Import(ProgressNotification notification, params ImportTask[] tasks); + Task>> Import(ProgressNotification notification, ImportTask[] tasks, ImportParameters parameters = default); /// /// Process a single import as an update for an existing model. diff --git a/osu.Game/Database/LegacyModelImporter.cs b/osu.Game/Database/LegacyModelImporter.cs index df354a856e..176c660e61 100644 --- a/osu.Game/Database/LegacyModelImporter.cs +++ b/osu.Game/Database/LegacyModelImporter.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Game.Beatmaps; using osu.Game.IO; namespace osu.Game.Database @@ -57,7 +58,12 @@ namespace osu.Game.Database return Task.CompletedTask; } - return Task.Run(async () => await Importer.Import(GetStableImportPaths(storage).ToArray()).ConfigureAwait(false)); + return Task.Run(async () => + { + var tasks = GetStableImportPaths(storage).Select(p => new ImportTask(p)).ToArray(); + + await Importer.Import(tasks, new ImportParameters { Batch = true, PreferHardLinks = true }).ConfigureAwait(false); + }); } /// diff --git a/osu.Game/Database/ModelDownloader.cs b/osu.Game/Database/ModelDownloader.cs index 3e2d034937..2f082306d0 100644 --- a/osu.Game/Database/ModelDownloader.cs +++ b/osu.Game/Database/ModelDownloader.cs @@ -73,7 +73,7 @@ namespace osu.Game.Database if (originalModel != null) importSuccessful = (await importer.ImportAsUpdate(notification, new ImportTask(filename), originalModel)) != null; else - importSuccessful = (await importer.Import(notification, new ImportTask(filename))).Any(); + importSuccessful = (await importer.Import(notification, new[] { new ImportTask(filename) })).Any(); // for now a failed import will be marked as a failed download for simplicity. if (!importSuccessful) diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index 1f9debc46f..bf51e8d49e 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -82,16 +82,16 @@ namespace osu.Game.Database public Task Import(params string[] paths) => Import(paths.Select(p => new ImportTask(p)).ToArray()); - public Task Import(params ImportTask[] tasks) + public Task Import(ImportTask[] tasks, ImportParameters parameters = default) { var notification = new ProgressNotification { State = ProgressNotificationState.Active }; PostNotification?.Invoke(notification); - return Import(notification, tasks); + return Import(notification, tasks, parameters); } - public async Task>> Import(ProgressNotification notification, params ImportTask[] tasks) + public async Task>> Import(ProgressNotification notification, ImportTask[] tasks, ImportParameters parameters = default) { if (tasks.Length == 0) { @@ -107,7 +107,7 @@ namespace osu.Game.Database var imported = new List>(); - bool isBatchImport = tasks.Length >= minimum_items_considered_batch_import; + parameters.Batch |= tasks.Length >= minimum_items_considered_batch_import; await Task.WhenAll(tasks.Select(async task => { @@ -116,7 +116,7 @@ namespace osu.Game.Database try { - var model = await Import(task, new ImportParameters { Batch = isBatchImport }, notification.CancellationToken).ConfigureAwait(false); + var model = await Import(task, parameters, notification.CancellationToken).ConfigureAwait(false); lock (imported) { diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5565fa7ef3..8fb6806c27 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -616,14 +616,14 @@ namespace osu.Game }, validScreens: validScreens); } - public override Task Import(params ImportTask[] imports) + public override Task Import(ImportTask[] imports, ImportParameters parameters = default) { // encapsulate task as we don't want to begin the import process until in a ready state. // ReSharper disable once AsyncVoidLambda // TODO: This is bad because `new Task` doesn't have a Func override. // Only used for android imports and a bit of a mess. Probably needs rethinking overall. - var importTask = new Task(async () => await base.Import(imports).ConfigureAwait(false)); + var importTask = new Task(async () => await base.Import(imports, parameters).ConfigureAwait(false)); waitForReady(() => this, _ => importTask.Start()); diff --git a/osu.Game/OsuGameBase_Importing.cs b/osu.Game/OsuGameBase_Importing.cs index e34e48f21d..565b0f37ae 100644 --- a/osu.Game/OsuGameBase_Importing.cs +++ b/osu.Game/OsuGameBase_Importing.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using osu.Game.Beatmaps; using osu.Game.Database; namespace osu.Game @@ -44,13 +45,13 @@ namespace osu.Game } } - public virtual async Task Import(params ImportTask[] tasks) + public virtual async Task Import(ImportTask[] tasks, ImportParameters parameters = default) { var tasksPerExtension = tasks.GroupBy(t => Path.GetExtension(t.Path).ToLowerInvariant()); await Task.WhenAll(tasksPerExtension.Select(taskGroup => { var importer = fileImporters.FirstOrDefault(i => i.HandledExtensions.Contains(taskGroup.Key)); - return importer?.Import(taskGroup.ToArray()) ?? Task.CompletedTask; + return importer?.Import(taskGroup.ToArray(), parameters) ?? Task.CompletedTask; })).ConfigureAwait(false); } diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 4af80c6fbb..bf8092fbfa 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -17,6 +17,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -269,7 +270,7 @@ namespace osu.Game.Overlays.FirstRunSetup return Task.CompletedTask; } - Task ICanAcceptFiles.Import(params ImportTask[] tasks) => throw new NotImplementedException(); + Task ICanAcceptFiles.Import(ImportTask[] tasks, ImportParameters parameters) => throw new NotImplementedException(); protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index a57db0eccf..5ec96a951b 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -169,13 +169,13 @@ namespace osu.Game.Scoring public Task Import(params string[] paths) => scoreImporter.Import(paths); - public Task Import(params ImportTask[] tasks) => scoreImporter.Import(tasks); + public Task Import(ImportTask[] imports, ImportParameters parameters = default) => scoreImporter.Import(imports, parameters); public override bool IsAvailableLocally(ScoreInfo model) => Realm.Run(realm => realm.All().Any(s => s.OnlineID == model.OnlineID)); public IEnumerable HandledExtensions => scoreImporter.HandledExtensions; - public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) => scoreImporter.Import(notification, tasks); + public Task>> Import(ProgressNotification notification, ImportTask[] tasks, ImportParameters parameters = default) => scoreImporter.Import(notification, tasks); public Task> ImportAsUpdate(ProgressNotification notification, ImportTask task, ScoreInfo original) => scoreImporter.ImportAsUpdate(notification, task, original); diff --git a/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs b/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs index 57d28824b1..b7ca84776b 100644 --- a/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs +++ b/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs @@ -16,6 +16,7 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Framework.Platform; +using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics.UserInterfaceV2; using osuTK; @@ -91,7 +92,7 @@ namespace osu.Game.Screens.Edit.Setup return Task.CompletedTask; } - Task ICanAcceptFiles.Import(params ImportTask[] tasks) => throw new NotImplementedException(); + Task ICanAcceptFiles.Import(ImportTask[] tasks, ImportParameters parameters) => throw new NotImplementedException(); protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 1dd9c93845..5531e08d3c 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -18,6 +18,7 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -411,7 +412,7 @@ namespace osu.Game.Skinning.Editor return Task.CompletedTask; } - public Task Import(params ImportTask[] tasks) => throw new NotImplementedException(); + Task ICanAcceptFiles.Import(ImportTask[] tasks, ImportParameters parameters) => throw new NotImplementedException(); public IEnumerable HandledExtensions => new[] { ".jpg", ".jpeg", ".png" }; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 40c43563b2..467a88c864 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -271,12 +271,12 @@ namespace osu.Game.Skinning public Task Import(params string[] paths) => skinImporter.Import(paths); - public Task Import(params ImportTask[] tasks) => skinImporter.Import(tasks); + public Task Import(ImportTask[] imports, ImportParameters parameters = default) => skinImporter.Import(imports, parameters); public IEnumerable HandledExtensions => skinImporter.HandledExtensions; - public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) => - skinImporter.Import(notification, tasks); + public Task>> Import(ProgressNotification notification, ImportTask[] tasks, ImportParameters parameters = default) => + skinImporter.Import(notification, tasks, parameters); public Task> ImportAsUpdate(ProgressNotification notification, ImportTask task, SkinInfo original) => skinImporter.ImportAsUpdate(notification, task, original); From b8904fe7474e17bf48a7dbbbd60ae338dd77529c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Dec 2022 21:41:26 +0900 Subject: [PATCH 123/384] Move `ImportParameters` to better home --- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 1 - osu.Game/Beatmaps/BeatmapManager.cs | 20 --------------- osu.Game/Database/ICanAcceptFiles.cs | 1 - osu.Game/Database/IModelImporter.cs | 1 - osu.Game/Database/ImportParameters.cs | 25 +++++++++++++++++++ osu.Game/Database/LegacyModelImporter.cs | 1 - .../Database/RealmArchiveModelImporter.cs | 1 - osu.Game/OsuGameBase_Importing.cs | 1 - .../FirstRunSetup/ScreenImportFromStable.cs | 1 - .../Screens/Edit/Setup/LabelledFileChooser.cs | 1 - osu.Game/Skinning/Editor/SkinEditor.cs | 1 - osu.Game/Skinning/SkinManager.cs | 1 - 12 files changed, 25 insertions(+), 30 deletions(-) create mode 100644 osu.Game/Database/ImportParameters.cs diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index f90c983627..0bd40e9962 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -12,7 +12,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Platform; -using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Extensions; using osu.Game.IO; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f0fd8bd2e0..f0533f27be 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -526,24 +526,4 @@ namespace osu.Game.Beatmaps public override string HumanisedModelName => "beatmap"; } - - public struct ImportParameters - { - /// - /// Whether this import is part of a larger batch. - /// - /// - /// May skip intensive pre-import checks in favour of faster processing. - /// - /// More specifically, imports will be skipped before they begin, given an existing model matches on hash and filenames. Should generally only be used for large batch imports, as it may defy user expectations when updating an existing model. - /// - /// Will also change scheduling behaviour to run at a lower priority. - /// - public bool Batch { get; set; } - - /// - /// Whether this import should use hard links rather than file copy operations if available. - /// - public bool PreferHardLinks { get; set; } - } } diff --git a/osu.Game/Database/ICanAcceptFiles.cs b/osu.Game/Database/ICanAcceptFiles.cs index 4fdedd4688..da970a29d4 100644 --- a/osu.Game/Database/ICanAcceptFiles.cs +++ b/osu.Game/Database/ICanAcceptFiles.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Threading.Tasks; -using osu.Game.Beatmaps; namespace osu.Game.Database { diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index 5896715a8f..dcbbad0d35 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using osu.Game.Beatmaps; using osu.Game.Overlays.Notifications; namespace osu.Game.Database diff --git a/osu.Game/Database/ImportParameters.cs b/osu.Game/Database/ImportParameters.cs new file mode 100644 index 0000000000..83ca0ac694 --- /dev/null +++ b/osu.Game/Database/ImportParameters.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Database +{ + public struct ImportParameters + { + /// + /// Whether this import is part of a larger batch. + /// + /// + /// May skip intensive pre-import checks in favour of faster processing. + /// + /// More specifically, imports will be skipped before they begin, given an existing model matches on hash and filenames. Should generally only be used for large batch imports, as it may defy user expectations when updating an existing model. + /// + /// Will also change scheduling behaviour to run at a lower priority. + /// + public bool Batch { get; set; } + + /// + /// Whether this import should use hard links rather than file copy operations if available. + /// + public bool PreferHardLinks { get; set; } + } +} diff --git a/osu.Game/Database/LegacyModelImporter.cs b/osu.Game/Database/LegacyModelImporter.cs index 176c660e61..29386a1103 100644 --- a/osu.Game/Database/LegacyModelImporter.cs +++ b/osu.Game/Database/LegacyModelImporter.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Threading.Tasks; using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Game.Beatmaps; using osu.Game.IO; namespace osu.Game.Database diff --git a/osu.Game/Database/RealmArchiveModelImporter.cs b/osu.Game/Database/RealmArchiveModelImporter.cs index bf51e8d49e..4e8b27e0b4 100644 --- a/osu.Game/Database/RealmArchiveModelImporter.cs +++ b/osu.Game/Database/RealmArchiveModelImporter.cs @@ -13,7 +13,6 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Framework.Threading; -using osu.Game.Beatmaps; using osu.Game.Extensions; using osu.Game.IO.Archives; using osu.Game.Models; diff --git a/osu.Game/OsuGameBase_Importing.cs b/osu.Game/OsuGameBase_Importing.cs index 565b0f37ae..cf65460bab 100644 --- a/osu.Game/OsuGameBase_Importing.cs +++ b/osu.Game/OsuGameBase_Importing.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; -using osu.Game.Beatmaps; using osu.Game.Database; namespace osu.Game diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index bf8092fbfa..8ce92dab12 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -17,7 +17,6 @@ using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Framework.Logging; using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs b/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs index b7ca84776b..d14357e875 100644 --- a/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs +++ b/osu.Game/Screens/Edit/Setup/LabelledFileChooser.cs @@ -16,7 +16,6 @@ using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Framework.Localisation; using osu.Framework.Platform; -using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics.UserInterfaceV2; using osuTK; diff --git a/osu.Game/Skinning/Editor/SkinEditor.cs b/osu.Game/Skinning/Editor/SkinEditor.cs index 5531e08d3c..0ed4e5afd2 100644 --- a/osu.Game/Skinning/Editor/SkinEditor.cs +++ b/osu.Game/Skinning/Editor/SkinEditor.cs @@ -18,7 +18,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Testing; -using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 467a88c864..2ad62dbb61 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -22,7 +22,6 @@ using osu.Framework.Testing; using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Audio; -using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO; using osu.Game.Overlays.Notifications; From edc78205d5312e9278f2a22ef156fd34af492595 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Dec 2022 22:06:03 +0900 Subject: [PATCH 124/384] Add comments about why both positional input checks are required in `OsuClickableContainer` --- osu.Game/Graphics/Containers/OsuClickableContainer.cs | 6 +++++- osu.Game/Graphics/UserInterface/OsuButton.cs | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index ecae456e88..6ec393df4b 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs @@ -17,7 +17,11 @@ namespace osu.Game.Graphics.Containers private readonly Container content = new Container { RelativeSizeAxes = Axes.Both }; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && Content.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + // base call is checked for cases when `OsuClickableContainer` has masking applied to it directly (ie. externally in object initialisation). + base.ReceivePositionalInputAt(screenSpacePos) + // Implementations often apply masking / edge rounding at a content level, so it's imperative to check that as well. + && Content.ReceivePositionalInputAt(screenSpacePos); protected override Container Content => content; diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 00cd3b13e5..3fccf51cc2 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -61,7 +61,11 @@ namespace osu.Game.Graphics.UserInterface protected override Container Content { get; } - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && Content.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + // base call is checked for cases when `OsuClickableContainer` has masking applied to it directly (ie. externally in object initialisation). + base.ReceivePositionalInputAt(screenSpacePos) + // Implementations often apply masking / edge rounding at a content level, so it's imperative to check that as well. + && Content.ReceivePositionalInputAt(screenSpacePos); protected Box Hover; protected Box Background; From efe057176e26b82ec320cce03115a7e121fb3d0d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Dec 2022 22:13:48 +0900 Subject: [PATCH 125/384] Make `OsuButton` `abstract` --- .../UserInterface/TestSceneOsuButton.cs | 47 ------------------- osu.Game/Graphics/UserInterface/OsuButton.cs | 4 +- 2 files changed, 2 insertions(+), 49 deletions(-) delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs deleted file mode 100644 index 41e5d47093..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuButton.cs +++ /dev/null @@ -1,47 +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 NUnit.Framework; -using osu.Framework.Graphics; -using osu.Game.Graphics.UserInterface; -using osuTK; - -namespace osu.Game.Tests.Visual.UserInterface -{ - public partial class TestSceneOsuButton : OsuTestScene - { - [Test] - public void TestToggleEnabled() - { - OsuButton button = null; - - AddStep("add button", () => Child = button = new OsuButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(200), - Text = "Button" - }); - - AddToggleStep("toggle enabled", toggle => - { - for (int i = 0; i < 6; i++) - button.Action = toggle ? () => { } : null; - }); - } - - [Test] - public void TestInitiallyDisabled() - { - AddStep("add button", () => Child = new OsuButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(200), - Text = "Button" - }); - } - } -} diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 3fccf51cc2..805dfcaa95 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -19,7 +19,7 @@ namespace osu.Game.Graphics.UserInterface /// /// A button with added default sound effects. /// - public partial class OsuButton : Button + public abstract partial class OsuButton : Button { public LocalisableString Text { @@ -73,7 +73,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box flashLayer; - public OsuButton(HoverSampleSet? hoverSounds = HoverSampleSet.Button) + protected OsuButton(HoverSampleSet? hoverSounds = HoverSampleSet.Button) { Height = 40; From bf56f5f8c08583927a35cc02da3881dba40a196a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Dec 2022 18:27:14 +0900 Subject: [PATCH 126/384] Show error message on attempting to open a URL with an unsupported protocol --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 10 ++++++++++ osu.Game/OsuGame.cs | 11 +++++++++++ 2 files changed, 21 insertions(+) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index ebfa9bd8b7..3c35dc311f 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -26,6 +26,16 @@ namespace osu.Game.Tests.Chat MessageFormatter.WebsiteRootUrl = originalWebsiteRootUrl; } + [Test] + public void TestUnsupportedProtocolLink() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a gopher://really-old-protocol we don't support." }); + + Assert.AreEqual(result.Content, result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("gopher://really-old-protocol", result.Links[0].Url); + } + [Test] public void TestBareLink() { diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5565fa7ef3..bd33ab6545 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -16,6 +16,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Configuration; +using osu.Framework.Extensions; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Graphics; @@ -406,6 +407,16 @@ namespace osu.Game if (url.StartsWith('/')) url = $"{API.APIEndpointUrl}{url}"; + if (!url.CheckIsValidUrl()) + { + Notifications.Post(new SimpleErrorNotification + { + Text = $"The URL {url} has an unsupported or dangerous protocol and will not be opened.", + }); + + return; + } + externalLinkOpener.OpenUrlExternally(url, bypassExternalUrlWarning); }); From 3d65984a893533166df3a93e3396625d79b84abd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 13 Dec 2022 22:34:01 +0900 Subject: [PATCH 127/384] 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 147d236d98..c2c74e2c0e 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 8639ce6361..31cd851c03 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 184e3303c8..b2aa3caa14 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -62,7 +62,7 @@ - + @@ -82,7 +82,7 @@ - + From f4316a9827d89fcf62120419f46b290c9a2a4e6c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Dec 2022 11:30:01 +0900 Subject: [PATCH 128/384] Fix incorrect grammar in hard link explanation text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 8ce92dab12..04aa976ff1 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -129,7 +129,7 @@ namespace osu.Game.Overlays.FirstRunSetup copyInformation.Text = "Data migration will use \"hard links\". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation."; } else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) - copyInformation.Text = "Lightweight linking of files are not supported on your operating system yet, so a copy of all files will be made during import."; + copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; else { copyInformation.Text = From a3c3112f897a0d9c327c4aaf6fa96c00bff3abe4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Dec 2022 11:34:06 +0900 Subject: [PATCH 129/384] Add `SetLastError` hint to `CreateHardLink` pinvoke method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/IO/HardLinkHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index 7e1f92c0ad..1393bf26fd 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -70,7 +70,7 @@ namespace osu.Game.IO return result; } - [DllImport("Kernel32.dll", CharSet = CharSet.Unicode)] + [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)] From fb85eaee950ed490e9238fa261766eeef58802a1 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 14 Dec 2022 18:30:31 +0900 Subject: [PATCH 130/384] Add description for lazer score version 30000001 --- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 5452e4b9eb..a78ae24da2 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -25,6 +25,11 @@ namespace osu.Game.Scoring.Legacy /// Database version in stable-compatible YYYYMMDD format. /// Should be incremented if any changes are made to the format/usage. /// + /// + /// + /// 30000001: Appends to the end of scores. + /// + /// public const int LATEST_VERSION = 30000001; /// From ee945c9b58a8463598685a53fb4ba38cacf71efe Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Wed, 14 Dec 2022 10:34:21 -0500 Subject: [PATCH 131/384] disable mouse input to touchscreen controls --- .../UI/CatchTouchInputMapper.cs | 24 ------------------- osu.Game.Rulesets.Mania/UI/Column.cs | 12 ---------- .../UI/DrumTouchInputArea.cs | 18 -------------- 3 files changed, 54 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index d23913136d..55a90d62e5 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -106,41 +106,17 @@ namespace osu.Game.Rulesets.Catch.UI return false; } - protected override bool OnMouseDown(MouseDownEvent e) - { - return updateAction(e.Button, getTouchCatchActionFromInput(e.ScreenSpaceMousePosition)); - } - protected override bool OnTouchDown(TouchDownEvent e) { return updateAction(e.Touch.Source, getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position)); } - protected override bool OnMouseMove(MouseMoveEvent e) - { - Show(); - - TouchCatchAction? action = getTouchCatchActionFromInput(e.ScreenSpaceMousePosition); - - // multiple mouse buttons may be pressed and handling the same action. - foreach (MouseButton button in e.PressedButtons) - updateAction(button, action); - - return false; - } - protected override void OnTouchMove(TouchMoveEvent e) { updateAction(e.Touch.Source, getTouchCatchActionFromInput(e.ScreenSpaceTouch.Position)); base.OnTouchMove(e); } - protected override void OnMouseUp(MouseUpEvent e) - { - updateAction(e.Button, null); - base.OnMouseUp(e); - } - protected override void OnTouchUp(TouchUpEvent e) { updateAction(e.Touch.Source, null); diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 6a31fb3fda..f355c43ad0 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -206,18 +206,6 @@ namespace osu.Game.Rulesets.Mania.UI keyBindingContainer = maniaInputManager?.KeyBindingContainer; } - protected override bool OnMouseDown(MouseDownEvent e) - { - keyBindingContainer?.TriggerPressed(column.Action.Value); - return base.OnMouseDown(e); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - keyBindingContainer?.TriggerReleased(column.Action.Value); - base.OnMouseUp(e); - } - protected override bool OnTouchDown(TouchDownEvent e) { keyBindingContainer?.TriggerPressed(column.Action.Value); diff --git a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs index ab8c0a484e..0232c10d65 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrumTouchInputArea.cs @@ -107,24 +107,6 @@ namespace osu.Game.Rulesets.Taiko.UI return false; } - 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); - } - protected override bool OnTouchDown(TouchDownEvent e) { handleDown(e.Touch.Source, e.ScreenSpaceTouchDownPosition); From f5cc2f6ed5e3597d032c638bbf498f7a5d818f77 Mon Sep 17 00:00:00 2001 From: Dylan Nantz Date: Wed, 14 Dec 2022 11:19:16 -0500 Subject: [PATCH 132/384] remove unnecessary using --- osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index 55a90d62e5..10e43cf74a 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -11,7 +11,6 @@ using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Graphics; using osuTK; -using osuTK.Input; namespace osu.Game.Rulesets.Catch.UI { From 6bf1477939980b597d07784744dfa65cb85e0c65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Dec 2022 14:17:28 +0900 Subject: [PATCH 133/384] Fix some hard links not being created due to missing directory structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Database/RealmFileStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index 71ea6cba58..04b503b808 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -66,7 +66,7 @@ namespace osu.Game.Database if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows && data is FileStream fs && preferHardLinks) { // attempt to do a fast hard link rather than copy. - if (HardLinkHelper.CreateHardLink(Storage.GetFullPath(file.GetStoragePath()), fs.Name, IntPtr.Zero)) + if (HardLinkHelper.CreateHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name, IntPtr.Zero)) return; } From a7f3e139461d1c63a4aa4fb98a01b859e3ab6cb9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Dec 2022 16:56:09 +0900 Subject: [PATCH 134/384] Fix `MultipleSelectionFilter` not correctly handling initial selection --- .../BeatmapSearchMultipleSelectionFilterRow.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index 79a794a9ad..5d1ccbd58b 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; @@ -18,6 +19,7 @@ using osuTK; namespace osu.Game.Overlays.BeatmapListing { public partial class BeatmapSearchMultipleSelectionFilterRow : BeatmapSearchFilterRow> + where T : Enum { public new readonly BindableList Current = new BindableList(); @@ -31,7 +33,7 @@ namespace osu.Game.Overlays.BeatmapListing [BackgroundDependencyLoader] private void load() { - Current.BindTo(filter.Current); + filter.Current.BindTo(Current); } protected sealed override Drawable CreateFilter() => filter = CreateMultipleSelectionFilter(); @@ -64,6 +66,14 @@ namespace osu.Game.Overlays.BeatmapListing foreach (var item in Children) item.Active.BindValueChanged(active => toggleItem(item.Value, active.NewValue)); + + Current.BindCollectionChanged(currentChanged, true); + } + + private void currentChanged(object sender, NotifyCollectionChangedEventArgs e) + { + foreach (var c in Children) + c.Active.Value = Current.Contains(c.Value); } /// @@ -79,7 +89,10 @@ namespace osu.Game.Overlays.BeatmapListing private void toggleItem(T value, bool active) { if (active) - Current.Add(value); + { + if (!Current.Contains(value)) + Current.Add(value); + } else Current.Remove(value); } From 78bc94d3cb179c390c2753c1b1a5073085ab274d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Dec 2022 16:57:39 +0900 Subject: [PATCH 135/384] Set featured artists filter to enabled by default --- osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index f28ec9c295..23de1cf76d 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -146,6 +146,7 @@ namespace osu.Game.Overlays.BeatmapListing } }); + generalFilter.Current.Add(SearchGeneral.FeaturedArtists); categoryFilter.Current.Value = SearchCategory.Leaderboard; } From 0763b86236bad6fe3b1839f0103746ca2c47a8e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Dec 2022 17:32:27 +0900 Subject: [PATCH 136/384] Add more padding around text in dialog popups --- osu.Game/Overlays/Dialog/PopupDialog.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 80e0ffe427..f5a7e9e43d 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -198,6 +198,7 @@ namespace osu.Game.Overlays.Dialog TextAnchor = Anchor.TopCentre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Padding = new MarginPadding(5), }, }, }, From d2b98b803d398c5ecf4284dbbcdd5b13edad3192 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Dec 2022 17:35:39 +0900 Subject: [PATCH 137/384] Turn on featured artist filter by default and add disclaimer when toggling for the first time --- osu.Game/Configuration/SessionStatics.cs | 4 +- .../Localisation/BeatmapOverlayStrings.cs | 34 ++++++++++ .../BeatmapSearchGeneralFilterRow.cs | 63 +++++++++++++++++++ 3 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Localisation/BeatmapOverlayStrings.cs diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index 12a30a0c84..276563e163 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -19,6 +19,7 @@ namespace osu.Game.Configuration SetDefault(Static.LoginOverlayDisplayed, false); SetDefault(Static.MutedAudioNotificationShownOnce, false); SetDefault(Static.LowBatteryNotificationShownOnce, false); + SetDefault(Static.FeaturedArtistDisclaimerShownOnce, false); SetDefault(Static.LastHoverSoundPlaybackTime, (double?)null); SetDefault(Static.SeasonalBackgrounds, null); } @@ -42,6 +43,7 @@ namespace osu.Game.Configuration LoginOverlayDisplayed, MutedAudioNotificationShownOnce, LowBatteryNotificationShownOnce, + FeaturedArtistDisclaimerShownOnce, /// /// Info about seasonal backgrounds available fetched from API - see . @@ -53,6 +55,6 @@ namespace osu.Game.Configuration /// The last playback time in milliseconds of a hover sample (from ). /// Used to debounce hover sounds game-wide to avoid volume saturation, especially in scrolling views with many UI controls like . /// - LastHoverSoundPlaybackTime + LastHoverSoundPlaybackTime, } } diff --git a/osu.Game/Localisation/BeatmapOverlayStrings.cs b/osu.Game/Localisation/BeatmapOverlayStrings.cs new file mode 100644 index 0000000000..efa9fcf155 --- /dev/null +++ b/osu.Game/Localisation/BeatmapOverlayStrings.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class BeatmapOverlayStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.BeatmapOverlayStrings"; + + /// + /// "User content disclaimer" + /// + public static LocalisableString UserContentDisclaimer => new TranslatableString(getKey(@"user_content_disclaimer"), @"User content disclaimer"); + + /// + /// "By turning off the "featured artist" filter, all user uploaded content will be displayed. + /// + /// This includes content which may not be correctly licensed for use and as such may not be safe for streaming, sharing, or consumption." + /// + public static LocalisableString ByTurningOffTheFeatured => new TranslatableString(getKey(@"by_turning_off_the_featured"), + @"By turning off the ""featured artist"" filter, all user uploaded content will be displayed. + +This includes content which may not be correctly licensed for use and as such may not be safe for streaming, sharing, or consumption."); + + /// + /// "I understand" + /// + public static LocalisableString Understood => new TranslatableString(getKey(@"understood"), @"I understand"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs index 10ec66e396..41a78473e5 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs @@ -3,10 +3,18 @@ #nullable disable +using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Localisation; +using osu.Game.Overlays.Dialog; using osu.Game.Resources.Localisation.Web; using osuTK.Graphics; +using CommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings; namespace osu.Game.Overlays.BeatmapListing { @@ -32,6 +40,8 @@ namespace osu.Game.Overlays.BeatmapListing private partial class FeaturedArtistsTabItem : MultipleSelectionFilterTabItem { + private Bindable disclaimerShown; + public FeaturedArtistsTabItem() : base(SearchGeneral.FeaturedArtists) { @@ -40,7 +50,60 @@ namespace osu.Game.Overlays.BeatmapListing [Resolved] private OsuColour colours { get; set; } + [Resolved] + private SessionStatics sessionStatics { get; set; } + + [Resolved(canBeNull: true)] + private IDialogOverlay dialogOverlay { get; set; } + protected override Color4 GetStateColour() => colours.Orange1; + + protected override void LoadComplete() + { + base.LoadComplete(); + + disclaimerShown = sessionStatics.GetBindable(Static.FeaturedArtistDisclaimerShownOnce); + } + + protected override bool OnClick(ClickEvent e) + { + if (!disclaimerShown.Value && dialogOverlay != null) + { + dialogOverlay.Push(new FeaturedArtistConfirmDialog(() => + { + disclaimerShown.Value = true; + base.OnClick(e); + })); + + return true; + } + + return base.OnClick(e); + } + } + } + + internal partial class FeaturedArtistConfirmDialog : PopupDialog + { + public FeaturedArtistConfirmDialog(Action confirm) + { + HeaderText = BeatmapOverlayStrings.UserContentDisclaimer; + BodyText = BeatmapOverlayStrings.ByTurningOffTheFeatured; + + Icon = FontAwesome.Solid.ExclamationTriangle; + + Buttons = new PopupDialogButton[] + { + new PopupDialogDangerousButton + { + Text = BeatmapOverlayStrings.Understood, + Action = confirm + }, + new PopupDialogCancelButton + { + Text = CommonStrings.ButtonsCancel, + }, + }; } } } From 57048f0ebaaf11ef14253c78005bfcd84a5f603d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Dec 2022 18:42:58 +0900 Subject: [PATCH 138/384] Add test coverage of featured artist filter confirmation process --- .../Navigation/TestSceneScreenNavigation.cs | 23 +++++++++++++++++++ .../Online/TestSceneBeatmapListingOverlay.cs | 9 ++++++++ 2 files changed, 32 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index d8fda5b21f..ddb01b90ce 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -19,6 +19,7 @@ using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Leaderboards; using osu.Game.Overlays; +using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.Mods; using osu.Game.Overlays.Toolbar; using osu.Game.Rulesets.Mods; @@ -515,6 +516,28 @@ namespace osu.Game.Tests.Visual.Navigation AddWaitStep("wait two frames", 2); } + [Test] + public void TestFeaturedArtistDisclaimerDialog() + { + BeatmapListingOverlay getBeatmapListingOverlay() => Game.ChildrenOfType().FirstOrDefault(); + + AddStep("Wait for notifications to load", () => Game.SearchBeatmapSet(string.Empty)); + AddUntilStep("wait for dialog overlay", () => Game.ChildrenOfType().SingleOrDefault() != null); + + AddUntilStep("Wait for beatmap overlay to load", () => getBeatmapListingOverlay()?.State.Value == Visibility.Visible); + AddAssert("featured artist filter is on", () => getBeatmapListingOverlay().ChildrenOfType().First().Current.Contains(SearchGeneral.FeaturedArtists)); + AddStep("toggle featured artist filter", + () => getBeatmapListingOverlay().ChildrenOfType>().First(i => i.Value == SearchGeneral.FeaturedArtists).TriggerClick()); + + AddAssert("disclaimer dialog is shown", () => Game.ChildrenOfType().Single().CurrentDialog != null); + AddAssert("featured artist filter is still on", () => getBeatmapListingOverlay().ChildrenOfType().First().Current.Contains(SearchGeneral.FeaturedArtists)); + + AddStep("confirm", () => InputManager.Key(Key.Enter)); + AddAssert("dialog dismissed", () => Game.ChildrenOfType().Single().CurrentDialog == null); + + AddUntilStep("featured artist filter is off", () => !getBeatmapListingOverlay().ChildrenOfType().First().Current.Contains(SearchGeneral.FeaturedArtists)); + } + [Test] public void TestMainOverlaysClosesNotificationOverlay() { diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs index c64343b47b..5e49cb633e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapListingOverlay.cs @@ -80,6 +80,15 @@ namespace osu.Game.Tests.Visual.Online AddStep("reset size", () => localConfig.SetValue(OsuSetting.BeatmapListingCardSize, BeatmapCardSize.Normal)); } + [Test] + public void TestFeaturedArtistFilter() + { + AddAssert("is visible", () => overlay.State.Value == Visibility.Visible); + AddAssert("featured artist filter is on", () => overlay.ChildrenOfType().First().Current.Contains(SearchGeneral.FeaturedArtists)); + AddStep("toggle featured artist filter", () => overlay.ChildrenOfType>().First(i => i.Value == SearchGeneral.FeaturedArtists).TriggerClick()); + AddAssert("featured artist filter is off", () => !overlay.ChildrenOfType().First().Current.Contains(SearchGeneral.FeaturedArtists)); + } + [Test] public void TestHideViaBack() { From 91adf2e80f62f7ffd296474c3836d7e8ef7f5510 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Dec 2022 22:44:47 +0900 Subject: [PATCH 139/384] Apply the wala-hyphen Co-authored-by: Walavouchey <36758269+Walavouchey@users.noreply.github.com> --- osu.Game/Localisation/BeatmapOverlayStrings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/BeatmapOverlayStrings.cs b/osu.Game/Localisation/BeatmapOverlayStrings.cs index efa9fcf155..b2625dc0e6 100644 --- a/osu.Game/Localisation/BeatmapOverlayStrings.cs +++ b/osu.Game/Localisation/BeatmapOverlayStrings.cs @@ -15,12 +15,12 @@ namespace osu.Game.Localisation public static LocalisableString UserContentDisclaimer => new TranslatableString(getKey(@"user_content_disclaimer"), @"User content disclaimer"); /// - /// "By turning off the "featured artist" filter, all user uploaded content will be displayed. + /// "By turning off the "Featured Artist" filter, all user-uploaded content will be displayed. /// /// This includes content which may not be correctly licensed for use and as such may not be safe for streaming, sharing, or consumption." /// public static LocalisableString ByTurningOffTheFeatured => new TranslatableString(getKey(@"by_turning_off_the_featured"), - @"By turning off the ""featured artist"" filter, all user uploaded content will be displayed. + @"By turning off the ""Featured Artist"" filter, all user-uploaded content will be displayed. This includes content which may not be correctly licensed for use and as such may not be safe for streaming, sharing, or consumption."); From 9813bc95444466b723608aab2b4ae72e0111f7df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Dec 2022 22:46:31 +0900 Subject: [PATCH 140/384] Reword disclaimer and fix translation keys --- osu.Game/Localisation/BeatmapOverlayStrings.cs | 11 +++++------ .../BeatmapListing/BeatmapSearchGeneralFilterRow.cs | 6 +++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/osu.Game/Localisation/BeatmapOverlayStrings.cs b/osu.Game/Localisation/BeatmapOverlayStrings.cs index b2625dc0e6..fc818f7596 100644 --- a/osu.Game/Localisation/BeatmapOverlayStrings.cs +++ b/osu.Game/Localisation/BeatmapOverlayStrings.cs @@ -12,22 +12,21 @@ namespace osu.Game.Localisation /// /// "User content disclaimer" /// - public static LocalisableString UserContentDisclaimer => new TranslatableString(getKey(@"user_content_disclaimer"), @"User content disclaimer"); + public static LocalisableString UserContentDisclaimerHeader => new TranslatableString(getKey(@"user_content_disclaimer"), @"User content disclaimer"); /// /// "By turning off the "Featured Artist" filter, all user-uploaded content will be displayed. /// - /// This includes content which may not be correctly licensed for use and as such may not be safe for streaming, sharing, or consumption." + /// This includes content that may not be correctly licensed for osu! usage. Browse at your own risk." /// - public static LocalisableString ByTurningOffTheFeatured => new TranslatableString(getKey(@"by_turning_off_the_featured"), - @"By turning off the ""Featured Artist"" filter, all user-uploaded content will be displayed. + public static LocalisableString UserContentDisclaimerDescription => new TranslatableString(getKey(@"by_turning_off_the_featured"), @"By turning off the ""Featured Artist"" filter, all user-uploaded content will be displayed. -This includes content which may not be correctly licensed for use and as such may not be safe for streaming, sharing, or consumption."); +This includes content that may not be correctly licensed for osu! usage. Browse at your own risk."); /// /// "I understand" /// - public static LocalisableString Understood => new TranslatableString(getKey(@"understood"), @"I understand"); + public static LocalisableString UserContentConfirmButtonText => new TranslatableString(getKey(@"understood"), @"I understand"); private static string getKey(string key) => $@"{prefix}:{key}"; } diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs index 41a78473e5..a4a914db55 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchGeneralFilterRow.cs @@ -87,8 +87,8 @@ namespace osu.Game.Overlays.BeatmapListing { public FeaturedArtistConfirmDialog(Action confirm) { - HeaderText = BeatmapOverlayStrings.UserContentDisclaimer; - BodyText = BeatmapOverlayStrings.ByTurningOffTheFeatured; + HeaderText = BeatmapOverlayStrings.UserContentDisclaimerHeader; + BodyText = BeatmapOverlayStrings.UserContentDisclaimerDescription; Icon = FontAwesome.Solid.ExclamationTriangle; @@ -96,7 +96,7 @@ namespace osu.Game.Overlays.BeatmapListing { new PopupDialogDangerousButton { - Text = BeatmapOverlayStrings.Understood, + Text = BeatmapOverlayStrings.UserContentConfirmButtonText, Action = confirm }, new PopupDialogCancelButton From c1647440641abe58d4ef1abf834e8f936d0877a2 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 16 Dec 2022 01:03:30 +0900 Subject: [PATCH 141/384] Add ability to set preview time --- .../Summary/Parts/PreviewTimePart.cs | 31 +++++++++++++++++++ .../Timelines/Summary/SummaryTimeline.cs | 8 +++++ osu.Game/Screens/Edit/Editor.cs | 12 +++++++ osu.Game/Screens/Edit/EditorBeatmap.cs | 5 +++ 4 files changed, 56 insertions(+) create mode 100644 osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs new file mode 100644 index 0000000000..a09d93fd5a --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs @@ -0,0 +1,31 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Graphics; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts +{ + public partial class PreviewTimePart : TimelinePart + { + protected override void LoadBeatmap(EditorBeatmap beatmap) + { + base.LoadBeatmap(beatmap); + Add(new PreviewTimeVisualisation(beatmap.PreviewTime)); + } + + private partial class PreviewTimeVisualisation : PointVisualisation + { + public PreviewTimeVisualisation(BindableInt time) + : base(time.Value) + { + time.BindValueChanged(s => X = s.NewValue); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) => Colour = colours.Lime; + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index 7f762b9d50..41377bcb18 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -41,6 +41,14 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary RelativeSizeAxes = Axes.Both, Height = 0.35f }, + new PreviewTimePart + { + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + Y = -10, + Height = 0.35f + }, new Container { Name = "centre line", diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index f3f2b8ad6b..16fff76ac7 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -322,6 +322,13 @@ namespace osu.Game.Screens.Edit State = { BindTarget = editorHitMarkers }, } } + }, + new MenuItem("Timing") + { + Items = new MenuItem[] + { + new EditorMenuItem("Set Current Position as Preview Point", MenuItemType.Standard, SetCurrectTimeAsPreview) + } } } }, @@ -801,6 +808,11 @@ namespace osu.Game.Screens.Edit protected void Redo() => changeHandler?.RestoreState(1); + protected void SetCurrectTimeAsPreview() + { + editorBeatmap.PreviewTime.Value = (int)clock.CurrentTime; + } + private void resetTrack(bool seekToStart = false) { Beatmap.Value.Track.Stop(); diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index e204b44db3..0a0557a992 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -86,6 +86,8 @@ namespace osu.Game.Screens.Edit [Resolved] private EditorClock editorClock { get; set; } + public BindableInt PreviewTime; + private readonly IBeatmapProcessor beatmapProcessor; private readonly Dictionary> startTimeBindables = new Dictionary>(); @@ -107,6 +109,9 @@ namespace osu.Game.Screens.Edit foreach (var obj in HitObjects) trackStartTime(obj); + + PreviewTime = new BindableInt(playableBeatmap.Metadata.PreviewTime); + PreviewTime.BindValueChanged(s => this.beatmapInfo.Metadata.PreviewTime = s.NewValue); } /// From 984f0b5fa961e5ed54dbbac132f39273fa2e7e8f Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 16 Dec 2022 01:35:54 +0900 Subject: [PATCH 142/384] Add test for set preview point --- .../Visual/Editing/TestScenePreviewTime.cs | 23 +++++++++++++++++++ osu.Game/Tests/Visual/EditorTestScene.cs | 2 ++ 2 files changed, 25 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs diff --git a/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs b/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs new file mode 100644 index 0000000000..f95851f646 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; + +namespace osu.Game.Tests.Visual.Editing +{ + public partial class TestScenePreviewTime : EditorTestScene + { + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + [Test] + public void TestSetPreviewTimingPoint() + { + AddStep("seek to 1000", () => EditorClock.Seek(1000)); + AddAssert("time is 1000", () => EditorClock.CurrentTime == 1000); + AddStep("set current time as preview point", () => Editor.SetCurrectTimeAsPreview()); + AddAssert("preview time is 1000", () => EditorBeatmap.PreviewTime.Value == 1000); + } + } +} diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 833c12ba54..9944a561ca 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -102,6 +102,8 @@ namespace osu.Game.Tests.Visual public new void Redo() => base.Redo(); + public new void SetCurrectTimeAsPreview() => base.SetCurrectTimeAsPreview(); + public new bool Save() => base.Save(); public new void Cut() => base.Cut(); From 467e87902198e8ba3a83653dde6992e2382769f9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Dec 2022 01:25:56 +0300 Subject: [PATCH 143/384] Migrate osu! iOS to .NET 6 and target iOS 13.4 --- README.md | 2 +- .../Application.cs | 3 +- osu.Game.Rulesets.Catch.Tests.iOS/Info.plist | 2 +- .../osu.Game.Rulesets.Catch.Tests.iOS.csproj | 28 +--- .../Application.cs | 3 +- osu.Game.Rulesets.Mania.Tests.iOS/Info.plist | 2 +- .../osu.Game.Rulesets.Mania.Tests.iOS.csproj | 28 +--- .../Application.cs | 3 +- osu.Game.Rulesets.Osu.Tests.iOS/Info.plist | 2 +- .../osu.Game.Rulesets.Osu.Tests.iOS.csproj | 29 +---- .../Application.cs | 3 +- osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist | 2 +- .../osu.Game.Rulesets.Taiko.Tests.iOS.csproj | 28 +--- osu.Game.Tests.iOS/Application.cs | 3 +- osu.Game.Tests.iOS/Info.plist | 2 +- osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj | 44 ++----- osu.iOS.props | 80 +++--------- osu.iOS/Application.cs | 3 +- osu.iOS/Info.plist | 2 +- osu.iOS/osu.iOS.csproj | 123 ++---------------- 20 files changed, 72 insertions(+), 320 deletions(-) diff --git a/README.md b/README.md index 75d61dad4d..0de82eba75 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ If you are looking to install or test osu! without setting up a development envi **Latest build:** -| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 10.15+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 10+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | +| [Windows 8.1+ (x64)](https://github.com/ppy/osu/releases/latest/download/install.exe) | macOS 10.15+ ([Intel](https://github.com/ppy/osu/releases/latest/download/osu.app.Intel.zip), [Apple Silicon](https://github.com/ppy/osu/releases/latest/download/osu.app.Apple.Silicon.zip)) | [Linux (x64)](https://github.com/ppy/osu/releases/latest/download/osu.AppImage) | [iOS 13.4+](https://osu.ppy.sh/home/testflight) | [Android 5+](https://github.com/ppy/osu/releases/latest/download/sh.ppy.osulazer.apk) | | ------------- | ------------- | ------------- | ------------- | ------------- | - The iOS testflight link may fill up (Apple has a hard limit of 10,000 users). We reset it occasionally when this happens. Please do not ask about this. Check back regularly for link resets or follow [peppy](https://twitter.com/ppy) on twitter for announcements of link resets. diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs index 71d943ece1..1fcb0aa427 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Application.cs @@ -3,7 +3,6 @@ #nullable disable -using osu.Framework.iOS; using UIKit; namespace osu.Game.Rulesets.Catch.Tests.iOS @@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Catch.Tests.iOS { public static void Main(string[] args) { - UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate)); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist index 16a2b99997..5ace6c07f5 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist @@ -13,7 +13,7 @@ LSRequiresIPhoneOS MinimumOSVersion - 10.0 + 13.4 UIDeviceFamily 1 diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj index be6044bbd0..acf12bb0ac 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Catch.Tests.iOS/osu.Game.Rulesets.Catch.Tests.iOS.csproj @@ -1,35 +1,19 @@ - - + - Debug - iPhoneSimulator - {4004C7B7-1A62-43F1-9DF2-52450FA67E70} Exe + net6.0-ios + 13.4 osu.Game.Rulesets.Catch.Tests osu.Game.Rulesets.Catch.Tests.iOS - - - - Linker.xml - - - %(RecursiveDir)%(Filename)%(Extension) - - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} - osu.Game - - - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3} - osu.Game.Rulesets.Catch - + + - - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs index 2d1015387a..a508198f7f 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Application.cs @@ -3,7 +3,6 @@ #nullable disable -using osu.Framework.iOS; using UIKit; namespace osu.Game.Rulesets.Mania.Tests.iOS @@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Mania.Tests.iOS { public static void Main(string[] args) { - UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate)); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist index 82d1c8ea24..ff5dde856e 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist @@ -13,7 +13,7 @@ LSRequiresIPhoneOS MinimumOSVersion - 10.0 + 13.4 UIDeviceFamily 1 diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj index 88ad484bc1..51e07dd6c1 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Mania.Tests.iOS/osu.Game.Rulesets.Mania.Tests.iOS.csproj @@ -1,35 +1,19 @@ - - + - Debug - iPhoneSimulator - {39FD990E-B6CE-4B2A-999F-BC008CF2C64C} Exe + net6.0-ios + 13.4 osu.Game.Rulesets.Mania.Tests osu.Game.Rulesets.Mania.Tests.iOS - - - - Linker.xml - - - %(RecursiveDir)%(Filename)%(Extension) - - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} - osu.Game - - - {48F4582B-7687-4621-9CBE-5C24197CB536} - osu.Game.Rulesets.Mania - + + - - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs index ad23f3ee33..6ef29fa68e 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Application.cs @@ -3,7 +3,6 @@ #nullable disable -using osu.Framework.iOS; using UIKit; namespace osu.Game.Rulesets.Osu.Tests.iOS @@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Osu.Tests.iOS { public static void Main(string[] args) { - UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate)); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist index a88b74695c..1e33f2ff16 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist @@ -13,7 +13,7 @@ LSRequiresIPhoneOS MinimumOSVersion - 10.0 + 13.4 UIDeviceFamily 1 diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj index 545abcec6c..7d50deb8ba 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Osu.Tests.iOS/osu.Game.Rulesets.Osu.Tests.iOS.csproj @@ -1,35 +1,20 @@ - - + - Debug - iPhoneSimulator - {6653CA6F-DB06-4604-A3FD-762E25C2AF96} + Exe + net6.0-ios + 13.4 Exe osu.Game.Rulesets.Osu.Tests osu.Game.Rulesets.Osu.Tests.iOS - - - - Linker.xml - - - %(RecursiveDir)%(Filename)%(Extension) - - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} - osu.Game - - - {C92A607B-1FDD-4954-9F92-03FF547D9080} - osu.Game.Rulesets.Osu - + + - - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs index 1ebbd61a94..0e3a953728 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Application.cs @@ -3,7 +3,6 @@ #nullable disable -using osu.Framework.iOS; using UIKit; namespace osu.Game.Rulesets.Taiko.Tests.iOS @@ -12,7 +11,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.iOS { public static void Main(string[] args) { - UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate)); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist index 9628475b3e..76cb3c0db0 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist @@ -13,7 +13,7 @@ LSRequiresIPhoneOS MinimumOSVersion - 10.0 + 13.4 UIDeviceFamily 1 diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj index 8ee640cd99..e648a11299 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/osu.Game.Rulesets.Taiko.Tests.iOS.csproj @@ -1,35 +1,19 @@ - - + - Debug - iPhoneSimulator - {7E408809-66AC-49D1-AF4D-98834F9B979A} Exe + net6.0-ios + 13.4 osu.Game.Rulesets.Taiko.Tests osu.Game.Rulesets.Taiko.Tests.iOS - - - - Linker.xml - - - %(RecursiveDir)%(Filename)%(Extension) - - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} - osu.Game - - - {F167E17A-7DE6-4AF5-B920-A5112296C695} - osu.Game.Rulesets.Taiko - + + - - \ No newline at end of file + diff --git a/osu.Game.Tests.iOS/Application.cs b/osu.Game.Tests.iOS/Application.cs index cf36fea139..4678be4fb8 100644 --- a/osu.Game.Tests.iOS/Application.cs +++ b/osu.Game.Tests.iOS/Application.cs @@ -3,7 +3,6 @@ #nullable disable -using osu.Framework.iOS; using UIKit; namespace osu.Game.Tests.iOS @@ -12,7 +11,7 @@ namespace osu.Game.Tests.iOS { public static void Main(string[] args) { - UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate)); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } diff --git a/osu.Game.Tests.iOS/Info.plist b/osu.Game.Tests.iOS/Info.plist index 31e2b3f257..ac661f6263 100644 --- a/osu.Game.Tests.iOS/Info.plist +++ b/osu.Game.Tests.iOS/Info.plist @@ -13,7 +13,7 @@ LSRequiresIPhoneOS MinimumOSVersion - 10.0 + 13.4 UIDeviceFamily 1 diff --git a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj index 05b3cad6da..79771fcd50 100644 --- a/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj +++ b/osu.Game.Tests.iOS/osu.Game.Tests.iOS.csproj @@ -1,54 +1,26 @@ - - + - Debug - iPhoneSimulator Exe - {65FF8E19-6934-469B-B690-23C6D6E56A17} + net6.0-ios + 13.4 osu.Game.Tests osu.Game.Tests.iOS - - - - Linker.xml - - - %(RecursiveDir)%(Filename)%(Extension) - - $(NoWarn);CA2007 - - - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} - osu.Game - - - {C92A607B-1FDD-4954-9F92-03FF547D9080} - osu.Game.Rulesets.Osu - - - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3} - osu.Game.Rulesets.Catch - - - {48F4582B-7687-4621-9CBE-5C24197CB536} - osu.Game.Rulesets.Mania - - - {F167E17A-7DE6-4AF5-B920-A5112296C695} - osu.Game.Rulesets.Taiko - + + + + + - diff --git a/osu.iOS.props b/osu.iOS.props index 184e3303c8..d67248f8e0 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -1,72 +1,14 @@  - 8.0 - {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - Resources - PackageReference - bin\$(Platform)\$(Configuration) - cjk,mideast,other,rare,west - false - NSUrlSessionHandler - iPhone Developer + true true - - - --nosymbolstrip=BASS_FX_BPM_BeatCallbackReset --nosymbolstrip=BASS_FX_BPM_BeatCallbackSet --nosymbolstrip=BASS_FX_BPM_BeatDecodeGet --nosymbolstrip=BASS_FX_BPM_BeatFree --nosymbolstrip=BASS_FX_BPM_BeatGetParameters --nosymbolstrip=BASS_FX_BPM_BeatSetParameters --nosymbolstrip=BASS_FX_BPM_CallbackReset --nosymbolstrip=BASS_FX_BPM_CallbackSet --nosymbolstrip=BASS_FX_BPM_DecodeGet --nosymbolstrip=BASS_FX_BPM_Free --nosymbolstrip=BASS_FX_BPM_Translate --nosymbolstrip=BASS_FX_GetVersion --nosymbolstrip=BASS_FX_ReverseCreate --nosymbolstrip=BASS_FX_ReverseGetSource --nosymbolstrip=BASS_FX_TempoCreate --nosymbolstrip=BASS_FX_TempoGetRateRatio --nosymbolstrip=BASS_FX_TempoGetSource --nosymbolstrip=BASS_Mixer_ChannelFlags --nosymbolstrip=BASS_Mixer_ChannelGetData --nosymbolstrip=BASS_Mixer_ChannelGetEnvelopePos --nosymbolstrip=BASS_Mixer_ChannelGetLevel --nosymbolstrip=BASS_Mixer_ChannelGetLevelEx --nosymbolstrip=BASS_Mixer_ChannelGetMatrix --nosymbolstrip=BASS_Mixer_ChannelGetMixer --nosymbolstrip=BASS_Mixer_ChannelGetPosition --nosymbolstrip=BASS_Mixer_ChannelGetPositionEx --nosymbolstrip=BASS_Mixer_ChannelIsActive --nosymbolstrip=BASS_Mixer_ChannelRemove --nosymbolstrip=BASS_Mixer_ChannelRemoveSync --nosymbolstrip=BASS_Mixer_ChannelSetEnvelope --nosymbolstrip=BASS_Mixer_ChannelSetEnvelopePos --nosymbolstrip=BASS_Mixer_ChannelSetMatrix --nosymbolstrip=BASS_Mixer_ChannelSetMatrixEx --nosymbolstrip=BASS_Mixer_ChannelSetPosition --nosymbolstrip=BASS_Mixer_ChannelSetSync --nosymbolstrip=BASS_Mixer_GetVersion --nosymbolstrip=BASS_Mixer_StreamAddChannel --nosymbolstrip=BASS_Mixer_StreamAddChannelEx --nosymbolstrip=BASS_Mixer_StreamCreate --nosymbolstrip=BASS_Mixer_StreamGetChannels --nosymbolstrip=BASS_Split_StreamCreate --nosymbolstrip=BASS_Split_StreamGetAvailable --nosymbolstrip=BASS_Split_StreamGetSource --nosymbolstrip=BASS_Split_StreamGetSplits --nosymbolstrip=BASS_Split_StreamReset --nosymbolstrip=BASS_Split_StreamResetEx - - --nolinkaway --nostrip $(GeneratedMtouchSymbolStripFlags) - - - true - full - false - DEBUG;ENABLE_TEST_CLOUD; - true - true - - - pdbonly - true + + ios-arm64 - x86_64 - None - - - true - SdkOnly - ARM64 - Entitlements.plist - - - true - 25823 - false - - - true - - - true - 28126 - - - - - - - - - - - - - - - - $(NoWarn);NU1605 + iossimulator-x64 @@ -77,6 +19,9 @@ none + + + @@ -86,7 +31,16 @@ - + + + + + + <_LinkerFrameworks Remove="Quartz"/> + + diff --git a/osu.iOS/Application.cs b/osu.iOS/Application.cs index c5b2d0b451..64eb5c63f5 100644 --- a/osu.iOS/Application.cs +++ b/osu.iOS/Application.cs @@ -3,7 +3,6 @@ #nullable disable -using osu.Framework.iOS; using UIKit; namespace osu.iOS @@ -12,7 +11,7 @@ namespace osu.iOS { public static void Main(string[] args) { - UIApplication.Main(args, typeof(GameUIApplication), typeof(AppDelegate)); + UIApplication.Main(args, null, typeof(AppDelegate)); } } } diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index 16cb68fa7d..c4b08ab78e 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -15,7 +15,7 @@ LSRequiresIPhoneOS MinimumOSVersion - 10.0 + 13.4 UIDeviceFamily 1 diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index b9da874f30..95ca3e2142 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -1,124 +1,19 @@ - - - - Debug - iPhoneSimulator + + + net6.0-ios + 13.4 Exe - {3F082D0B-A964-43D7-BDF7-C256D76A50D0} - osu.iOS - osu.iOS false - - - - - - - - - - - - - - - - - - - - - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} - osu.Game - - - {58F6C80C-1253-4A0E-A465-B8C85EBEADF3} - osu.Game.Rulesets.Catch - - - {48F4582B-7687-4621-9CBE-5C24197CB536} - osu.Game.Rulesets.Mania - - - {C92A607B-1FDD-4954-9F92-03FF547D9080} - osu.Game.Rulesets.Osu - - - {F167E17A-7DE6-4AF5-B920-A5112296C695} - osu.Game.Rulesets.Taiko - - - - - - - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - - - false - + + + + + - From c900965a82d5c0ab86d9c017015a17eef45e4773 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Dec 2022 01:26:13 +0300 Subject: [PATCH 144/384] Replace `Xamarin.Essentials` with .NET MAUI Essentials --- osu.iOS/OsuGameIOS.cs | 2 +- osu.iOS/osu.iOS.csproj | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index b3194e497b..cb3b260033 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -5,6 +5,7 @@ using System; using Foundation; +using Microsoft.Maui.Devices; using osu.Framework.Graphics; using osu.Framework.Input.Handlers; using osu.Framework.iOS.Input; @@ -12,7 +13,6 @@ using osu.Game; using osu.Game.Overlays.Settings; using osu.Game.Updater; using osu.Game.Utils; -using Xamarin.Essentials; namespace osu.iOS { diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index 95ca3e2142..ce19cb7ee6 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -4,6 +4,7 @@ 13.4 Exe false + true @@ -13,7 +14,4 @@ - - - From e85975b5f1269c7c2a106be6e677fd4d029630e0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Dec 2022 01:28:01 +0300 Subject: [PATCH 145/384] Work around JIT compilation error in Realm --- osu.iOS.props | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.iOS.props b/osu.iOS.props index d67248f8e0..4a0fe55d4e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -3,6 +3,8 @@ iPhone Developer true true + + true ios-arm64 From 660cc72f2a36f89c75567f3910bdaee52b8e955b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 12 Dec 2022 01:30:49 +0300 Subject: [PATCH 146/384] Avoid configuring web proxies on iOS for now --- osu.Game/Online/HubClientConnector.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index ca6d2932f7..ff63c0657c 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -59,11 +59,15 @@ namespace osu.Game.Online var builder = new HubConnectionBuilder() .WithUrl(endpoint, options => { - // Use HttpClient.DefaultProxy once on net6 everywhere. - // The credential setter can also be removed at this point. - options.Proxy = WebRequest.DefaultWebProxy; - if (options.Proxy != null) - options.Proxy.Credentials = CredentialCache.DefaultCredentials; + // Configuring proxies is not supported on iOS, see https://github.com/xamarin/xamarin-macios/issues/14632. + if (RuntimeInfo.OS != RuntimeInfo.Platform.iOS) + { + // Use HttpClient.DefaultProxy once on net6 everywhere. + // The credential setter can also be removed at this point. + options.Proxy = WebRequest.DefaultWebProxy; + if (options.Proxy != null) + options.Proxy.Credentials = CredentialCache.DefaultCredentials; + } options.Headers.Add("Authorization", $"Bearer {api.AccessToken}"); options.Headers.Add("OsuVersionHash", versionHash); From 5fbd4ad3b467414e925b29e118654e91637fe8c1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 14 Dec 2022 17:01:05 +0300 Subject: [PATCH 147/384] Update usages of `SupportsJIT` in line with framework changes --- osu.Game/Online/HubClientConnector.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/HubClientConnector.cs b/osu.Game/Online/HubClientConnector.cs index ff63c0657c..8fd79bd703 100644 --- a/osu.Game/Online/HubClientConnector.cs +++ b/osu.Game/Online/HubClientConnector.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Net; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; @@ -73,7 +74,7 @@ namespace osu.Game.Online options.Headers.Add("OsuVersionHash", versionHash); }); - if (RuntimeInfo.SupportsJIT && preferMessagePack) + if (RuntimeFeature.IsDynamicCodeCompiled && preferMessagePack) { builder.AddMessagePackProtocol(options => { From b488421c7aa0fd7431043d09695f2f0a4978a808 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Dec 2022 12:24:17 +0300 Subject: [PATCH 148/384] Remove `MtouchUseLlvm` key for now I haven't found a way to disable LLVM, and the issue may potentially be resolved (doesn't occur for me), so we might as well just remove the key for now. --- osu.iOS/osu.iOS.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index ce19cb7ee6..def538af1a 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -3,7 +3,6 @@ net6.0-ios 13.4 Exe - false true From d791bc56e7b64980b2969c8ea1ceefe4b4450da7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Dec 2022 14:43:41 +0300 Subject: [PATCH 149/384] Remove transitive dependencies --- osu.iOS.props | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/osu.iOS.props b/osu.iOS.props index 4a0fe55d4e..064ee5cceb 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -2,7 +2,6 @@ iPhone Developer true - true true @@ -24,19 +23,6 @@ - - - - - - - - - - - - - From 2c78fed5e4511c43c2d02e6a8fb268a28f80a967 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 15 Dec 2022 18:21:23 +0300 Subject: [PATCH 150/384] Update workflows in line with framework changes --- .github/workflows/ci.yml | 8 ++++---- appveyor.yml | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 56b3ebe87b..798e54e155 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,8 +132,8 @@ jobs: with: dotnet-version: "6.0.x" - # Contrary to seemingly any other msbuild, msbuild running on macOS/Mono - # cannot accept .sln(f) files as arguments. - # Build just the main game for now. + - name: Install .NET Workloads + run: dotnet workload install maui-ios + - name: Build - run: msbuild osu.iOS/osu.iOS.csproj /restore /p:Configuration=Debug + run: dotnet build -c Debug osu.iOS diff --git a/appveyor.yml b/appveyor.yml index 5be73f9875..19ef6ae63f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ clone_depth: 1 version: '{branch}-{build}' -image: Visual Studio 2019 +image: Visual Studio 2022 cache: - '%LOCALAPPDATA%\NuGet\v3-cache -> appveyor.yml' @@ -11,6 +11,7 @@ dotnet_csproj: before_build: - cmd: dotnet --info # Useful when version mismatch between CI and local + - cmd: dotnet workload install maui-ios # Change to `dotnet workload restore` once there's no old projects - cmd: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects build: From 67787da4cfbc25d4a077e5ebd3b400167039d2f9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 16 Dec 2022 00:46:32 +0300 Subject: [PATCH 151/384] Suppress MT7091 --- osu.iOS.props | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.iOS.props b/osu.iOS.props index 064ee5cceb..9168c019ba 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -4,6 +4,10 @@ true true + + $(NoWarn);MT7091 ios-arm64 From 0f34d908c791692c4d10ff516f8274b0d56a4413 Mon Sep 17 00:00:00 2001 From: Susko3 Date: Fri, 16 Dec 2022 00:05:47 +0100 Subject: [PATCH 152/384] Use `LocalisableString`s for date and time formats --- osu.Game/Graphics/DateTooltip.cs | 5 +++-- osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs | 5 ++--- osu.Game/Overlays/Changelog/ChangelogListing.cs | 3 ++- osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs | 3 ++- osu.Game/Overlays/Chat/ChatLine.cs | 5 ++--- .../Overlays/Dashboard/Home/News/FeaturedNewsItemPanel.cs | 5 +++-- osu.Game/Overlays/Dashboard/Home/News/NewsGroupItem.cs | 5 +++-- osu.Game/Overlays/News/Sidebar/MonthSection.cs | 3 ++- osu.Game/Overlays/Toolbar/DigitalClockDisplay.cs | 3 ++- .../Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs | 4 +++- 10 files changed, 24 insertions(+), 17 deletions(-) diff --git a/osu.Game/Graphics/DateTooltip.cs b/osu.Game/Graphics/DateTooltip.cs index d9bb2b610a..c62f53f1d4 100644 --- a/osu.Game/Graphics/DateTooltip.cs +++ b/osu.Game/Graphics/DateTooltip.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics.Sprites; using osuTK; @@ -69,8 +70,8 @@ namespace osu.Game.Graphics { DateTimeOffset localDate = date.ToLocalTime(); - dateText.Text = $"{localDate:d MMMM yyyy} "; - timeText.Text = $"{localDate:HH:mm:ss \"UTC\"z}"; + dateText.Text = LocalisableString.Interpolate($"{localDate:d MMMM yyyy} "); + timeText.Text = LocalisableString.Interpolate($"{localDate:HH:mm:ss \"UTC\"z}"); } public void Move(Vector2 pos) => Position = pos; diff --git a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs index 170f266307..0b2e401f57 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScoreTooltip.cs @@ -136,9 +136,8 @@ namespace osu.Game.Online.Leaderboards { if (displayedScore != null) { - timestampLabel.Text = prefer24HourTime.Value - ? $"Played on {displayedScore.Date.ToLocalTime():d MMMM yyyy HH:mm}" - : $"Played on {displayedScore.Date.ToLocalTime():d MMMM yyyy h:mm tt}"; + timestampLabel.Text = LocalisableString.Format("Played on {0}", + displayedScore.Date.ToLocalTime().ToLocalisableString(prefer24HourTime.Value ? @"d MMMM yyyy HH:mm" : @"d MMMM yyyy h:mm tt")); } } diff --git a/osu.Game/Overlays/Changelog/ChangelogListing.cs b/osu.Game/Overlays/Changelog/ChangelogListing.cs index d30fd97652..d7c9ff67fe 100644 --- a/osu.Game/Overlays/Changelog/ChangelogListing.cs +++ b/osu.Game/Overlays/Changelog/ChangelogListing.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -51,7 +52,7 @@ namespace osu.Game.Overlays.Changelog Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Margin = new MarginPadding { Top = 20 }, - Text = build.CreatedAt.Date.ToString("dd MMMM yyyy"), + Text = build.CreatedAt.Date.ToLocalisableString("dd MMMM yyyy"), Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 24), }); diff --git a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs index a729ef2cc8..13a19de22a 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs @@ -7,6 +7,7 @@ using System; using System.Threading; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -114,7 +115,7 @@ namespace osu.Game.Overlays.Changelog { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = Build.CreatedAt.Date.ToString("dd MMMM yyyy"), + Text = Build.CreatedAt.Date.ToLocalisableString("dd MMMM yyyy"), Font = OsuFont.GetFont(weight: FontWeight.Regular, size: 14), Margin = new MarginPadding { Top = 5 }, Scale = new Vector2(1.25f), diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 2b8718939e..70c3bf181c 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -175,9 +176,7 @@ namespace osu.Game.Overlays.Chat private void updateTimestamp() { - drawableTimestamp.Text = prefer24HourTime.Value - ? $@"{message.Timestamp.LocalDateTime:HH:mm:ss}" - : $@"{message.Timestamp.LocalDateTime:hh:mm:ss tt}"; + drawableTimestamp.Text = message.Timestamp.LocalDateTime.ToLocalisableString(prefer24HourTime.Value ? @"HH:mm:ss" : @"hh:mm:ss tt"); } } } diff --git a/osu.Game/Overlays/Dashboard/Home/News/FeaturedNewsItemPanel.cs b/osu.Game/Overlays/Dashboard/Home/News/FeaturedNewsItemPanel.cs index 3066d253eb..dabe65964a 100644 --- a/osu.Game/Overlays/Dashboard/Home/News/FeaturedNewsItemPanel.cs +++ b/osu.Game/Overlays/Dashboard/Home/News/FeaturedNewsItemPanel.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -167,7 +168,7 @@ namespace osu.Game.Overlays.Dashboard.Home.News Origin = Anchor.TopRight, Font = OsuFont.GetFont(weight: FontWeight.Bold), // using Bold since there is no 800 weight alternative Colour = colourProvider.Light1, - Text = $"{date:dd}" + Text = date.ToLocalisableString(@"dd") }, new TextFlowContainer(f => { @@ -178,7 +179,7 @@ namespace osu.Game.Overlays.Dashboard.Home.News Anchor = Anchor.TopRight, Origin = Anchor.TopRight, AutoSizeAxes = Axes.Both, - Text = $"{date:MMM yyyy}" + Text = date.ToLocalisableString(@"MMM yyyy") } } }; diff --git a/osu.Game/Overlays/Dashboard/Home/News/NewsGroupItem.cs b/osu.Game/Overlays/Dashboard/Home/News/NewsGroupItem.cs index e277a5fa16..9b27d1a193 100644 --- a/osu.Game/Overlays/Dashboard/Home/News/NewsGroupItem.cs +++ b/osu.Game/Overlays/Dashboard/Home/News/NewsGroupItem.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; @@ -98,12 +99,12 @@ namespace osu.Game.Overlays.Dashboard.Home.News Margin = new MarginPadding { Vertical = 5 } }; - textFlow.AddText($"{date:dd}", t => + textFlow.AddText(date.ToLocalisableString(@"dd"), t => { t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); }); - textFlow.AddText($"{date: MMM}", t => + textFlow.AddText(date.ToLocalisableString(@" MMM"), t => { t.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Regular); }); diff --git a/osu.Game/Overlays/News/Sidebar/MonthSection.cs b/osu.Game/Overlays/News/Sidebar/MonthSection.cs index d205fcb908..30d29048ba 100644 --- a/osu.Game/Overlays/News/Sidebar/MonthSection.cs +++ b/osu.Game/Overlays/News/Sidebar/MonthSection.cs @@ -19,6 +19,7 @@ using osu.Framework.Graphics.Sprites; using System.Diagnostics; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Platform; namespace osu.Game.Overlays.News.Sidebar @@ -99,7 +100,7 @@ namespace osu.Game.Overlays.News.Sidebar Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Text = date.ToString("MMM yyyy") + Text = date.ToLocalisableString(@"MMM yyyy") }, icon = new SpriteIcon { diff --git a/osu.Game/Overlays/Toolbar/DigitalClockDisplay.cs b/osu.Game/Overlays/Toolbar/DigitalClockDisplay.cs index bc803db739..ada2f6ff86 100644 --- a/osu.Game/Overlays/Toolbar/DigitalClockDisplay.cs +++ b/osu.Game/Overlays/Toolbar/DigitalClockDisplay.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -69,7 +70,7 @@ namespace osu.Game.Overlays.Toolbar protected override void UpdateDisplay(DateTimeOffset now) { - realTime.Text = use24HourDisplay ? $"{now:HH:mm:ss}" : $"{now:h:mm:ss tt}"; + realTime.Text = now.ToLocalisableString(use24HourDisplay ? @"HH:mm:ss" : @"h:mm:ss tt"); gameTime.Text = $"running {new TimeSpan(TimeSpan.TicksPerSecond * (int)(Clock.CurrentTime / 1000)):c}"; } diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 8fe0ae509b..f23b469f5c 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -9,6 +9,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; @@ -309,7 +310,8 @@ namespace osu.Game.Screens.Ranking.Expanded private void updateDisplay() { - Text = prefer24HourTime.Value ? $"Played on {time.ToLocalTime():d MMMM yyyy HH:mm}" : $"Played on {time.ToLocalTime():d MMMM yyyy h:mm tt}"; + Text = LocalisableString.Format("Played on {0}", + time.ToLocalTime().ToLocalisableString(prefer24HourTime.Value ? @"d MMMM yyyy HH:mm" : @"d MMMM yyyy h:mm tt")); } } } From f0246df1a9751da8826b215a69725f14571572f5 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 16 Dec 2022 09:58:58 +0900 Subject: [PATCH 153/384] only get; --- osu.Game/Screens/Edit/EditorBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 0a0557a992..1059654077 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Edit [Resolved] private EditorClock editorClock { get; set; } - public BindableInt PreviewTime; + public BindableInt PreviewTime { get; } private readonly IBeatmapProcessor beatmapProcessor; From 79e27c2d9d7dbeb1444de238a72686cb90c26053 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 16 Dec 2022 10:44:07 +0900 Subject: [PATCH 154/384] `PreviewTimePart` will not show if preview time is -1 --- .../Visual/Editing/TestScenePreviewTime.cs | 14 +++++++++++++- .../Timelines/Summary/Parts/PreviewTimePart.cs | 4 ++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs b/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs index f95851f646..213eb4b175 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs @@ -1,9 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; +using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; namespace osu.Game.Tests.Visual.Editing { @@ -12,12 +15,21 @@ namespace osu.Game.Tests.Visual.Editing protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); [Test] - public void TestSetPreviewTimingPoint() + public void TestSceneSetPreviewTimingPoint() { AddStep("seek to 1000", () => EditorClock.Seek(1000)); AddAssert("time is 1000", () => EditorClock.CurrentTime == 1000); AddStep("set current time as preview point", () => Editor.SetCurrectTimeAsPreview()); AddAssert("preview time is 1000", () => EditorBeatmap.PreviewTime.Value == 1000); } + + [Test] + public void TestScenePreviewTimeline() + { + AddStep("set preview time to -1", () => EditorBeatmap.PreviewTime.Value = -1); + AddAssert("preview time line should not show", () => Editor.ChildrenOfType().Single().Alpha == 0); + AddStep("set preview time to 1000", () => EditorBeatmap.PreviewTime.Value = 1000); + AddAssert("preview time line should show", () => Editor.ChildrenOfType().Single().Alpha == 1); + } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs index a09d93fd5a..6149900fdc 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs @@ -14,6 +14,10 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { base.LoadBeatmap(beatmap); Add(new PreviewTimeVisualisation(beatmap.PreviewTime)); + beatmap.PreviewTime.BindValueChanged(s => + { + Alpha = s.NewValue == -1 ? 0 : 1; + }, true); } private partial class PreviewTimeVisualisation : PointVisualisation From a4d28aff6d73d28515c5a158d8a8152ac6be7eef Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 16 Dec 2022 10:48:56 +0900 Subject: [PATCH 155/384] fix typo --- osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 4 ++-- osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs b/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs index 213eb4b175..ad49f3ac0a 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Editing { AddStep("seek to 1000", () => EditorClock.Seek(1000)); AddAssert("time is 1000", () => EditorClock.CurrentTime == 1000); - AddStep("set current time as preview point", () => Editor.SetCurrectTimeAsPreview()); + AddStep("set current time as preview point", () => Editor.SetCurrentTimeAsPreview()); AddAssert("preview time is 1000", () => EditorBeatmap.PreviewTime.Value == 1000); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 16fff76ac7..62bb7d3133 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -327,7 +327,7 @@ namespace osu.Game.Screens.Edit { Items = new MenuItem[] { - new EditorMenuItem("Set Current Position as Preview Point", MenuItemType.Standard, SetCurrectTimeAsPreview) + new EditorMenuItem("Set Current Position as Preview Point", MenuItemType.Standard, SetCurrentTimeAsPreview) } } } @@ -808,7 +808,7 @@ namespace osu.Game.Screens.Edit protected void Redo() => changeHandler?.RestoreState(1); - protected void SetCurrectTimeAsPreview() + protected void SetCurrentTimeAsPreview() { editorBeatmap.PreviewTime.Value = (int)clock.CurrentTime; } diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 9944a561ca..9c8ac65add 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual public new void Redo() => base.Redo(); - public new void SetCurrectTimeAsPreview() => base.SetCurrectTimeAsPreview(); + public new void SetCurrentTimeAsPreview() => base.SetCurrentTimeAsPreview(); public new bool Save() => base.Save(); From e63b544167f4d0aa8c7e808e7a5f38805b360915 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Dec 2022 17:02:51 +0900 Subject: [PATCH 156/384] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index c2c74e2c0e..0e5714e90f 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 31cd851c03..9e757b1802 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 9168c019ba..98d3e129d4 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -25,7 +25,7 @@ - + + true true - - osu.licenseheader - - - - - - - - - - - - @@ -61,4 +19,9 @@ + + + true + diff --git a/osu.Android/Properties/AndroidManifest.xml b/osu.Android/AndroidManifest.xml similarity index 98% rename from osu.Android/Properties/AndroidManifest.xml rename to osu.Android/AndroidManifest.xml index 165a64a424..be326be5eb 100644 --- a/osu.Android/Properties/AndroidManifest.xml +++ b/osu.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index be40db7508..ca3d628447 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Android.App; using Android.Content; @@ -74,11 +75,23 @@ namespace osu.Android Debug.Assert(Resources?.DisplayMetrics != null); Point displaySize = new Point(); +#pragma warning disable 618 // GetSize is deprecated WindowManager.DefaultDisplay.GetSize(displaySize); +#pragma warning restore 618 float smallestWidthDp = Math.Min(displaySize.X, displaySize.Y) / Resources.DisplayMetrics.Density; bool isTablet = smallestWidthDp >= 600f; RequestedOrientation = DefaultOrientation = isTablet ? ScreenOrientation.FullUser : ScreenOrientation.SensorLandscape; + + // Currently (SDK 6.0.200), BundleAssemblies is not runnable for net6-android. + // The assembly files are not available as files either after native AOT. + // Manually load them so that they can be loaded by RulesetStore.loadFromAppDomain. + // REMEMBER to fully uninstall previous version every time when investigating this! + // Don't forget osu.Game.Tests.Android too. + Assembly.Load("osu.Game.Rulesets.Osu"); + Assembly.Load("osu.Game.Rulesets.Taiko"); + Assembly.Load("osu.Game.Rulesets.Catch"); + Assembly.Load("osu.Game.Rulesets.Mania"); } protected override void OnNewIntent(Intent intent) => handleIntent(intent); @@ -127,7 +140,7 @@ namespace osu.Android cursor.MoveToFirst(); - int filenameColumn = cursor.GetColumnIndex(OpenableColumns.DisplayName); + int filenameColumn = cursor.GetColumnIndex(IOpenableColumns.DisplayName); string filename = cursor.GetString(filenameColumn); // SharpCompress requires archive streams to be seekable, which the stream opened by diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 1c6f41a7ec..0227d2aec2 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -5,7 +5,7 @@ using System; using Android.App; -using Android.OS; +using Microsoft.Maui.Devices; using osu.Framework.Allocation; using osu.Framework.Android.Input; using osu.Framework.Input.Handlers; @@ -14,7 +14,6 @@ using osu.Game; using osu.Game.Overlays.Settings; using osu.Game.Updater; using osu.Game.Utils; -using Xamarin.Essentials; namespace osu.Android { @@ -48,7 +47,7 @@ namespace osu.Android // https://stackoverflow.com/questions/52977079/android-sdk-28-versioncode-in-packageinfo-has-been-deprecated string versionName = string.Empty; - if (Build.VERSION.SdkInt >= BuildVersionCodes.P) + if (OperatingSystem.IsAndroidVersionAtLeast(28)) { versionName = packageInfo.LongVersionCode.ToString(); // ensure we only read the trailing portion of long (the part we are interested in). diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index 004cc8c39c..de53e5dd59 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -1,73 +1,19 @@ - - + - Debug - AnyCPU - 8.0.30703 - 2.0 - {D1D5F9A8-B40B-40E6-B02F-482D03346D3D} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {122416d6-6b49-4ee2-a1e8-b825f31c79fe} + net6.0-android + Exe osu.Android osu.Android - Properties\AndroidManifest.xml - armeabi-v7a;x86;arm64-v8a - false - - - cjk;mideast;other;rare;west - d8 - r8 - - - None - cjk;mideast;other;rare;west - true + true + + false - - - - - - + + + + + - - - - - - {58f6c80c-1253-4a0e-a465-b8c85ebeadf3} - osu.Game.Rulesets.Catch - - - {48f4582b-7687-4621-9cbe-5c24197cb536} - osu.Game.Rulesets.Mania - - - {c92a607b-1fdd-4954-9f92-03ff547d9080} - osu.Game.Rulesets.Osu - - - {f167e17a-7de6-4af5-b920-a5112296c695} - osu.Game.Rulesets.Taiko - - - {2a66dd92-adb1-4994-89e2-c94e04acda0d} - osu.Game - - - - - - - - 5.0.0 - - - - - - - \ No newline at end of file + diff --git a/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Catch.Tests.Android/AndroidManifest.xml similarity index 98% rename from osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml rename to osu.Game.Rulesets.Catch.Tests.Android/AndroidManifest.xml index f8c3fcd894..bf7c0bfeca 100644 --- a/osu.Game.Rulesets.Catch.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Rulesets.Catch.Tests.Android/AndroidManifest.xml @@ -1,6 +1,6 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj b/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj index 94fdba4a3e..4ee3219442 100644 --- a/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj +++ b/osu.Game.Rulesets.Catch.Tests.Android/osu.Game.Rulesets.Catch.Tests.Android.csproj @@ -1,49 +1,24 @@ - - + - Debug - AnyCPU - 8.0.30703 - 2.0 - {C5379ECB-3A94-4D2F-AC3B-2615AC23EB0D} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {122416d6-6b49-4ee2-a1e8-b825f31c79fe} + net6.0-android + Exe osu.Game.Rulesets.Catch.Tests osu.Game.Rulesets.Catch.Tests.Android - Properties\AndroidManifest.xml - armeabi-v7a;x86;arm64-v8a - - - None - cjk;mideast;other;rare;west - true - - - - - - - + %(RecursiveDir)%(Filename)%(Extension) + + + %(RecursiveDir)%(Filename)%(Extension) + Android\%(RecursiveDir)%(Filename)%(Extension) + - - {58f6c80c-1253-4a0e-a465-b8c85ebeadf3} - osu.Game.Rulesets.Catch - - - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} - osu.Game - + + - - - 5.0.0 - - - \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml similarity index 98% rename from osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml rename to osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml index de7935b2ef..4a1545a423 100644 --- a/osu.Game.Rulesets.Mania.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Rulesets.Mania.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj b/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj index 9674186039..25335754d2 100644 --- a/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj +++ b/osu.Game.Rulesets.Mania.Tests.Android/osu.Game.Rulesets.Mania.Tests.Android.csproj @@ -1,49 +1,24 @@ - - + - Debug - AnyCPU - 8.0.30703 - 2.0 - {531F1092-DB27-445D-AA33-2A77C7187C99} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {122416d6-6b49-4ee2-a1e8-b825f31c79fe} + net6.0-android + Exe osu.Game.Rulesets.Mania.Tests osu.Game.Rulesets.Mania.Tests.Android - Properties\AndroidManifest.xml - armeabi-v7a;x86;arm64-v8a - - - None - cjk;mideast;other;rare;west - true - - - - - - - + %(RecursiveDir)%(Filename)%(Extension) + + + %(RecursiveDir)%(Filename)%(Extension) + Android\%(RecursiveDir)%(Filename)%(Extension) + - - {48f4582b-7687-4621-9cbe-5c24197cb536} - osu.Game.Rulesets.Mania - - - {2A66DD92-ADB1-4994-89E2-C94E04ACDA0D} - osu.Game - + + - - - 5.0.0 - - - \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml similarity index 98% rename from osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml rename to osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml index 3ce17ccc27..45d27dda70 100644 --- a/osu.Game.Rulesets.Osu.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Rulesets.Osu.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj b/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj index f4b673f10b..e8a46a9828 100644 --- a/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj +++ b/osu.Game.Rulesets.Osu.Tests.Android/osu.Game.Rulesets.Osu.Tests.Android.csproj @@ -1,49 +1,27 @@ - - + - Debug - AnyCPU - 8.0.30703 - 2.0 - {90CAB706-39CB-4B93-9629-3218A6FF8E9B} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {122416d6-6b49-4ee2-a1e8-b825f31c79fe} + net6.0-android + Exe osu.Game.Rulesets.Osu.Tests osu.Game.Rulesets.Osu.Tests.Android - Properties\AndroidManifest.xml - armeabi-v7a;x86;arm64-v8a - - - None - cjk;mideast;other;rare;west - true - - - - - - - + %(RecursiveDir)%(Filename)%(Extension) + + + %(RecursiveDir)%(Filename)%(Extension) + Android\%(RecursiveDir)%(Filename)%(Extension) + - - {c92a607b-1fdd-4954-9f92-03ff547d9080} - osu.Game.Rulesets.Osu - - - {2a66dd92-adb1-4994-89e2-c94e04acda0d} - osu.Game - + + - - 5.0.0 - + - \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml similarity index 98% rename from osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml rename to osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml index d9de0fde4e..452b9683ec 100644 --- a/osu.Game.Rulesets.Taiko.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Rulesets.Taiko.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj b/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj index 4d4dabebe6..a639326ebd 100644 --- a/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj +++ b/osu.Game.Rulesets.Taiko.Tests.Android/osu.Game.Rulesets.Taiko.Tests.Android.csproj @@ -1,49 +1,24 @@ - - + - Debug - AnyCPU - 8.0.30703 - 2.0 - {3701A0A1-8476-42C6-B5C4-D24129B4A484} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {122416d6-6b49-4ee2-a1e8-b825f31c79fe} + net6.0-android + Exe osu.Game.Rulesets.Taiko.Tests osu.Game.Rulesets.Taiko.Tests.Android - Properties\AndroidManifest.xml - armeabi-v7a;x86;arm64-v8a - - - None - cjk;mideast;other;rare;west - true - - - - - - - + %(RecursiveDir)%(Filename)%(Extension) + + + %(RecursiveDir)%(Filename)%(Extension) + Android\%(RecursiveDir)%(Filename)%(Extension) + - - {f167e17a-7de6-4af5-b920-a5112296c695} - osu.Game.Rulesets.Taiko - - - {2a66dd92-adb1-4994-89e2-c94e04acda0d} - osu.Game - + + - - - 5.0.0 - - - \ No newline at end of file diff --git a/osu.Game.Tests.Android/Properties/AndroidManifest.xml b/osu.Game.Tests.Android/AndroidManifest.xml similarity index 98% rename from osu.Game.Tests.Android/Properties/AndroidManifest.xml rename to osu.Game.Tests.Android/AndroidManifest.xml index 4a63f0c357..f25b2e5328 100644 --- a/osu.Game.Tests.Android/Properties/AndroidManifest.xml +++ b/osu.Game.Tests.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Game.Tests.Android/MainActivity.cs b/osu.Game.Tests.Android/MainActivity.cs index 6c4f9bac58..bdb947fbb4 100644 --- a/osu.Game.Tests.Android/MainActivity.cs +++ b/osu.Game.Tests.Android/MainActivity.cs @@ -3,7 +3,9 @@ #nullable disable +using System.Reflection; using Android.App; +using Android.OS; using osu.Framework.Android; namespace osu.Game.Tests.Android @@ -12,5 +14,16 @@ namespace osu.Game.Tests.Android public class MainActivity : AndroidGameActivity { protected override Framework.Game CreateGame() => new OsuTestBrowser(); + + protected override void OnCreate(Bundle savedInstanceState) + { + base.OnCreate(savedInstanceState); + + // See the comment in OsuGameActivity + Assembly.Load("osu.Game.Rulesets.Osu"); + Assembly.Load("osu.Game.Rulesets.Taiko"); + Assembly.Load("osu.Game.Rulesets.Catch"); + Assembly.Load("osu.Game.Rulesets.Mania"); + } } } diff --git a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj index afafec6b1f..b745d91980 100644 --- a/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj +++ b/osu.Game.Tests.Android/osu.Game.Tests.Android.csproj @@ -1,88 +1,34 @@ - - + - Debug - AnyCPU - 8.0.30703 - 2.0 - {5CC222DC-5716-4499-B897-DCBDDA4A5CF9} - {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {122416d6-6b49-4ee2-a1e8-b825f31c79fe} + net6.0-android + Exe osu.Game.Tests osu.Game.Tests.Android - Properties\AndroidManifest.xml - armeabi-v7a;x86;arm64-v8a - - - - - - $(NoWarn);CA2007 - - None - cjk;mideast;other;rare;west - true - - + %(RecursiveDir)%(Filename)%(Extension) - - + + %(RecursiveDir)%(Filename)%(Extension) - - - %(RecursiveDir)%(Filename)%(Extension) - - - %(RecursiveDir)%(Filename)%(Extension) - - - %(RecursiveDir)%(Filename)%(Extension) - - - %(RecursiveDir)%(Filename)%(Extension) - - - %(RecursiveDir)%(Filename)%(Extension) - - - %(RecursiveDir)%(Filename)%(Extension) - + Android\%(RecursiveDir)%(Filename)%(Extension) + - - {58f6c80c-1253-4a0e-a465-b8c85ebeadf3} - osu.Game.Rulesets.Catch - - - {48f4582b-7687-4621-9cbe-5c24197cb536} - osu.Game.Rulesets.Mania - - - {c92a607b-1fdd-4954-9f92-03ff547d9080} - osu.Game.Rulesets.Osu - - - {f167e17a-7de6-4af5-b920-a5112296c695} - osu.Game.Rulesets.Taiko - - - {2a66dd92-adb1-4994-89e2-c94e04acda0d} - osu.Game - + + + + + - - 5.0.0 - - diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs index 8d82843134..d925141510 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentActions.cs @@ -189,7 +189,7 @@ namespace osu.Game.Tests.Visual.Online if (request is not CommentDeleteRequest req) return false; - req.TriggerFailure(new Exception()); + req.TriggerFailure(new InvalidOperationException()); delete = true; return false; }; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs index e47b7e25a8..11d55bc0bd 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneUpdateBeatmapSetButton.cs @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.SongSelect if (testRequest.Progress >= 0.5f) { - testRequest.TriggerFailure(new Exception()); + testRequest.TriggerFailure(new InvalidOperationException()); return true; } } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index adebbe6181..4114a3ed1e 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -114,7 +114,10 @@ namespace osu.Game.Rulesets { try { - string[] files = Directory.GetFiles(RuntimeInfo.StartupDirectory, @$"{ruleset_library_prefix}.*.dll"); + // On net6-android (Debug), StartupDirectory can be different from where assemblies are placed. + // Search sub-directories too. + + string[] files = Directory.GetFiles(RuntimeInfo.StartupDirectory, @$"{ruleset_library_prefix}.*.dll", SearchOption.AllDirectories); foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests"))) loadRulesetFromFile(file); diff --git a/osu.sln b/osu.sln index aeec0843be..5b23ff9ae6 100644 --- a/osu.sln +++ b/osu.sln @@ -63,6 +63,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution CodeAnalysis\osu.ruleset = CodeAnalysis\osu.ruleset osu.sln.DotSettings = osu.sln.DotSettings osu.TestProject.props = osu.TestProject.props + global.json = global.json EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Benchmarks", "osu.Game.Benchmarks\osu.Game.Benchmarks.csproj", "{93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}" From d3995693a012a2d1cd6f3306d41d44ec9c6a7084 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Dec 2022 16:39:03 +0900 Subject: [PATCH 172/384] Use `maui-android` --- .github/workflows/ci.yml | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 834b2b68a1..213c5082ab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,7 +114,7 @@ jobs: dotnet-version: "6.0.x" - name: Install .NET workloads - run: dotnet workload install android + run: dotnet workload install maui-android - name: Compile run: dotnet build -c Debug osu.Android.slnf diff --git a/appveyor.yml b/appveyor.yml index 3757cbf41f..ed48a997e8 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,7 +11,7 @@ dotnet_csproj: before_build: - cmd: dotnet --info # Useful when version mismatch between CI and local - - cmd: dotnet workload install android # Change to `dotnet workload restore` once there's no old projects + - cmd: dotnet workload install maui-android # Change to `dotnet workload restore` once there's no old projects - cmd: dotnet workload install maui-ios # Change to `dotnet workload restore` once there's no old projects - cmd: nuget restore -verbosity quiet # Only nuget.exe knows both new (.NET Core) and old (Xamarin) projects From 8a01a2261277047aad86b2ce7620026150490cd1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Dec 2022 16:41:04 +0900 Subject: [PATCH 173/384] Fix two null refrences in `OsuGame` --- osu.Game/OsuGame.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index db521cd379..a0a45e18a8 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -333,7 +333,7 @@ namespace osu.Game /// The link to load. public void HandleLink(LinkDetails link) => Schedule(() => { - string argString = link.Argument.ToString(); + string argString = link.Argument.ToString() ?? string.Empty; switch (link.Action) { @@ -1042,6 +1042,8 @@ namespace osu.Game { if (entry.Level < LogLevel.Important || entry.Target > LoggingTarget.Database) return; + Debug.Assert(entry.Target != null); + const int short_term_display_limit = 3; if (recentLogCount < short_term_display_limit) @@ -1054,7 +1056,7 @@ namespace osu.Game } else if (recentLogCount == short_term_display_limit) { - string logFile = $@"{entry.Target.ToString().ToLowerInvariant()}.log"; + string logFile = $@"{entry.Target.Value.ToString().ToLowerInvariant()}.log"; Schedule(() => Notifications.Post(new SimpleNotification { From 4a7d7c6ed96a1208841d9dea928078e424b71c40 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Dec 2022 16:42:21 +0900 Subject: [PATCH 174/384] Use `MaxBy` in all locations that can and update inspection level to match `dotnet-build` --- osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapGrid.cs | 4 +--- osu.Game.Tournament/Screens/Ladder/LadderScreen.cs | 4 ++-- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 4 ++-- osu.Game/Rulesets/RulesetStore.cs | 5 +---- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 2 +- osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs | 4 +--- osu.Game/Scoring/ScoreImporter.cs | 3 +-- .../Screens/Edit/Compose/Components/BeatDivisorControl.cs | 2 +- osu.Game/Screens/Edit/Compose/ComposeScreen.cs | 2 +- .../OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs | 4 +--- osu.Game/Storyboards/Storyboard.cs | 4 ++-- osu.Game/Storyboards/StoryboardSprite.cs | 2 +- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 4 +--- osu.sln.DotSettings | 1 + 14 files changed, 17 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapGrid.cs b/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapGrid.cs index e5cdcf706c..43918bda57 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchDistanceSnapGrid.cs @@ -121,9 +121,7 @@ namespace osu.Game.Rulesets.Catch.Edit return new SnapResult(originPosition, StartTime); } - return enumerateSnappingCandidates(time) - .OrderBy(pos => Vector2.DistanceSquared(screenSpacePosition, pos.ScreenSpacePosition)) - .FirstOrDefault(); + return enumerateSnappingCandidates(time).MinBy(pos => Vector2.DistanceSquared(screenSpacePosition, pos.ScreenSpacePosition)); } private IEnumerable enumerateSnappingCandidates(double time) diff --git a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs index f976f2b16b..176c06c0e5 100644 --- a/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs +++ b/osu.Game.Tournament/Screens/Ladder/LadderScreen.cs @@ -158,7 +158,7 @@ namespace osu.Game.Tournament.Screens.Ladder foreach (var round in LadderInfo.Rounds) { - var topMatch = MatchesContainer.Where(p => !p.Match.Losers.Value && p.Match.Round.Value == round).OrderBy(p => p.Y).FirstOrDefault(); + var topMatch = MatchesContainer.Where(p => !p.Match.Losers.Value && p.Match.Round.Value == round).MinBy(p => p.Y); if (topMatch == null) continue; @@ -172,7 +172,7 @@ namespace osu.Game.Tournament.Screens.Ladder foreach (var round in LadderInfo.Rounds) { - var topMatch = MatchesContainer.Where(p => p.Match.Losers.Value && p.Match.Round.Value == round).OrderBy(p => p.Y).FirstOrDefault(); + var topMatch = MatchesContainer.Where(p => p.Match.Losers.Value && p.Match.Round.Value == round).MinBy(p => p.Y); if (topMatch == null) continue; diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 5d308bd2bb..55119c800a 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -70,14 +70,14 @@ namespace osu.Game.Beatmaps.ControlPoints /// [JsonIgnore] public double BPMMaximum => - 60000 / (TimingPoints.OrderBy(c => c.BeatLength).FirstOrDefault() ?? TimingControlPoint.DEFAULT).BeatLength; + 60000 / (TimingPoints.MinBy(c => c.BeatLength) ?? TimingControlPoint.DEFAULT).BeatLength; /// /// Finds the minimum BPM represented by any timing control point. /// [JsonIgnore] public double BPMMinimum => - 60000 / (TimingPoints.OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? TimingControlPoint.DEFAULT).BeatLength; + 60000 / (TimingPoints.MaxBy(c => c.BeatLength) ?? TimingControlPoint.DEFAULT).BeatLength; /// /// Remove all s and return to a pristine state. diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 4114a3ed1e..881b09bd1b 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -75,10 +75,7 @@ namespace osu.Game.Rulesets return false; return args.Name.Contains(name, StringComparison.Ordinal); - }) - // Pick the greatest assembly version. - .OrderByDescending(a => a.GetName().Version) - .FirstOrDefault(); + }).MaxBy(a => a.GetName().Version); if (domainAssembly != null) return domainAssembly; diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index b048566c8a..4228840461 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -534,7 +534,7 @@ namespace osu.Game.Rulesets.Scoring break; default: - maxResult = maxBasicResult ??= ruleset.GetHitResults().OrderByDescending(kvp => Judgement.ToNumericResult(kvp.result)).First().result; + maxResult = maxBasicResult ??= ruleset.GetHitResults().MaxBy(kvp => Judgement.ToNumericResult(kvp.result)).result; break; } diff --git a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs index d068f8d016..d2244df3b8 100644 --- a/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs +++ b/osu.Game/Rulesets/UI/GameplaySampleTriggerSource.cs @@ -79,9 +79,7 @@ namespace osu.Game.Rulesets.UI // We need to use lifetime entries to find the next object (we can't just use `hitObjectContainer.Objects` due to pooling - it may even be empty). // If required, we can make this lookup more efficient by adding support to get next-future-entry in LifetimeEntryManager. fallbackObject = hitObjectContainer.Entries - .Where(e => e.Result?.HasResult != true) - .OrderBy(e => e.HitObject.StartTime) - .FirstOrDefault(); + .Where(e => e.Result?.HasResult != true).MinBy(e => e.HitObject.StartTime); // In the case there are no unjudged objects, the last hit object should be used instead. fallbackObject ??= hitObjectContainer.Entries.LastOrDefault(); diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 4656f02897..797d80b7fa 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -101,8 +101,7 @@ namespace osu.Game.Scoring // Populate the maximum statistics. HitResult maxBasicResult = rulesetInstance.GetHitResults() .Select(h => h.result) - .Where(h => h.IsBasic()) - .OrderByDescending(Judgement.ToNumericResult).First(); + .Where(h => h.IsBasic()).MaxBy(Judgement.ToNumericResult); foreach ((HitResult result, int count) in score.Statistics) { diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 903c117422..9f422d5aa9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -479,7 +479,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // copied from SliderBar so we can do custom spacing logic. float xPosition = (ToLocalSpace(screenSpaceMousePosition).X - RangePadding) / UsableWidth; - CurrentNumber.Value = beatDivisor.ValidDivisors.Value.Presets.OrderBy(d => Math.Abs(getMappedPosition(d) - xPosition)).First(); + CurrentNumber.Value = beatDivisor.ValidDivisors.Value.Presets.MinBy(d => Math.Abs(getMappedPosition(d) - xPosition)); OnUserChange(Current.Value); } diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 3af7a400e2..dc026f7eac 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Edit.Compose if (composer == null) return string.Empty; - double displayTime = EditorBeatmap.SelectedHitObjects.OrderBy(h => h.StartTime).FirstOrDefault()?.StartTime ?? clock.CurrentTime; + double displayTime = EditorBeatmap.SelectedHitObjects.MinBy(h => h.StartTime)?.StartTime ?? clock.CurrentTime; string selectionAsString = composer.ConvertSelectionToString(); return !string.IsNullOrEmpty(selectionAsString) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index 33ca806696..2d2aa0f1d5 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -171,9 +171,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate if (!isCandidateAudioSource(currentAudioSource?.SpectatorPlayerClock)) { - currentAudioSource = instances.Where(i => isCandidateAudioSource(i.SpectatorPlayerClock)) - .OrderBy(i => Math.Abs(i.SpectatorPlayerClock.CurrentTime - syncManager.CurrentMasterTime)) - .FirstOrDefault(); + currentAudioSource = instances.Where(i => isCandidateAudioSource(i.SpectatorPlayerClock)).MinBy(i => Math.Abs(i.SpectatorPlayerClock.CurrentTime - syncManager.CurrentMasterTime)); // Only bind adjustments if there's actually a valid source, else just use the previous ones to ensure no sudden changes to audio. if (currentAudioSource != null) diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 8133244e89..566e064aad 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -32,7 +32,7 @@ namespace osu.Game.Storyboards /// /// This iterates all elements and as such should be used sparingly or stored locally. /// - public double? EarliestEventTime => Layers.SelectMany(l => l.Elements).OrderBy(e => e.StartTime).FirstOrDefault()?.StartTime; + public double? EarliestEventTime => Layers.SelectMany(l => l.Elements).MinBy(e => e.StartTime)?.StartTime; /// /// Across all layers, find the latest point in time that a storyboard element ends at. @@ -42,7 +42,7 @@ namespace osu.Game.Storyboards /// This iterates all elements and as such should be used sparingly or stored locally. /// Videos and samples return StartTime as their EndTIme. /// - public double? LatestEventTime => Layers.SelectMany(l => l.Elements).OrderBy(e => e.GetEndTime()).LastOrDefault()?.GetEndTime(); + public double? LatestEventTime => Layers.SelectMany(l => l.Elements).MaxBy(e => e.GetEndTime())?.GetEndTime(); /// /// Depth of the currently front-most storyboard layer, excluding the overlay layer. diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index cd7788bb08..5b7b194be7 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -48,7 +48,7 @@ namespace osu.Game.Storyboards if (alphaCommands.Count > 0) { - var firstAlpha = alphaCommands.OrderBy(t => t.startTime).First(); + var firstAlpha = alphaCommands.MinBy(t => t.startTime); if (firstAlpha.isZeroStartValue) return firstAlpha.startTime; diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 765c665966..ad5e3f6c4d 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -108,9 +108,7 @@ namespace osu.Game.Tests.Visual.Multiplayer // simulate the server's automatic assignment of users to teams on join. // the "best" team is the one with the least users on it. int bestTeam = teamVersus.Teams - .Select(team => (teamID: team.ID, userCount: ServerRoom.Users.Count(u => (u.MatchState as TeamVersusUserState)?.TeamID == team.ID))) - .OrderBy(pair => pair.userCount) - .First().teamID; + .Select(team => (teamID: team.ID, userCount: ServerRoom.Users.Count(u => (u.MatchState as TeamVersusUserState)?.TeamID == team.ID))).MinBy(pair => pair.userCount).teamID; user.MatchState = new TeamVersusUserState { TeamID = bestTeam }; ((IMultiplayerClient)this).MatchUserStateChanged(clone(user.UserID), clone(user.MatchState)).WaitSafely(); diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 154ad0fe8c..ef3b08e1f5 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -218,6 +218,7 @@ WARNING WARNING WARNING + WARNING WARNING HINT WARNING From 12c77cca26b66c70e8efb0ae8d9a594c62b27fb8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Dec 2022 15:44:10 +0900 Subject: [PATCH 175/384] Remove `rollForward` rule for now --- global.json | 6 ------ osu.sln | 1 - 2 files changed, 7 deletions(-) delete mode 100644 global.json diff --git a/global.json b/global.json deleted file mode 100644 index f20ff84190..0000000000 --- a/global.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "sdk": { - "version": "6.0.300", - "rollForward": "major" - } -} diff --git a/osu.sln b/osu.sln index 5b23ff9ae6..aeec0843be 100644 --- a/osu.sln +++ b/osu.sln @@ -63,7 +63,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution CodeAnalysis\osu.ruleset = CodeAnalysis\osu.ruleset osu.sln.DotSettings = osu.sln.DotSettings osu.TestProject.props = osu.TestProject.props - global.json = global.json EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "osu.Game.Benchmarks", "osu.Game.Benchmarks\osu.Game.Benchmarks.csproj", "{93632F2D-2BB4-46C1-A7B8-F8CF2FB27118}" From 99ad78cdd631e5b999dd8ebcef6e978906fcb63c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Dec 2022 22:01:13 +0900 Subject: [PATCH 176/384] Remove no longer necessary workarounds from mobile props files --- osu.Android.props | 4 ---- osu.iOS.props | 9 --------- 2 files changed, 13 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 5da7eae380..a0ad0cdcdc 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -12,10 +12,6 @@ - - - - diff --git a/osu.iOS.props b/osu.iOS.props index 98d3e129d4..4dac17c024 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -15,15 +15,6 @@ iossimulator-x64 - - - - none - - - none - - From ecac6299c6679d6c29133cb9fab88e53520b7104 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Dec 2022 22:01:20 +0900 Subject: [PATCH 177/384] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index a0ad0cdcdc..31670bedb5 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8bb68faf7b..8b4fa2dc6b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 4dac17c024..0ff4fafb31 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -16,7 +16,7 @@ iossimulator-x64 - + - - - <_LinkerFrameworks Remove="Quartz"/> - - From 2249c97a6f1232f9c626ff0648fbf2c43714281a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 19 Dec 2022 18:27:19 +0300 Subject: [PATCH 179/384] Pin certain system package versions to avoid downgrade errors --- osu.iOS.props | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.iOS.props b/osu.iOS.props index b5f470eda1..c6798cd5e4 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,5 +17,10 @@ + + + + + From d47a8b2e26df2146e468e4606de0b3adf8690130 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 19 Dec 2022 18:42:51 +0300 Subject: [PATCH 180/384] Remove unnecessary discard --- osu.iOS/OsuGameIOS.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index cb3b260033..3e79bc6ad6 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -33,7 +33,7 @@ namespace osu.iOS { switch (handler) { - case IOSMouseHandler _: + case IOSMouseHandler: return new IOSMouseSettings(); default: From 36c620287b1a17faff7c6317dfa0159344b974ae Mon Sep 17 00:00:00 2001 From: Susko3 Date: Mon, 19 Dec 2022 18:11:05 +0100 Subject: [PATCH 181/384] Remove no longer needed `Linker.xml` configurations for mobile projects --- osu.Android.props | 3 --- osu.Android/Linker.xml | 7 ------- osu.iOS/Linker.xml | 31 ------------------------------- 3 files changed, 41 deletions(-) delete mode 100644 osu.Android/Linker.xml delete mode 100644 osu.iOS/Linker.xml diff --git a/osu.Android.props b/osu.Android.props index 31670bedb5..e934b2da6a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -12,9 +12,6 @@ - - - diff --git a/osu.Android/Linker.xml b/osu.Android/Linker.xml deleted file mode 100644 index c7d31fe087..0000000000 --- a/osu.Android/Linker.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/osu.iOS/Linker.xml b/osu.iOS/Linker.xml deleted file mode 100644 index b55be3be39..0000000000 --- a/osu.iOS/Linker.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 186ccc64fd26e5e56a5cffbeff25302a9c3d2aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Dec 2022 18:19:26 +0100 Subject: [PATCH 182/384] Fix welcome screen language buttons not working --- osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs index fe3aaeb9d8..f6133e3643 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs @@ -147,7 +147,7 @@ namespace osu.Game.Overlays.FirstRunSetup [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] + AddRange(new Drawable[] { backgroundBox = new Box { @@ -162,7 +162,7 @@ namespace osu.Game.Overlays.FirstRunSetup Colour = colourProvider.Light1, Text = Language.GetDescription(), } - }; + }); } protected override void LoadComplete() From 0dce8996344e162e746bf783f43d2c193e4d146f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Dec 2022 18:25:45 +0100 Subject: [PATCH 183/384] Throw on `OsuClickableContainer.ClearInternal()` invocations As they may cause critical failure due to getting rid of the `Content`. --- osu.Game/Graphics/Containers/OsuClickableContainer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index 6ec393df4b..ce50dbdc39 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -46,5 +47,8 @@ namespace osu.Game.Graphics.Containers AddInternal(content); Add(CreateHoverSounds(sampleSet)); } + + protected override void ClearInternal(bool disposeChildren = true) => + throw new InvalidOperationException($"Clearing {nameof(InternalChildren)} will cause critical failure. Use {nameof(Clear)} instead."); } } From 6e55f2f779f69d942df61ca62676766c717d04b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 16 Dec 2022 20:57:27 +0100 Subject: [PATCH 184/384] Add test for markdown footnotes --- .../Visual/Online/TestSceneWikiMarkdownContainer.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index b486f800c6..c0a7b9b921 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -199,6 +199,17 @@ Line after image"; }); } + [Test] + public void TestFootnotes() + { + AddStep("set content", () => markdownContainer.Text = @"This text has a footnote[^test]. + +Here's some more text[^test2] with another footnote! + +[^test]: This is a **footnote**. +[^test2]: This is another footnote [with a link](https://google.com/)!"); + } + private partial class TestMarkdownContainer : WikiMarkdownContainer { public LinkInline Link; From 112613c2f09c717d75ec32b30fab8ef3336bde5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 16 Dec 2022 20:59:14 +0100 Subject: [PATCH 185/384] Add styling for footnote links --- .../Footnotes/OsuMarkdownFootnoteLink.cs | 70 +++++++++++++++++ .../Footnotes/OsuMarkdownFootnoteTooltip.cs | 76 +++++++++++++++++++ .../Markdown/OsuMarkdownContainer.cs | 2 + .../Markdown/OsuMarkdownTextFlowContainer.cs | 4 + 4 files changed, 152 insertions(+) create mode 100644 osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteLink.cs create mode 100644 osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteTooltip.cs diff --git a/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteLink.cs b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteLink.cs new file mode 100644 index 0000000000..b35c08e335 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteLink.cs @@ -0,0 +1,70 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using Markdig.Extensions.Footnotes; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.Containers.Markdown.Footnotes +{ + public partial class OsuMarkdownFootnoteLink : OsuHoverContainer, IHasCustomTooltip + { + private readonly FootnoteLink footnoteLink; + + private SpriteText spriteText = null!; + + [Resolved] + private IMarkdownTextComponent parentTextComponent { get; set; } = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + [Resolved] + private OsuMarkdownContainer markdownContainer { get; set; } = null!; + + protected override IEnumerable EffectTargets => spriteText.Yield(); + + public OsuMarkdownFootnoteLink(FootnoteLink footnoteLink) + { + this.footnoteLink = footnoteLink; + + AutoSizeAxes = Axes.Both; + Action = () => { }; // TODO + } + + [BackgroundDependencyLoader] + private void load() + { + IdleColour = colourProvider.Light2; + HoverColour = colourProvider.Light1; + + spriteText = parentTextComponent.CreateSpriteText(); + + Add(spriteText.With(t => + { + float baseSize = t.Font.Size; + t.Font = t.Font.With(size: baseSize * 0.58f); + t.Margin = new MarginPadding { Bottom = 0.33f * baseSize }; + t.Text = LocalisableString.Format("[{0}]", footnoteLink.Index); + })); + } + + public object TooltipContent + { + get + { + var span = footnoteLink.Footnote.LastChild.Span; + return markdownContainer.Text.Substring(span.Start, span.Length); + } + } + + public ITooltip GetCustomTooltip() => new OsuMarkdownFootnoteTooltip(colourProvider); + } +} diff --git a/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteTooltip.cs b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteTooltip.cs new file mode 100644 index 0000000000..af64913212 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteTooltip.cs @@ -0,0 +1,76 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig.Extensions.Footnotes; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Graphics.Containers.Markdown.Footnotes +{ + public partial class OsuMarkdownFootnoteTooltip : CompositeDrawable, ITooltip + { + private readonly FootnoteMarkdownContainer markdownContainer; + + [Cached] + private OverlayColourProvider colourProvider; + + public OsuMarkdownFootnoteTooltip(OverlayColourProvider colourProvider) + { + this.colourProvider = colourProvider; + + Masking = true; + Width = 200; + AutoSizeAxes = Axes.Y; + CornerRadius = 4; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6 + }, + markdownContainer = new FootnoteMarkdownContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + DocumentMargin = new MarginPadding(), + DocumentPadding = new MarginPadding { Horizontal = 10, Vertical = 5 } + } + }; + } + + public void Move(Vector2 pos) => Position = pos; + + public void SetContent(object content) => markdownContainer.SetContent((string)content); + + private partial class FootnoteMarkdownContainer : OsuMarkdownContainer + { + private string? lastFootnote; + + public void SetContent(string footnote) + { + if (footnote == lastFootnote) + return; + + lastFootnote = Text = footnote; + } + + public override MarkdownTextFlowContainer CreateTextFlow() => new FootnoteMarkdownTextFlowContainer(); + } + + private partial class FootnoteMarkdownTextFlowContainer : OsuMarkdownTextFlowContainer + { + protected override void AddFootnoteBacklink(FootnoteLink footnoteBacklink) + { + // we don't want footnote backlinks to show up in tooltips. + } + } + } +} diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index e884b5db69..a7d518c693 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -11,6 +11,7 @@ using Markdig.Extensions.Footnotes; using Markdig.Extensions.Tables; using Markdig.Extensions.Yaml; using Markdig.Syntax; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; @@ -19,6 +20,7 @@ using osu.Game.Graphics.Sprites; namespace osu.Game.Graphics.Containers.Markdown { + [Cached] public partial class OsuMarkdownContainer : MarkdownContainer { /// diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs index 7de63fe09c..0cafb68648 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs @@ -6,6 +6,7 @@ using System; using System.Linq; using Markdig.Extensions.CustomContainers; +using Markdig.Extensions.Footnotes; using Markdig.Syntax.Inlines; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -13,6 +14,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Containers.Markdown.Footnotes; using osu.Game.Overlays; using osu.Game.Users; using osu.Game.Users.Drawables; @@ -36,6 +38,8 @@ namespace osu.Game.Graphics.Containers.Markdown Text = codeInline.Content }); + protected override void AddFootnoteLink(FootnoteLink footnoteLink) => AddDrawable(new OsuMarkdownFootnoteLink(footnoteLink)); + protected override SpriteText CreateEmphasisedSpriteText(bool bold, bool italic) => CreateSpriteText().With(t => t.Font = t.Font.With(weight: bold ? FontWeight.Bold : FontWeight.Regular, italics: italic)); From 73a4310935a857f18c455c25579aa8a212c453d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 16 Dec 2022 21:26:13 +0100 Subject: [PATCH 186/384] Add styling for footnote groups --- .../Markdown/Footnotes/OsuMarkdownFootnote.cs | 30 +++++++++++ .../Footnotes/OsuMarkdownFootnoteBacklink.cs | 52 +++++++++++++++++++ .../Markdown/OsuMarkdownContainer.cs | 7 +++ .../Markdown/OsuMarkdownTextFlowContainer.cs | 2 + 4 files changed, 91 insertions(+) create mode 100644 osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnote.cs create mode 100644 osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteBacklink.cs diff --git a/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnote.cs b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnote.cs new file mode 100644 index 0000000000..e92d866eed --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnote.cs @@ -0,0 +1,30 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig.Extensions.Footnotes; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Containers.Markdown.Footnotes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; + +namespace osu.Game.Graphics.Containers.Markdown.Footnotes +{ + public partial class OsuMarkdownFootnote : MarkdownFootnote + { + public OsuMarkdownFootnote(Footnote footnote) + : base(footnote) + { + } + + public override SpriteText CreateOrderMarker(int order) => CreateSpriteText().With(marker => + { + marker.Text = LocalisableString.Format("{0}.", order); + }); + + public override MarkdownTextFlowContainer CreateTextFlow() => base.CreateTextFlow().With(textFlow => + { + textFlow.Margin = new MarginPadding { Left = 30 }; + }); + } +} diff --git a/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteBacklink.cs b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteBacklink.cs new file mode 100644 index 0000000000..a401eadaa3 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteBacklink.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using Markdig.Extensions.Footnotes; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Sprites; +using osu.Game.Overlays; +using osuTK; + +namespace osu.Game.Graphics.Containers.Markdown.Footnotes +{ + public partial class OsuMarkdownFootnoteBacklink : OsuHoverContainer + { + private readonly FootnoteLink backlink; + + private SpriteIcon spriteIcon = null!; + + [Resolved] + private IMarkdownTextComponent parentTextComponent { get; set; } = null!; + + protected override IEnumerable EffectTargets => spriteIcon.Yield(); + + public OsuMarkdownFootnoteBacklink(FootnoteLink backlink) + { + this.backlink = backlink; + AutoSizeAxes = Axes.X; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + float fontSize = parentTextComponent.CreateSpriteText().Font.Size; + Height = fontSize; + + IdleColour = colourProvider.Light2; + HoverColour = colourProvider.Light1; + + Add(spriteIcon = new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Left = 5 }, + Size = new Vector2(fontSize / 2), + Icon = FontAwesome.Solid.ArrowUp, + }); + } + } +} diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index a7d518c693..65e1749261 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -15,8 +15,11 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers.Markdown; +using osu.Framework.Graphics.Containers.Markdown.Footnotes; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Containers.Markdown.Footnotes; using osu.Game.Graphics.Sprites; +using osuTK; namespace osu.Game.Graphics.Containers.Markdown { @@ -101,6 +104,10 @@ namespace osu.Game.Graphics.Containers.Markdown return new OsuMarkdownUnorderedListItem(level); } + protected override MarkdownFootnoteGroup CreateFootnoteGroup(FootnoteGroup footnoteGroup) => base.CreateFootnoteGroup(footnoteGroup).With(g => g.Spacing = new Vector2(5)); + + protected override MarkdownFootnote CreateFootnote(Footnote footnote) => new OsuMarkdownFootnote(footnote); + // reference: https://github.com/ppy/osu-web/blob/05488a96b25b5a09f2d97c54c06dd2bae59d1dc8/app/Libraries/Markdown/OsuMarkdown.php#L301 protected override MarkdownPipeline CreateBuilder() { diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs index 0cafb68648..5f5b9acf56 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownTextFlowContainer.cs @@ -40,6 +40,8 @@ namespace osu.Game.Graphics.Containers.Markdown protected override void AddFootnoteLink(FootnoteLink footnoteLink) => AddDrawable(new OsuMarkdownFootnoteLink(footnoteLink)); + protected override void AddFootnoteBacklink(FootnoteLink footnoteBacklink) => AddDrawable(new OsuMarkdownFootnoteBacklink(footnoteBacklink)); + protected override SpriteText CreateEmphasisedSpriteText(bool bold, bool italic) => CreateSpriteText().With(t => t.Font = t.Font.With(weight: bold ? FontWeight.Bold : FontWeight.Regular, italics: italic)); From 3c1a46605ef59a6a8b633c5961d3d7989bfa4025 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 16 Dec 2022 21:34:12 +0100 Subject: [PATCH 187/384] Set up test for two-way navigation testing --- .../Online/TestSceneWikiMarkdownContainer.cs | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index c0a7b9b921..b1edea1e4e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -31,6 +31,8 @@ namespace osu.Game.Tests.Visual.Online [SetUp] public void Setup() => Schedule(() => { + OverlayScrollContainer scrollContainer; + Children = new Drawable[] { new Box @@ -38,15 +40,25 @@ namespace osu.Game.Tests.Visual.Online Colour = overlayColour.Background5, RelativeSizeAxes = Axes.Both, }, - new BasicScrollContainer + scrollContainer = new OverlayScrollContainer { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(20), - Child = markdownContainer = new TestMarkdownContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - } + } + }; + + scrollContainer.Child = new DependencyProvidingContainer + { + CachedDependencies = new (Type, object)[] + { + (typeof(OverlayScrollContainer), scrollContainer) + }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = markdownContainer = new TestMarkdownContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, } }; }); @@ -206,6 +218,16 @@ Line after image"; Here's some more text[^test2] with another footnote! +# Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam efficitur laoreet posuere. Ut accumsan tortor in ipsum tincidunt ultrices. Suspendisse a malesuada tellus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Fusce a sagittis nibh. In et velit sit amet mauris aliquet consectetur quis vehicula lorem. Etiam sit amet tellus ac velit ornare maximus. Donec quis metus eget libero ullamcorper imperdiet id vitae arcu. Vivamus iaculis rhoncus purus malesuada mollis. Vestibulum dictum at nisi sed tincidunt. Suspendisse finibus, ipsum ut dapibus commodo, leo eros porttitor sapien, non scelerisque nisi ligula sed ex. Pellentesque magna orci, hendrerit eu iaculis sit amet, ullamcorper in urna. Vivamus dictum mauris orci, nec facilisis dolor fringilla eu. Sed at porttitor nisi, at venenatis urna. Ut at orci vitae libero semper ullamcorper eu ut risus. Mauris hendrerit varius enim, ut varius nisi feugiat mattis. + +## In at eros urna. Sed ipsum lorem, tempor sit amet purus in, vehicula pellentesque leo. Fusce volutpat pellentesque velit sit amet porttitor. Nulla eget erat ex. Praesent eu lacinia est, quis vehicula lacus. Donec consequat ultrices neque, at finibus quam efficitur vel. Vestibulum molestie nisl sit amet metus semper, at vestibulum massa rhoncus. Quisque imperdiet suscipit augue, et dignissim odio eleifend ut. + +Aliquam sed vestibulum mauris, ut lobortis elit. Sed quis lacinia erat. Nam ultricies, risus non pellentesque sollicitudin, mauris dolor tincidunt neque, ac porta ipsum dui quis libero. Integer eget velit neque. Vestibulum venenatis mauris vitae rutrum vestibulum. Maecenas suscipit eu purus eu tempus. Nam dui nisl, bibendum condimentum mollis et, gravida vel dui. Sed et eros rutrum, facilisis sapien eu, mattis ligula. Fusce finibus pulvinar dolor quis consequat. + +Donec ipsum felis, feugiat vel fermentum at, commodo eu sapien. Suspendisse nec enim vitae felis laoreet laoreet. Phasellus purus quam, fermentum a pharetra vel, tempor et urna. Integer vitae quam diam. Aliquam tincidunt tortor a iaculis convallis. Suspendisse potenti. Cras quis risus quam. Nullam tincidunt in lorem posuere sagittis. + +Phasellus eu nunc nec ligula semper fringilla. Aliquam magna neque, placerat sed urna tristique, laoreet pharetra nulla. Vivamus maximus turpis purus, eu viverra dolor sodales porttitor. Praesent bibendum sapien purus, sed ultricies dolor iaculis sed. Fusce congue hendrerit malesuada. Nulla nulla est, auctor ac fringilla sed, ornare a lorem. Donec quis velit imperdiet, imperdiet sem non, pellentesque sapien. Maecenas in orci id ipsum placerat facilisis non sed nisi. Duis dictum lorem sodales odio dictum eleifend. Vestibulum bibendum euismod quam, eget pharetra orci facilisis sed. Vivamus at diam non ipsum consequat tristique. Pellentesque gravida dignissim pellentesque. Donec ullamcorper lacinia orci, id consequat purus faucibus quis. Phasellus metus nunc, iaculis a interdum vel, congue sed erat. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Etiam eros libero, hendrerit luctus nulla vitae, luctus maximus nunc. + [^test]: This is a **footnote**. [^test2]: This is another footnote [with a link](https://google.com/)!"); } From 5fb2a83f12bf10f85cacc7b000847394a1691379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 16 Dec 2022 21:53:31 +0100 Subject: [PATCH 188/384] Add failing test case for footnote link navigation --- .../Online/TestSceneWikiMarkdownContainer.cs | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index b1edea1e4e..3ec29d5e7c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -16,13 +16,16 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Graphics.Containers.Markdown; +using osu.Game.Graphics.Containers.Markdown.Footnotes; using osu.Game.Overlays; using osu.Game.Overlays.Wiki.Markdown; +using osuTK.Input; namespace osu.Game.Tests.Visual.Online { - public partial class TestSceneWikiMarkdownContainer : OsuTestScene + public partial class TestSceneWikiMarkdownContainer : OsuManualInputManagerTestScene { + private OverlayScrollContainer scrollContainer; private TestMarkdownContainer markdownContainer; [Cached] @@ -31,8 +34,6 @@ namespace osu.Game.Tests.Visual.Online [SetUp] public void Setup() => Schedule(() => { - OverlayScrollContainer scrollContainer; - Children = new Drawable[] { new Box @@ -230,6 +231,31 @@ Phasellus eu nunc nec ligula semper fringilla. Aliquam magna neque, placerat sed [^test]: This is a **footnote**. [^test2]: This is another footnote [with a link](https://google.com/)!"); + AddStep("shrink scroll height", () => scrollContainer.Height = 0.5f); + + AddStep("press second footnote link", () => + { + InputManager.MoveMouseTo(markdownContainer.ChildrenOfType().ElementAt(1)); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("second footnote scrolled into view", () => + { + var footnote = markdownContainer.ChildrenOfType().ElementAt(1); + return scrollContainer.ScreenSpaceDrawQuad.Contains(footnote.ScreenSpaceDrawQuad.TopLeft) + && scrollContainer.ScreenSpaceDrawQuad.Contains(footnote.ScreenSpaceDrawQuad.BottomRight); + }); + + AddStep("press first footnote backlink", () => + { + InputManager.MoveMouseTo(markdownContainer.ChildrenOfType().First()); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("first footnote link scrolled into view", () => + { + var footnote = markdownContainer.ChildrenOfType().First(); + return scrollContainer.ScreenSpaceDrawQuad.Contains(footnote.ScreenSpaceDrawQuad.TopLeft) + && scrollContainer.ScreenSpaceDrawQuad.Contains(footnote.ScreenSpaceDrawQuad.BottomRight); + }); } private partial class TestMarkdownContainer : WikiMarkdownContainer From a88812861e933820a76fbd11b29f585f0b98268a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 16 Dec 2022 21:56:32 +0100 Subject: [PATCH 189/384] Implement bidirectional footnote link navigation --- .../Footnotes/OsuMarkdownFootnoteBacklink.cs | 18 ++++++++++---- .../Footnotes/OsuMarkdownFootnoteLink.cs | 24 +++++++++++++------ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteBacklink.cs b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteBacklink.cs index a401eadaa3..22c02ea720 100644 --- a/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteBacklink.cs +++ b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteBacklink.cs @@ -2,12 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using Markdig.Extensions.Footnotes; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; using osu.Game.Overlays; using osuTK; @@ -27,14 +29,13 @@ namespace osu.Game.Graphics.Containers.Markdown.Footnotes public OsuMarkdownFootnoteBacklink(FootnoteLink backlink) { this.backlink = backlink; - AutoSizeAxes = Axes.X; } - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) + [BackgroundDependencyLoader(true)] + private void load(OverlayColourProvider colourProvider, OsuMarkdownContainer markdownContainer, OverlayScrollContainer? scrollContainer) { float fontSize = parentTextComponent.CreateSpriteText().Font.Size; - Height = fontSize; + Size = new Vector2(fontSize); IdleColour = colourProvider.Light2; HoverColour = colourProvider.Light1; @@ -47,6 +48,15 @@ namespace osu.Game.Graphics.Containers.Markdown.Footnotes Size = new Vector2(fontSize / 2), Icon = FontAwesome.Solid.ArrowUp, }); + + if (scrollContainer != null) + { + Action = () => + { + var footnoteLink = markdownContainer.ChildrenOfType().Single(footnoteLink => footnoteLink.FootnoteLink.Index == backlink.Index); + scrollContainer.ScrollIntoView(footnoteLink); + }; + } } } } diff --git a/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteLink.cs b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteLink.cs index b35c08e335..c9bd408e9e 100644 --- a/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteLink.cs +++ b/osu.Game/Graphics/Containers/Markdown/Footnotes/OsuMarkdownFootnoteLink.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using Markdig.Extensions.Footnotes; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; @@ -10,13 +11,14 @@ using osu.Framework.Graphics.Containers.Markdown; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Framework.Testing; using osu.Game.Overlays; namespace osu.Game.Graphics.Containers.Markdown.Footnotes { public partial class OsuMarkdownFootnoteLink : OsuHoverContainer, IHasCustomTooltip { - private readonly FootnoteLink footnoteLink; + public readonly FootnoteLink FootnoteLink; private SpriteText spriteText = null!; @@ -33,14 +35,13 @@ namespace osu.Game.Graphics.Containers.Markdown.Footnotes public OsuMarkdownFootnoteLink(FootnoteLink footnoteLink) { - this.footnoteLink = footnoteLink; + FootnoteLink = footnoteLink; AutoSizeAxes = Axes.Both; - Action = () => { }; // TODO } - [BackgroundDependencyLoader] - private void load() + [BackgroundDependencyLoader(true)] + private void load(OsuMarkdownContainer markdownContainer, OverlayScrollContainer? scrollContainer) { IdleColour = colourProvider.Light2; HoverColour = colourProvider.Light1; @@ -52,15 +53,24 @@ namespace osu.Game.Graphics.Containers.Markdown.Footnotes float baseSize = t.Font.Size; t.Font = t.Font.With(size: baseSize * 0.58f); t.Margin = new MarginPadding { Bottom = 0.33f * baseSize }; - t.Text = LocalisableString.Format("[{0}]", footnoteLink.Index); + t.Text = LocalisableString.Format("[{0}]", FootnoteLink.Index); })); + + if (scrollContainer != null) + { + Action = () => + { + var footnote = markdownContainer.ChildrenOfType().Single(footnote => footnote.Footnote.Label == FootnoteLink.Footnote.Label); + scrollContainer.ScrollIntoView(footnote); + }; + } } public object TooltipContent { get { - var span = footnoteLink.Footnote.LastChild.Span; + var span = FootnoteLink.Footnote.LastChild.Span; return markdownContainer.Text.Substring(span.Start, span.Length); } } From db1380a346799cd00ef2a4a26227087cd5ae1d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Dec 2022 20:16:36 +0100 Subject: [PATCH 190/384] Refactor markdown extension management --- .../Markdown/OsuMarkdownContainer.cs | 48 +++------------ .../Markdown/OsuMarkdownContainerOptions.cs | 61 +++++++++++++++++++ .../Comments/CommentMarkdownContainer.cs | 5 +- .../Wiki/Markdown/WikiMarkdownContainer.cs | 7 ++- 4 files changed, 77 insertions(+), 44 deletions(-) create mode 100644 osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainerOptions.cs diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs index e884b5db69..7e9581c056 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainer.cs @@ -4,10 +4,6 @@ #nullable disable using Markdig; -using Markdig.Extensions.AutoLinks; -using Markdig.Extensions.CustomContainers; -using Markdig.Extensions.EmphasisExtras; -using Markdig.Extensions.Footnotes; using Markdig.Extensions.Tables; using Markdig.Extensions.Yaml; using Markdig.Syntax; @@ -21,24 +17,6 @@ namespace osu.Game.Graphics.Containers.Markdown { public partial class OsuMarkdownContainer : MarkdownContainer { - /// - /// Allows this markdown container to parse and link footnotes. - /// - /// - protected virtual bool Footnotes => false; - - /// - /// Allows this markdown container to make URL text clickable. - /// - /// - protected virtual bool Autolinks => false; - - /// - /// Allows this markdown container to parse custom containers (used for flags and infoboxes). - /// - /// - protected virtual bool CustomContainers => false; - public OsuMarkdownContainer() { LineSpacing = 21; @@ -99,25 +77,13 @@ namespace osu.Game.Graphics.Containers.Markdown return new OsuMarkdownUnorderedListItem(level); } - // reference: https://github.com/ppy/osu-web/blob/05488a96b25b5a09f2d97c54c06dd2bae59d1dc8/app/Libraries/Markdown/OsuMarkdown.php#L301 - protected override MarkdownPipeline CreateBuilder() - { - var pipeline = new MarkdownPipelineBuilder() - .UseAutoIdentifiers() - .UsePipeTables() - .UseEmphasisExtras(EmphasisExtraOptions.Strikethrough) - .UseYamlFrontMatter(); + protected sealed override MarkdownPipeline CreateBuilder() + => Options.BuildPipeline(); - if (Footnotes) - pipeline = pipeline.UseFootnotes(); - - if (Autolinks) - pipeline = pipeline.UseAutoLinks(); - - if (CustomContainers) - pipeline.UseCustomContainers(); - - return pipeline.Build(); - } + /// + /// Creates a instance which is used to determine + /// which CommonMark/Markdig extensions should be enabled for this . + /// + protected virtual OsuMarkdownContainerOptions Options => new OsuMarkdownContainerOptions(); } } diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainerOptions.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainerOptions.cs new file mode 100644 index 0000000000..f2fd0e06a6 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainerOptions.cs @@ -0,0 +1,61 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig; +using Markdig.Extensions.AutoLinks; +using Markdig.Extensions.CustomContainers; +using Markdig.Extensions.EmphasisExtras; +using Markdig.Extensions.Footnotes; + +namespace osu.Game.Graphics.Containers.Markdown +{ + /// + /// Groups options of customising the set of available extensions to instances. + /// + public class OsuMarkdownContainerOptions + { + /// + /// Allows the to parse and link footnotes. + /// + /// + public bool Footnotes { get; init; } + + /// + /// Allows the container to make URL text clickable. + /// + /// + public bool Autolinks { get; init; } + + /// + /// Allows the to parse custom containers (used for flags and infoboxes). + /// + /// + public bool CustomContainers { get; init; } + + /// + /// Returns a prepared according to the options specified by the current instance. + /// + /// + /// Compare: https://github.com/ppy/osu-web/blob/05488a96b25b5a09f2d97c54c06dd2bae59d1dc8/app/Libraries/Markdown/OsuMarkdown.php#L301 + /// + public MarkdownPipeline BuildPipeline() + { + var pipeline = new MarkdownPipelineBuilder() + .UseAutoIdentifiers() + .UsePipeTables() + .UseEmphasisExtras(EmphasisExtraOptions.Strikethrough) + .UseYamlFrontMatter(); + + if (Footnotes) + pipeline = pipeline.UseFootnotes(); + + if (Autolinks) + pipeline = pipeline.UseAutoLinks(); + + if (CustomContainers) + pipeline.UseCustomContainers(); + + return pipeline.Build(); + } + } +} diff --git a/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs b/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs index 664946fc63..9cc20caa05 100644 --- a/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs +++ b/osu.Game/Overlays/Comments/CommentMarkdownContainer.cs @@ -11,7 +11,10 @@ namespace osu.Game.Overlays.Comments { public partial class CommentMarkdownContainer : OsuMarkdownContainer { - protected override bool Autolinks => true; + protected override OsuMarkdownContainerOptions Options => new OsuMarkdownContainerOptions + { + Autolinks = true + }; protected override MarkdownHeading CreateHeading(HeadingBlock headingBlock) => new CommentMarkdownHeading(headingBlock); diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs index 4ef9be90c9..bb296c5d41 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs @@ -16,8 +16,11 @@ namespace osu.Game.Overlays.Wiki.Markdown { public partial class WikiMarkdownContainer : OsuMarkdownContainer { - protected override bool Footnotes => true; - protected override bool CustomContainers => true; + protected override OsuMarkdownContainerOptions Options => new OsuMarkdownContainerOptions + { + Footnotes = true, + CustomContainers = true + }; public string CurrentPath { From 12aa2e96db35ebff527dc153760048e2c6bf0e24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Dec 2022 21:21:18 +0100 Subject: [PATCH 191/384] Add test case covering failure --- .../Visual/Online/TestSceneWikiMarkdownContainer.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index b486f800c6..6185c43f99 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -199,6 +199,16 @@ Line after image"; }); } + [Test] + public void TestHeadingWithIdAttribute() + { + AddStep("Add heading with ID", () => + { + markdownContainer.Text = "# This is a heading with an ID {#this-is-the-id}"; + }); + AddAssert("ID not visible", () => markdownContainer.ChildrenOfType().All(spriteText => spriteText.Text != "{#this-is-the-id}")); + } + private partial class TestMarkdownContainer : WikiMarkdownContainer { public LinkInline Link; From 7c282d9def40871269abd7532a9545d2b6383bc7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Dec 2022 20:23:01 +0100 Subject: [PATCH 192/384] Enable generic attribute support for wiki markdown containers --- .../Extensions/BlockAttributeExtension.cs | 34 +++++++++++++++++++ .../Extensions/OsuMarkdownExtensions.cs | 21 ++++++++++++ .../Markdown/OsuMarkdownContainerOptions.cs | 12 ++++++- .../Wiki/Markdown/WikiMarkdownContainer.cs | 3 +- 4 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Graphics/Containers/Markdown/Extensions/BlockAttributeExtension.cs create mode 100644 osu.Game/Graphics/Containers/Markdown/Extensions/OsuMarkdownExtensions.cs diff --git a/osu.Game/Graphics/Containers/Markdown/Extensions/BlockAttributeExtension.cs b/osu.Game/Graphics/Containers/Markdown/Extensions/BlockAttributeExtension.cs new file mode 100644 index 0000000000..caed4b26b9 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/Extensions/BlockAttributeExtension.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig; +using Markdig.Extensions.GenericAttributes; +using Markdig.Renderers; +using Markdig.Syntax; + +namespace osu.Game.Graphics.Containers.Markdown.Extensions +{ + /// + /// A variant of + /// which only handles generic attributes in the current markdown and ignores inline generic attributes. + /// + /// + /// For rationale, see implementation of . + /// + public class BlockAttributeExtension : IMarkdownExtension + { + private readonly GenericAttributesExtension genericAttributesExtension = new GenericAttributesExtension(); + + public void Setup(MarkdownPipelineBuilder pipeline) + { + genericAttributesExtension.Setup(pipeline); + + // GenericAttributesExtension registers a GenericAttributesParser in pipeline.InlineParsers. + // this conflicts with the CustomContainerExtension, leading to some custom containers (e.g. flags) not displaying. + // as a workaround, remove the inline parser here before it can do damage. + pipeline.InlineParsers.RemoveAll(parser => parser is GenericAttributesParser); + } + + public void Setup(MarkdownPipeline pipeline, IMarkdownRenderer renderer) => genericAttributesExtension.Setup(pipeline, renderer); + } +} diff --git a/osu.Game/Graphics/Containers/Markdown/Extensions/OsuMarkdownExtensions.cs b/osu.Game/Graphics/Containers/Markdown/Extensions/OsuMarkdownExtensions.cs new file mode 100644 index 0000000000..10542abe71 --- /dev/null +++ b/osu.Game/Graphics/Containers/Markdown/Extensions/OsuMarkdownExtensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Markdig; + +namespace osu.Game.Graphics.Containers.Markdown.Extensions +{ + public static class OsuMarkdownExtensions + { + /// + /// Uses the block attributes extension. + /// + /// The pipeline. + /// The modified pipeline. + public static MarkdownPipelineBuilder UseBlockAttributes(this MarkdownPipelineBuilder pipeline) + { + pipeline.Extensions.AddIfNotAlready(); + return pipeline; + } + } +} diff --git a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainerOptions.cs b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainerOptions.cs index f2fd0e06a6..1648ffbf90 100644 --- a/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainerOptions.cs +++ b/osu.Game/Graphics/Containers/Markdown/OsuMarkdownContainerOptions.cs @@ -6,6 +6,7 @@ using Markdig.Extensions.AutoLinks; using Markdig.Extensions.CustomContainers; using Markdig.Extensions.EmphasisExtras; using Markdig.Extensions.Footnotes; +using osu.Game.Graphics.Containers.Markdown.Extensions; namespace osu.Game.Graphics.Containers.Markdown { @@ -32,6 +33,12 @@ namespace osu.Game.Graphics.Containers.Markdown /// public bool CustomContainers { get; init; } + /// + /// Allows the to parse custom attributes in block elements (used e.g. for custom anchor names in the wiki). + /// + /// + public bool BlockAttributes { get; init; } + /// /// Returns a prepared according to the options specified by the current instance. /// @@ -53,7 +60,10 @@ namespace osu.Game.Graphics.Containers.Markdown pipeline = pipeline.UseAutoLinks(); if (CustomContainers) - pipeline.UseCustomContainers(); + pipeline = pipeline.UseCustomContainers(); + + if (BlockAttributes) + pipeline = pipeline.UseBlockAttributes(); return pipeline.Build(); } diff --git a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs index bb296c5d41..7c36caa62f 100644 --- a/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs +++ b/osu.Game/Overlays/Wiki/Markdown/WikiMarkdownContainer.cs @@ -19,7 +19,8 @@ namespace osu.Game.Overlays.Wiki.Markdown protected override OsuMarkdownContainerOptions Options => new OsuMarkdownContainerOptions { Footnotes = true, - CustomContainers = true + CustomContainers = true, + BlockAttributes = true }; public string CurrentPath From 30b9f5d92e901279ae75ca997f47f12e19ecc196 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 19 Dec 2022 21:31:54 +0100 Subject: [PATCH 193/384] Add test coverage for correct operation of flag extension --- osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs index 6185c43f99..b7989d3f44 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiMarkdownContainer.cs @@ -18,6 +18,7 @@ using osu.Framework.Utils; using osu.Game.Graphics.Containers.Markdown; using osu.Game.Overlays; using osu.Game.Overlays.Wiki.Markdown; +using osu.Game.Users.Drawables; namespace osu.Game.Tests.Visual.Online { @@ -197,6 +198,7 @@ Line after image"; markdownContainer.CurrentPath = @"https://dev.ppy.sh"; markdownContainer.Text = "::{flag=\"AU\"}:: ::{flag=\"ZZ\"}::"; }); + AddAssert("Two flags visible", () => markdownContainer.ChildrenOfType().Count(), () => Is.EqualTo(2)); } [Test] From 01f09529a825d80c8e6ccb390b644b7586234a70 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 18 Dec 2022 22:52:21 -0800 Subject: [PATCH 194/384] Link beatmap set genre and language to listing filters --- osu.Game/Online/Chat/MessageFormatter.cs | 2 ++ osu.Game/OsuGame.cs | 12 ++++++++++++ .../BeatmapListing/BeatmapListingFilterControl.cs | 7 +++++++ osu.Game/Overlays/BeatmapListingOverlay.cs | 12 ++++++++++++ osu.Game/Overlays/BeatmapSet/MetadataSection.cs | 10 ++++++++++ 5 files changed, 43 insertions(+) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index b9bca9dc20..523185a7cb 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -341,6 +341,8 @@ namespace osu.Game.Online.Chat OpenWiki, Custom, OpenChangelog, + FilterBeatmapSetGenre, + FilterBeatmapSetLanguage, } public class Link : IComparable diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a0a45e18a8..4c47eda38b 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -356,6 +356,14 @@ namespace osu.Game SearchBeatmapSet(argString); break; + case LinkAction.FilterBeatmapSetGenre: + FilterBeatmapSetGenre(argString); + break; + + case LinkAction.FilterBeatmapSetLanguage: + FilterBeatmapSetLanguage(argString); + break; + case LinkAction.OpenEditorTimestamp: case LinkAction.JoinMultiplayerMatch: case LinkAction.Spectate: @@ -460,6 +468,10 @@ namespace osu.Game /// The query to search for. public void SearchBeatmapSet(string query) => waitForReady(() => beatmapListing, _ => beatmapListing.ShowWithSearch(query)); + public void FilterBeatmapSetGenre(string genre) => waitForReady(() => beatmapListing, _ => beatmapListing.ShowWithGenreFilter(genre)); + + public void FilterBeatmapSetLanguage(string language) => waitForReady(() => beatmapListing, _ => beatmapListing.ShowWithLanguageFilter(language)); + /// /// Show a wiki's page as an overlay /// diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index c5c252fb5d..5eb1ff7024 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -145,6 +146,12 @@ namespace osu.Game.Overlays.BeatmapListing public void Search(string query) => Schedule(() => searchControl.Query.Value = query); + public void FilterGenre(string genre) + => Schedule(() => searchControl.Genre.Value = genre.DehumanizeTo()); + + public void FilterLanguage(string language) + => Schedule(() => searchControl.Language.Value = language.DehumanizeTo()); + protected override void LoadComplete() { base.LoadComplete(); diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index d6d4f1a67b..010499df6f 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -110,6 +110,18 @@ namespace osu.Game.Overlays ScrollFlow.ScrollToStart(); } + public void ShowWithGenreFilter(string genre) + { + ShowWithSearch(string.Empty); + filterControl.FilterGenre(genre); + } + + public void ShowWithLanguageFilter(string language) + { + ShowWithSearch(string.Empty); + filterControl.FilterLanguage(language); + } + protected override BeatmapListingHeader CreateHeader() => new BeatmapListingHeader(); protected override Color4 BackgroundColour => ColourProvider.Background6; diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSection.cs b/osu.Game/Overlays/BeatmapSet/MetadataSection.cs index 6390c52ff3..caf1b7197c 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataSection.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSection.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using Humanizer; using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -12,6 +13,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; +using osu.Game.Overlays.BeatmapListing; using osuTK; using osuTK.Graphics; @@ -116,6 +118,14 @@ namespace osu.Game.Overlays.BeatmapSet break; + case MetadataType.Genre: + loaded.AddLink(text.DehumanizeTo().GetLocalisableDescription(), LinkAction.FilterBeatmapSetGenre, text); + break; + + case MetadataType.Language: + loaded.AddLink(text.DehumanizeTo().GetLocalisableDescription(), LinkAction.FilterBeatmapSetLanguage, text); + break; + default: loaded.AddText(text); break; From c119d41a2dac3c8d50304aeb7b16484281404212 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 20 Dec 2022 17:52:53 +0900 Subject: [PATCH 195/384] Only show song select for now at ui scale adjust first run screen Having both was a bit too much. Still not happy with this but it's a bit less sensory overload. I think while it's cool being able to show nested screens like this, it needs more thought to actually be a good experience. --- osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs index 63688841d0..a3969883e0 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs @@ -58,7 +58,7 @@ namespace osu.Game.Overlays.FirstRunSetup Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.None, - Size = new Vector2(screen_width, screen_width / 16f * 9 / 2), + Size = new Vector2(screen_width, screen_width / 16f * 9), Children = new Drawable[] { new GridContainer @@ -68,7 +68,6 @@ namespace osu.Game.Overlays.FirstRunSetup { new Drawable[] { - new SampleScreenContainer(new PinnedMainMenu()), new SampleScreenContainer(new NestedSongSelect()), }, // TODO: add more screens here in the future (gameplay / results) @@ -109,17 +108,6 @@ namespace osu.Game.Overlays.FirstRunSetup public override bool? AllowTrackAdjustments => false; } - private partial class PinnedMainMenu : MainMenu - { - public override void OnEntering(ScreenTransitionEvent e) - { - base.OnEntering(e); - - Buttons.ReturnToTopOnIdle = false; - Buttons.State = ButtonSystemState.TopLevel; - } - } - private partial class UIScaleSlider : OsuSliderBar { public override LocalisableString TooltipText => base.TooltipText + "x"; From 439b8ac56ae50fdb77f8d8fd174925a1a6b52324 Mon Sep 17 00:00:00 2001 From: wiskerz76 Date: Mon, 19 Dec 2022 16:33:35 -0500 Subject: [PATCH 196/384] Fix file select popup getting stuck when switching first run screens while selecting Closes #21663 Supersedes #21724 --- .../FirstRunSetup/ScreenImportFromStable.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 04aa976ff1..8b85bb49a5 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -126,7 +126,8 @@ namespace osu.Game.Overlays.FirstRunSetup if (available) { - copyInformation.Text = "Data migration will use \"hard links\". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation."; + copyInformation.Text = + "Data migration will use \"hard links\". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation."; } else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; @@ -173,6 +174,18 @@ namespace osu.Game.Overlays.FirstRunSetup c.Current.Disabled = !allow; } + public override void OnSuspending(ScreenTransitionEvent e) + { + stableLocatorTextBox.HidePopover(); + base.OnSuspending(e); + } + + public override bool OnExiting(ScreenExitEvent e) + { + stableLocatorTextBox.HidePopover(); + return base.OnExiting(e); + } + private partial class ImportCheckbox : SettingsCheckbox { public readonly StableContent StableContent; From 2f0c772dcb4d5110727de6389db3f1e9c31d474c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 12 Dec 2022 15:52:29 +0900 Subject: [PATCH 197/384] Add argon pro skin --- .../Overlays/Settings/Sections/SkinSection.cs | 1 + osu.Game/Skinning/ArgonProSkin.cs | 48 +++++++++++++++++++ osu.Game/Skinning/ArgonSkin.cs | 6 +-- osu.Game/Skinning/SkinInfo.cs | 1 + osu.Game/Skinning/SkinManager.cs | 1 + osu.Game/Skinning/SkinnableSprite.cs | 1 + 6 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Skinning/ArgonProSkin.cs diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 826a1e7404..f75656cc99 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -105,6 +105,7 @@ namespace osu.Game.Overlays.Settings.Sections dropdownItems.Clear(); dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.ARGON_SKIN).ToLive(realm)); + dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.ARGON_PRO_SKIN).ToLive(realm)); dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.TRIANGLES_SKIN).ToLive(realm)); dropdownItems.Add(sender.Single(s => s.ID == SkinInfo.CLASSIC_SKIN).ToLive(realm)); diff --git a/osu.Game/Skinning/ArgonProSkin.cs b/osu.Game/Skinning/ArgonProSkin.cs new file mode 100644 index 0000000000..2bc8e55ec0 --- /dev/null +++ b/osu.Game/Skinning/ArgonProSkin.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Framework.Audio.Sample; +using osu.Game.Audio; +using osu.Game.Extensions; +using osu.Game.IO; + +namespace osu.Game.Skinning +{ + public class ArgonProSkin : ArgonSkin + { + public new static SkinInfo CreateInfo() => new SkinInfo + { + ID = Skinning.SkinInfo.ARGON_PRO_SKIN, + Name = "osu! \"argon\" pro (2022)", + Creator = "team osu!", + Protected = true, + InstantiationInfo = typeof(ArgonProSkin).GetInvariantInstantiationInfo() + }; + + public override ISample? GetSample(ISampleInfo sampleInfo) + { + foreach (string lookup in sampleInfo.LookupNames) + { + string remappedLookup = lookup.Replace("Gameplay/", "Gameplay/Pro/"); + + var sample = Samples?.Get(remappedLookup) ?? Resources.AudioManager?.Samples.Get(remappedLookup); + if (sample != null) + return sample; + } + + return null; + } + + public ArgonProSkin(IStorageResourceProvider resources) + : this(CreateInfo(), resources) + { + } + + [UsedImplicitly(ImplicitUseKindFlags.InstantiatedWithFixedConstructorSignature)] + public ArgonProSkin(SkinInfo skin, IStorageResourceProvider resources) + : base(skin, resources) + { + } + } +} diff --git a/osu.Game/Skinning/ArgonSkin.cs b/osu.Game/Skinning/ArgonSkin.cs index 6a0c4a23e5..d78147aaea 100644 --- a/osu.Game/Skinning/ArgonSkin.cs +++ b/osu.Game/Skinning/ArgonSkin.cs @@ -30,7 +30,7 @@ namespace osu.Game.Skinning InstantiationInfo = typeof(ArgonSkin).GetInvariantInstantiationInfo() }; - private readonly IStorageResourceProvider resources; + protected readonly IStorageResourceProvider Resources; public ArgonSkin(IStorageResourceProvider resources) : this(CreateInfo(), resources) @@ -41,7 +41,7 @@ namespace osu.Game.Skinning public ArgonSkin(SkinInfo skin, IStorageResourceProvider resources) : base(skin, resources) { - this.resources = resources; + Resources = resources; Configuration.CustomComboColours = new List { @@ -72,7 +72,7 @@ namespace osu.Game.Skinning { foreach (string lookup in sampleInfo.LookupNames) { - var sample = Samples?.Get(lookup) ?? resources.AudioManager?.Samples.Get(lookup); + var sample = Samples?.Get(lookup) ?? Resources.AudioManager?.Samples.Get(lookup); if (sample != null) return sample; } diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 7b31c8fe88..9ad91f8725 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -20,6 +20,7 @@ namespace osu.Game.Skinning { internal static readonly Guid TRIANGLES_SKIN = new Guid("2991CFD8-2140-469A-BCB9-2EC23FBCE4AD"); internal static readonly Guid ARGON_SKIN = new Guid("CFFA69DE-B3E3-4DEE-8563-3C4F425C05D0"); + internal static readonly Guid ARGON_PRO_SKIN = new Guid("9FC9CF5D-0F16-4C71-8256-98868321AC43"); internal static readonly Guid CLASSIC_SKIN = new Guid("81F02CD3-EEC6-4865-AC23-FAE26A386187"); internal static readonly Guid RANDOM_SKIN = new Guid("D39DFEFB-477C-4372-B1EA-2BCEA5FB8908"); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 2ad62dbb61..f750bfad8a 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -84,6 +84,7 @@ namespace osu.Game.Skinning DefaultClassicSkin = new DefaultLegacySkin(this), trianglesSkin = new TrianglesSkin(this), argonSkin = new ArgonSkin(this), + new ArgonProSkin(this), }; // Ensure the default entries are present. diff --git a/osu.Game/Skinning/SkinnableSprite.cs b/osu.Game/Skinning/SkinnableSprite.cs index 1a8a3a26c9..a66f3e0549 100644 --- a/osu.Game/Skinning/SkinnableSprite.cs +++ b/osu.Game/Skinning/SkinnableSprite.cs @@ -111,6 +111,7 @@ namespace osu.Game.Skinning // Temporarily used to exclude undesirable ISkin implementations static bool isUserSkin(ISkin skin) => skin.GetType() == typeof(TrianglesSkin) + || skin.GetType() == typeof(ArgonProSkin) || skin.GetType() == typeof(ArgonSkin) || skin.GetType() == typeof(DefaultLegacySkin) || skin.GetType() == typeof(LegacySkin); From f7c854f1b0ebd59bac44c175dd54694c90ec4b39 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Tue, 20 Dec 2022 21:18:32 +0900 Subject: [PATCH 198/384] Change asset folder --- osu.Game/Skinning/ArgonProSkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/ArgonProSkin.cs b/osu.Game/Skinning/ArgonProSkin.cs index 2bc8e55ec0..b753dd8fbe 100644 --- a/osu.Game/Skinning/ArgonProSkin.cs +++ b/osu.Game/Skinning/ArgonProSkin.cs @@ -24,7 +24,7 @@ namespace osu.Game.Skinning { foreach (string lookup in sampleInfo.LookupNames) { - string remappedLookup = lookup.Replace("Gameplay/", "Gameplay/Pro/"); + string remappedLookup = lookup.Replace(@"Gameplay/", @"Gameplay/ArgonPro/"); var sample = Samples?.Get(remappedLookup) ?? Resources.AudioManager?.Samples.Get(remappedLookup); if (sample != null) From 2c402d474035be888284b7318050aabb9abb3423 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Dec 2022 01:24:41 +0900 Subject: [PATCH 199/384] Update resources --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8b4fa2dc6b..83dbf7e370 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ - + From bf074adb137c2178eebd38dd285fbaec82c86364 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Dec 2022 18:24:26 +0100 Subject: [PATCH 200/384] Remove unused using directive --- osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs index a3969883e0..1bcb1bcdf4 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenUIScale.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Textures; using osu.Framework.Localisation; -using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; From cebd5f6dc2384d6468f56f31385cda8e7c035d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Dec 2022 20:36:27 +0100 Subject: [PATCH 201/384] Fix restore default button having a minuscule hit area Another casualty of edc78205d5312e9278f2a22ef156fd34af492595. This particular button was actually *relying* on receiving positional events from its entire bounding box rather than `Content`, in order for the button to be htitable more easily, which broke as other buttons were fixed to behave more in line with expectations. Upon closer inspection this is another case of a weird carried-over construction. The button doesn't really need to inherit `OsuButton` or do any of the arcane stuff that it was doing, so it's now a plain `OsuClickableContainer` with less `Content` hackery. --- .../Overlays/RestoreDefaultValueButton.cs | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/RestoreDefaultValueButton.cs b/osu.Game/Overlays/RestoreDefaultValueButton.cs index 24dec44588..9d5e5db6e6 100644 --- a/osu.Game/Overlays/RestoreDefaultValueButton.cs +++ b/osu.Game/Overlays/RestoreDefaultValueButton.cs @@ -7,18 +7,20 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osuTK; namespace osu.Game.Overlays { - public partial class RestoreDefaultValueButton : OsuButton, IHasTooltip, IHasCurrentValue + public partial class RestoreDefaultValueButton : OsuClickableContainer, IHasCurrentValue { public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks; @@ -51,15 +53,32 @@ namespace osu.Game.Overlays private const float size = 4; + private CircularContainer circle = null!; + private Box background = null!; + + public RestoreDefaultValueButton() + : base(HoverSampleSet.Button) + { + } + [BackgroundDependencyLoader] private void load(OsuColour colour) { - BackgroundColour = colour.Lime1; + // size intentionally much larger than actual drawn content, so that the button is easier to click. Size = new Vector2(3 * size); - Content.RelativeSizeAxes = Axes.None; - Content.Size = new Vector2(size); - Content.CornerRadius = size / 2; + Add(circle = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(size), + Masking = true, + Child = background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colour.Lime1 + } + }); Alpha = 0f; @@ -77,7 +96,7 @@ namespace osu.Game.Overlays FinishTransforms(true); } - public LocalisableString TooltipText => "revert to default"; + public override LocalisableString TooltipText => "revert to default"; protected override bool OnHover(HoverEvent e) { @@ -104,8 +123,8 @@ namespace osu.Game.Overlays if (!Current.Disabled) { this.FadeTo(Current.IsDefault ? 0 : 1, fade_duration, Easing.OutQuint); - Background.FadeColour(IsHovered ? colours.Lime0 : colours.Lime1, fade_duration, Easing.OutQuint); - Content.TweenEdgeEffectTo(new EdgeEffectParameters + background.FadeColour(IsHovered ? colours.Lime0 : colours.Lime1, fade_duration, Easing.OutQuint); + circle.TweenEdgeEffectTo(new EdgeEffectParameters { Colour = (IsHovered ? colours.Lime1 : colours.Lime3).Opacity(0.4f), Radius = IsHovered ? 8 : 4, @@ -114,8 +133,8 @@ namespace osu.Game.Overlays } else { - Background.FadeColour(colours.Lime3, fade_duration, Easing.OutQuint); - Content.TweenEdgeEffectTo(new EdgeEffectParameters + background.FadeColour(colours.Lime3, fade_duration, Easing.OutQuint); + circle.TweenEdgeEffectTo(new EdgeEffectParameters { Colour = colours.Lime3.Opacity(0.1f), Radius = 2, From b03291330f2fb89dd482a53b62c23caeeb1b0757 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 20 Dec 2022 21:23:50 +0100 Subject: [PATCH 202/384] Add score processed callback to spectator client --- osu.Game/Online/Spectator/ISpectatorClient.cs | 7 +++++++ osu.Game/Online/Spectator/OnlineSpectatorClient.cs | 1 + osu.Game/Online/Spectator/SpectatorClient.cs | 12 ++++++++++++ 3 files changed, 20 insertions(+) diff --git a/osu.Game/Online/Spectator/ISpectatorClient.cs b/osu.Game/Online/Spectator/ISpectatorClient.cs index ccba280001..605ebc4ef0 100644 --- a/osu.Game/Online/Spectator/ISpectatorClient.cs +++ b/osu.Game/Online/Spectator/ISpectatorClient.cs @@ -32,5 +32,12 @@ namespace osu.Game.Online.Spectator /// The user. /// The frame data. Task UserSentFrames(int userId, FrameDataBundle data); + + /// + /// Signals that a user's submitted score was fully processed. + /// + /// The ID of the user who achieved the score. + /// The ID of the score. + Task UserScoreProcessed(int userId, long scoreId); } } diff --git a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs index 01b775549e..3118e05053 100644 --- a/osu.Game/Online/Spectator/OnlineSpectatorClient.cs +++ b/osu.Game/Online/Spectator/OnlineSpectatorClient.cs @@ -41,6 +41,7 @@ namespace osu.Game.Online.Spectator connection.On(nameof(ISpectatorClient.UserBeganPlaying), ((ISpectatorClient)this).UserBeganPlaying); connection.On(nameof(ISpectatorClient.UserSentFrames), ((ISpectatorClient)this).UserSentFrames); connection.On(nameof(ISpectatorClient.UserFinishedPlaying), ((ISpectatorClient)this).UserFinishedPlaying); + connection.On(nameof(ISpectatorClient.UserScoreProcessed), ((ISpectatorClient)this).UserScoreProcessed); }; IsConnected.BindTo(connector.IsConnected); diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index fce61c019b..b60cef2835 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -64,6 +64,11 @@ namespace osu.Game.Online.Spectator /// public virtual event Action? OnUserFinishedPlaying; + /// + /// Called whenever a user-submitted score has been fully processed. + /// + public virtual event Action? OnUserScoreProcessed; + /// /// A dictionary containing all users currently being watched, with the number of watching components for each user. /// @@ -160,6 +165,13 @@ namespace osu.Game.Online.Spectator return Task.CompletedTask; } + Task ISpectatorClient.UserScoreProcessed(int userId, long scoreId) + { + Schedule(() => OnUserScoreProcessed?.Invoke(userId, scoreId)); + + return Task.CompletedTask; + } + public void BeginPlaying(long? scoreToken, GameplayState state, Score score) { // This schedule is only here to match the one below in `EndPlaying`. From 19f66c806e7a7df51dee54d8ff935cdf0b96e5a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Dec 2022 16:31:53 +0800 Subject: [PATCH 203/384] Fix language dropdown in settings not updating after changing language in first run dialog Closes #21744. --- .../Overlays/Settings/Sections/General/LanguageSettings.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs index a4ec919658..982cbec376 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LanguageSettings.cs @@ -44,10 +44,13 @@ namespace osu.Game.Overlays.Settings.Sections.General }, }; - localisationParameters.BindValueChanged(p - => languageSelection.Current.Value = LanguageExtensions.GetLanguageFor(frameworkLocale.Value, p.NewValue), true); + frameworkLocale.BindValueChanged(_ => updateSelection()); + localisationParameters.BindValueChanged(_ => updateSelection(), true); languageSelection.Current.BindValueChanged(val => frameworkLocale.Value = val.NewValue.ToCultureCode()); } + + private void updateSelection() => + languageSelection.Current.Value = LanguageExtensions.GetLanguageFor(frameworkLocale.Value, localisationParameters.Value); } } From 1d39e8d0cecc26b4d5d89d392c48e8319b82156f Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 21 Dec 2022 10:18:47 -0800 Subject: [PATCH 204/384] Refactor `MetadataSection` to use generics and inheritance --- osu.Game/Overlays/BeatmapSet/Info.cs | 10 +- .../Overlays/BeatmapSet/MetadataSection.cs | 98 +++++++------------ .../BeatmapSet/MetadataSectionDescription.cs | 21 ++++ .../BeatmapSet/MetadataSectionGenre.cs | 25 +++++ .../BeatmapSet/MetadataSectionLanguage.cs | 25 +++++ .../BeatmapSet/MetadataSectionSource.cs | 25 +++++ .../BeatmapSet/MetadataSectionTags.cs | 35 +++++++ osu.Game/Screens/Select/BeatmapDetails.cs | 12 +-- 8 files changed, 180 insertions(+), 71 deletions(-) create mode 100644 osu.Game/Overlays/BeatmapSet/MetadataSectionDescription.cs create mode 100644 osu.Game/Overlays/BeatmapSet/MetadataSectionGenre.cs create mode 100644 osu.Game/Overlays/BeatmapSet/MetadataSectionLanguage.cs create mode 100644 osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs create mode 100644 osu.Game/Overlays/BeatmapSet/MetadataSectionTags.cs diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 514a4ea8cd..a5ebc6c1e4 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -59,7 +59,7 @@ namespace osu.Game.Overlays.BeatmapSet Child = new Container { RelativeSizeAxes = Axes.Both, - Child = new MetadataSection(MetadataType.Description), + Child = new MetadataSectionDescription(), }, }, new Container @@ -78,10 +78,10 @@ namespace osu.Game.Overlays.BeatmapSet Direction = FillDirection.Full, Children = new[] { - source = new MetadataSection(MetadataType.Source), - genre = new MetadataSection(MetadataType.Genre) { Width = 0.5f }, - language = new MetadataSection(MetadataType.Language) { Width = 0.5f }, - tags = new MetadataSection(MetadataType.Tags), + source = new MetadataSectionSource(), + genre = new MetadataSectionGenre { Width = 0.5f }, + language = new MetadataSectionLanguage { Width = 0.5f }, + tags = new MetadataSectionTags(), }, }, }, diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSection.cs b/osu.Game/Overlays/BeatmapSet/MetadataSection.cs index caf1b7197c..6af77f975e 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataSection.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSection.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 Humanizer; using osu.Framework.Extensions; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -12,27 +9,45 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Online.Chat; -using osu.Game.Overlays.BeatmapListing; using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet { - public partial class MetadataSection : Container + public abstract partial class MetadataSection : MetadataSection + { + public override string Text + { + set + { + if (string.IsNullOrEmpty(value)) + { + this.FadeOut(TRANSITION_DURATION); + return; + } + + base.Text = value; + } + } + + protected MetadataSection(MetadataType type, Action? searchAction = null) + : base(type, searchAction) + { + } + } + + public abstract partial class MetadataSection : Container { private readonly FillFlowContainer textContainer; - private readonly MetadataType type; - private TextFlowContainer textFlow; + private TextFlowContainer? textFlow; - private readonly Action searchAction; + protected readonly Action? SearchAction; - private const float transition_duration = 250; + protected const float TRANSITION_DURATION = 250; - public MetadataSection(MetadataType type, Action searchAction = null) + protected MetadataSection(MetadataType type, Action? searchAction = null) { - this.type = type; - this.searchAction = searchAction; + SearchAction = searchAction; Alpha = 0; @@ -55,7 +70,7 @@ namespace osu.Game.Overlays.BeatmapSet AutoSizeAxes = Axes.Y, Child = new OsuSpriteText { - Text = this.type.GetLocalisableDescription(), + Text = type.GetLocalisableDescription(), Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14), }, }, @@ -63,23 +78,23 @@ namespace osu.Game.Overlays.BeatmapSet }; } - public string Text + public virtual T Text { set { - if (string.IsNullOrEmpty(value)) + if (value == null) { - this.FadeOut(transition_duration); + this.FadeOut(TRANSITION_DURATION); return; } - this.FadeIn(transition_duration); + this.FadeIn(TRANSITION_DURATION); setTextAsync(value); } } - private void setTextAsync(string text) + private void setTextAsync(T text) { LoadComponentAsync(new LinkFlowContainer(s => s.Font = s.Font.With(size: 14)) { @@ -90,52 +105,15 @@ namespace osu.Game.Overlays.BeatmapSet { textFlow?.Expire(); - switch (type) - { - case MetadataType.Tags: - string[] tags = text.Split(" "); - - for (int i = 0; i <= tags.Length - 1; i++) - { - string tag = tags[i]; - - if (searchAction != null) - loaded.AddLink(tag, () => searchAction(tag)); - else - loaded.AddLink(tag, LinkAction.SearchBeatmapSet, tag); - - if (i != tags.Length - 1) - loaded.AddText(" "); - } - - break; - - case MetadataType.Source: - if (searchAction != null) - loaded.AddLink(text, () => searchAction(text)); - else - loaded.AddLink(text, LinkAction.SearchBeatmapSet, text); - - break; - - case MetadataType.Genre: - loaded.AddLink(text.DehumanizeTo().GetLocalisableDescription(), LinkAction.FilterBeatmapSetGenre, text); - break; - - case MetadataType.Language: - loaded.AddLink(text.DehumanizeTo().GetLocalisableDescription(), LinkAction.FilterBeatmapSetLanguage, text); - break; - - default: - loaded.AddText(text); - break; - } + AddMetadata(text, loaded); textContainer.Add(textFlow = loaded); // fade in if we haven't yet. - textContainer.FadeIn(transition_duration); + textContainer.FadeIn(TRANSITION_DURATION); }); } + + protected abstract void AddMetadata(T text, LinkFlowContainer loaded); } } diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSectionDescription.cs b/osu.Game/Overlays/BeatmapSet/MetadataSectionDescription.cs new file mode 100644 index 0000000000..a9503cded3 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/MetadataSectionDescription.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays.BeatmapSet +{ + public partial class MetadataSectionDescription : MetadataSection + { + public MetadataSectionDescription(Action? searchAction = null) + : base(MetadataType.Description, searchAction) + { + } + + protected override void AddMetadata(string text, LinkFlowContainer loaded) + { + loaded.AddText(text); + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSectionGenre.cs b/osu.Game/Overlays/BeatmapSet/MetadataSectionGenre.cs new file mode 100644 index 0000000000..eaf2c9721f --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/MetadataSectionGenre.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using Humanizer; +using osu.Framework.Extensions; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Chat; +using osu.Game.Overlays.BeatmapListing; + +namespace osu.Game.Overlays.BeatmapSet +{ + public partial class MetadataSectionGenre : MetadataSection + { + public MetadataSectionGenre(Action? searchAction = null) + : base(MetadataType.Genre, searchAction) + { + } + + protected override void AddMetadata(string text, LinkFlowContainer loaded) + { + loaded.AddLink(text.DehumanizeTo().GetLocalisableDescription(), LinkAction.FilterBeatmapSetGenre, text); + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSectionLanguage.cs b/osu.Game/Overlays/BeatmapSet/MetadataSectionLanguage.cs new file mode 100644 index 0000000000..6c68a5d682 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/MetadataSectionLanguage.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using Humanizer; +using osu.Framework.Extensions; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Chat; +using osu.Game.Overlays.BeatmapListing; + +namespace osu.Game.Overlays.BeatmapSet +{ + public partial class MetadataSectionLanguage : MetadataSection + { + public MetadataSectionLanguage(Action? searchAction = null) + : base(MetadataType.Language, searchAction) + { + } + + protected override void AddMetadata(string text, LinkFlowContainer loaded) + { + loaded.AddLink(text.DehumanizeTo().GetLocalisableDescription(), LinkAction.FilterBeatmapSetLanguage, text); + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs b/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs new file mode 100644 index 0000000000..6deb866f19 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Chat; + +namespace osu.Game.Overlays.BeatmapSet +{ + public partial class MetadataSectionSource : MetadataSection + { + public MetadataSectionSource(Action? searchAction = null) + : base(MetadataType.Source, searchAction) + { + } + + protected override void AddMetadata(string text, LinkFlowContainer loaded) + { + if (SearchAction != null) + loaded.AddLink(text, () => SearchAction(text)); + else + loaded.AddLink(text, LinkAction.SearchBeatmapSet, text); + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSectionTags.cs b/osu.Game/Overlays/BeatmapSet/MetadataSectionTags.cs new file mode 100644 index 0000000000..1b384fcdd3 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/MetadataSectionTags.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 osu.Game.Graphics.Containers; +using osu.Game.Online.Chat; + +namespace osu.Game.Overlays.BeatmapSet +{ + public partial class MetadataSectionTags : MetadataSection + { + public MetadataSectionTags(Action? searchAction = null) + : base(MetadataType.Tags, searchAction) + { + } + + protected override void AddMetadata(string text, LinkFlowContainer loaded) + { + string[] tags = text.Split(" "); + + for (int i = 0; i <= tags.Length - 1; i++) + { + string tag = tags[i]; + + if (SearchAction != null) + loaded.AddLink(tag, () => SearchAction(tag)); + else + loaded.AddLink(tag, LinkAction.SearchBeatmapSet, tag); + + if (i != tags.Length - 1) + loaded.AddText(" "); + } + } + } +} diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 0a14df6480..b158bef8dc 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -141,9 +141,9 @@ namespace osu.Game.Screens.Select LayoutEasing = Easing.OutQuad, Children = new[] { - description = new MetadataSection(MetadataType.Description, searchOnSongSelect), - source = new MetadataSection(MetadataType.Source, searchOnSongSelect), - tags = new MetadataSection(MetadataType.Tags, searchOnSongSelect), + description = new MetadataSectionDescription(searchOnSongSelect), + source = new MetadataSectionSource(searchOnSongSelect), + tags = new MetadataSectionTags(searchOnSongSelect), }, }, }, @@ -187,9 +187,9 @@ namespace osu.Game.Screens.Select private void updateStatistics() { advanced.BeatmapInfo = BeatmapInfo; - description.Text = BeatmapInfo?.DifficultyName; - source.Text = BeatmapInfo?.Metadata.Source; - tags.Text = BeatmapInfo?.Metadata.Tags; + description.Text = BeatmapInfo?.DifficultyName ?? string.Empty; + source.Text = BeatmapInfo?.Metadata.Source ?? string.Empty; + tags.Text = BeatmapInfo?.Metadata.Tags ?? string.Empty; // failTimes may have been previously fetched if (ratings != null && failTimes != null) From 3ec31a5f51762fc1d9ed81256bc40a13597cf04e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 21 Dec 2022 19:30:21 +0100 Subject: [PATCH 205/384] Fix language selector in first run dialog not updating after changing language in settings --- .../Overlays/FirstRunSetup/ScreenWelcome.cs | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs index f6133e3643..4af40e5ad6 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs @@ -87,18 +87,21 @@ namespace osu.Game.Overlays.FirstRunSetup }); frameworkLocale = frameworkConfig.GetBindable(FrameworkSetting.Locale); + frameworkLocale.BindValueChanged(_ => onLanguageChange()); localisationParameters = localisation.CurrentParameters.GetBoundCopy(); - localisationParameters.BindValueChanged(p => - { - var language = LanguageExtensions.GetLanguageFor(frameworkLocale.Value, p.NewValue); + localisationParameters.BindValueChanged(_ => onLanguageChange(), true); + } - // Changing language may cause a short period of blocking the UI thread while the new glyphs are loaded. - // Scheduling ensures the button animation plays smoothly after any blocking operation completes. - // Note that a delay is required (the alternative would be a double-schedule; delay feels better). - updateSelectedDelegate?.Cancel(); - updateSelectedDelegate = Scheduler.AddDelayed(() => updateSelectedStates(language), 50); - }, true); + private void onLanguageChange() + { + var language = LanguageExtensions.GetLanguageFor(frameworkLocale.Value, localisationParameters.Value); + + // Changing language may cause a short period of blocking the UI thread while the new glyphs are loaded. + // Scheduling ensures the button animation plays smoothly after any blocking operation completes. + // Note that a delay is required (the alternative would be a double-schedule; delay feels better). + updateSelectedDelegate?.Cancel(); + updateSelectedDelegate = Scheduler.AddDelayed(() => updateSelectedStates(language), 50); } private void updateSelectedStates(Language language) From e1e6d76f308e6187246ba415f5188ccd7595f058 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 21 Dec 2022 11:02:04 -0800 Subject: [PATCH 206/384] Check id for genre/language instead and fallback to name if not defined --- .../Visual/Online/TestSceneBeatmapSetOverlay.cs | 2 ++ osu.Game/OsuGame.cs | 9 +++++---- .../BeatmapListing/BeatmapListingFilterControl.cs | 9 ++++----- osu.Game/Overlays/BeatmapListingOverlay.cs | 4 ++-- osu.Game/Overlays/BeatmapSet/Info.cs | 11 +++++++---- .../Overlays/BeatmapSet/MetadataSectionGenre.cs | 15 ++++++++++----- .../BeatmapSet/MetadataSectionLanguage.cs | 15 ++++++++++----- 7 files changed, 40 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 3335f69dbb..7d978b9726 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -54,6 +54,8 @@ namespace osu.Game.Tests.Visual.Online { overlay.ShowBeatmapSet(new APIBeatmapSet { + Genre = new BeatmapSetOnlineGenre { Id = 15, Name = "Future genre" }, + Language = new BeatmapSetOnlineLanguage { Id = 15, Name = "Future language" }, OnlineID = 1235, Title = @"an awesome beatmap", Artist = @"naru narusegawa", diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4c47eda38b..508281164f 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -46,6 +46,7 @@ using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Overlays; +using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.Music; using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Toolbar; @@ -357,11 +358,11 @@ namespace osu.Game break; case LinkAction.FilterBeatmapSetGenre: - FilterBeatmapSetGenre(argString); + FilterBeatmapSetGenre((SearchGenre)link.Argument); break; case LinkAction.FilterBeatmapSetLanguage: - FilterBeatmapSetLanguage(argString); + FilterBeatmapSetLanguage((SearchLanguage)link.Argument); break; case LinkAction.OpenEditorTimestamp: @@ -468,9 +469,9 @@ namespace osu.Game /// The query to search for. public void SearchBeatmapSet(string query) => waitForReady(() => beatmapListing, _ => beatmapListing.ShowWithSearch(query)); - public void FilterBeatmapSetGenre(string genre) => waitForReady(() => beatmapListing, _ => beatmapListing.ShowWithGenreFilter(genre)); + public void FilterBeatmapSetGenre(SearchGenre genre) => waitForReady(() => beatmapListing, _ => beatmapListing.ShowWithGenreFilter(genre)); - public void FilterBeatmapSetLanguage(string language) => waitForReady(() => beatmapListing, _ => beatmapListing.ShowWithLanguageFilter(language)); + public void FilterBeatmapSetLanguage(SearchLanguage language) => waitForReady(() => beatmapListing, _ => beatmapListing.ShowWithLanguageFilter(language)); /// /// Show a wiki's page as an overlay diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 5eb1ff7024..37a29b1c50 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Humanizer; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -146,11 +145,11 @@ namespace osu.Game.Overlays.BeatmapListing public void Search(string query) => Schedule(() => searchControl.Query.Value = query); - public void FilterGenre(string genre) - => Schedule(() => searchControl.Genre.Value = genre.DehumanizeTo()); + public void FilterGenre(SearchGenre genre) + => Schedule(() => searchControl.Genre.Value = genre); - public void FilterLanguage(string language) - => Schedule(() => searchControl.Language.Value = language.DehumanizeTo()); + public void FilterLanguage(SearchLanguage language) + => Schedule(() => searchControl.Language.Value = language); protected override void LoadComplete() { diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 010499df6f..73961487ed 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -110,13 +110,13 @@ namespace osu.Game.Overlays ScrollFlow.ScrollToStart(); } - public void ShowWithGenreFilter(string genre) + public void ShowWithGenreFilter(SearchGenre genre) { ShowWithSearch(string.Empty); filterControl.FilterGenre(genre); } - public void ShowWithLanguageFilter(string language) + public void ShowWithLanguageFilter(SearchLanguage language) { ShowWithSearch(string.Empty); filterControl.FilterLanguage(language); diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index a5ebc6c1e4..8a1384f0d3 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; @@ -34,7 +35,9 @@ namespace osu.Game.Overlays.BeatmapSet public Info() { - MetadataSection source, tags, genre, language; + MetadataSection source, tags; + MetadataSectionGenre genre; + MetadataSectionLanguage language; OsuSpriteText notRankedPlaceholder; RelativeSizeAxes = Axes.X; @@ -76,7 +79,7 @@ namespace osu.Game.Overlays.BeatmapSet RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Full, - Children = new[] + Children = new Drawable[] { source = new MetadataSectionSource(), genre = new MetadataSectionGenre { Width = 0.5f }, @@ -120,8 +123,8 @@ namespace osu.Game.Overlays.BeatmapSet { source.Text = b.NewValue?.Source ?? string.Empty; tags.Text = b.NewValue?.Tags ?? string.Empty; - genre.Text = b.NewValue?.Genre.Name ?? string.Empty; - language.Text = b.NewValue?.Language.Name ?? string.Empty; + genre.Text = b.NewValue?.Genre ?? new BeatmapSetOnlineGenre { Id = 1 }; + language.Text = b.NewValue?.Language ?? new BeatmapSetOnlineLanguage { Id = 1 }; bool setHasLeaderboard = b.NewValue?.Status > 0; successRate.Alpha = setHasLeaderboard ? 1 : 0; notRankedPlaceholder.Alpha = setHasLeaderboard ? 0 : 1; diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSectionGenre.cs b/osu.Game/Overlays/BeatmapSet/MetadataSectionGenre.cs index eaf2c9721f..0e045b2bf1 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataSectionGenre.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSectionGenre.cs @@ -2,24 +2,29 @@ // See the LICENCE file in the repository root for full licence text. using System; -using Humanizer; using osu.Framework.Extensions; +using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Online.Chat; using osu.Game.Overlays.BeatmapListing; namespace osu.Game.Overlays.BeatmapSet { - public partial class MetadataSectionGenre : MetadataSection + public partial class MetadataSectionGenre : MetadataSection { - public MetadataSectionGenre(Action? searchAction = null) + public MetadataSectionGenre(Action? searchAction = null) : base(MetadataType.Genre, searchAction) { } - protected override void AddMetadata(string text, LinkFlowContainer loaded) + protected override void AddMetadata(BeatmapSetOnlineGenre text, LinkFlowContainer loaded) { - loaded.AddLink(text.DehumanizeTo().GetLocalisableDescription(), LinkAction.FilterBeatmapSetGenre, text); + var genre = (SearchGenre)text.Id; + + if (Enum.IsDefined(genre)) + loaded.AddLink(genre.GetLocalisableDescription(), LinkAction.FilterBeatmapSetGenre, genre); + else + loaded.AddText(text.Name); } } } diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSectionLanguage.cs b/osu.Game/Overlays/BeatmapSet/MetadataSectionLanguage.cs index 6c68a5d682..6e59818b93 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataSectionLanguage.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSectionLanguage.cs @@ -2,24 +2,29 @@ // See the LICENCE file in the repository root for full licence text. using System; -using Humanizer; using osu.Framework.Extensions; +using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Online.Chat; using osu.Game.Overlays.BeatmapListing; namespace osu.Game.Overlays.BeatmapSet { - public partial class MetadataSectionLanguage : MetadataSection + public partial class MetadataSectionLanguage : MetadataSection { - public MetadataSectionLanguage(Action? searchAction = null) + public MetadataSectionLanguage(Action? searchAction = null) : base(MetadataType.Language, searchAction) { } - protected override void AddMetadata(string text, LinkFlowContainer loaded) + protected override void AddMetadata(BeatmapSetOnlineLanguage text, LinkFlowContainer loaded) { - loaded.AddLink(text.DehumanizeTo().GetLocalisableDescription(), LinkAction.FilterBeatmapSetLanguage, text); + var language = (SearchLanguage)text.Id; + + if (Enum.IsDefined(language)) + loaded.AddLink(language.GetLocalisableDescription(), LinkAction.FilterBeatmapSetLanguage, language); + else + loaded.AddText(text.Name); } } } From 0a49c8c5d67b67c7148ba2ca9fe92c89bb070b46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 21 Dec 2022 20:03:46 +0100 Subject: [PATCH 207/384] Add missing unsubscriptions in multiple mania components --- osu.Game.Rulesets.Mania/UI/Column.cs | 3 +++ osu.Game.Rulesets.Mania/UI/Stage.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 6a31fb3fda..10460f52fd 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -134,6 +134,9 @@ namespace osu.Game.Rulesets.Mania.UI protected override void Dispose(bool isDisposing) { + // must happen before children are disposed in base call to prevent illegal accesses to the hit explosion pool. + NewResult -= OnNewResult; + base.Dispose(isDisposing); if (skin != null) diff --git a/osu.Game.Rulesets.Mania/UI/Stage.cs b/osu.Game.Rulesets.Mania/UI/Stage.cs index fc38a96a35..c1d3e85bf1 100644 --- a/osu.Game.Rulesets.Mania/UI/Stage.cs +++ b/osu.Game.Rulesets.Mania/UI/Stage.cs @@ -156,6 +156,9 @@ namespace osu.Game.Rulesets.Mania.UI protected override void Dispose(bool isDisposing) { + // must happen before children are disposed in base call to prevent illegal accesses to the judgement pool. + NewResult -= OnNewResult; + base.Dispose(isDisposing); if (currentSkin != null) From 6948035a3cc89d6cba9ad7263e73f89d1f94aacc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 21 Dec 2022 21:59:46 +0100 Subject: [PATCH 208/384] Ensure score submission attempt completion before notifying spectator server when exiting play early When a `SubmittingPlayer` gameplay session ends with the successful completion of a beatmap, `PrepareScoreForResultsAsync()` ensures that the score submission request is sent to and responded to by osu-web before calling `ISpectatorClient.EndPlaying()`. While previously this was mostly an implementation detail, this becomes important when considering that more and more server-side flows (replay upload, notifying about score processing completion) hook into `EndPlaying()`, and assume that by the point that message arrives at osu-spectator-server, the score has already been submitted and has been assigned a score ID that corresponds to the score submission token. As it turns out, in the early-exit path (when the user exits the play midway through, retries, or just fails), the same ordering guarantees were not provided. The score's submission ran concurrently to the spectator client `EndPlaying()` call, therefore creating a network race. osu-server-spectator components that implciitly relied on the ordering provided by the happy path, could therefore fail to unmap the score submission token to a score ID. Note that as written, the osu-server-spectator replay upload flow is not really affected by this, as it self-corrects by essentially polling the database and trying to unmap the score submission token to a score ID for up to 30 seconds. However, this change would have the benefit of reducing the polls required in such cases to just one DB retrieval. --- osu.Game/Screens/Play/SubmittingPlayer.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 1eec71f33a..5fa6508a31 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -13,6 +13,7 @@ using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.API; +using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Scoring; @@ -158,8 +159,11 @@ namespace osu.Game.Screens.Play if (LoadedBeatmapSuccessfully) { - submitScore(Score.DeepClone()); - spectatorClient.EndPlaying(GameplayState); + Task.Run(async () => + { + await submitScore(Score.DeepClone()).ConfigureAwait(false); + spectatorClient.EndPlaying(GameplayState); + }).FireAndForget(); } return exiting; From 66a02374dae305eefddbb04b9864482457246622 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Thu, 22 Dec 2022 01:23:24 +0300 Subject: [PATCH 209/384] Clear cached waveform on track change in editor --- osu.Game/Beatmaps/WorkingBeatmap.cs | 8 +++++++- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 393c4ba892..f454f08e87 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -48,7 +48,7 @@ namespace osu.Game.Beatmaps private readonly object beatmapFetchLock = new object(); - private readonly Lazy waveform; + private Lazy waveform; private readonly Lazy storyboard; private readonly Lazy skin; private Track track; // track is not Lazy as we allow transferring and loading multiple times. @@ -329,6 +329,12 @@ namespace osu.Game.Beatmaps #endregion + public void InvalidateWaveform() + { + if (waveform.IsValueCreated) + waveform = new Lazy(GetWaveform); + } + public override string ToString() => BeatmapInfo.ToString(); public abstract Stream GetStream(string storagePath); diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index ca0f50cd34..01217021fd 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -118,7 +118,7 @@ namespace osu.Game.Screens.Edit.Setup } working.Value.Metadata.AudioFile = destination.Name; - + working.Value.InvalidateWaveform(); editorBeatmap.SaveState(); music.ReloadCurrentTrack(); From 7089bb6c2399ccb6c511acb6465b8ce3053b54c5 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Thu, 22 Dec 2022 01:23:59 +0300 Subject: [PATCH 210/384] Listen for track reload in timeline --- .../Compose/Components/Timeline/Timeline.cs | 24 +++++++++++++------ 1 file changed, 17 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 45f902d0de..d135d108de 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -15,6 +15,7 @@ using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osuTK; using osuTK.Input; @@ -35,14 +36,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public readonly Bindable TicksVisible = new Bindable(); - public readonly IBindable Beatmap = new Bindable(); - [Resolved] private EditorClock editorClock { get; set; } [Resolved] private EditorBeatmap editorBeatmap { get; set; } + [Resolved] + private MusicController musicController { get; set; } + /// /// The timeline's scroll position in the last frame. /// @@ -139,11 +141,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); - Beatmap.BindTo(beatmap); - Beatmap.BindValueChanged(b => - { - waveform.Waveform = b.NewValue.Waveform; - }, true); + musicController.TrackChanged += onTrackReload; + waveform.Waveform = beatmap.Value.Waveform; Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); } @@ -181,6 +180,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateWaveformOpacity() => waveform.FadeTo(WaveformVisible.Value ? waveformOpacity.Value : 0, 200, Easing.OutQuint); + private void onTrackReload(WorkingBeatmap beatmap, TrackChangeDirection tcd) + { + waveform.Waveform = beatmap.Waveform; + } + protected override void Update() { base.Update(); @@ -321,5 +325,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline double time = TimeAtPosition(Content.ToLocalSpace(screenSpacePosition).X); return new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(time)); } + + protected override void Dispose(bool isDisposing) + { + musicController.TrackChanged -= onTrackReload; + base.Dispose(isDisposing); + } } } From a18ece8610acee982222a611212345971b45632d Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Thu, 22 Dec 2022 01:24:23 +0300 Subject: [PATCH 211/384] Listen for track reload in timing screen --- .../Screens/Edit/Timing/TapTimingControl.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 09b3851333..10db426416 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -9,6 +9,7 @@ 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.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -29,9 +30,13 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private Bindable selectedGroup { get; set; } = null!; + [Resolved] + private MusicController musicController { get; set; } = null!; + private readonly BindableBool isHandlingTapping = new BindableBool(); private MetronomeDisplay metronome = null!; + private Container waveformContainer = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider, OsuColour colours) @@ -88,7 +93,11 @@ namespace osu.Game.Screens.Edit.Timing Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - new WaveformComparisonDisplay(), + waveformContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Child = new WaveformComparisonDisplay(), + } } }, } @@ -179,6 +188,13 @@ namespace osu.Game.Screens.Edit.Timing if (handling.NewValue) start(); }, true); + + musicController.TrackChanged += onTrackReload; + } + + private void onTrackReload(WorkingBeatmap beatmap, TrackChangeDirection tcd) + { + waveformContainer.Child = new WaveformComparisonDisplay(); } private void start() @@ -233,6 +249,12 @@ namespace osu.Game.Screens.Edit.Timing timing.BeatLength = 60000 / (timing.BPM + adjust); } + protected override void Dispose(bool isDisposing) + { + musicController.TrackChanged -= onTrackReload; + base.Dispose(isDisposing); + } + private partial class InlineButton : OsuButton { private readonly IconUsage icon; From f5b3988dd2bd7ccc82437e191592d89650d34a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Dec 2022 08:01:52 +0100 Subject: [PATCH 212/384] Add data structure for delivering statistics updates --- osu.Game/Online/Solo/SoloStatisticsUpdate.cs | 42 ++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 osu.Game/Online/Solo/SoloStatisticsUpdate.cs diff --git a/osu.Game/Online/Solo/SoloStatisticsUpdate.cs b/osu.Game/Online/Solo/SoloStatisticsUpdate.cs new file mode 100644 index 0000000000..cb9dac97c7 --- /dev/null +++ b/osu.Game/Online/Solo/SoloStatisticsUpdate.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Online.Solo +{ + /// + /// Contains data about the change in a user's profile statistics after completing a score. + /// + public class SoloStatisticsUpdate + { + /// + /// The score set by the user that triggered the update. + /// + public ScoreInfo Score { get; } + + /// + /// The user's profile statistics prior to the score being set. + /// + public UserStatistics Before { get; } + + /// + /// The user's profile statistics after the score was set. + /// + public UserStatistics After { get; } + + /// + /// Creates a new . + /// + /// The score set by the user that triggered the update. + /// The user's profile statistics prior to the score being set. + /// The user's profile statistics after the score was set. + public SoloStatisticsUpdate(ScoreInfo score, UserStatistics before, UserStatistics after) + { + Score = score; + Before = before; + After = after; + } + } +} From f2e8776529176f7ec1ab03d12ea4eee6a5a44f18 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Thu, 22 Dec 2022 15:35:53 +0300 Subject: [PATCH 213/384] Bind to clock instead of music controller --- .../Compose/Components/Timeline/Timeline.cs | 25 +++++++------------ .../Screens/Edit/Timing/TapTimingControl.cs | 21 ++++------------ 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index d135d108de..918b5f8109 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -15,7 +16,6 @@ using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; -using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osuTK; using osuTK.Input; @@ -42,9 +42,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private EditorBeatmap editorBeatmap { get; set; } - [Resolved] - private MusicController musicController { get; set; } - /// /// The timeline's scroll position in the last frame. /// @@ -94,6 +91,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private double trackLengthForZoom; + private readonly IBindable track = new Bindable(); + [BackgroundDependencyLoader] private void load(IBindable beatmap, OsuColour colours, OsuConfigManager config) { @@ -141,7 +140,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); - musicController.TrackChanged += onTrackReload; + track.BindTo(editorClock.Track); + track.BindValueChanged(_ => + { + waveform.Waveform = beatmap.Value.Waveform; + }, true); + waveform.Waveform = beatmap.Value.Waveform; Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); @@ -180,11 +184,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateWaveformOpacity() => waveform.FadeTo(WaveformVisible.Value ? waveformOpacity.Value : 0, 200, Easing.OutQuint); - private void onTrackReload(WorkingBeatmap beatmap, TrackChangeDirection tcd) - { - waveform.Waveform = beatmap.Waveform; - } - protected override void Update() { base.Update(); @@ -325,11 +324,5 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline double time = TimeAtPosition(Content.ToLocalSpace(screenSpacePosition).X); return new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(time)); } - - protected override void Dispose(bool isDisposing) - { - musicController.TrackChanged -= onTrackReload; - base.Dispose(isDisposing); - } } } diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 10db426416..36cd5f8b70 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -3,13 +3,13 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -30,8 +30,7 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private Bindable selectedGroup { get; set; } = null!; - [Resolved] - private MusicController musicController { get; set; } = null!; + private readonly IBindable track = new Bindable(); private readonly BindableBool isHandlingTapping = new BindableBool(); @@ -39,7 +38,7 @@ namespace osu.Game.Screens.Edit.Timing private Container waveformContainer = null!; [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, OsuColour colours) + private void load(OverlayColourProvider colourProvider, OsuColour colours, EditorClock clock) { const float padding = 10; @@ -189,12 +188,8 @@ namespace osu.Game.Screens.Edit.Timing start(); }, true); - musicController.TrackChanged += onTrackReload; - } - - private void onTrackReload(WorkingBeatmap beatmap, TrackChangeDirection tcd) - { - waveformContainer.Child = new WaveformComparisonDisplay(); + track.BindTo(clock.Track); + track.ValueChanged += _ => waveformContainer.Child = new WaveformComparisonDisplay(); } private void start() @@ -249,12 +244,6 @@ namespace osu.Game.Screens.Edit.Timing timing.BeatLength = 60000 / (timing.BPM + adjust); } - protected override void Dispose(bool isDisposing) - { - musicController.TrackChanged -= onTrackReload; - base.Dispose(isDisposing); - } - private partial class InlineButton : OsuButton { private readonly IconUsage icon; From d0645ce15173c32e45886bac43dfb5bd09df69df Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Thu, 22 Dec 2022 15:59:51 +0300 Subject: [PATCH 214/384] Rewrite waveform invalidation --- osu.Game/Beatmaps/WorkingBeatmap.cs | 23 +++++++++++-------- .../Screens/Edit/Setup/ResourcesSection.cs | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index f454f08e87..e61ec3f2a8 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -34,8 +34,6 @@ namespace osu.Game.Beatmaps // TODO: remove once the fallback lookup is not required (and access via `working.BeatmapInfo.Metadata` directly). public BeatmapMetadata Metadata => BeatmapInfo.Metadata; - public Waveform Waveform => waveform.Value; - public Storyboard Storyboard => storyboard.Value; public Texture Background => GetBackground(); // Texture uses ref counting, so we want to return a new instance every usage. @@ -48,7 +46,7 @@ namespace osu.Game.Beatmaps private readonly object beatmapFetchLock = new object(); - private Lazy waveform; + private Waveform waveform; private readonly Lazy storyboard; private readonly Lazy skin; private Track track; // track is not Lazy as we allow transferring and loading multiple times. @@ -60,7 +58,6 @@ namespace osu.Game.Beatmaps BeatmapInfo = beatmapInfo; BeatmapSetInfo = beatmapInfo.BeatmapSet ?? new BeatmapSetInfo(); - waveform = new Lazy(GetWaveform); storyboard = new Lazy(GetStoryboard); skin = new Lazy(GetSkin); } @@ -171,6 +168,18 @@ namespace osu.Game.Beatmaps #endregion + #region Waveform + + public Waveform Waveform => waveform ??= GetWaveform(); + + /// + /// Reloads waveform of beatmap's track even if one is already cached. + /// + /// Newly loaded waveform. + public Waveform LoadWaveform() => waveform = GetWaveform(); + + #endregion + #region Beatmap public virtual bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false; @@ -329,12 +338,6 @@ namespace osu.Game.Beatmaps #endregion - public void InvalidateWaveform() - { - if (waveform.IsValueCreated) - waveform = new Lazy(GetWaveform); - } - public override string ToString() => BeatmapInfo.ToString(); public abstract Stream GetStream(string storagePath); diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 01217021fd..36ac9883ba 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -118,7 +118,7 @@ namespace osu.Game.Screens.Edit.Setup } working.Value.Metadata.AudioFile = destination.Name; - working.Value.InvalidateWaveform(); + working.Value.LoadWaveform(); editorBeatmap.SaveState(); music.ReloadCurrentTrack(); From 422fdd8ae5a3f8deae62b788fb41d84f8e8608e8 Mon Sep 17 00:00:00 2001 From: Flutterish Date: Thu, 22 Dec 2022 16:56:27 +0100 Subject: [PATCH 215/384] dont post notifications from custom log targets --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index a0a45e18a8..af58a72ae8 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1040,7 +1040,7 @@ namespace osu.Game Logger.NewEntry += entry => { - if (entry.Level < LogLevel.Important || entry.Target > LoggingTarget.Database) return; + if (entry.Level < LogLevel.Important || entry.Target is null or > LoggingTarget.Database) return; Debug.Assert(entry.Target != null); From 5df440e20eb8389e14e029879af28d1bab630a94 Mon Sep 17 00:00:00 2001 From: Flutterish Date: Thu, 22 Dec 2022 17:27:55 +0100 Subject: [PATCH 216/384] dont use `is..or` syntax --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index af58a72ae8..b5e1023ac6 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1040,7 +1040,7 @@ namespace osu.Game Logger.NewEntry += entry => { - if (entry.Level < LogLevel.Important || entry.Target is null or > LoggingTarget.Database) return; + if (entry.Level < LogLevel.Important || entry.Target == null || entry.Target > LoggingTarget.Database) return; Debug.Assert(entry.Target != null); From 8be6350c019bddbb8064d73aa013ffbc71e58c09 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 22 Dec 2022 20:07:53 +0300 Subject: [PATCH 217/384] Remove no longer necessary assert --- osu.Game/OsuGame.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b5e1023ac6..de9a009f44 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1040,9 +1040,7 @@ namespace osu.Game Logger.NewEntry += entry => { - if (entry.Level < LogLevel.Important || entry.Target == null || entry.Target > LoggingTarget.Database) return; - - Debug.Assert(entry.Target != null); + if (entry.Level < LogLevel.Important || entry.Target > LoggingTarget.Database || entry.Target == null) return; const int short_term_display_limit = 3; From 20370bd5ae4c9350697c357642102861dae7efc4 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Thu, 22 Dec 2022 20:49:09 +0300 Subject: [PATCH 218/384] Invalidate waveform on track load --- osu.Game/Beatmaps/WorkingBeatmap.cs | 15 ++++++++------- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index e61ec3f2a8..345220a3a3 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -105,7 +105,14 @@ namespace osu.Game.Beatmaps public virtual bool TrackLoaded => track != null; - public Track LoadTrack() => track = GetBeatmapTrack() ?? GetVirtualTrack(1000); + public Track LoadTrack() + { + // track could be changed, clearing waveform cache + waveform = null; + + track = GetBeatmapTrack() ?? GetVirtualTrack(1000); + return track; + } public void PrepareTrackForPreview(bool looping, double offsetFromPreviewPoint = 0) { @@ -172,12 +179,6 @@ namespace osu.Game.Beatmaps public Waveform Waveform => waveform ??= GetWaveform(); - /// - /// Reloads waveform of beatmap's track even if one is already cached. - /// - /// Newly loaded waveform. - public Waveform LoadWaveform() => waveform = GetWaveform(); - #endregion #region Beatmap diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 36ac9883ba..ca0f50cd34 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -118,7 +118,7 @@ namespace osu.Game.Screens.Edit.Setup } working.Value.Metadata.AudioFile = destination.Name; - working.Value.LoadWaveform(); + editorBeatmap.SaveState(); music.ReloadCurrentTrack(); From ac872fac9e562b4e4cba19ecf5a8bc31ba269f4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Dec 2022 09:04:53 +0100 Subject: [PATCH 219/384] Implement solo statistics watcher --- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 osu.Game/Online/Solo/SoloStatisticsWatcher.cs diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs new file mode 100644 index 0000000000..197ad410a9 --- /dev/null +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -0,0 +1,140 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; +using osu.Framework.Graphics; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Spectator; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Online.Solo +{ + /// + /// A persistent component that binds to the spectator server and API in order to deliver updates about the logged in user's gameplay statistics. + /// + public partial class SoloStatisticsWatcher : Component + { + [Resolved] + private SpectatorClient spectatorClient { get; set; } = null!; + + [Resolved] + private IAPIProvider api { get; set; } = null!; + + private readonly Dictionary callbacks = new Dictionary(); + private readonly HashSet scoresWithoutCallback = new HashSet(); + + private readonly Dictionary latestStatistics = new Dictionary(); + + protected override void LoadComplete() + { + base.LoadComplete(); + + api.LocalUser.BindValueChanged(user => onUserChanged(user.NewValue), true); + spectatorClient.OnUserScoreProcessed += userScoreProcessed; + } + + /// + /// Registers for a user statistics update after the given has been processed server-side. + /// + /// The score to listen for the statistics update for. + /// The callback to be invoked once the statistics update has been prepared. + public void RegisterForStatisticsUpdateAfter(ScoreInfo score, Action onUpdateReady) => Schedule(() => + { + if (!api.IsLoggedIn) + return; + + var callback = new StatisticsUpdateCallback(score, onUpdateReady); + + if (scoresWithoutCallback.Remove(score.OnlineID)) + { + requestStatisticsUpdate(api.LocalUser.Value.Id, callback); + return; + } + + callbacks[score.OnlineID] = callback; + }); + + private void onUserChanged(APIUser? localUser) => Schedule(() => + { + callbacks.Clear(); + scoresWithoutCallback.Clear(); + latestStatistics.Clear(); + + if (!api.IsLoggedIn) + return; + + Debug.Assert(localUser != null && localUser.OnlineID > 1); + + var userRequest = new GetUsersRequest(new[] { localUser.OnlineID }); + userRequest.Success += response => Schedule(() => + { + foreach (var rulesetStats in response.Users.Single().RulesetsStatistics) + latestStatistics.Add(rulesetStats.Key, rulesetStats.Value); + }); + api.Queue(userRequest); + }); + + private void userScoreProcessed(int userId, long scoreId) + { + if (userId != api.LocalUser.Value?.OnlineID) + return; + + if (!callbacks.TryGetValue(scoreId, out var callback)) + { + scoresWithoutCallback.Add(scoreId); + return; + } + + requestStatisticsUpdate(userId, callback); + callbacks.Remove(scoreId); + } + + private void requestStatisticsUpdate(int userId, StatisticsUpdateCallback callback) + { + var request = new GetUserRequest(userId, callback.Score.Ruleset); + request.Success += user => Schedule(() => dispatchStatisticsUpdate(callback, user.Statistics)); + api.Queue(request); + } + + private void dispatchStatisticsUpdate(StatisticsUpdateCallback callback, UserStatistics updatedStatistics) + { + string rulesetName = callback.Score.Ruleset.ShortName; + + if (!latestStatistics.TryGetValue(rulesetName, out var latestRulesetStatistics)) + return; + + var update = new SoloStatisticsUpdate(callback.Score, latestRulesetStatistics, updatedStatistics); + callback.OnUpdateReady.Invoke(update); + + latestStatistics[rulesetName] = updatedStatistics; + } + + protected override void Dispose(bool isDisposing) + { + if (spectatorClient.IsNotNull()) + spectatorClient.OnUserScoreProcessed -= userScoreProcessed; + + base.Dispose(isDisposing); + } + + private class StatisticsUpdateCallback + { + public ScoreInfo Score { get; } + public Action OnUpdateReady { get; } + + public StatisticsUpdateCallback(ScoreInfo score, Action onUpdateReady) + { + Score = score; + OnUpdateReady = onUpdateReady; + } + } + } +} From 722cf48614fb696a8f9f8ff01512da826edeac90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Dec 2022 10:14:37 +0100 Subject: [PATCH 220/384] Add test coverage for statistics watcher --- .../Online/TestSceneSoloStatisticsWatcher.cs | 241 ++++++++++++++++++ 1 file changed, 241 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs new file mode 100644 index 0000000000..0797113ca1 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs @@ -0,0 +1,241 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Testing; +using osu.Game.Models; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Solo; +using osu.Game.Online.Spectator; +using osu.Game.Rulesets.Osu; +using osu.Game.Scoring; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + [HeadlessTest] + public partial class TestSceneSoloStatisticsWatcher : OsuTestScene + { + protected override bool UseOnlineAPI => false; + + private SoloStatisticsWatcher watcher = null!; + + [Resolved] + private SpectatorClient spectatorClient { get; set; } = null!; + + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + private Action? handleGetUsersRequest; + private Action? handleGetUserRequest; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("set up request handling", () => + { + handleGetUserRequest = null; + handleGetUsersRequest = null; + + dummyAPI.HandleRequest = request => + { + switch (request) + { + case GetUsersRequest getUsersRequest: + handleGetUsersRequest?.Invoke(getUsersRequest); + return true; + + case GetUserRequest getUserRequest: + handleGetUserRequest?.Invoke(getUserRequest); + return true; + + default: + return false; + } + }; + }); + + AddStep("create watcher", () => + { + Child = watcher = new SoloStatisticsWatcher(); + }); + } + + [Test] + public void TestStatisticsUpdateFiredAfterRegistrationAddedAndScoreProcessed() + { + AddStep("fetch initial stats", () => + { + handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1234)); + dummyAPI.LocalUser.Value = new APIUser { Id = 1234 }; + }); + + SoloStatisticsUpdate? update = null; + + AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( + new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) + { + Ruleset = new OsuRuleset().RulesetInfo, + OnlineID = 5678 + }, + receivedUpdate => update = receivedUpdate)); + + AddStep("feign score processing", + () => handleGetUserRequest = + req => req.TriggerSuccess(createIncrementalUserResponse(1234, 5_000_000))); + + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1234, 5678)); + AddUntilStep("update received", () => update != null); + AddAssert("values before are correct", () => update?.Before.TotalScore, () => Is.EqualTo(4_000_000)); + AddAssert("values after are correct", () => update?.After.TotalScore, () => Is.EqualTo(5_000_000)); + } + + [Test] + public void TestStatisticsUpdateFiredAfterScoreProcessedAndRegistrationAdded() + { + AddStep("fetch initial stats", () => + { + handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1235)); + dummyAPI.LocalUser.Value = new APIUser { Id = 1235 }; + }); + + AddStep("feign score processing", + () => handleGetUserRequest = + req => req.TriggerSuccess(createIncrementalUserResponse(1235, 5_000_000))); + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1235, 5678)); + + SoloStatisticsUpdate? update = null; + + // note ordering - this test checks that even if the registration is late, it will receive data. + AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( + new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) + { + Ruleset = new OsuRuleset().RulesetInfo, + OnlineID = 5678 + }, + receivedUpdate => update = receivedUpdate)); + AddUntilStep("update received", () => update != null); + AddAssert("values before are correct", () => update?.Before.TotalScore, () => Is.EqualTo(4_000_000)); + AddAssert("values after are correct", () => update?.After.TotalScore, () => Is.EqualTo(5_000_000)); + } + + [Test] + public void TestStatisticsUpdateNotFiredIfUserLoggedOut() + { + AddStep("fetch initial stats", () => + { + handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1236)); + dummyAPI.LocalUser.Value = new APIUser { Id = 1236 }; + }); + + SoloStatisticsUpdate? update = null; + + AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( + new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) + { + Ruleset = new OsuRuleset().RulesetInfo, + OnlineID = 5678 + }, + receivedUpdate => update = receivedUpdate)); + + AddStep("feign score processing", + () => handleGetUserRequest = + req => req.TriggerSuccess(createIncrementalUserResponse(1236, 5_000_000))); + + AddStep("log out user", () => dummyAPI.Logout()); + + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1236, 5678)); + AddWaitStep("wait a bit", 5); + AddAssert("update not received", () => update == null); + } + + [Test] + public void TestStatisticsUpdateNotFiredIfAnotherUserLoggedIn() + { + AddStep("fetch initial stats", () => + { + handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1237)); + dummyAPI.LocalUser.Value = new APIUser { Id = 1237 }; + }); + + SoloStatisticsUpdate? update = null; + + AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( + new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) + { + Ruleset = new OsuRuleset().RulesetInfo, + OnlineID = 5678 + }, + receivedUpdate => update = receivedUpdate)); + + AddStep("feign score processing", + () => handleGetUserRequest = + req => req.TriggerSuccess(createIncrementalUserResponse(1237, 5_000_000))); + + AddStep("log out user", () => dummyAPI.LocalUser.Value = new APIUser { Id = 5555 }); + + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1237, 5678)); + AddWaitStep("wait a bit", 5); + AddAssert("update not received", () => update == null); + } + + [Test] + public void TestStatisticsUpdateNotFiredIfScoreIdDoesNotMatch() + { + AddStep("fetch initial stats", () => + { + handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1238)); + dummyAPI.LocalUser.Value = new APIUser { Id = 1238 }; + }); + + SoloStatisticsUpdate? update = null; + + AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( + new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) + { + Ruleset = new OsuRuleset().RulesetInfo, + OnlineID = 5678 + }, + receivedUpdate => update = receivedUpdate)); + + AddStep("feign score processing", + () => handleGetUserRequest = + req => req.TriggerSuccess(createIncrementalUserResponse(1238, 5_000_000))); + + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1238, 9012)); + AddWaitStep("wait a bit", 5); + AddAssert("update not received", () => update == null); + } + + private GetUsersResponse createInitialUserResponse(int userId) => new GetUsersResponse + { + Users = new List + { + new APIUser + { + Id = userId, + RulesetsStatistics = new Dictionary + { + ["osu"] = new UserStatistics { TotalScore = 4_000_000 }, + ["taiko"] = new UserStatistics { TotalScore = 3_000_000 }, + ["fruits"] = new UserStatistics { TotalScore = 2_000_000 }, + ["mania"] = new UserStatistics { TotalScore = 1_000_000 } + } + } + } + }; + + private APIUser createIncrementalUserResponse(int userId, long totalScore) => new APIUser + { + Id = userId, + Statistics = new UserStatistics + { + TotalScore = totalScore + } + }; + } +} From 48dc2332fd5bc60d977897e3a4d6477dc8b0deec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Dec 2022 11:10:33 +0100 Subject: [PATCH 221/384] Refactor test to be easier to work with --- .../Online/TestSceneSoloStatisticsWatcher.cs | 241 +++++++++--------- .../Online/API/Requests/GetUsersRequest.cs | 6 +- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 2 +- 3 files changed, 130 insertions(+), 119 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs index 0797113ca1..008d54be63 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Testing; @@ -12,6 +13,7 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Solo; using osu.Game.Online.Spectator; +using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Users; @@ -33,9 +35,12 @@ namespace osu.Game.Tests.Visual.Online private Action? handleGetUsersRequest; private Action? handleGetUserRequest; + private readonly Dictionary<(int userId, string rulesetName), UserStatistics> serverSideStatistics = new Dictionary<(int userId, string rulesetName), UserStatistics>(); + [SetUpSteps] public void SetUpSteps() { + AddStep("clear server-side stats", () => serverSideStatistics.Clear()); AddStep("set up request handling", () => { handleGetUserRequest = null; @@ -46,11 +51,52 @@ namespace osu.Game.Tests.Visual.Online switch (request) { case GetUsersRequest getUsersRequest: - handleGetUsersRequest?.Invoke(getUsersRequest); + if (handleGetUsersRequest != null) + { + handleGetUsersRequest?.Invoke(getUsersRequest); + } + else + { + int userId = getUsersRequest.UserIds.Single(); + var response = new GetUsersResponse + { + Users = new List + { + new APIUser + { + Id = userId, + RulesetsStatistics = new Dictionary + { + ["osu"] = tryGetStatistics(userId, "osu"), + ["taiko"] = tryGetStatistics(userId, "taiko"), + ["fruits"] = tryGetStatistics(userId, "fruits"), + ["mania"] = tryGetStatistics(userId, "mania"), + } + } + } + }; + getUsersRequest.TriggerSuccess(response); + } + return true; case GetUserRequest getUserRequest: - handleGetUserRequest?.Invoke(getUserRequest); + if (handleGetUserRequest != null) + { + handleGetUserRequest.Invoke(getUserRequest); + } + else + { + int userId = int.Parse(getUserRequest.Lookup); + string rulesetName = getUserRequest.Ruleset.ShortName; + var response = new APIUser + { + Id = userId, + Statistics = tryGetStatistics(userId, rulesetName) + }; + getUserRequest.TriggerSuccess(response); + } + return true; default: @@ -65,120 +111,90 @@ namespace osu.Game.Tests.Visual.Online }); } + private UserStatistics tryGetStatistics(int userId, string rulesetName) + => serverSideStatistics.TryGetValue((userId, rulesetName), out var stats) ? stats : new UserStatistics(); + [Test] public void TestStatisticsUpdateFiredAfterRegistrationAddedAndScoreProcessed() { - AddStep("fetch initial stats", () => - { - handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1234)); - dummyAPI.LocalUser.Value = new APIUser { Id = 1234 }; - }); + int userId = getUserId(); + long scoreId = getScoreId(); + setUpUser(userId); + + var ruleset = new OsuRuleset().RulesetInfo; SoloStatisticsUpdate? update = null; + registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); - AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( - new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) - { - Ruleset = new OsuRuleset().RulesetInfo, - OnlineID = 5678 - }, - receivedUpdate => update = receivedUpdate)); + feignScoreProcessing(userId, ruleset, 5_000_000); - AddStep("feign score processing", - () => handleGetUserRequest = - req => req.TriggerSuccess(createIncrementalUserResponse(1234, 5_000_000))); - - AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1234, 5678)); + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); AddUntilStep("update received", () => update != null); - AddAssert("values before are correct", () => update?.Before.TotalScore, () => Is.EqualTo(4_000_000)); - AddAssert("values after are correct", () => update?.After.TotalScore, () => Is.EqualTo(5_000_000)); + AddAssert("values before are correct", () => update!.Before.TotalScore, () => Is.EqualTo(4_000_000)); + AddAssert("values after are correct", () => update!.After.TotalScore, () => Is.EqualTo(5_000_000)); } [Test] public void TestStatisticsUpdateFiredAfterScoreProcessedAndRegistrationAdded() { - AddStep("fetch initial stats", () => - { - handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1235)); - dummyAPI.LocalUser.Value = new APIUser { Id = 1235 }; - }); + int userId = getUserId(); + setUpUser(userId); - AddStep("feign score processing", - () => handleGetUserRequest = - req => req.TriggerSuccess(createIncrementalUserResponse(1235, 5_000_000))); - AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1235, 5678)); + long scoreId = getScoreId(); + var ruleset = new OsuRuleset().RulesetInfo; + + // note ordering - in this test processing completes *before* the registration is added. + feignScoreProcessing(userId, ruleset, 5_000_000); SoloStatisticsUpdate? update = null; + registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); - // note ordering - this test checks that even if the registration is late, it will receive data. - AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( - new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) - { - Ruleset = new OsuRuleset().RulesetInfo, - OnlineID = 5678 - }, - receivedUpdate => update = receivedUpdate)); + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); AddUntilStep("update received", () => update != null); - AddAssert("values before are correct", () => update?.Before.TotalScore, () => Is.EqualTo(4_000_000)); - AddAssert("values after are correct", () => update?.After.TotalScore, () => Is.EqualTo(5_000_000)); + AddAssert("values before are correct", () => update!.Before.TotalScore, () => Is.EqualTo(4_000_000)); + AddAssert("values after are correct", () => update!.After.TotalScore, () => Is.EqualTo(5_000_000)); } [Test] public void TestStatisticsUpdateNotFiredIfUserLoggedOut() { - AddStep("fetch initial stats", () => - { - handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1236)); - dummyAPI.LocalUser.Value = new APIUser { Id = 1236 }; - }); + int userId = getUserId(); + setUpUser(userId); + + long scoreId = getScoreId(); + var ruleset = new OsuRuleset().RulesetInfo; SoloStatisticsUpdate? update = null; + registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); - AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( - new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) - { - Ruleset = new OsuRuleset().RulesetInfo, - OnlineID = 5678 - }, - receivedUpdate => update = receivedUpdate)); - - AddStep("feign score processing", - () => handleGetUserRequest = - req => req.TriggerSuccess(createIncrementalUserResponse(1236, 5_000_000))); + feignScoreProcessing(userId, ruleset, 5_000_000); AddStep("log out user", () => dummyAPI.Logout()); - AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1236, 5678)); + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); AddWaitStep("wait a bit", 5); AddAssert("update not received", () => update == null); + + AddStep("log in user", () => dummyAPI.Login("user", "password")); } [Test] public void TestStatisticsUpdateNotFiredIfAnotherUserLoggedIn() { - AddStep("fetch initial stats", () => - { - handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1237)); - dummyAPI.LocalUser.Value = new APIUser { Id = 1237 }; - }); + int userId = getUserId(); + setUpUser(userId); + + long scoreId = getScoreId(); + var ruleset = new OsuRuleset().RulesetInfo; SoloStatisticsUpdate? update = null; + registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); - AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( - new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) - { - Ruleset = new OsuRuleset().RulesetInfo, - OnlineID = 5678 - }, - receivedUpdate => update = receivedUpdate)); + feignScoreProcessing(userId, ruleset, 5_000_000); - AddStep("feign score processing", - () => handleGetUserRequest = - req => req.TriggerSuccess(createIncrementalUserResponse(1237, 5_000_000))); + AddStep("change user", () => dummyAPI.LocalUser.Value = new APIUser { Id = getUserId() }); - AddStep("log out user", () => dummyAPI.LocalUser.Value = new APIUser { Id = 5555 }); - - AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1237, 5678)); + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); AddWaitStep("wait a bit", 5); AddAssert("update not received", () => update == null); } @@ -186,56 +202,51 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestStatisticsUpdateNotFiredIfScoreIdDoesNotMatch() { - AddStep("fetch initial stats", () => - { - handleGetUsersRequest = req => req.TriggerSuccess(createInitialUserResponse(1238)); - dummyAPI.LocalUser.Value = new APIUser { Id = 1238 }; - }); + int userId = getUserId(); + setUpUser(userId); + + long scoreId = getScoreId(); + var ruleset = new OsuRuleset().RulesetInfo; SoloStatisticsUpdate? update = null; + registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); - AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( - new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) - { - Ruleset = new OsuRuleset().RulesetInfo, - OnlineID = 5678 - }, - receivedUpdate => update = receivedUpdate)); + feignScoreProcessing(userId, ruleset, 5_000_000); - AddStep("feign score processing", - () => handleGetUserRequest = - req => req.TriggerSuccess(createIncrementalUserResponse(1238, 5_000_000))); - - AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(1238, 9012)); + AddStep("signal another score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, getScoreId())); AddWaitStep("wait a bit", 5); AddAssert("update not received", () => update == null); } - private GetUsersResponse createInitialUserResponse(int userId) => new GetUsersResponse - { - Users = new List - { - new APIUser - { - Id = userId, - RulesetsStatistics = new Dictionary - { - ["osu"] = new UserStatistics { TotalScore = 4_000_000 }, - ["taiko"] = new UserStatistics { TotalScore = 3_000_000 }, - ["fruits"] = new UserStatistics { TotalScore = 2_000_000 }, - ["mania"] = new UserStatistics { TotalScore = 1_000_000 } - } - } - } - }; + private int nextUserId = 2000; + private long nextScoreId = 50000; - private APIUser createIncrementalUserResponse(int userId, long totalScore) => new APIUser + private int getUserId() => ++nextUserId; + private long getScoreId() => ++nextScoreId; + + private void setUpUser(int userId) { - Id = userId, - Statistics = new UserStatistics + AddStep("fetch initial stats", () => { - TotalScore = totalScore - } - }; + serverSideStatistics[(userId, "osu")] = new UserStatistics { TotalScore = 4_000_000 }; + serverSideStatistics[(userId, "taiko")] = new UserStatistics { TotalScore = 3_000_000 }; + serverSideStatistics[(userId, "fruits")] = new UserStatistics { TotalScore = 2_000_000 }; + serverSideStatistics[(userId, "mania")] = new UserStatistics { TotalScore = 1_000_000 }; + + dummyAPI.LocalUser.Value = new APIUser { Id = userId }; + }); + } + + private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action onUpdateReady) => + AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( + new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) + { + Ruleset = rulesetInfo, + OnlineID = scoreId + }, + onUpdateReady)); + + private void feignScoreProcessing(int userId, RulesetInfo rulesetInfo, long newTotalScore) + => AddStep("feign score processing", () => serverSideStatistics[(userId, rulesetInfo.ShortName)] = new UserStatistics { TotalScore = newTotalScore }); } } diff --git a/osu.Game/Online/API/Requests/GetUsersRequest.cs b/osu.Game/Online/API/Requests/GetUsersRequest.cs index bbaf241384..b57bb215aa 100644 --- a/osu.Game/Online/API/Requests/GetUsersRequest.cs +++ b/osu.Game/Online/API/Requests/GetUsersRequest.cs @@ -9,7 +9,7 @@ namespace osu.Game.Online.API.Requests { public class GetUsersRequest : APIRequest { - private readonly int[] userIds; + public readonly int[] UserIds; private const int max_ids_per_request = 50; @@ -18,9 +18,9 @@ namespace osu.Game.Online.API.Requests if (userIds.Length > max_ids_per_request) throw new ArgumentException($"{nameof(GetUsersRequest)} calls only support up to {max_ids_per_request} IDs at once"); - this.userIds = userIds; + UserIds = userIds; } - protected override string Target => "users/?ids[]=" + string.Join("&ids[]=", userIds); + protected override string Target => "users/?ids[]=" + string.Join("&ids[]=", UserIds); } } diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 197ad410a9..1befbe2af0 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -71,7 +71,7 @@ namespace osu.Game.Online.Solo if (!api.IsLoggedIn) return; - Debug.Assert(localUser != null && localUser.OnlineID > 1); + Debug.Assert(localUser != null); var userRequest = new GetUsersRequest(new[] { localUser.OnlineID }); userRequest.Success += response => Schedule(() => From fa2d50fe3164612e17bedb6d1892294cc9af0a0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Dec 2022 19:29:51 +0100 Subject: [PATCH 222/384] Limit tracking unhandled scores to just the last one --- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 1befbe2af0..48f39504a3 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -29,7 +29,7 @@ namespace osu.Game.Online.Solo private IAPIProvider api { get; set; } = null!; private readonly Dictionary callbacks = new Dictionary(); - private readonly HashSet scoresWithoutCallback = new HashSet(); + private long? lastProcessedScoreId; private readonly Dictionary latestStatistics = new Dictionary(); @@ -53,7 +53,7 @@ namespace osu.Game.Online.Solo var callback = new StatisticsUpdateCallback(score, onUpdateReady); - if (scoresWithoutCallback.Remove(score.OnlineID)) + if (lastProcessedScoreId == score.OnlineID) { requestStatisticsUpdate(api.LocalUser.Value.Id, callback); return; @@ -65,7 +65,7 @@ namespace osu.Game.Online.Solo private void onUserChanged(APIUser? localUser) => Schedule(() => { callbacks.Clear(); - scoresWithoutCallback.Clear(); + lastProcessedScoreId = null; latestStatistics.Clear(); if (!api.IsLoggedIn) @@ -87,11 +87,10 @@ namespace osu.Game.Online.Solo if (userId != api.LocalUser.Value?.OnlineID) return; + lastProcessedScoreId = scoreId; + if (!callbacks.TryGetValue(scoreId, out var callback)) - { - scoresWithoutCallback.Add(scoreId); return; - } requestStatisticsUpdate(userId, callback); callbacks.Remove(scoreId); From 27afeb9e301d0392be3a22155643389468c04448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 22 Dec 2022 19:46:41 +0100 Subject: [PATCH 223/384] Add test coverage of merging ignored score updates --- .../Online/TestSceneSoloStatisticsWatcher.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs index 008d54be63..b1badc6282 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs @@ -218,6 +218,34 @@ namespace osu.Game.Tests.Visual.Online AddAssert("update not received", () => update == null); } + // the behaviour exercised in this test may not be final, it is mostly assumed for simplicity. + // in the long run we may want each score's update to be entirely isolated from others, rather than have prior unobserved updates merge into the latest. + [Test] + public void TestIgnoredScoreUpdateIsMergedIntoNextOne() + { + int userId = getUserId(); + setUpUser(userId); + + long firstScoreId = getScoreId(); + var ruleset = new OsuRuleset().RulesetInfo; + + feignScoreProcessing(userId, ruleset, 5_000_000); + + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, firstScoreId)); + + long secondScoreId = getScoreId(); + + feignScoreProcessing(userId, ruleset, 6_000_000); + + SoloStatisticsUpdate? update = null; + registerForUpdates(secondScoreId, ruleset, receivedUpdate => update = receivedUpdate); + + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, secondScoreId)); + AddUntilStep("update received", () => update != null); + AddAssert("values before are correct", () => update!.Before.TotalScore, () => Is.EqualTo(4_000_000)); + AddAssert("values after are correct", () => update!.After.TotalScore, () => Is.EqualTo(6_000_000)); + } + private int nextUserId = 2000; private long nextScoreId = 50000; From 08d2fbeb8e99cc2ed2b658f1ce1d30f859108c2b Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Thu, 22 Dec 2022 21:27:59 +0100 Subject: [PATCH 224/384] Use new ArgumentNullException.ThrowIfNull throw-helper API --- .../Beatmaps/Patterns/Legacy/PatternGenerator.cs | 4 ++-- .../Beatmaps/Patterns/PatternGenerator.cs | 6 +++--- osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs | 3 +-- osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 3 +-- osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs | 4 ++-- osu.Game.Tournament/Components/TournamentBeatmapPanel.cs | 2 +- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 3 +-- osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs | 3 +-- osu.Game/Beatmaps/Drawables/OnlineBeatmapSetCover.cs | 3 +-- osu.Game/Beatmaps/Formats/Decoder.cs | 3 +-- osu.Game/Graphics/Containers/LogoTrackingContainer.cs | 3 +-- osu.Game/Graphics/UserInterface/Nub.cs | 3 +-- osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs | 3 +-- osu.Game/Online/Chat/ChannelManager.cs | 6 ++---- osu.Game/Overlays/ChangelogOverlay.cs | 6 +++--- osu.Game/Overlays/OnScreenDisplay.cs | 4 ++-- osu.Game/Overlays/Volume/MuteButton.cs | 3 +-- osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs | 3 +-- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 3 +-- osu.Game/Rulesets/UI/JudgementContainer.cs | 2 +- osu.Game/Scoring/ScoreImporter.cs | 4 ++-- osu.Game/Screens/Menu/LogoVisualisation.cs | 2 +- osu.Game/Screens/Play/HUD/ModDisplay.cs | 3 +-- osu.Game/Screens/Play/HUD/ModFlowDisplay.cs | 3 +-- osu.Game/Screens/Play/KeyCounterDisplay.cs | 2 +- osu.Game/Users/Drawables/DrawableFlag.cs | 3 +-- osu.Game/Users/UserPanel.cs | 3 +-- 27 files changed, 36 insertions(+), 54 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index 308238d87a..77f93b4ef9 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -35,8 +35,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy protected PatternGenerator(LegacyRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, IBeatmap originalBeatmap) : base(hitObject, beatmap, previousPattern) { - if (random == null) throw new ArgumentNullException(nameof(random)); - if (originalBeatmap == null) throw new ArgumentNullException(nameof(originalBeatmap)); + ArgumentNullException.ThrowIfNull(random); + ArgumentNullException.ThrowIfNull(originalBeatmap); Random = random; OriginalBeatmap = originalBeatmap; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs index b2e89c3410..931673f337 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/PatternGenerator.cs @@ -33,9 +33,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns protected PatternGenerator(HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern) { - if (hitObject == null) throw new ArgumentNullException(nameof(hitObject)); - if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); - if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern)); + ArgumentNullException.ThrowIfNull(hitObject); + ArgumentNullException.ThrowIfNull(beatmap); + ArgumentNullException.ThrowIfNull(previousPattern); HitObject = hitObject; Beatmap = beatmap; diff --git a/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs b/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs index 1a67117c03..4d93826240 100644 --- a/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs +++ b/osu.Game.Rulesets.Mania/MathUtils/LegacySortHelper.cs @@ -22,8 +22,7 @@ namespace osu.Game.Rulesets.Mania.MathUtils public static void Sort(T[] keys, IComparer comparer) { - if (keys == null) - throw new ArgumentNullException(nameof(keys)); + ArgumentNullException.ThrowIfNull(keys); if (keys.Length == 0) return; diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 01e9926ad7..e3ebadc836 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -29,8 +29,7 @@ namespace osu.Game.Rulesets.Mania.UI public ManiaPlayfield(List stageDefinitions) { - if (stageDefinitions == null) - throw new ArgumentNullException(nameof(stageDefinitions)); + ArgumentNullException.ThrowIfNull(stageDefinitions); if (stageDefinitions.Count <= 0) throw new ArgumentException("Can't have zero or fewer stages."); diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs index 2f62968029..74e16f7e0b 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGeneratorBase.cs @@ -84,8 +84,8 @@ namespace osu.Game.Rulesets.Osu.Replays { public int Compare(ReplayFrame? f1, ReplayFrame? f2) { - if (f1 == null) throw new ArgumentNullException(nameof(f1)); - if (f2 == null) throw new ArgumentNullException(nameof(f2)); + ArgumentNullException.ThrowIfNull(f1); + ArgumentNullException.ThrowIfNull(f2); return f1.Time.CompareTo(f2.Time); } diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index 52769321a9..1157b50377 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tournament.Components public TournamentBeatmapPanel(TournamentBeatmap beatmap, string mod = null) { - if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); + ArgumentNullException.ThrowIfNull(beatmap); Beatmap = beatmap; this.mod = mod; diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 55119c800a..29b7191ecf 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -211,8 +211,7 @@ namespace osu.Game.Beatmaps.ControlPoints public static T BinarySearch(IReadOnlyList list, double time) where T : class, IControlPoint { - if (list == null) - throw new ArgumentNullException(nameof(list)); + ArgumentNullException.ThrowIfNull(list); if (list.Count == 0) return null; diff --git a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs index d31a7ae2fe..767504fcb1 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapBackgroundSprite.cs @@ -15,8 +15,7 @@ namespace osu.Game.Beatmaps.Drawables public BeatmapBackgroundSprite(IWorkingBeatmap working) { - if (working == null) - throw new ArgumentNullException(nameof(working)); + ArgumentNullException.ThrowIfNull(working); this.working = working; } diff --git a/osu.Game/Beatmaps/Drawables/OnlineBeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/OnlineBeatmapSetCover.cs index e4ffc1d553..fc7c14e734 100644 --- a/osu.Game/Beatmaps/Drawables/OnlineBeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/OnlineBeatmapSetCover.cs @@ -18,8 +18,7 @@ namespace osu.Game.Beatmaps.Drawables public OnlineBeatmapSetCover(IBeatmapSetOnlineInfo set, BeatmapSetCoverType type = BeatmapSetCoverType.Cover) { - if (set == null) - throw new ArgumentNullException(nameof(set)); + ArgumentNullException.ThrowIfNull(set); this.set = set; this.type = type; diff --git a/osu.Game/Beatmaps/Formats/Decoder.cs b/osu.Game/Beatmaps/Formats/Decoder.cs index ca1bcc97fd..4f0f11d053 100644 --- a/osu.Game/Beatmaps/Formats/Decoder.cs +++ b/osu.Game/Beatmaps/Formats/Decoder.cs @@ -57,8 +57,7 @@ namespace osu.Game.Beatmaps.Formats public static Decoder GetDecoder(LineBufferedReader stream) where T : new() { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); + ArgumentNullException.ThrowIfNull(stream); if (!decoders.TryGetValue(typeof(T), out var typedDecoders)) throw new IOException(@"Unknown decoder type"); diff --git a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs index 735b8b4e7d..984d60d35e 100644 --- a/osu.Game/Graphics/Containers/LogoTrackingContainer.cs +++ b/osu.Game/Graphics/Containers/LogoTrackingContainer.cs @@ -36,8 +36,7 @@ namespace osu.Game.Graphics.Containers /// The easing type of the initial transform. public void StartTracking(OsuLogo logo, double duration = 0, Easing easing = Easing.None) { - if (logo == null) - throw new ArgumentNullException(nameof(logo)); + ArgumentNullException.ThrowIfNull(logo); if (logo.IsTracking && Logo == null) throw new InvalidOperationException($"Cannot track an instance of {typeof(OsuLogo)} to multiple {typeof(LogoTrackingContainer)}s"); diff --git a/osu.Game/Graphics/UserInterface/Nub.cs b/osu.Game/Graphics/UserInterface/Nub.cs index 4f56872f42..7921dcf593 100644 --- a/osu.Game/Graphics/UserInterface/Nub.cs +++ b/osu.Game/Graphics/UserInterface/Nub.cs @@ -114,8 +114,7 @@ namespace osu.Game.Graphics.UserInterface get => current; set { - if (value == null) - throw new ArgumentNullException(nameof(value)); + ArgumentNullException.ThrowIfNull(value); current.UnbindBindings(); current.BindTo(value); diff --git a/osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs b/osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs index 8e6c3e5f3d..d47f936eb3 100644 --- a/osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs +++ b/osu.Game/IO/FileAbstraction/StreamFileAbstraction.cs @@ -23,8 +23,7 @@ namespace osu.Game.IO.FileAbstraction public void CloseStream(Stream stream) { - if (stream == null) - throw new ArgumentNullException(nameof(stream)); + ArgumentNullException.ThrowIfNull(stream); stream.Close(); } diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index a4661dcbd7..5d55374373 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -118,8 +118,7 @@ namespace osu.Game.Online.Chat /// public void OpenChannel(string name) { - if (name == null) - throw new ArgumentNullException(nameof(name)); + ArgumentNullException.ThrowIfNull(name); CurrentChannel.Value = AvailableChannels.FirstOrDefault(c => c.Name == name) ?? throw new ChannelNotFoundException(name); } @@ -130,8 +129,7 @@ namespace osu.Game.Online.Chat /// The user the private channel is opened with. public void OpenPrivateChannel(APIUser user) { - if (user == null) - throw new ArgumentNullException(nameof(user)); + ArgumentNullException.ThrowIfNull(user); if (user.Id == api.LocalUser.Value.Id) return; diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index 90863a90a2..671d649dcf 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -70,7 +70,7 @@ namespace osu.Game.Overlays /// are specified, the header will instantly display them. public void ShowBuild([NotNull] APIChangelogBuild build) { - if (build == null) throw new ArgumentNullException(nameof(build)); + ArgumentNullException.ThrowIfNull(build); Current.Value = build; Show(); @@ -78,8 +78,8 @@ namespace osu.Game.Overlays public void ShowBuild([NotNull] string updateStream, [NotNull] string version) { - if (updateStream == null) throw new ArgumentNullException(nameof(updateStream)); - if (version == null) throw new ArgumentNullException(nameof(version)); + ArgumentNullException.ThrowIfNull(updateStream); + ArgumentNullException.ThrowIfNull(version); performAfterFetch(() => { diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs index d60077cfa9..4f2dba7b2c 100644 --- a/osu.Game/Overlays/OnScreenDisplay.cs +++ b/osu.Game/Overlays/OnScreenDisplay.cs @@ -58,7 +58,7 @@ namespace osu.Game.Overlays /// If is already being tracked from the same . public void BeginTracking(object source, ITrackableConfigManager configManager) { - if (configManager == null) throw new ArgumentNullException(nameof(configManager)); + ArgumentNullException.ThrowIfNull(configManager); if (trackedConfigManagers.ContainsKey((source, configManager))) throw new InvalidOperationException($"{nameof(configManager)} is already registered."); @@ -82,7 +82,7 @@ namespace osu.Game.Overlays /// If is not being tracked from the same . public void StopTracking(object source, ITrackableConfigManager configManager) { - if (configManager == null) throw new ArgumentNullException(nameof(configManager)); + ArgumentNullException.ThrowIfNull(configManager); if (!trackedConfigManagers.TryGetValue((source, configManager), out var existing)) return; diff --git a/osu.Game/Overlays/Volume/MuteButton.cs b/osu.Game/Overlays/Volume/MuteButton.cs index 3bea1c840e..9cc346a38b 100644 --- a/osu.Game/Overlays/Volume/MuteButton.cs +++ b/osu.Game/Overlays/Volume/MuteButton.cs @@ -28,8 +28,7 @@ namespace osu.Game.Overlays.Volume get => current; set { - if (value == null) - throw new ArgumentNullException(nameof(value)); + ArgumentNullException.ThrowIfNull(value); current.UnbindBindings(); current.BindTo(value); diff --git a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs index 9d9c10b3ea..38ced4c9e7 100644 --- a/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs +++ b/osu.Game/Rulesets/Mods/DifficultyAdjustSettingsControl.cs @@ -126,8 +126,7 @@ namespace osu.Game.Rulesets.Mods get => this; set { - if (value == null) - throw new ArgumentNullException(nameof(value)); + ArgumentNullException.ThrowIfNull(value); if (currentBound != null) UnbindFrom(currentBound); BindTo(currentBound = value); diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 096132d024..be5a7f71e7 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -208,8 +208,7 @@ namespace osu.Game.Rulesets.Objects.Drawables /// public void Apply([NotNull] HitObject hitObject) { - if (hitObject == null) - throw new ArgumentNullException($"Cannot apply a null {nameof(HitObject)}."); + ArgumentNullException.ThrowIfNull(hitObject); Apply(new SyntheticHitObjectEntry(hitObject)); } diff --git a/osu.Game/Rulesets/UI/JudgementContainer.cs b/osu.Game/Rulesets/UI/JudgementContainer.cs index 8381e6d6b5..7181e80206 100644 --- a/osu.Game/Rulesets/UI/JudgementContainer.cs +++ b/osu.Game/Rulesets/UI/JudgementContainer.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.UI { public override void Add(T judgement) { - if (judgement == null) throw new ArgumentNullException(nameof(judgement)); + ArgumentNullException.ThrowIfNull(judgement); // remove any existing judgements for the judged object. // this can be the case when rewinding. diff --git a/osu.Game/Scoring/ScoreImporter.cs b/osu.Game/Scoring/ScoreImporter.cs index 797d80b7fa..a3d7fe5de0 100644 --- a/osu.Game/Scoring/ScoreImporter.cs +++ b/osu.Game/Scoring/ScoreImporter.cs @@ -71,8 +71,8 @@ namespace osu.Game.Scoring // These properties are known to be non-null, but these final checks ensure a null hasn't come from somewhere (or the refetch has failed). // Under no circumstance do we want these to be written to realm as null. - if (model.BeatmapInfo == null) throw new ArgumentNullException(nameof(model.BeatmapInfo)); - if (model.Ruleset == null) throw new ArgumentNullException(nameof(model.Ruleset)); + ArgumentNullException.ThrowIfNull(model.BeatmapInfo); + ArgumentNullException.ThrowIfNull(model.Ruleset); PopulateMaximumStatistics(model); diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index c67850bdf6..5000a97b3d 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -147,7 +147,7 @@ namespace osu.Game.Screens.Menu private void addAmplitudesFromSource(IHasAmplitudes source) { - if (source == null) throw new ArgumentNullException(nameof(source)); + ArgumentNullException.ThrowIfNull(source); var amplitudes = source.CurrentAmplitudes.FrequencyAmplitudes.Span; diff --git a/osu.Game/Screens/Play/HUD/ModDisplay.cs b/osu.Game/Screens/Play/HUD/ModDisplay.cs index 3b50a22e3c..8b2b8f9464 100644 --- a/osu.Game/Screens/Play/HUD/ModDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModDisplay.cs @@ -33,8 +33,7 @@ namespace osu.Game.Screens.Play.HUD get => current.Current; set { - if (value == null) - throw new ArgumentNullException(nameof(value)); + ArgumentNullException.ThrowIfNull(value); current.Current = value; } diff --git a/osu.Game/Screens/Play/HUD/ModFlowDisplay.cs b/osu.Game/Screens/Play/HUD/ModFlowDisplay.cs index 23030e640b..38027c64ac 100644 --- a/osu.Game/Screens/Play/HUD/ModFlowDisplay.cs +++ b/osu.Game/Screens/Play/HUD/ModFlowDisplay.cs @@ -30,8 +30,7 @@ namespace osu.Game.Screens.Play.HUD get => current.Current; set { - if (value == null) - throw new ArgumentNullException(nameof(value)); + ArgumentNullException.ThrowIfNull(value); current.Current = value; } diff --git a/osu.Game/Screens/Play/KeyCounterDisplay.cs b/osu.Game/Screens/Play/KeyCounterDisplay.cs index d9ad3cfaf7..bb50d4a539 100644 --- a/osu.Game/Screens/Play/KeyCounterDisplay.cs +++ b/osu.Game/Screens/Play/KeyCounterDisplay.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Play public override void Add(KeyCounter key) { - if (key == null) throw new ArgumentNullException(nameof(key)); + ArgumentNullException.ThrowIfNull(key); base.Add(key); key.IsCounting = IsCounting; diff --git a/osu.Game/Users/Drawables/DrawableFlag.cs b/osu.Game/Users/Drawables/DrawableFlag.cs index 0d209f47e8..929a29251d 100644 --- a/osu.Game/Users/Drawables/DrawableFlag.cs +++ b/osu.Game/Users/Drawables/DrawableFlag.cs @@ -27,8 +27,7 @@ namespace osu.Game.Users.Drawables [BackgroundDependencyLoader] private void load(TextureStore ts) { - if (ts == null) - throw new ArgumentNullException(nameof(ts)); + ArgumentNullException.ThrowIfNull(ts); string textureName = countryCode == CountryCode.Unknown ? "__" : countryCode.ToString(); Texture = ts.Get($@"Flags/{textureName}") ?? ts.Get(@"Flags/__"); diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index e7af127a30..2f7232d5ea 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -36,8 +36,7 @@ namespace osu.Game.Users protected UserPanel(APIUser user) : base(HoverSampleSet.Button) { - if (user == null) - throw new ArgumentNullException(nameof(user)); + ArgumentNullException.ThrowIfNull(user); User = user; } From 30de9ba795a94810411db4791403c03876b13393 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Dec 2022 00:35:59 +0300 Subject: [PATCH 225/384] Dispose previous waveform on track reload --- osu.Game/Beatmaps/WorkingBeatmap.cs | 9 ++++++--- .../Screens/Edit/Compose/Components/Timeline/Timeline.cs | 7 +------ 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 345220a3a3..ab790617bb 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -46,10 +46,11 @@ namespace osu.Game.Beatmaps private readonly object beatmapFetchLock = new object(); - private Waveform waveform; private readonly Lazy storyboard; private readonly Lazy skin; + private Track track; // track is not Lazy as we allow transferring and loading multiple times. + private Waveform waveform; // waveform is also not Lazy as the track may change. protected WorkingBeatmap(BeatmapInfo beatmapInfo, AudioManager audioManager) { @@ -107,10 +108,12 @@ namespace osu.Game.Beatmaps public Track LoadTrack() { - // track could be changed, clearing waveform cache + track = GetBeatmapTrack() ?? GetVirtualTrack(1000); + + // the track may have changed, recycle the current waveform. + waveform?.Dispose(); waveform = null; - track = GetBeatmapTrack() ?? GetVirtualTrack(1000); return track; } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 918b5f8109..75de15fe56 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -141,12 +141,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); track.BindTo(editorClock.Track); - track.BindValueChanged(_ => - { - waveform.Waveform = beatmap.Value.Waveform; - }, true); - - waveform.Waveform = beatmap.Value.Waveform; + track.BindValueChanged(_ => waveform.Waveform = beatmap.Value.Waveform, true); Zoom = (float)(defaultTimelineZoom * editorBeatmap.BeatmapInfo.TimelineZoom); } From a6650136269a8d8841fabea30bf9310fc07e639f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Dec 2022 00:56:37 +0300 Subject: [PATCH 226/384] Add failing test case --- .../TestSceneZoomableScrollContainer.cs | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs index 6bc2922253..a141e4d431 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editing { public partial class TestSceneZoomableScrollContainer : OsuManualInputManagerTestScene { - private ZoomableScrollContainer scrollContainer; + private TestZoomableScrollContainer scrollContainer; private Drawable innerBox; [SetUpSteps] @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Editing RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(30) }, - scrollContainer = new ZoomableScrollContainer(1, 60, 1) + scrollContainer = new TestZoomableScrollContainer(1, 60, 1) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -93,6 +93,14 @@ namespace osu.Game.Tests.Visual.Editing AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth); } + [Test] + public void TestWidthUpdatesOnSecondZoomSetup() + { + AddAssert("Inner container width = 1x", () => innerBox.DrawWidth == scrollContainer.DrawWidth); + AddStep("reload zoom", () => scrollContainer.SetupZoom(10, 10, 60)); + AddAssert("Inner container width = 10x", () => innerBox.DrawWidth == scrollContainer.DrawWidth * 10); + } + [Test] public void TestZoom0() { @@ -190,5 +198,15 @@ namespace osu.Game.Tests.Visual.Editing private Quad scrollQuad => scrollContainer.ScreenSpaceDrawQuad; private Quad boxQuad => innerBox.ScreenSpaceDrawQuad; + + private partial class TestZoomableScrollContainer : ZoomableScrollContainer + { + public TestZoomableScrollContainer(int minimum, float maximum, float initial) + : base(minimum, maximum, initial) + { + } + + public new void SetupZoom(float initial, float minimum, float maximum) => base.SetupZoom(initial, minimum, maximum); + } } } From 0cb9b7983498cbc313a070174868a487a254e247 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 23 Dec 2022 00:56:02 +0300 Subject: [PATCH 227/384] Fix `ZoomableScrollContainer` potentially not updating content width on setup --- .../Compose/Components/Timeline/ZoomableScrollContainer.cs | 6 ++++-- 1 file changed, 4 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 28f7731354..951f4129d4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -99,9 +99,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline minZoom = minimum; maxZoom = maximum; - CurrentZoom = zoomTarget = initial; - isZoomSetUp = true; + CurrentZoom = zoomTarget = initial; + zoomedContentWidthCache.Invalidate(); + + isZoomSetUp = true; zoomedContent.Show(); } From f25439e359b0fb67c66b1a1e85a19fa461029a23 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Fri, 23 Dec 2022 01:54:49 +0300 Subject: [PATCH 228/384] Move track change subscription to LoadComplete --- osu.Game/Screens/Edit/Timing/TapTimingControl.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 36cd5f8b70..0d29b69d96 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -189,6 +189,10 @@ namespace osu.Game.Screens.Edit.Timing }, true); track.BindTo(clock.Track); + } + + protected override void LoadComplete() + { track.ValueChanged += _ => waveformContainer.Child = new WaveformComparisonDisplay(); } From 5eccafe19046c331468c6c6e2c9b47c84ead38a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Dec 2022 16:45:40 +0800 Subject: [PATCH 229/384] Fix wiki overlay showing error message when load is cancelled --- osu.Game/Overlays/WikiOverlay.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index a06c180948..9ccd0af2b6 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -118,7 +118,11 @@ namespace osu.Game.Overlays Loading.Show(); request.Success += response => Schedule(() => onSuccess(response)); - request.Failure += _ => Schedule(onFail); + request.Failure += ex => + { + if (ex is not OperationCanceledException) + Schedule(onFail); + }; api.PerformAsync(request); } From a677c8be0651e3440f0f21a0546160100f9be035 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Dec 2022 21:17:42 +0800 Subject: [PATCH 230/384] Change path on error --- 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 9ccd0af2b6..a9a131e481 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -21,6 +21,8 @@ namespace osu.Game.Overlays { private const string index_path = @"main_page"; + public string CurrentPath => path.Value; + private readonly Bindable path = new Bindable(index_path); private readonly Bindable wikiData = new Bindable(); @@ -105,6 +107,9 @@ namespace osu.Game.Overlays if (e.NewValue == wikiData.Value?.Path) return; + if (e.NewValue == "error") + return; + cancellationToken?.Cancel(); request?.Cancel(); @@ -152,6 +157,7 @@ namespace osu.Game.Overlays private void onFail() { + path.Value = "error"; LoadDisplay(articlePage = new WikiArticlePage($@"{api.WebsiteRootUrl}/wiki/", $"Something went wrong when trying to fetch page \"{path.Value}\".\n\n[Return to the main page](Main_Page).")); } From 4a69cb4aae964ea8bd61178337e2b203a0e5cdc8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Dec 2022 21:19:04 +0800 Subject: [PATCH 231/384] Add test coverage of wiki cancellation not causing error --- osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs index 620fd710e3..4ad03c052f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Linq; using System.Net; using NUnit.Framework; using osu.Game.Online.API; @@ -29,6 +30,15 @@ namespace osu.Game.Tests.Visual.Online AddStep("Show main page", () => wiki.Show()); } + [Test] + public void TestCancellationDoesntShowError() + { + AddStep("Show main page", () => wiki.Show()); + AddStep("Show another page", () => wiki.ShowPage("Article_styling_criteria/Formatting")); + + AddUntilStep("Current path is not error", () => wiki.CurrentPath != "error"); + } + [Test] public void TestArticlePage() { From 137a32ade66cf6bc2b3e0a7cffcfbe1049a072c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 23 Dec 2022 16:39:35 +0100 Subject: [PATCH 232/384] Remove unused using directive --- osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs index 4ad03c052f..806d231cb4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using System.Linq; using System.Net; using NUnit.Framework; using osu.Game.Online.API; From 3dfbb47b010aa6351951310dc9b008ce3cd2b6cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 23 Dec 2022 16:42:24 +0100 Subject: [PATCH 233/384] Add test coverage for wrong error message --- osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs index 806d231cb4..b0e4303ca4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneWikiOverlay.cs @@ -4,8 +4,11 @@ #nullable disable using System; +using System.Linq; using System.Net; using NUnit.Framework; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -65,7 +68,9 @@ namespace osu.Game.Tests.Visual.Online public void TestErrorPage() { setUpWikiResponse(responseArticlePage); - AddStep("Show Error Page", () => wiki.ShowPage("Error")); + AddStep("Show nonexistent page", () => wiki.ShowPage("This_page_will_error_out")); + AddUntilStep("Wait for error page", () => wiki.CurrentPath == "error"); + AddUntilStep("Error message correct", () => wiki.ChildrenOfType().Any(text => text.Text == "\"This_page_will_error_out\".")); } private void setUpWikiResponse(APIWikiPage r, string redirectionPath = null) From 9a2cc043611ab6e78feef7c18958747baa795061 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 23 Dec 2022 16:42:46 +0100 Subject: [PATCH 234/384] Fix wrong path being used in fail handler --- osu.Game/Overlays/WikiOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/WikiOverlay.cs b/osu.Game/Overlays/WikiOverlay.cs index a9a131e481..88dc2cd7a4 100644 --- a/osu.Game/Overlays/WikiOverlay.cs +++ b/osu.Game/Overlays/WikiOverlay.cs @@ -126,7 +126,7 @@ namespace osu.Game.Overlays request.Failure += ex => { if (ex is not OperationCanceledException) - Schedule(onFail); + Schedule(onFail, request.Path); }; api.PerformAsync(request); @@ -155,11 +155,11 @@ namespace osu.Game.Overlays } } - private void onFail() + private void onFail(string originalPath) { path.Value = "error"; LoadDisplay(articlePage = new WikiArticlePage($@"{api.WebsiteRootUrl}/wiki/", - $"Something went wrong when trying to fetch page \"{path.Value}\".\n\n[Return to the main page](Main_Page).")); + $"Something went wrong when trying to fetch page \"{originalPath}\".\n\n[Return to the main page](Main_Page).")); } private void showParentPage() From 494886ef9277f4c9a626553fa08c21797eab362e Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 23 Dec 2022 11:11:15 -0800 Subject: [PATCH 235/384] Rename `Text` to `Metadata` --- osu.Game/Overlays/BeatmapSet/Info.cs | 8 ++++---- osu.Game/Overlays/BeatmapSet/MetadataSection.cs | 14 +++++++------- .../BeatmapSet/MetadataSectionDescription.cs | 4 ++-- .../Overlays/BeatmapSet/MetadataSectionGenre.cs | 6 +++--- .../Overlays/BeatmapSet/MetadataSectionLanguage.cs | 6 +++--- .../Overlays/BeatmapSet/MetadataSectionSource.cs | 6 +++--- .../Overlays/BeatmapSet/MetadataSectionTags.cs | 4 ++-- osu.Game/Screens/Select/BeatmapDetails.cs | 6 +++--- 8 files changed, 27 insertions(+), 27 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 8a1384f0d3..8126db3be0 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -121,10 +121,10 @@ namespace osu.Game.Overlays.BeatmapSet BeatmapSet.ValueChanged += b => { - source.Text = b.NewValue?.Source ?? string.Empty; - tags.Text = b.NewValue?.Tags ?? string.Empty; - genre.Text = b.NewValue?.Genre ?? new BeatmapSetOnlineGenre { Id = 1 }; - language.Text = b.NewValue?.Language ?? new BeatmapSetOnlineLanguage { Id = 1 }; + source.Metadata = b.NewValue?.Source ?? string.Empty; + tags.Metadata = b.NewValue?.Tags ?? string.Empty; + genre.Metadata = b.NewValue?.Genre ?? new BeatmapSetOnlineGenre { Id = 1 }; + language.Metadata = b.NewValue?.Language ?? new BeatmapSetOnlineLanguage { Id = 1 }; bool setHasLeaderboard = b.NewValue?.Status > 0; successRate.Alpha = setHasLeaderboard ? 1 : 0; notRankedPlaceholder.Alpha = setHasLeaderboard ? 0 : 1; diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSection.cs b/osu.Game/Overlays/BeatmapSet/MetadataSection.cs index 6af77f975e..d32d8e83fb 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataSection.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSection.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.BeatmapSet { public abstract partial class MetadataSection : MetadataSection { - public override string Text + public override string Metadata { set { @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.BeatmapSet return; } - base.Text = value; + base.Metadata = value; } } @@ -78,7 +78,7 @@ namespace osu.Game.Overlays.BeatmapSet }; } - public virtual T Text + public virtual T Metadata { set { @@ -90,11 +90,11 @@ namespace osu.Game.Overlays.BeatmapSet this.FadeIn(TRANSITION_DURATION); - setTextAsync(value); + setTextFlowAsync(value); } } - private void setTextAsync(T text) + private void setTextFlowAsync(T metadata) { LoadComponentAsync(new LinkFlowContainer(s => s.Font = s.Font.With(size: 14)) { @@ -105,7 +105,7 @@ namespace osu.Game.Overlays.BeatmapSet { textFlow?.Expire(); - AddMetadata(text, loaded); + AddMetadata(metadata, loaded); textContainer.Add(textFlow = loaded); @@ -114,6 +114,6 @@ namespace osu.Game.Overlays.BeatmapSet }); } - protected abstract void AddMetadata(T text, LinkFlowContainer loaded); + protected abstract void AddMetadata(T metadata, LinkFlowContainer loaded); } } diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSectionDescription.cs b/osu.Game/Overlays/BeatmapSet/MetadataSectionDescription.cs index a9503cded3..e6837951c9 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataSectionDescription.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSectionDescription.cs @@ -13,9 +13,9 @@ namespace osu.Game.Overlays.BeatmapSet { } - protected override void AddMetadata(string text, LinkFlowContainer loaded) + protected override void AddMetadata(string metadata, LinkFlowContainer loaded) { - loaded.AddText(text); + loaded.AddText(metadata); } } } diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSectionGenre.cs b/osu.Game/Overlays/BeatmapSet/MetadataSectionGenre.cs index 0e045b2bf1..d41115f2b8 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataSectionGenre.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSectionGenre.cs @@ -17,14 +17,14 @@ namespace osu.Game.Overlays.BeatmapSet { } - protected override void AddMetadata(BeatmapSetOnlineGenre text, LinkFlowContainer loaded) + protected override void AddMetadata(BeatmapSetOnlineGenre metadata, LinkFlowContainer loaded) { - var genre = (SearchGenre)text.Id; + var genre = (SearchGenre)metadata.Id; if (Enum.IsDefined(genre)) loaded.AddLink(genre.GetLocalisableDescription(), LinkAction.FilterBeatmapSetGenre, genre); else - loaded.AddText(text.Name); + loaded.AddText(metadata.Name); } } } diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSectionLanguage.cs b/osu.Game/Overlays/BeatmapSet/MetadataSectionLanguage.cs index 6e59818b93..e831b1eaca 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataSectionLanguage.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSectionLanguage.cs @@ -17,14 +17,14 @@ namespace osu.Game.Overlays.BeatmapSet { } - protected override void AddMetadata(BeatmapSetOnlineLanguage text, LinkFlowContainer loaded) + protected override void AddMetadata(BeatmapSetOnlineLanguage metadata, LinkFlowContainer loaded) { - var language = (SearchLanguage)text.Id; + var language = (SearchLanguage)metadata.Id; if (Enum.IsDefined(language)) loaded.AddLink(language.GetLocalisableDescription(), LinkAction.FilterBeatmapSetLanguage, language); else - loaded.AddText(text.Name); + loaded.AddText(metadata.Name); } } } diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs b/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs index 6deb866f19..544dc0dfe4 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSectionSource.cs @@ -14,12 +14,12 @@ namespace osu.Game.Overlays.BeatmapSet { } - protected override void AddMetadata(string text, LinkFlowContainer loaded) + protected override void AddMetadata(string metadata, LinkFlowContainer loaded) { if (SearchAction != null) - loaded.AddLink(text, () => SearchAction(text)); + loaded.AddLink(metadata, () => SearchAction(metadata)); else - loaded.AddLink(text, LinkAction.SearchBeatmapSet, text); + loaded.AddLink(metadata, LinkAction.SearchBeatmapSet, metadata); } } } diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSectionTags.cs b/osu.Game/Overlays/BeatmapSet/MetadataSectionTags.cs index 1b384fcdd3..fc16ba19d8 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataSectionTags.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSectionTags.cs @@ -14,9 +14,9 @@ namespace osu.Game.Overlays.BeatmapSet { } - protected override void AddMetadata(string text, LinkFlowContainer loaded) + protected override void AddMetadata(string metadata, LinkFlowContainer loaded) { - string[] tags = text.Split(" "); + string[] tags = metadata.Split(" "); for (int i = 0; i <= tags.Length - 1; i++) { diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index b158bef8dc..712b610515 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -187,9 +187,9 @@ namespace osu.Game.Screens.Select private void updateStatistics() { advanced.BeatmapInfo = BeatmapInfo; - description.Text = BeatmapInfo?.DifficultyName ?? string.Empty; - source.Text = BeatmapInfo?.Metadata.Source ?? string.Empty; - tags.Text = BeatmapInfo?.Metadata.Tags ?? string.Empty; + description.Metadata = BeatmapInfo?.DifficultyName ?? string.Empty; + source.Metadata = BeatmapInfo?.Metadata.Source ?? string.Empty; + tags.Metadata = BeatmapInfo?.Metadata.Tags ?? string.Empty; // failTimes may have been previously fetched if (ratings != null && failTimes != null) From 2dbcf05fe4317713f7d0d35034deeef48ba78c83 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 23 Dec 2022 11:13:27 -0800 Subject: [PATCH 236/384] Use enum values as ids in fallback instead --- osu.Game/Overlays/BeatmapSet/Info.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 8126db3be0..d184f0d0fd 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays.BeatmapListing; namespace osu.Game.Overlays.BeatmapSet { @@ -123,8 +124,8 @@ namespace osu.Game.Overlays.BeatmapSet { source.Metadata = b.NewValue?.Source ?? string.Empty; tags.Metadata = b.NewValue?.Tags ?? string.Empty; - genre.Metadata = b.NewValue?.Genre ?? new BeatmapSetOnlineGenre { Id = 1 }; - language.Metadata = b.NewValue?.Language ?? new BeatmapSetOnlineLanguage { Id = 1 }; + genre.Metadata = b.NewValue?.Genre ?? new BeatmapSetOnlineGenre { Id = (int)SearchGenre.Unspecified }; + language.Metadata = b.NewValue?.Language ?? new BeatmapSetOnlineLanguage { Id = (int)SearchLanguage.Unspecified }; bool setHasLeaderboard = b.NewValue?.Status > 0; successRate.Alpha = setHasLeaderboard ? 1 : 0; notRankedPlaceholder.Alpha = setHasLeaderboard ? 0 : 1; From 727ac00f6d5b744c6e83be66b7a6a98c7d6a984c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Dec 2022 03:22:04 +0800 Subject: [PATCH 237/384] Combine base class for `JudgementPiece` --- .../Skinning/Argon/ArgonJudgementPiece.cs | 11 ++----- .../Skinning/Argon/ArgonJudgementPiece.cs | 11 ++----- .../Skinning/Argon/ArgonJudgementPiece.cs | 11 ++----- .../Skinning/Argon/ArgonJudgementPiece.cs | 11 ++----- .../Judgements/DefaultJudgementPiece.cs | 12 ++------ .../Rulesets/Judgements/JudgementPiece.cs | 29 +++++++++++++++++++ 6 files changed, 39 insertions(+), 46 deletions(-) create mode 100644 osu.Game/Rulesets/Judgements/JudgementPiece.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs index 82d10e500d..5cb03f6536 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs @@ -3,11 +3,9 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -18,20 +16,16 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Catch.Skinning.Argon { - public partial class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement + public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement { - protected readonly HitResult Result; - - protected SpriteText JudgementText { get; private set; } = null!; - private RingExplosion? ringExplosion; [Resolved] private OsuColour colours { get; set; } = null!; public ArgonJudgementPiece(HitResult result) + : base(result) { - Result = result; Origin = Anchor.Centre; Y = 160; } @@ -47,7 +41,6 @@ namespace osu.Game.Rulesets.Catch.Skinning.Argon { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = Result.GetDescription().ToUpperInvariant(), Colour = colours.ForHitResult(Result), Blending = BlendingParameters.Additive, Spacing = new Vector2(10, 0), diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs index 2dbf475c7e..870a142ca7 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs @@ -3,11 +3,9 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -18,20 +16,16 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning.Argon { - public partial class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement + public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement { - protected readonly HitResult Result; - - protected SpriteText JudgementText { get; private set; } = null!; - private RingExplosion? ringExplosion; [Resolved] private OsuColour colours { get; set; } = null!; public ArgonJudgementPiece(HitResult result) + : base(result) { - Result = result; Origin = Anchor.Centre; Y = 160; } @@ -47,7 +41,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = Result.GetDescription().ToUpperInvariant(), Colour = colours.ForHitResult(Result), Blending = BlendingParameters.Additive, Spacing = new Vector2(10, 0), diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs index f5f410210b..7b1a0d8d08 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs @@ -3,10 +3,8 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -17,20 +15,16 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Skinning.Argon { - public partial class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement + public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement { - protected readonly HitResult Result; - - protected SpriteText JudgementText { get; private set; } = null!; - private RingExplosion? ringExplosion; [Resolved] private OsuColour colours { get; set; } = null!; public ArgonJudgementPiece(HitResult result) + : base(result) { - Result = result; Origin = Anchor.Centre; } @@ -45,7 +39,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = Result.GetDescription().ToUpperInvariant(), Colour = colours.ForHitResult(Result), Blending = BlendingParameters.Additive, Spacing = new Vector2(5, 0), diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs index 6756001089..e8240911b0 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs @@ -3,11 +3,9 @@ using System; using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -18,20 +16,16 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Taiko.Skinning.Argon { - public partial class ArgonJudgementPiece : CompositeDrawable, IAnimatableJudgement + public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement { - protected readonly HitResult Result; - - protected SpriteText JudgementText { get; private set; } = null!; - private RingExplosion? ringExplosion; [Resolved] private OsuColour colours { get; set; } = null!; public ArgonJudgementPiece(HitResult result) + : base(result) { - Result = result; RelativePositionAxes = Axes.Both; RelativeSizeAxes = Axes.Both; } @@ -45,7 +39,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = Result.GetDescription().ToUpperInvariant(), Colour = colours.ForHitResult(Result), Blending = BlendingParameters.Additive, Spacing = new Vector2(10, 0), diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index 2b8bd08ede..485e2b409d 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -4,10 +4,7 @@ #nullable disable using osu.Framework.Allocation; -using osu.Framework.Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Scoring; @@ -15,18 +12,14 @@ using osuTK; namespace osu.Game.Rulesets.Judgements { - public partial class DefaultJudgementPiece : CompositeDrawable, IAnimatableJudgement + public partial class DefaultJudgementPiece : JudgementPiece, IAnimatableJudgement { - protected readonly HitResult Result; - - protected SpriteText JudgementText { get; private set; } - [Resolved] private OsuColour colours { get; set; } public DefaultJudgementPiece(HitResult result) + : base(result) { - Result = result; Origin = Anchor.Centre; } @@ -41,7 +34,6 @@ namespace osu.Game.Rulesets.Judgements { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = Result.GetDescription().ToUpperInvariant(), Colour = colours.ForHitResult(Result), Font = OsuFont.Numeric.With(size: 20), Scale = new Vector2(0.85f, 1), diff --git a/osu.Game/Rulesets/Judgements/JudgementPiece.cs b/osu.Game/Rulesets/Judgements/JudgementPiece.cs new file mode 100644 index 0000000000..4e9b495cb5 --- /dev/null +++ b/osu.Game/Rulesets/Judgements/JudgementPiece.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Extensions; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Judgements +{ + public abstract partial class JudgementPiece : CompositeDrawable + { + protected readonly HitResult Result; + + protected SpriteText JudgementText { get; set; } = null!; + + protected JudgementPiece(HitResult result) + { + Result = result; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + JudgementText.Text = Result.GetDescription().ToUpperInvariant(); + } + } +} From e8a0f8996cfbf76747647c2ab1a0573007393063 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Dec 2022 03:35:27 +0800 Subject: [PATCH 238/384] Remove unused osu!catch `ArgonJudgementPiece` --- .../Skinning/Argon/ArgonJudgementPiece.cs | 186 ------------------ 1 file changed, 186 deletions(-) delete mode 100644 osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.cs deleted file mode 100644 index 5cb03f6536..0000000000 --- a/osu.Game.Rulesets.Catch/Skinning/Argon/ArgonJudgementPiece.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. - -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Utils; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Scoring; -using osuTK; -using osuTK.Graphics; - -namespace osu.Game.Rulesets.Catch.Skinning.Argon -{ - public partial class ArgonJudgementPiece : JudgementPiece, IAnimatableJudgement - { - private RingExplosion? ringExplosion; - - [Resolved] - private OsuColour colours { get; set; } = null!; - - public ArgonJudgementPiece(HitResult result) - : base(result) - { - Origin = Anchor.Centre; - Y = 160; - } - - [BackgroundDependencyLoader] - private void load() - { - AutoSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - JudgementText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = colours.ForHitResult(Result), - Blending = BlendingParameters.Additive, - Spacing = new Vector2(10, 0), - Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular), - }, - }; - - if (Result.IsHit()) - { - AddInternal(ringExplosion = new RingExplosion(Result) - { - Colour = colours.ForHitResult(Result), - }); - } - } - - /// - /// Plays the default animation for this judgement piece. - /// - /// - /// The base implementation only handles fade (for all result types) and misses. - /// Individual rulesets are recommended to implement their appropriate hit animations. - /// - public virtual void PlayAnimation() - { - switch (Result) - { - default: - JudgementText - .ScaleTo(Vector2.One) - .ScaleTo(new Vector2(1.4f), 1800, Easing.OutQuint); - break; - - case HitResult.Miss: - this.ScaleTo(1.6f); - this.ScaleTo(1, 100, Easing.In); - - this.MoveTo(Vector2.Zero); - this.MoveToOffset(new Vector2(0, 100), 800, Easing.InQuint); - - this.RotateTo(0); - this.RotateTo(40, 800, Easing.InQuint); - break; - } - - this.FadeOutFromOne(800); - - ringExplosion?.PlayAnimation(); - } - - public Drawable? GetAboveHitObjectsProxiedContent() => null; - - private partial class RingExplosion : CompositeDrawable - { - private readonly float travel = 52; - - public RingExplosion(HitResult result) - { - const float thickness = 4; - - const float small_size = 9; - const float large_size = 14; - - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - Blending = BlendingParameters.Additive; - - int countSmall = 0; - int countLarge = 0; - - switch (result) - { - case HitResult.Meh: - countSmall = 3; - travel *= 0.3f; - break; - - case HitResult.Ok: - case HitResult.Good: - countSmall = 4; - travel *= 0.6f; - break; - - case HitResult.Great: - case HitResult.Perfect: - countSmall = 4; - countLarge = 4; - break; - } - - for (int i = 0; i < countSmall; i++) - AddInternal(new RingPiece(thickness) { Size = new Vector2(small_size) }); - - for (int i = 0; i < countLarge; i++) - AddInternal(new RingPiece(thickness) { Size = new Vector2(large_size) }); - } - - public void PlayAnimation() - { - foreach (var c in InternalChildren) - { - const float start_position_ratio = 0.3f; - - float direction = RNG.NextSingle(0, 360); - float distance = RNG.NextSingle(travel / 2, travel); - - c.MoveTo(new Vector2( - MathF.Cos(direction) * distance * start_position_ratio, - MathF.Sin(direction) * distance * start_position_ratio - )); - - c.MoveTo(new Vector2( - MathF.Cos(direction) * distance, - MathF.Sin(direction) * distance - ), 600, Easing.OutQuint); - } - - this.FadeOutFromOne(1000, Easing.OutQuint); - } - - public partial class RingPiece : CircularContainer - { - public RingPiece(float thickness = 9) - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - - Masking = true; - BorderThickness = thickness; - BorderColour = Color4.White; - - Child = new Box - { - AlwaysPresent = true, - Alpha = 0, - RelativeSizeAxes = Axes.Both - }; - } - } - } - } -} From 03603f8b548bc6869dac942543db678edf9ecc7a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Dec 2022 03:35:44 +0800 Subject: [PATCH 239/384] Don't show great or higher judgements when using argon "pro" skin --- .../Skinning/Argon/ManiaArgonSkinTransformer.cs | 4 ++++ .../Skinning/Argon/OsuArgonSkinTransformer.cs | 4 ++++ .../Skinning/Argon/TaikoArgonSkinTransformer.cs | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs index eb7f63fbe2..057b7eb0d9 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ManiaArgonSkinTransformer.cs @@ -27,6 +27,10 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon switch (lookup) { case GameplaySkinComponentLookup resultComponent: + // This should eventually be moved to a skin setting, when supported. + if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great) + return Drawable.Empty(); + return new ArgonJudgementPiece(resultComponent.Component); case ManiaSkinComponentLookup maniaComponent: diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs index 86194d2c43..f98a47097d 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/OsuArgonSkinTransformer.cs @@ -19,6 +19,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon switch (lookup) { case GameplaySkinComponentLookup resultComponent: + // This should eventually be moved to a skin setting, when supported. + if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great) + return Drawable.Empty(); + return new ArgonJudgementPiece(resultComponent.Component); case OsuSkinComponentLookup osuComponent: diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs index a5d091a1c8..780018af4e 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/TaikoArgonSkinTransformer.cs @@ -19,6 +19,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon switch (component) { case GameplaySkinComponentLookup resultComponent: + // This should eventually be moved to a skin setting, when supported. + if (Skin is ArgonProSkin && resultComponent.Component >= HitResult.Great) + return Drawable.Empty(); + return new ArgonJudgementPiece(resultComponent.Component); case TaikoSkinComponentLookup taikoComponent: From 3e782c5f5fd6eee9a34150aeaeb9aefbd9db97f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 23 Dec 2022 23:46:41 +0100 Subject: [PATCH 240/384] Extract interface for solo statistics watcher --- .../Online/Solo/ISoloStatisticsWatcher.cs | 23 +++++++++++++++++++ osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 2 +- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/Solo/ISoloStatisticsWatcher.cs diff --git a/osu.Game/Online/Solo/ISoloStatisticsWatcher.cs b/osu.Game/Online/Solo/ISoloStatisticsWatcher.cs new file mode 100644 index 0000000000..84986297bf --- /dev/null +++ b/osu.Game/Online/Solo/ISoloStatisticsWatcher.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Game.Scoring; + +namespace osu.Game.Online.Solo +{ + /// + /// A component that delivers updates to the logged in user's gameplay statistics after completed scores. + /// + [Cached] + public interface ISoloStatisticsWatcher + { + /// + /// Registers for a user statistics update after the given has been processed server-side. + /// + /// The score to listen for the statistics update for. + /// The callback to be invoked once the statistics update has been prepared. + void RegisterForStatisticsUpdateAfter(ScoreInfo score, Action onUpdateReady); + } +} diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 48f39504a3..6a9a20e58d 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -20,7 +20,7 @@ namespace osu.Game.Online.Solo /// /// A persistent component that binds to the spectator server and API in order to deliver updates about the logged in user's gameplay statistics. /// - public partial class SoloStatisticsWatcher : Component + public partial class SoloStatisticsWatcher : Component, ISoloStatisticsWatcher { [Resolved] private SpectatorClient spectatorClient { get; set; } = null!; From c7f248e13c7ce98c093f8f41cad30d3b9d7ca621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 23 Dec 2022 14:27:22 +0100 Subject: [PATCH 241/384] Implement overall ranking display for solo results screen --- .../Visual/Ranking/TestSceneOverallRanking.cs | 142 +++++++++++++++++ .../Statistics/User/AccuracyChangeRow.cs | 35 +++++ .../Statistics/User/GlobalRankChangeRow.cs | 58 +++++++ .../Statistics/User/MaximumComboChangeRow.cs | 34 +++++ .../Ranking/Statistics/User/OverallRanking.cs | 90 +++++++++++ .../User/PerformancePointsChangeRow.cs | 56 +++++++ .../Statistics/User/RankedScoreChangeRow.cs | 35 +++++ .../Statistics/User/RankingChangeRow.cs | 144 ++++++++++++++++++ .../Statistics/User/TotalScoreChangeRow.cs | 35 +++++ 9 files changed, 629 insertions(+) create mode 100644 osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/User/AccuracyChangeRow.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/User/GlobalRankChangeRow.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/User/MaximumComboChangeRow.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/User/PerformancePointsChangeRow.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/User/RankedScoreChangeRow.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/User/TotalScoreChangeRow.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs new file mode 100644 index 0000000000..8d2147056c --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs @@ -0,0 +1,142 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Online.Solo; +using osu.Game.Scoring; +using osu.Game.Users; +using OverallRanking = osu.Game.Screens.Ranking.Statistics.User.OverallRanking; + +namespace osu.Game.Tests.Visual.Ranking +{ + public partial class TestSceneOverallRanking : OsuTestScene + { + [Cached(typeof(ISoloStatisticsWatcher))] + private MockSoloStatisticsWatcher soloStatisticsWatcher { get; } = new MockSoloStatisticsWatcher(); + + [Test] + public void TestUpdatePending() + { + createDisplay(); + } + + [Test] + public void TestAllIncreased() + { + createDisplay(); + AddStep("trigger update success", () => + { + soloStatisticsWatcher.TriggerSuccess( + new UserStatistics + { + GlobalRank = 12_345, + Accuracy = 0.9899, + MaxCombo = 2_322, + RankedScore = 23_123_543_456, + TotalScore = 123_123_543_456, + PP = 5_072 + }, + new UserStatistics + { + GlobalRank = 1_234, + Accuracy = 0.9907, + MaxCombo = 2_352, + RankedScore = 23_124_231_435, + TotalScore = 123_124_231_435, + PP = 5_434 + }); + }); + } + + [Test] + public void TestAllDecreased() + { + createDisplay(); + AddStep("trigger update success", () => + { + soloStatisticsWatcher.TriggerSuccess( + new UserStatistics + { + GlobalRank = 1_234, + Accuracy = 0.9907, + MaxCombo = 2_352, + RankedScore = 23_124_231_435, + TotalScore = 123_124_231_435, + PP = 5_434 + }, + new UserStatistics + { + GlobalRank = 12_345, + Accuracy = 0.9899, + MaxCombo = 2_322, + RankedScore = 23_123_543_456, + TotalScore = 123_123_543_456, + PP = 5_072 + }); + }); + } + + [Test] + public void TestNoChanges() + { + var statistics = new UserStatistics + { + GlobalRank = 12_345, + Accuracy = 0.9899, + MaxCombo = 2_322, + RankedScore = 23_123_543_456, + TotalScore = 123_123_543_456, + PP = 5_072 + }; + + createDisplay(); + AddStep("trigger update success", () => soloStatisticsWatcher.TriggerSuccess(statistics, statistics)); + } + + [Test] + public void TestNotRanked() + { + var statistics = new UserStatistics + { + GlobalRank = null, + Accuracy = 0.9899, + MaxCombo = 2_322, + RankedScore = 23_123_543_456, + TotalScore = 123_123_543_456, + PP = null + }; + + createDisplay(); + AddStep("trigger update success", () => soloStatisticsWatcher.TriggerSuccess(statistics, statistics)); + } + + private void createDisplay() => AddStep("create display", () => Child = new OverallRanking(new ScoreInfo()) + { + Width = 400, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }); + + private class MockSoloStatisticsWatcher : ISoloStatisticsWatcher + { + private ScoreInfo? score; + private Action? onUpdateReady; + + public void RegisterForStatisticsUpdateAfter(ScoreInfo score, Action onUpdateReady) + { + this.score = score; + this.onUpdateReady = onUpdateReady; + } + + public void TriggerSuccess(UserStatistics before, UserStatistics after) + { + Debug.Assert(score != null && onUpdateReady != null); + onUpdateReady.Invoke(new SoloStatisticsUpdate(score, before, after)); + } + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/User/AccuracyChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/AccuracyChangeRow.cs new file mode 100644 index 0000000000..0f5dd9074a --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/User/AccuracyChangeRow.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 osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Utils; + +namespace osu.Game.Screens.Ranking.Statistics.User +{ + public partial class AccuracyChangeRow : RankingChangeRow + { + public AccuracyChangeRow() + : base(stats => stats.Accuracy) + { + } + + protected override LocalisableString Label => UsersStrings.ShowStatsHitAccuracy; + + protected override LocalisableString FormatCurrentValue(double current) => current.FormatAccuracy(); + + protected override int CalculateDifference(double previous, double current, out LocalisableString formattedDifference) + { + double difference = current - previous; + + if (difference < 0) + formattedDifference = difference.FormatAccuracy(); + else if (difference > 0) + formattedDifference = LocalisableString.Interpolate($@"+{difference.FormatAccuracy()}"); + else + formattedDifference = string.Empty; + + return current.CompareTo(previous); + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/User/GlobalRankChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/GlobalRankChangeRow.cs new file mode 100644 index 0000000000..0d91d6f8f9 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/User/GlobalRankChangeRow.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 System.Diagnostics; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; +using osu.Game.Utils; + +namespace osu.Game.Screens.Ranking.Statistics.User +{ + public partial class GlobalRankChangeRow : RankingChangeRow + { + public GlobalRankChangeRow() + : base(stats => stats.GlobalRank) + { + } + + protected override LocalisableString Label => UsersStrings.ShowRankGlobalSimple; + + protected override LocalisableString FormatCurrentValue(int? current) + => current == null ? string.Empty : current.Value.FormatRank(); + + protected override int CalculateDifference(int? previous, int? current, out LocalisableString formattedDifference) + { + if (previous == null && current == null) + { + formattedDifference = string.Empty; + return 0; + } + + if (previous == null && current != null) + { + formattedDifference = LocalisableString.Interpolate($"+{current.Value.FormatRank()}"); + return 1; + } + + if (previous != null && current == null) + { + formattedDifference = LocalisableString.Interpolate($"-{previous.Value.FormatRank()}"); + return -1; + } + + Debug.Assert(previous != null && current != null); + + // note that ranks work backwards, i.e. lower rank is _better_. + int difference = previous.Value - current.Value; + + if (difference < 0) + formattedDifference = difference.FormatRank(); + else if (difference > 0) + formattedDifference = LocalisableString.Interpolate($"+{difference.FormatRank()}"); + else + formattedDifference = string.Empty; + + return difference; + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/User/MaximumComboChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/MaximumComboChangeRow.cs new file mode 100644 index 0000000000..37e3cec52f --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/User/MaximumComboChangeRow.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Screens.Ranking.Statistics.User +{ + public partial class MaximumComboChangeRow : RankingChangeRow + { + public MaximumComboChangeRow() + : base(stats => stats.MaxCombo) + { + } + + protected override LocalisableString Label => UsersStrings.ShowStatsMaximumCombo; + + protected override LocalisableString FormatCurrentValue(int current) => LocalisableString.Interpolate($@"{current:N0}x"); + + protected override int CalculateDifference(int previous, int current, out LocalisableString formattedDifference) + { + int difference = current - previous; + + if (difference < 0) + formattedDifference = LocalisableString.Interpolate($@"{difference:N0}x"); + else if (difference > 0) + formattedDifference = LocalisableString.Interpolate($@"+{difference:N0}x"); + else + formattedDifference = string.Empty; + + return current.CompareTo(previous); + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs new file mode 100644 index 0000000000..499deb92be --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs @@ -0,0 +1,90 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Solo; +using osu.Game.Scoring; +using osuTK; + +namespace osu.Game.Screens.Ranking.Statistics.User +{ + public partial class OverallRanking : CompositeDrawable + { + private const float transition_duration = 300; + + private readonly ScoreInfo score; + + private readonly Bindable statisticsUpdate = new Bindable(); + + private LoadingLayer loadingLayer = null!; + private FillFlowContainer content = null!; + + [Resolved] + private ISoloStatisticsWatcher statisticsWatcher { get; set; } = null!; + + public OverallRanking(ScoreInfo score) + { + this.score = score; + } + + [BackgroundDependencyLoader] + private void load() + { + AutoSizeAxes = Axes.Y; + AutoSizeEasing = Easing.OutQuint; + AutoSizeDuration = transition_duration; + + InternalChildren = new Drawable[] + { + loadingLayer = new LoadingLayer(withBox: false) + { + RelativeSizeAxes = Axes.Both, + }, + content = new FillFlowContainer + { + AlwaysPresent = true, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + Children = new Drawable[] + { + new GlobalRankChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, + new AccuracyChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, + new MaximumComboChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, + new RankedScoreChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, + new TotalScoreChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, + new PerformancePointsChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + statisticsWatcher.RegisterForStatisticsUpdateAfter(score, update => statisticsUpdate.Value = update); + statisticsUpdate.BindValueChanged(onUpdateReceived, true); + FinishTransforms(true); + } + + private void onUpdateReceived(ValueChangedEvent update) + { + if (update.NewValue == null) + { + loadingLayer.Show(); + content.FadeOut(transition_duration, Easing.OutQuint); + } + else + { + loadingLayer.Hide(); + content.FadeIn(transition_duration, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/User/PerformancePointsChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/PerformancePointsChangeRow.cs new file mode 100644 index 0000000000..c1faf1a3e3 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/User/PerformancePointsChangeRow.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Screens.Ranking.Statistics.User +{ + public partial class PerformancePointsChangeRow : RankingChangeRow + { + public PerformancePointsChangeRow() + : base(stats => stats.PP) + { + } + + protected override LocalisableString Label => RankingsStrings.StatPerformance; + + protected override LocalisableString FormatCurrentValue(decimal? current) + => current == null ? string.Empty : LocalisableString.Interpolate($@"{current:N0}pp"); + + protected override int CalculateDifference(decimal? previous, decimal? current, out LocalisableString formattedDifference) + { + if (previous == null && current == null) + { + formattedDifference = string.Empty; + return 0; + } + + if (previous == null && current != null) + { + formattedDifference = LocalisableString.Interpolate($"+{current.Value:N0}pp"); + return 1; + } + + if (previous != null && current == null) + { + formattedDifference = LocalisableString.Interpolate($"-{previous.Value:N0}pp"); + return -1; + } + + Debug.Assert(previous != null && current != null); + + decimal difference = current.Value - previous.Value; + + if (difference < 0) + formattedDifference = LocalisableString.Interpolate($@"{difference:N0}pp"); + else if (difference > 0) + formattedDifference = LocalisableString.Interpolate($@"+{difference:N0}pp"); + else + formattedDifference = string.Empty; + + return current.Value.CompareTo(previous.Value); + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/User/RankedScoreChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/RankedScoreChangeRow.cs new file mode 100644 index 0000000000..1cdf22bd75 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/User/RankedScoreChangeRow.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 osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Screens.Ranking.Statistics.User +{ + public partial class RankedScoreChangeRow : RankingChangeRow + { + public RankedScoreChangeRow() + : base(stats => stats.RankedScore) + { + } + + protected override LocalisableString Label => UsersStrings.ShowStatsRankedScore; + + protected override LocalisableString FormatCurrentValue(long current) => current.ToLocalisableString(@"N0"); + + protected override int CalculateDifference(long previous, long current, out LocalisableString formattedDifference) + { + long difference = current - previous; + + if (difference < 0) + formattedDifference = difference.ToLocalisableString(@"N0"); + else if (difference > 0) + formattedDifference = LocalisableString.Interpolate($@"+{difference:N0}"); + else + formattedDifference = string.Empty; + + return current.CompareTo(previous); + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs new file mode 100644 index 0000000000..5348b4a522 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/User/RankingChangeRow.cs @@ -0,0 +1,144 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Solo; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Screens.Ranking.Statistics.User +{ + public abstract partial class RankingChangeRow : CompositeDrawable + { + public Bindable StatisticsUpdate { get; } = new Bindable(); + + private readonly Func accessor; + + private OsuSpriteText currentValueText = null!; + private SpriteIcon changeIcon = null!; + private OsuSpriteText changeText = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + protected RankingChangeRow( + Func accessor) + { + this.accessor = accessor; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + new OsuSpriteText + { + Text = Label, + Font = OsuFont.Default.With(size: 18) + }, + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Direction = FillDirection.Vertical, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new FillFlowContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5), + Children = new Drawable[] + { + changeIcon = new SpriteIcon + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(18) + }, + currentValueText = new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Font = OsuFont.Default.With(size: 18, weight: FontWeight.Bold) + }, + } + }, + changeText = new OsuSpriteText + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Font = OsuFont.Default.With(weight: FontWeight.Bold) + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + StatisticsUpdate.BindValueChanged(onStatisticsUpdate, true); + } + + private void onStatisticsUpdate(ValueChangedEvent statisticsUpdate) + { + var update = statisticsUpdate.NewValue; + + if (update == null) + return; + + T previousValue = accessor.Invoke(update.Before); + T currentValue = accessor.Invoke(update.After); + int comparisonResult = CalculateDifference(previousValue, currentValue, out var formattedDifference); + + Colour4 comparisonColour; + IconUsage icon; + + if (comparisonResult < 0) + { + comparisonColour = colours.Red1; + icon = FontAwesome.Solid.ArrowDown; + } + else if (comparisonResult > 0) + { + comparisonColour = colours.Lime1; + icon = FontAwesome.Solid.ArrowUp; + } + else + { + comparisonColour = colours.Orange1; + icon = FontAwesome.Solid.Minus; + } + + currentValueText.Text = FormatCurrentValue(currentValue); + + changeIcon.Icon = icon; + changeIcon.Colour = comparisonColour; + + changeText.Text = formattedDifference; + changeText.Colour = comparisonColour; + } + + protected abstract LocalisableString Label { get; } + + protected abstract LocalisableString FormatCurrentValue(T current); + protected abstract int CalculateDifference(T previous, T current, out LocalisableString formattedDifference); + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/User/TotalScoreChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/TotalScoreChangeRow.cs new file mode 100644 index 0000000000..346de18e14 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/User/TotalScoreChangeRow.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 osu.Framework.Extensions.LocalisationExtensions; +using osu.Framework.Localisation; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Screens.Ranking.Statistics.User +{ + public partial class TotalScoreChangeRow : RankingChangeRow + { + public TotalScoreChangeRow() + : base(stats => stats.TotalScore) + { + } + + protected override LocalisableString Label => UsersStrings.ShowStatsTotalScore; + + protected override LocalisableString FormatCurrentValue(long current) => current.ToLocalisableString(@"N0"); + + protected override int CalculateDifference(long previous, long current, out LocalisableString formattedDifference) + { + long difference = current - previous; + + if (difference < 0) + formattedDifference = difference.ToLocalisableString(@"N0"); + else if (difference > 0) + formattedDifference = LocalisableString.Interpolate($@"+{difference:N0}"); + else + formattedDifference = string.Empty; + + return current.CompareTo(previous); + } + } +} From 5e9fb1063a9ad5c4775205497cd5a4904f01c699 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Dec 2022 12:22:36 +0800 Subject: [PATCH 242/384] Move judgement text creation to base class and tidy things up --- .../Skinning/Argon/ArgonJudgementPiece.cs | 28 ++++++++--------- .../Skinning/Argon/ArgonJudgementPiece.cs | 28 ++++++++--------- .../Skinning/Argon/ArgonJudgementPiece.cs | 26 ++++++++-------- .../Judgements/DefaultJudgementPiece.cs | 30 +++++++------------ .../Rulesets/Judgements/JudgementPiece.cs | 15 ++++++++-- 5 files changed, 61 insertions(+), 66 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs index 870a142ca7..4ce3c50f7c 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonJudgementPiece.cs @@ -6,6 +6,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.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -26,6 +27,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon public ArgonJudgementPiece(HitResult result) : base(result) { + AutoSizeAxes = Axes.Both; + Origin = Anchor.Centre; Y = 160; } @@ -33,21 +36,6 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon [BackgroundDependencyLoader] private void load() { - AutoSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - JudgementText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = colours.ForHitResult(Result), - Blending = BlendingParameters.Additive, - Spacing = new Vector2(10, 0), - Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular), - }, - }; - if (Result.IsHit()) { AddInternal(ringExplosion = new RingExplosion(Result) @@ -57,6 +45,16 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon } } + protected override SpriteText CreateJudgementText() => + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Blending = BlendingParameters.Additive, + Spacing = new Vector2(10, 0), + Font = OsuFont.Default.With(size: 28, weight: FontWeight.Regular), + }; + /// /// Plays the default animation for this judgement piece. /// diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs index 7b1a0d8d08..6f55d93eff 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonJudgementPiece.cs @@ -5,6 +5,7 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -25,27 +26,14 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon public ArgonJudgementPiece(HitResult result) : base(result) { + AutoSizeAxes = Axes.Both; + Origin = Anchor.Centre; } [BackgroundDependencyLoader] private void load() { - AutoSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - JudgementText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = colours.ForHitResult(Result), - Blending = BlendingParameters.Additive, - Spacing = new Vector2(5, 0), - Font = OsuFont.Default.With(size: 20, weight: FontWeight.Bold), - }, - }; - if (Result.IsHit()) { AddInternal(ringExplosion = new RingExplosion(Result) @@ -55,6 +43,16 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon } } + protected override SpriteText CreateJudgementText() => + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Blending = BlendingParameters.Additive, + Spacing = new Vector2(5, 0), + Font = OsuFont.Default.With(size: 20, weight: FontWeight.Bold), + }; + /// /// Plays the default animation for this judgement piece. /// diff --git a/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs index e8240911b0..bbd62ff85b 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Argon/ArgonJudgementPiece.cs @@ -6,6 +6,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.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -33,20 +34,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] - { - JudgementText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = colours.ForHitResult(Result), - Blending = BlendingParameters.Additive, - Spacing = new Vector2(10, 0), - RelativePositionAxes = Axes.Both, - Font = OsuFont.Default.With(size: 20, weight: FontWeight.Regular), - }, - }; - if (Result.IsHit()) { AddInternal(ringExplosion = new RingExplosion(Result) @@ -57,6 +44,17 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon } } + protected override SpriteText CreateJudgementText() => + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Blending = BlendingParameters.Additive, + Spacing = new Vector2(10, 0), + RelativePositionAxes = Axes.Both, + Font = OsuFont.Default.With(size: 20, weight: FontWeight.Regular), + }; + /// /// Plays the default animation for this judgement piece. /// diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index 485e2b409d..6551752826 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.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 osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Scoring; @@ -15,31 +14,24 @@ namespace osu.Game.Rulesets.Judgements public partial class DefaultJudgementPiece : JudgementPiece, IAnimatableJudgement { [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; public DefaultJudgementPiece(HitResult result) : base(result) { + AutoSizeAxes = Axes.Both; + Origin = Anchor.Centre; } - [BackgroundDependencyLoader] - private void load() - { - AutoSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] + protected override SpriteText CreateJudgementText() => + new OsuSpriteText { - JudgementText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Colour = colours.ForHitResult(Result), - Font = OsuFont.Numeric.With(size: 20), - Scale = new Vector2(0.85f, 1), - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Numeric.With(size: 20), + Scale = new Vector2(0.85f, 1), }; - } /// /// Plays the default animation for this judgement piece. @@ -67,6 +59,6 @@ namespace osu.Game.Rulesets.Judgements this.FadeOutFromOne(800); } - public Drawable GetAboveHitObjectsProxiedContent() => null; + public Drawable? GetAboveHitObjectsProxiedContent() => null; } } diff --git a/osu.Game/Rulesets/Judgements/JudgementPiece.cs b/osu.Game/Rulesets/Judgements/JudgementPiece.cs index 4e9b495cb5..9c31c6aa34 100644 --- a/osu.Game/Rulesets/Judgements/JudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/JudgementPiece.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Judgements @@ -12,18 +14,25 @@ namespace osu.Game.Rulesets.Judgements { protected readonly HitResult Result; - protected SpriteText JudgementText { get; set; } = null!; + protected SpriteText JudgementText { get; private set; } = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; protected JudgementPiece(HitResult result) { Result = result; } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { - base.LoadComplete(); + JudgementText = CreateJudgementText(); + JudgementText.Colour = colours.ForHitResult(Result); JudgementText.Text = Result.GetDescription().ToUpperInvariant(); } + + protected abstract SpriteText CreateJudgementText(); } } From 91bde14fb345c034589738e8361fd5ad39df991a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Dec 2022 15:42:22 +0800 Subject: [PATCH 243/384] Add button to settings to show lazer upgrade guide --- .../Settings/Sections/GeneralSection.cs | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs index c62d44fd30..2ca932f2ac 100644 --- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; +using osu.Game.Graphics; using osu.Game.Localisation; using osu.Game.Overlays.Settings.Sections.General; @@ -15,7 +14,10 @@ namespace osu.Game.Overlays.Settings.Sections public partial class GeneralSection : SettingsSection { [Resolved(CanBeNull = true)] - private FirstRunSetupOverlay firstRunSetupOverlay { get; set; } + private FirstRunSetupOverlay? firstRunSetupOverlay { get; set; } + + [Resolved(CanBeNull = true)] + private OsuGame? game { get; set; } public override LocalisableString Header => GeneralSettingsStrings.GeneralSectionHeader; @@ -24,15 +26,24 @@ namespace osu.Game.Overlays.Settings.Sections Icon = FontAwesome.Solid.Cog }; - public GeneralSection() + [BackgroundDependencyLoader] + private void load(OsuColour colours) { Children = new Drawable[] { new SettingsButton { Text = GeneralSettingsStrings.RunSetupWizard, + TooltipText = FirstRunSetupOverlayStrings.FirstRunSetupDescription, Action = () => firstRunSetupOverlay?.Show(), }, + new SettingsButton + { + Text = "Learn more about lazer", + TooltipText = "Check out the feature comparison and FAQ", + BackgroundColour = colours.YellowDark, + Action = () => game?.ShowWiki(@"Help_centre/Upgrading_to_lazer") + }, new LanguageSettings(), new UpdateSettings(), }; From f973befcd4f0f24701917117b5a9fe41d345b7fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 09:34:30 +0100 Subject: [PATCH 244/384] Remove unused resolved member --- osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs index 6551752826..d5f586dc35 100644 --- a/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/DefaultJudgementPiece.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; @@ -13,9 +12,6 @@ namespace osu.Game.Rulesets.Judgements { public partial class DefaultJudgementPiece : JudgementPiece, IAnimatableJudgement { - [Resolved] - private OsuColour colours { get; set; } = null!; - public DefaultJudgementPiece(HitResult result) : base(result) { From 80de5dac66776d9da6348ff60421bb1e0c66a49f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 09:37:40 +0100 Subject: [PATCH 245/384] Fix judgement text never being added to hierarchy --- osu.Game/Rulesets/Judgements/JudgementPiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Judgements/JudgementPiece.cs b/osu.Game/Rulesets/Judgements/JudgementPiece.cs index 9c31c6aa34..03f211c318 100644 --- a/osu.Game/Rulesets/Judgements/JudgementPiece.cs +++ b/osu.Game/Rulesets/Judgements/JudgementPiece.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Judgements [BackgroundDependencyLoader] private void load() { - JudgementText = CreateJudgementText(); + AddInternal(JudgementText = CreateJudgementText()); JudgementText.Colour = colours.ForHitResult(Result); JudgementText.Text = Result.GetDescription().ToUpperInvariant(); From 4e5109a6495668f118e7466c5d1ec8cc2b8c435b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 10:27:28 +0100 Subject: [PATCH 246/384] Use plain bindable flow instead of binding to watcher directly --- .../Visual/Ranking/TestSceneOverallRanking.cs | 113 +++++++----------- .../Online/Solo/ISoloStatisticsWatcher.cs | 23 ---- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 2 +- .../Ranking/Statistics/User/OverallRanking.cs | 20 ++-- 4 files changed, 53 insertions(+), 105 deletions(-) delete mode 100644 osu.Game/Online/Solo/ISoloStatisticsWatcher.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs index 8d2147056c..11bb61affb 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs @@ -1,10 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Diagnostics; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Online.Solo; using osu.Game.Scoring; @@ -15,8 +12,7 @@ namespace osu.Game.Tests.Visual.Ranking { public partial class TestSceneOverallRanking : OsuTestScene { - [Cached(typeof(ISoloStatisticsWatcher))] - private MockSoloStatisticsWatcher soloStatisticsWatcher { get; } = new MockSoloStatisticsWatcher(); + private OverallRanking overallRanking = null!; [Test] public void TestUpdatePending() @@ -28,56 +24,50 @@ namespace osu.Game.Tests.Visual.Ranking public void TestAllIncreased() { createDisplay(); - AddStep("trigger update success", () => - { - soloStatisticsWatcher.TriggerSuccess( - new UserStatistics - { - GlobalRank = 12_345, - Accuracy = 0.9899, - MaxCombo = 2_322, - RankedScore = 23_123_543_456, - TotalScore = 123_123_543_456, - PP = 5_072 - }, - new UserStatistics - { - GlobalRank = 1_234, - Accuracy = 0.9907, - MaxCombo = 2_352, - RankedScore = 23_124_231_435, - TotalScore = 123_124_231_435, - PP = 5_434 - }); - }); + displayUpdate( + new UserStatistics + { + GlobalRank = 12_345, + Accuracy = 0.9899, + MaxCombo = 2_322, + RankedScore = 23_123_543_456, + TotalScore = 123_123_543_456, + PP = 5_072 + }, + new UserStatistics + { + GlobalRank = 1_234, + Accuracy = 0.9907, + MaxCombo = 2_352, + RankedScore = 23_124_231_435, + TotalScore = 123_124_231_435, + PP = 5_434 + }); } [Test] public void TestAllDecreased() { createDisplay(); - AddStep("trigger update success", () => - { - soloStatisticsWatcher.TriggerSuccess( - new UserStatistics - { - GlobalRank = 1_234, - Accuracy = 0.9907, - MaxCombo = 2_352, - RankedScore = 23_124_231_435, - TotalScore = 123_124_231_435, - PP = 5_434 - }, - new UserStatistics - { - GlobalRank = 12_345, - Accuracy = 0.9899, - MaxCombo = 2_322, - RankedScore = 23_123_543_456, - TotalScore = 123_123_543_456, - PP = 5_072 - }); - }); + displayUpdate( + new UserStatistics + { + GlobalRank = 1_234, + Accuracy = 0.9907, + MaxCombo = 2_352, + RankedScore = 23_124_231_435, + TotalScore = 123_124_231_435, + PP = 5_434 + }, + new UserStatistics + { + GlobalRank = 12_345, + Accuracy = 0.9899, + MaxCombo = 2_322, + RankedScore = 23_123_543_456, + TotalScore = 123_123_543_456, + PP = 5_072 + }); } [Test] @@ -94,7 +84,7 @@ namespace osu.Game.Tests.Visual.Ranking }; createDisplay(); - AddStep("trigger update success", () => soloStatisticsWatcher.TriggerSuccess(statistics, statistics)); + displayUpdate(statistics, statistics); } [Test] @@ -111,32 +101,17 @@ namespace osu.Game.Tests.Visual.Ranking }; createDisplay(); - AddStep("trigger update success", () => soloStatisticsWatcher.TriggerSuccess(statistics, statistics)); + displayUpdate(statistics, statistics); } - private void createDisplay() => AddStep("create display", () => Child = new OverallRanking(new ScoreInfo()) + private void createDisplay() => AddStep("create display", () => Child = overallRanking = new OverallRanking(new ScoreInfo()) { Width = 400, Anchor = Anchor.Centre, Origin = Anchor.Centre }); - private class MockSoloStatisticsWatcher : ISoloStatisticsWatcher - { - private ScoreInfo? score; - private Action? onUpdateReady; - - public void RegisterForStatisticsUpdateAfter(ScoreInfo score, Action onUpdateReady) - { - this.score = score; - this.onUpdateReady = onUpdateReady; - } - - public void TriggerSuccess(UserStatistics before, UserStatistics after) - { - Debug.Assert(score != null && onUpdateReady != null); - onUpdateReady.Invoke(new SoloStatisticsUpdate(score, before, after)); - } - } + private void displayUpdate(UserStatistics before, UserStatistics after) => + AddStep("display update", () => overallRanking.StatisticsUpdate.Value = new SoloStatisticsUpdate(new ScoreInfo(), before, after)); } } diff --git a/osu.Game/Online/Solo/ISoloStatisticsWatcher.cs b/osu.Game/Online/Solo/ISoloStatisticsWatcher.cs deleted file mode 100644 index 84986297bf..0000000000 --- a/osu.Game/Online/Solo/ISoloStatisticsWatcher.cs +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Allocation; -using osu.Game.Scoring; - -namespace osu.Game.Online.Solo -{ - /// - /// A component that delivers updates to the logged in user's gameplay statistics after completed scores. - /// - [Cached] - public interface ISoloStatisticsWatcher - { - /// - /// Registers for a user statistics update after the given has been processed server-side. - /// - /// The score to listen for the statistics update for. - /// The callback to be invoked once the statistics update has been prepared. - void RegisterForStatisticsUpdateAfter(ScoreInfo score, Action onUpdateReady); - } -} diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 6a9a20e58d..48f39504a3 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -20,7 +20,7 @@ namespace osu.Game.Online.Solo /// /// A persistent component that binds to the spectator server and API in order to deliver updates about the logged in user's gameplay statistics. /// - public partial class SoloStatisticsWatcher : Component, ISoloStatisticsWatcher + public partial class SoloStatisticsWatcher : Component { [Resolved] private SpectatorClient spectatorClient { get; set; } = null!; diff --git a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs index 499deb92be..8f922f8015 100644 --- a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs +++ b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs @@ -18,14 +18,11 @@ namespace osu.Game.Screens.Ranking.Statistics.User private readonly ScoreInfo score; - private readonly Bindable statisticsUpdate = new Bindable(); + public Bindable StatisticsUpdate { get; } = new Bindable(); private LoadingLayer loadingLayer = null!; private FillFlowContainer content = null!; - [Resolved] - private ISoloStatisticsWatcher statisticsWatcher { get; set; } = null!; - public OverallRanking(ScoreInfo score) { this.score = score; @@ -53,12 +50,12 @@ namespace osu.Game.Screens.Ranking.Statistics.User Spacing = new Vector2(10), Children = new Drawable[] { - new GlobalRankChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, - new AccuracyChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, - new MaximumComboChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, - new RankedScoreChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, - new TotalScoreChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } }, - new PerformancePointsChangeRow { StatisticsUpdate = { BindTarget = statisticsUpdate } } + new GlobalRankChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, + new AccuracyChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, + new MaximumComboChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, + new RankedScoreChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, + new TotalScoreChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } }, + new PerformancePointsChangeRow { StatisticsUpdate = { BindTarget = StatisticsUpdate } } } } }; @@ -68,8 +65,7 @@ namespace osu.Game.Screens.Ranking.Statistics.User { base.LoadComplete(); - statisticsWatcher.RegisterForStatisticsUpdateAfter(score, update => statisticsUpdate.Value = update); - statisticsUpdate.BindValueChanged(onUpdateReceived, true); + StatisticsUpdate.BindValueChanged(onUpdateReceived, true); FinishTransforms(true); } From 2c060ac8d44bbf0857e3177cbfd9ab4babf332fb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Dec 2022 17:32:04 +0800 Subject: [PATCH 247/384] Add localisation support for new button's strings --- osu.Game/Localisation/GeneralSettingsStrings.cs | 10 ++++++++++ osu.Game/Overlays/Settings/Sections/GeneralSection.cs | 4 ++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Localisation/GeneralSettingsStrings.cs b/osu.Game/Localisation/GeneralSettingsStrings.cs index 3278b20983..a525af508b 100644 --- a/osu.Game/Localisation/GeneralSettingsStrings.cs +++ b/osu.Game/Localisation/GeneralSettingsStrings.cs @@ -64,6 +64,16 @@ namespace osu.Game.Localisation /// public static LocalisableString RunSetupWizard => new TranslatableString(getKey(@"run_setup_wizard"), @"Run setup wizard"); + /// + /// "Learn more about lazer" + /// + public static LocalisableString LearnMoreAboutLazer => new TranslatableString(getKey(@"learn_more_about_lazer"), @"Learn more about lazer"); + + /// + /// "Check out the feature comparison and FAQ" + /// + public static LocalisableString LearnMoreAboutLazerTooltip => new TranslatableString(getKey(@"check_out_the_feature_comparison"), @"Check out the feature comparison and FAQ"); + /// /// "You are running the latest release ({0})" /// diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs index 2ca932f2ac..84c1f3cfb6 100644 --- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs @@ -39,8 +39,8 @@ namespace osu.Game.Overlays.Settings.Sections }, new SettingsButton { - Text = "Learn more about lazer", - TooltipText = "Check out the feature comparison and FAQ", + Text = GeneralSectionStrings.LearnMoreAboutLazer, + TooltipText = GeneralSectionStrings.CheckOutTheFeatureComparison, BackgroundColour = colours.YellowDark, Action = () => game?.ShowWiki(@"Help_centre/Upgrading_to_lazer") }, From 301eb71e22dc81e88d0537ac1299ce224648f81c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 10:39:05 +0100 Subject: [PATCH 248/384] Fix wrong member names --- osu.Game/Overlays/Settings/Sections/GeneralSection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs index 84c1f3cfb6..d4fd78f0c8 100644 --- a/osu.Game/Overlays/Settings/Sections/GeneralSection.cs +++ b/osu.Game/Overlays/Settings/Sections/GeneralSection.cs @@ -39,8 +39,8 @@ namespace osu.Game.Overlays.Settings.Sections }, new SettingsButton { - Text = GeneralSectionStrings.LearnMoreAboutLazer, - TooltipText = GeneralSectionStrings.CheckOutTheFeatureComparison, + Text = GeneralSettingsStrings.LearnMoreAboutLazer, + TooltipText = GeneralSettingsStrings.LearnMoreAboutLazerTooltip, BackgroundColour = colours.YellowDark, Action = () => game?.ShowWiki(@"Help_centre/Upgrading_to_lazer") }, From 83a50816b6b1b2b922d34a8d59553a205312571c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 10:44:38 +0100 Subject: [PATCH 249/384] Remove unused constructor param --- osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs | 2 +- .../Screens/Ranking/Statistics/User/OverallRanking.cs | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs index 11bb61affb..c2d7b33079 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs @@ -104,7 +104,7 @@ namespace osu.Game.Tests.Visual.Ranking displayUpdate(statistics, statistics); } - private void createDisplay() => AddStep("create display", () => Child = overallRanking = new OverallRanking(new ScoreInfo()) + private void createDisplay() => AddStep("create display", () => Child = overallRanking = new OverallRanking { Width = 400, Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs index 8f922f8015..447f206128 100644 --- a/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs +++ b/osu.Game/Screens/Ranking/Statistics/User/OverallRanking.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Solo; -using osu.Game.Scoring; using osuTK; namespace osu.Game.Screens.Ranking.Statistics.User @@ -16,18 +15,11 @@ namespace osu.Game.Screens.Ranking.Statistics.User { private const float transition_duration = 300; - private readonly ScoreInfo score; - public Bindable StatisticsUpdate { get; } = new Bindable(); private LoadingLayer loadingLayer = null!; private FillFlowContainer content = null!; - public OverallRanking(ScoreInfo score) - { - this.score = score; - } - [BackgroundDependencyLoader] private void load() { From d6e079a2b4e09cb80a30189c84a57c9eb3605f83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 11:28:46 +0100 Subject: [PATCH 250/384] Ignore statistics update requests from third-party rulesets for now --- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 48f39504a3..1209747132 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -8,6 +8,7 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -51,6 +52,9 @@ namespace osu.Game.Online.Solo if (!api.IsLoggedIn) return; + if (!score.Ruleset.IsLegacyRuleset()) + return; + var callback = new StatisticsUpdateCallback(score, onUpdateReady); if (lastProcessedScoreId == score.OnlineID) From fd9110a61e8424384ad90fbea0def6101e3ed430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 13:28:25 +0100 Subject: [PATCH 251/384] Fix solo statistics watcher firing requests for invalid user with id 1 Can happen during login flow (see `APIAccess.attemptConnect()`). --- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 1209747132..0dfe8ebb8d 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; @@ -72,11 +71,9 @@ namespace osu.Game.Online.Solo lastProcessedScoreId = null; latestStatistics.Clear(); - if (!api.IsLoggedIn) + if (localUser == null || localUser.OnlineID <= 1) return; - Debug.Assert(localUser != null); - var userRequest = new GetUsersRequest(new[] { localUser.OnlineID }); userRequest.Success += response => Schedule(() => { From 3c26016b61bd43e85c386c69e447665aece584e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 13:30:54 +0100 Subject: [PATCH 252/384] Ensure latest stats are cleared on successful profile fetch --- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 0dfe8ebb8d..268483ab91 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -77,6 +77,7 @@ namespace osu.Game.Online.Solo var userRequest = new GetUsersRequest(new[] { localUser.OnlineID }); userRequest.Success += response => Schedule(() => { + latestStatistics.Clear(); foreach (var rulesetStats in response.Users.Single().RulesetsStatistics) latestStatistics.Add(rulesetStats.Key, rulesetStats.Value); }); From 6c4ca387e01fcbbe82b1b29c49e0b0011f2cb1d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 13:42:32 +0100 Subject: [PATCH 253/384] Fix wrong handling of missing ruleset statistics --- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 268483ab91..383f6a202d 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -31,7 +31,7 @@ namespace osu.Game.Online.Solo private readonly Dictionary callbacks = new Dictionary(); private long? lastProcessedScoreId; - private readonly Dictionary latestStatistics = new Dictionary(); + private Dictionary? latestStatistics; protected override void LoadComplete() { @@ -69,7 +69,7 @@ namespace osu.Game.Online.Solo { callbacks.Clear(); lastProcessedScoreId = null; - latestStatistics.Clear(); + latestStatistics = null; if (localUser == null || localUser.OnlineID <= 1) return; @@ -77,7 +77,7 @@ namespace osu.Game.Online.Solo var userRequest = new GetUsersRequest(new[] { localUser.OnlineID }); userRequest.Success += response => Schedule(() => { - latestStatistics.Clear(); + latestStatistics = new Dictionary(); foreach (var rulesetStats in response.Users.Single().RulesetsStatistics) latestStatistics.Add(rulesetStats.Key, rulesetStats.Value); }); @@ -109,9 +109,12 @@ namespace osu.Game.Online.Solo { string rulesetName = callback.Score.Ruleset.ShortName; - if (!latestStatistics.TryGetValue(rulesetName, out var latestRulesetStatistics)) + if (latestStatistics == null) return; + latestStatistics.TryGetValue(rulesetName, out UserStatistics? latestRulesetStatistics); + latestRulesetStatistics ??= new UserStatistics(); + var update = new SoloStatisticsUpdate(callback.Score, latestRulesetStatistics, updatedStatistics); callback.OnUpdateReady.Invoke(update); From 78c47a3695a9e8c1bbfa1ef8f07eb7ae2f3df5f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 13:45:04 +0100 Subject: [PATCH 254/384] Add callback to dictionary rather than overwrite Attempting to overwrite will henceforth throw an exception. --- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 383f6a202d..33344044b9 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -62,7 +62,7 @@ namespace osu.Game.Online.Solo return; } - callbacks[score.OnlineID] = callback; + callbacks.Add(score.OnlineID, callback); }); private void onUserChanged(APIUser? localUser) => Schedule(() => From 600ada46be2099891910cc303b3c1113c67f7fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 10:35:42 +0100 Subject: [PATCH 255/384] Add protected method for customising statistics panel --- osu.Game/Screens/Ranking/ResultsScreen.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index f3aca43a9d..78239e0dbe 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -96,11 +96,11 @@ namespace osu.Game.Screens.Ranking RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - statisticsPanel = new StatisticsPanel + statisticsPanel = CreateStatisticsPanel().With(panel => { - RelativeSizeAxes = Axes.Both, - Score = { BindTarget = SelectedScore } - }, + panel.RelativeSizeAxes = Axes.Both; + panel.Score.BindTarget = SelectedScore; + }), ScorePanelList = new ScorePanelList { RelativeSizeAxes = Axes.Both, @@ -231,6 +231,11 @@ namespace osu.Game.Screens.Ranking /// An responsible for the fetch operation. This will be queued and performed automatically. protected virtual APIRequest FetchNextPage(int direction, Action> scoresCallback) => null; + /// + /// Creates the to be used to display extended information about scores. + /// + protected virtual StatisticsPanel CreateStatisticsPanel() => new StatisticsPanel(); + private void fetchScoresCallback(IEnumerable scores) => Schedule(() => { foreach (var s in scores) From 3abdf557eaf431ac5643254665caf1b8c44df21d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 10:51:09 +0100 Subject: [PATCH 256/384] Add protected method for customising statistics panel rows --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 91102d6647..4c22afd8f7 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Ranking.Statistics bool hitEventsAvailable = newScore.HitEvents.Count != 0; Container container; - var statisticRows = newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, task.GetResultSafely()); + var statisticRows = CreateStatisticRows(newScore, task.GetResultSafely()); if (!hitEventsAvailable && statisticRows.SelectMany(r => r.Columns).All(c => c.RequiresHitEvents)) { @@ -218,6 +218,14 @@ namespace osu.Game.Screens.Ranking.Statistics }), localCancellationSource.Token); } + /// + /// Creates the s to be displayed in this panel for a given . + /// + /// The score to create the rows for. + /// The beatmap on which the score was set. + protected virtual ICollection CreateStatisticRows(ScoreInfo newScore, IBeatmap playableBeatmap) + => newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap); + protected override bool OnClick(ClickEvent e) { ToggleVisibility(); From da519acb20efa3f91eb504c1e1eb4fcf2a68ed2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 10:58:38 +0100 Subject: [PATCH 257/384] Add overall ranking display to solo results --- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 20 ++++++++ .../Ranking/Statistics/SoloStatisticsPanel.cs | 51 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 3774cf16b1..6d4feeb0db 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -7,11 +7,14 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Screens.Ranking { @@ -22,11 +25,28 @@ namespace osu.Game.Screens.Ranking [Resolved] private RulesetStore rulesets { get; set; } + [Resolved] + private SoloStatisticsWatcher soloStatisticsWatcher { get; set; } + + private readonly Bindable statisticsUpdate = new Bindable(); + public SoloResultsScreen(ScoreInfo score, bool allowRetry) : base(score, allowRetry) { } + protected override void LoadComplete() + { + base.LoadComplete(); + + soloStatisticsWatcher.RegisterForStatisticsUpdateAfter(Score, update => statisticsUpdate.Value = update); + } + + protected override StatisticsPanel CreateStatisticsPanel() => new SoloStatisticsPanel(Score) + { + StatisticsUpdate = { BindTarget = statisticsUpdate } + }; + protected override APIRequest FetchScores(Action> scoresCallback) { if (Score.BeatmapInfo.OnlineID <= 0 || Score.BeatmapInfo.Status <= BeatmapOnlineStatus.Pending) diff --git a/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs new file mode 100644 index 0000000000..4741ea2ea3 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs @@ -0,0 +1,51 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Online.Solo; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Statistics.User; + +namespace osu.Game.Screens.Ranking.Statistics +{ + public partial class SoloStatisticsPanel : StatisticsPanel + { + private readonly ScoreInfo achievedScore; + + public SoloStatisticsPanel(ScoreInfo achievedScore) + { + this.achievedScore = achievedScore; + } + + public Bindable StatisticsUpdate { get; } = new Bindable(); + + protected override ICollection CreateStatisticRows(ScoreInfo newScore, IBeatmap playableBeatmap) + { + var rows = base.CreateStatisticRows(newScore, playableBeatmap); + + if (newScore.UserID == achievedScore.UserID && newScore.OnlineID == achievedScore.OnlineID) + { + rows = rows.Append(new StatisticRow + { + Columns = new[] + { + new StatisticItem("Overall Ranking", () => new OverallRanking + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.5f, + StatisticsUpdate = { BindTarget = StatisticsUpdate } + }) + } + }).ToArray(); + } + + return rows; + } + } +} From 145130ba80f21d778c7b3f5d6aa2b95b989b5222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 11:20:31 +0100 Subject: [PATCH 258/384] Register solo statistics watcher at game level --- osu.Game/OsuGameBase.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7586dc6407..36fd5a4177 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -46,6 +46,7 @@ using osu.Game.Online.API; using osu.Game.Online.Chat; using osu.Game.Online.Metadata; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Solo; using osu.Game.Online.Spectator; using osu.Game.Overlays; using osu.Game.Overlays.Settings; @@ -193,6 +194,7 @@ namespace osu.Game protected MultiplayerClient MultiplayerClient { get; private set; } private MetadataClient metadataClient; + private SoloStatisticsWatcher soloStatisticsWatcher; private RealmAccess realm; @@ -301,6 +303,7 @@ namespace osu.Game dependencies.CacheAs(spectatorClient = new OnlineSpectatorClient(endpoints)); dependencies.CacheAs(MultiplayerClient = new OnlineMultiplayerClient(endpoints)); dependencies.CacheAs(metadataClient = new OnlineMetadataClient(endpoints)); + dependencies.CacheAs(soloStatisticsWatcher = new SoloStatisticsWatcher()); AddInternal(new BeatmapOnlineChangeIngest(beatmapUpdater, realm, metadataClient)); @@ -346,6 +349,7 @@ namespace osu.Game AddInternal(spectatorClient); AddInternal(MultiplayerClient); AddInternal(metadataClient); + AddInternal(soloStatisticsWatcher); AddInternal(rulesetConfigCache); From 36a6f3685ebdf6a50d3fb40ec2268fce0f23589b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 24 Dec 2022 11:27:42 +0100 Subject: [PATCH 259/384] Don't show global rankings display when not logged in --- osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs index 4741ea2ea3..57d072b7de 100644 --- a/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/SoloStatisticsPanel.cs @@ -27,7 +27,10 @@ namespace osu.Game.Screens.Ranking.Statistics { var rows = base.CreateStatisticRows(newScore, playableBeatmap); - if (newScore.UserID == achievedScore.UserID && newScore.OnlineID == achievedScore.OnlineID) + if (newScore.UserID > 1 + && newScore.UserID == achievedScore.UserID + && newScore.OnlineID > 0 + && newScore.OnlineID == achievedScore.OnlineID) { rows = rows.Append(new StatisticRow { From 8c7814aaf0d296886644206b8633cce64e76ff12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 24 Dec 2022 21:48:04 +0800 Subject: [PATCH 260/384] Fix weird using statement --- osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs index c2d7b33079..2edc577a95 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs @@ -5,8 +5,8 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online.Solo; using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Statistics.User; using osu.Game.Users; -using OverallRanking = osu.Game.Screens.Ranking.Statistics.User.OverallRanking; namespace osu.Game.Tests.Visual.Ranking { From 9d073f42283f4da57e1b219a34b22c062a14b59d Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 24 Dec 2022 11:26:09 -0800 Subject: [PATCH 261/384] Link beatmap set title and artist to listing search --- osu.Game/OsuGame.cs | 5 +++- .../BeatmapSet/BeatmapSetHeaderContent.cs | 29 ++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index de9a009f44..b55b943023 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -353,7 +353,10 @@ namespace osu.Game break; case LinkAction.SearchBeatmapSet: - SearchBeatmapSet(argString); + if (link.Argument is RomanisableString romanisable) + SearchBeatmapSet(romanisable.GetPreferred(frameworkConfig.GetBindable(FrameworkSetting.ShowUnicode).Value)); + else + SearchBeatmapSet(argString); break; case LinkAction.OpenEditorTimestamp: diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 0318dad0e3..6977110ad4 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -16,11 +16,12 @@ using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Chat; using osu.Game.Overlays.BeatmapSet.Buttons; using osuTK; @@ -41,7 +42,7 @@ namespace osu.Game.Overlays.BeatmapSet private readonly UpdateableOnlineBeatmapSetCover cover; private readonly Box coverGradient; - private readonly OsuSpriteText title, artist; + private readonly LinkFlowContainer title, artist; private readonly AuthorInfo author; private readonly ExplicitContentBeatmapBadge explicitContent; @@ -127,9 +128,12 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Top = 15 }, Children = new Drawable[] { - title = new OsuSpriteText + title = new LinkFlowContainer(s => { - Font = OsuFont.GetFont(size: 30, weight: FontWeight.SemiBold, italics: true) + s.Font = OsuFont.GetFont(size: 30, weight: FontWeight.SemiBold, italics: true); + }) + { + AutoSizeAxes = Axes.Both, }, externalLink = new ExternalLinkButton { @@ -160,9 +164,12 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Bottom = 20 }, Children = new Drawable[] { - artist = new OsuSpriteText + artist = new LinkFlowContainer(s => { - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true), + s.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true); + }) + { + AutoSizeAxes = Axes.Both, }, featuredArtist = new FeaturedArtistBeatmapBadge { @@ -275,8 +282,14 @@ namespace osu.Game.Overlays.BeatmapSet loading.Hide(); - title.Text = new RomanisableString(setInfo.NewValue.TitleUnicode, setInfo.NewValue.Title); - artist.Text = new RomanisableString(setInfo.NewValue.ArtistUnicode, setInfo.NewValue.Artist); + var titleText = new RomanisableString(setInfo.NewValue.TitleUnicode, setInfo.NewValue.Title); + var artistText = new RomanisableString(setInfo.NewValue.ArtistUnicode, setInfo.NewValue.Artist); + + title.Clear(); + artist.Clear(); + + title.AddLink(titleText, LinkAction.SearchBeatmapSet, titleText); + artist.AddLink(artistText, LinkAction.SearchBeatmapSet, artistText); explicitContent.Alpha = setInfo.NewValue.HasExplicitContent ? 1 : 0; spotlight.Alpha = setInfo.NewValue.FeaturedInSpotlight ? 1 : 0; From df645ef3cb8de16a67ac8e92831830bf10d5da16 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 24 Dec 2022 11:42:05 -0800 Subject: [PATCH 262/384] Change title/artist idle colour to white --- .../BeatmapSet/BeatmapSetHeaderContent.cs | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 6977110ad4..357c9bc55d 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -12,6 +13,7 @@ using osu.Framework.Graphics.Colour; using osu.Game.Graphics.Cursor; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; @@ -24,6 +26,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Overlays.BeatmapSet.Buttons; using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet { @@ -128,7 +131,7 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Top = 15 }, Children = new Drawable[] { - title = new LinkFlowContainer(s => + title = new MetadataFlowContainer(s => { s.Font = OsuFont.GetFont(size: 30, weight: FontWeight.SemiBold, italics: true); }) @@ -164,7 +167,7 @@ namespace osu.Game.Overlays.BeatmapSet Margin = new MarginPadding { Bottom = 20 }, Children = new Drawable[] { - artist = new LinkFlowContainer(s => + artist = new MetadataFlowContainer(s => { s.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true); }) @@ -340,5 +343,29 @@ namespace osu.Game.Overlays.BeatmapSet break; } } + + public partial class MetadataFlowContainer : LinkFlowContainer + { + public MetadataFlowContainer(Action defaultCreationParameters = null) + : base(defaultCreationParameters) + { + } + + protected override DrawableLinkCompiler CreateLinkCompiler(ITextPart textPart) => new MetadataLinkCompiler(textPart); + + public partial class MetadataLinkCompiler : DrawableLinkCompiler + { + public MetadataLinkCompiler(ITextPart part) + : base(part) + { + } + + [BackgroundDependencyLoader] + private void load() + { + IdleColour = Color4.White; + } + } + } } } From 4f6b3644f3208293b1e659d79942de42a5505561 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 24 Dec 2022 12:40:32 -0800 Subject: [PATCH 263/384] Fix title/artist overflowing to right side --- .../BeatmapSet/BeatmapSetHeaderContent.cs | 121 +++++++++--------- 1 file changed, 62 insertions(+), 59 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 357c9bc55d..b305e6ef05 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -48,9 +48,10 @@ namespace osu.Game.Overlays.BeatmapSet private readonly LinkFlowContainer title, artist; private readonly AuthorInfo author; - private readonly ExplicitContentBeatmapBadge explicitContent; - private readonly SpotlightBeatmapBadge spotlight; - private readonly FeaturedArtistBeatmapBadge featuredArtist; + private ExplicitContentBeatmapBadge explicitContent; + private SpotlightBeatmapBadge spotlight; + private FeaturedArtistBeatmapBadge featuredArtist; + private ExternalLinkButton externalLink; private readonly FillFlowContainer downloadButtonsContainer; private readonly BeatmapAvailability beatmapAvailability; @@ -69,8 +70,6 @@ namespace osu.Game.Overlays.BeatmapSet public BeatmapSetHeaderContent() { - ExternalLinkButton externalLink; - RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; InternalChild = new Container @@ -124,64 +123,19 @@ namespace osu.Game.Overlays.BeatmapSet AutoSizeAxes = Axes.Y, Child = Picker = new BeatmapPicker(), }, - new FillFlowContainer + title = new MetadataFlowContainer(s => + { + s.Font = OsuFont.GetFont(size: 30, weight: FontWeight.SemiBold, italics: true); + }) { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Top = 15 }, - Children = new Drawable[] - { - title = new MetadataFlowContainer(s => - { - s.Font = OsuFont.GetFont(size: 30, weight: FontWeight.SemiBold, italics: true); - }) - { - AutoSizeAxes = Axes.Both, - }, - 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 + artist = new MetadataFlowContainer(s => + { + s.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true); + }) { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Bottom = 20 }, - Children = new Drawable[] - { - artist = new MetadataFlowContainer(s => - { - s.Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, italics: true); - }) - { - AutoSizeAxes = Axes.Both, - }, - featuredArtist = new FeaturedArtistBeatmapBadge - { - Alpha = 0f, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Margin = new MarginPadding { Left = 10 } - } - } }, new Container { @@ -247,12 +201,17 @@ namespace osu.Game.Overlays.BeatmapSet Picker.Beatmap.ValueChanged += b => { Details.BeatmapInfo = b.NewValue; - externalLink.Link = $@"{api.WebsiteRootUrl}/beatmapsets/{BeatmapSet.Value?.OnlineID}#{b.NewValue?.Ruleset.ShortName}/{b.NewValue?.OnlineID}"; + updateExternalLink(); onlineStatusPill.Status = b.NewValue?.Status ?? BeatmapOnlineStatus.None; }; } + private void updateExternalLink() + { + if (externalLink != null) externalLink.Link = $@"{api.WebsiteRootUrl}/beatmapsets/{BeatmapSet.Value?.OnlineID}#{Picker.Beatmap.Value?.Ruleset.ShortName}/{Picker.Beatmap.Value?.OnlineID}"; + } + [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { @@ -292,8 +251,49 @@ namespace osu.Game.Overlays.BeatmapSet artist.Clear(); title.AddLink(titleText, LinkAction.SearchBeatmapSet, titleText); + + title.AddArbitraryDrawable(new Container + { + AutoSizeAxes = Axes.Both, + Child = externalLink = new ExternalLinkButton + { + Margin = new MarginPadding { Left = 5 }, + } + }); + + title.AddArbitraryDrawable(new Container + { + AutoSizeAxes = Axes.Both, + Child = explicitContent = new ExplicitContentBeatmapBadge + { + Alpha = 0f, + Margin = new MarginPadding { Left = 10 }, + } + }); + + title.AddArbitraryDrawable(new Container + { + AutoSizeAxes = Axes.Both, + Child = spotlight = new SpotlightBeatmapBadge + { + Alpha = 0f, + Margin = new MarginPadding { Left = 10 }, + } + }); + artist.AddLink(artistText, LinkAction.SearchBeatmapSet, artistText); + artist.AddArbitraryDrawable(new Container + { + AutoSizeAxes = Axes.Both, + Child = featuredArtist = new FeaturedArtistBeatmapBadge + { + Alpha = 0f, + Margin = new MarginPadding { Left = 10 } + } + }); + + updateExternalLink(); explicitContent.Alpha = setInfo.NewValue.HasExplicitContent ? 1 : 0; spotlight.Alpha = setInfo.NewValue.FeaturedInSpotlight ? 1 : 0; featuredArtist.Alpha = setInfo.NewValue.TrackId != null ? 1 : 0; @@ -349,6 +349,9 @@ namespace osu.Game.Overlays.BeatmapSet public MetadataFlowContainer(Action defaultCreationParameters = null) : base(defaultCreationParameters) { + TextAnchor = Anchor.CentreLeft; + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; } protected override DrawableLinkCompiler CreateLinkCompiler(ITextPart textPart) => new MetadataLinkCompiler(textPart); From ae967e08b0c3cd07714d397c9bf0a58224b63b53 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 24 Dec 2022 13:27:46 -0800 Subject: [PATCH 264/384] Add badges when needed instead of using alpha --- .../BeatmapSet/BeatmapSetHeaderContent.cs | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index b305e6ef05..043844b56b 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -261,42 +261,36 @@ namespace osu.Game.Overlays.BeatmapSet } }); - title.AddArbitraryDrawable(new Container + if (setInfo.NewValue.HasExplicitContent) { - AutoSizeAxes = Axes.Both, - Child = explicitContent = new ExplicitContentBeatmapBadge + title.AddArbitraryDrawable(new Container { - Alpha = 0f, - Margin = new MarginPadding { Left = 10 }, - } - }); + AutoSizeAxes = Axes.Both, + Child = explicitContent = new ExplicitContentBeatmapBadge { Margin = new MarginPadding { Left = 10 } }, + }); + } - title.AddArbitraryDrawable(new Container + if (setInfo.NewValue.FeaturedInSpotlight) { - AutoSizeAxes = Axes.Both, - Child = spotlight = new SpotlightBeatmapBadge + title.AddArbitraryDrawable(new Container { - Alpha = 0f, - Margin = new MarginPadding { Left = 10 }, - } - }); + AutoSizeAxes = Axes.Both, + Child = spotlight = new SpotlightBeatmapBadge { Margin = new MarginPadding { Left = 10 } }, + }); + } artist.AddLink(artistText, LinkAction.SearchBeatmapSet, artistText); - artist.AddArbitraryDrawable(new Container + if (setInfo.NewValue.TrackId != null) { - AutoSizeAxes = Axes.Both, - Child = featuredArtist = new FeaturedArtistBeatmapBadge + artist.AddArbitraryDrawable(new Container { - Alpha = 0f, - Margin = new MarginPadding { Left = 10 } - } - }); + AutoSizeAxes = Axes.Both, + Child = featuredArtist = new FeaturedArtistBeatmapBadge { Margin = new MarginPadding { Left = 10 } } + }); + } updateExternalLink(); - explicitContent.Alpha = setInfo.NewValue.HasExplicitContent ? 1 : 0; - spotlight.Alpha = setInfo.NewValue.FeaturedInSpotlight ? 1 : 0; - featuredArtist.Alpha = setInfo.NewValue.TrackId != null ? 1 : 0; onlineStatusPill.FadeIn(500, Easing.OutQuint); From b871d6f078ebfff9eb859011693de8f439d41462 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 24 Dec 2022 13:35:17 -0800 Subject: [PATCH 265/384] Remove unused fields --- osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 043844b56b..13506bfd1c 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -48,9 +48,6 @@ namespace osu.Game.Overlays.BeatmapSet private readonly LinkFlowContainer title, artist; private readonly AuthorInfo author; - private ExplicitContentBeatmapBadge explicitContent; - private SpotlightBeatmapBadge spotlight; - private FeaturedArtistBeatmapBadge featuredArtist; private ExternalLinkButton externalLink; private readonly FillFlowContainer downloadButtonsContainer; @@ -266,7 +263,7 @@ namespace osu.Game.Overlays.BeatmapSet title.AddArbitraryDrawable(new Container { AutoSizeAxes = Axes.Both, - Child = explicitContent = new ExplicitContentBeatmapBadge { Margin = new MarginPadding { Left = 10 } }, + Child = new ExplicitContentBeatmapBadge { Margin = new MarginPadding { Left = 10 } }, }); } @@ -275,7 +272,7 @@ namespace osu.Game.Overlays.BeatmapSet title.AddArbitraryDrawable(new Container { AutoSizeAxes = Axes.Both, - Child = spotlight = new SpotlightBeatmapBadge { Margin = new MarginPadding { Left = 10 } }, + Child = new SpotlightBeatmapBadge { Margin = new MarginPadding { Left = 10 } }, }); } @@ -286,7 +283,7 @@ namespace osu.Game.Overlays.BeatmapSet artist.AddArbitraryDrawable(new Container { AutoSizeAxes = Axes.Both, - Child = featuredArtist = new FeaturedArtistBeatmapBadge { Margin = new MarginPadding { Left = 10 } } + Child = new FeaturedArtistBeatmapBadge { Margin = new MarginPadding { Left = 10 } } }); } From e6f9d6202c4dd174ba21804d17c41fb185f73837 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 25 Dec 2022 23:40:01 +0800 Subject: [PATCH 266/384] Update appveyor deploy image --- appveyor_deploy.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor_deploy.yml b/appveyor_deploy.yml index adf98848bc..175c8d0f1b 100644 --- a/appveyor_deploy.yml +++ b/appveyor_deploy.yml @@ -1,6 +1,6 @@ clone_depth: 1 version: '{build}' -image: Visual Studio 2019 +image: Visual Studio 2022 test: off skip_non_tags: true configuration: Release @@ -83,4 +83,4 @@ artifacts: deploy: - provider: Environment - name: nuget \ No newline at end of file + name: nuget From 2c2f347e2585c00742f8ad852550b2938805bd75 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 25 Dec 2022 09:57:42 -0800 Subject: [PATCH 267/384] Add context menus to overlay panels/cards --- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 9 ++++- osu.Game/Overlays/Chat/DrawableUsername.cs | 5 +-- osu.Game/Overlays/News/NewsCard.cs | 9 ++++- osu.Game/Overlays/OnlineOverlay.cs | 10 ++++-- osu.Game/Users/UserPanel.cs | 36 +++++++++++++++++-- 5 files changed, 60 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 00f9a6b3d5..7e5b734979 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -5,6 +5,8 @@ using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -14,7 +16,7 @@ using osu.Game.Overlays; namespace osu.Game.Beatmaps.Drawables.Cards { - public abstract partial class BeatmapCard : OsuClickableContainer + public abstract partial class BeatmapCard : OsuClickableContainer, IHasContextMenu { public const float TRANSITION_DURATION = 400; public const float CORNER_RADIUS = 10; @@ -96,5 +98,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards throw new ArgumentOutOfRangeException(nameof(size), size, @"Unsupported card size"); } } + + public MenuItem[] ContextMenuItems => new MenuItem[] + { + new OsuMenuItem("View beatmap", MenuItemType.Highlighted, Action), + }; } } diff --git a/osu.Game/Overlays/Chat/DrawableUsername.cs b/osu.Game/Overlays/Chat/DrawableUsername.cs index 6bae498a6c..8005677dc2 100644 --- a/osu.Game/Overlays/Chat/DrawableUsername.cs +++ b/osu.Game/Overlays/Chat/DrawableUsername.cs @@ -20,6 +20,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; +using osu.Game.Resources.Localisation.Web; using osuTK; using osuTK.Graphics; @@ -148,11 +149,11 @@ namespace osu.Game.Overlays.Chat List items = new List { - new OsuMenuItem("View Profile", MenuItemType.Highlighted, openUserProfile) + new OsuMenuItem("View profile", MenuItemType.Highlighted, openUserProfile) }; if (!user.Equals(api.LocalUser.Value)) - items.Add(new OsuMenuItem("Start Chat", MenuItemType.Standard, openUserChannel)); + items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, openUserChannel)); return items.ToArray(); } diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs index e0be5cc4a9..f18ce7ff06 100644 --- a/osu.Game/Overlays/News/NewsCard.cs +++ b/osu.Game/Overlays/News/NewsCard.cs @@ -12,16 +12,18 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.News { - public partial class NewsCard : OsuHoverContainer + public partial class NewsCard : OsuHoverContainer, IHasContextMenu { protected override IEnumerable EffectTargets => new[] { background }; @@ -161,5 +163,10 @@ namespace osu.Game.Overlays.News DateTimeOffset IHasCustomTooltip.TooltipContent => date; } + + public MenuItem[] ContextMenuItems => new MenuItem[] + { + new OsuMenuItem("View news in browser", MenuItemType.Highlighted, Action), + }; } } diff --git a/osu.Game/Overlays/OnlineOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs index 0e0ce56446..ff225bd4ab 100644 --- a/osu.Game/Overlays/OnlineOverlay.cs +++ b/osu.Game/Overlays/OnlineOverlay.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; using osu.Game.Online; @@ -46,10 +47,15 @@ namespace osu.Game.Overlays Children = new Drawable[] { Header.With(h => h.Depth = float.MinValue), - content = new PopoverContainer + new OsuContextMenuContainer { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y + AutoSizeAxes = Axes.Y, + Child = content = new PopoverContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } } } } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 2f7232d5ea..2e9a870399 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -4,6 +4,7 @@ #nullable disable using System; +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; @@ -15,7 +16,10 @@ using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.Containers; using JetBrains.Annotations; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Chat; +using osu.Game.Resources.Localisation.Web; namespace osu.Game.Users { @@ -44,6 +48,15 @@ namespace osu.Game.Users [Resolved(canBeNull: true)] private UserProfileOverlay profileOverlay { get; set; } + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private ChannelManager channelManager { get; set; } + + [Resolved] + private ChatOverlay chatOverlay { get; set; } + [Resolved(canBeNull: true)] protected OverlayColourProvider ColourProvider { get; private set; } @@ -89,9 +102,26 @@ namespace osu.Game.Users Text = User.Username, }; - public MenuItem[] ContextMenuItems => new MenuItem[] + public MenuItem[] ContextMenuItems { - new OsuMenuItem("View Profile", MenuItemType.Highlighted, ViewProfile), - }; + get + { + List items = new List + { + new OsuMenuItem("View profile", MenuItemType.Highlighted, ViewProfile) + }; + + if (!User.Equals(api.LocalUser.Value)) + { + items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, () => + { + channelManager?.OpenPrivateChannel(User); + chatOverlay?.Show(); + })); + } + + return items.ToArray(); + } + } } } From b9dfb8b60208c51073352c071fd45c6db6a44f43 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 25 Dec 2022 10:07:42 -0800 Subject: [PATCH 268/384] Add localisation for context menu strings --- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 3 ++- osu.Game/Localisation/ContextMenuStrings.cs | 24 +++++++++++++++++++ osu.Game/Overlays/Chat/DrawableUsername.cs | 3 ++- osu.Game/Users/UserPanel.cs | 3 ++- 4 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Localisation/ContextMenuStrings.cs diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 7e5b734979..94b2956b4e 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; +using osu.Game.Localisation; namespace osu.Game.Beatmaps.Drawables.Cards { @@ -101,7 +102,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards public MenuItem[] ContextMenuItems => new MenuItem[] { - new OsuMenuItem("View beatmap", MenuItemType.Highlighted, Action), + new OsuMenuItem(ContextMenuStrings.ViewBeatmap, MenuItemType.Highlighted, Action), }; } } diff --git a/osu.Game/Localisation/ContextMenuStrings.cs b/osu.Game/Localisation/ContextMenuStrings.cs new file mode 100644 index 0000000000..8bc213016b --- /dev/null +++ b/osu.Game/Localisation/ContextMenuStrings.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +namespace osu.Game.Localisation +{ + public static class ContextMenuStrings + { + private const string prefix = @"osu.Game.Resources.Localisation.ContextMenu"; + + /// + /// "View profile" + /// + public static LocalisableString ViewProfile => new TranslatableString(getKey(@"view_profile"), @"View profile"); + + /// + /// "View beatmap" + /// + public static LocalisableString ViewBeatmap => new TranslatableString(getKey(@"view_beatmap"), @"View beatmap"); + + private static string getKey(string key) => $@"{prefix}:{key}"; + } +} diff --git a/osu.Game/Overlays/Chat/DrawableUsername.cs b/osu.Game/Overlays/Chat/DrawableUsername.cs index 8005677dc2..8cd16047f3 100644 --- a/osu.Game/Overlays/Chat/DrawableUsername.cs +++ b/osu.Game/Overlays/Chat/DrawableUsername.cs @@ -17,6 +17,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Localisation; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; @@ -149,7 +150,7 @@ namespace osu.Game.Overlays.Chat List items = new List { - new OsuMenuItem("View profile", MenuItemType.Highlighted, openUserProfile) + new OsuMenuItem(ContextMenuStrings.ViewProfile, MenuItemType.Highlighted, openUserProfile) }; if (!user.Equals(api.LocalUser.Value)) diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 2e9a870399..5c070bae3b 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -20,6 +20,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Resources.Localisation.Web; +using osu.Game.Localisation; namespace osu.Game.Users { @@ -108,7 +109,7 @@ namespace osu.Game.Users { List items = new List { - new OsuMenuItem("View profile", MenuItemType.Highlighted, ViewProfile) + new OsuMenuItem(ContextMenuStrings.ViewProfile, MenuItemType.Highlighted, ViewProfile) }; if (!User.Equals(api.LocalUser.Value)) From 1a571e1c7f36b86c81c68486245f11ef19a884a0 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 25 Dec 2022 15:03:08 -0800 Subject: [PATCH 269/384] Remove nullable disable on user panel --- osu.Game/Users/UserPanel.cs | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 5c070bae3b..e2dc511391 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -1,8 +1,6 @@ // Copyright (c) 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; @@ -15,7 +13,6 @@ using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.Containers; -using JetBrains.Annotations; using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; @@ -32,11 +29,11 @@ namespace osu.Game.Users /// Perform an action in addition to showing the user's profile. /// This should be used to perform auxiliary tasks and not as a primary action for clicking a user panel (to maintain a consistent UX). /// - public new Action Action; + public new Action? Action; - protected Action ViewProfile { get; private set; } + protected Action ViewProfile { get; private set; } = null!; - protected Drawable Background { get; private set; } + protected Drawable Background { get; private set; } = null!; protected UserPanel(APIUser user) : base(HoverSampleSet.Button) @@ -46,23 +43,23 @@ namespace osu.Game.Users User = user; } - [Resolved(canBeNull: true)] - private UserProfileOverlay profileOverlay { get; set; } + [Resolved] + private UserProfileOverlay? profileOverlay { get; set; } [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; [Resolved] - private ChannelManager channelManager { get; set; } + private ChannelManager? channelManager { get; set; } [Resolved] - private ChatOverlay chatOverlay { get; set; } - - [Resolved(canBeNull: true)] - protected OverlayColourProvider ColourProvider { get; private set; } + private ChatOverlay? chatOverlay { get; set; } [Resolved] - protected OsuColour Colours { get; private set; } + protected OverlayColourProvider? ColourProvider { get; private set; } + + [Resolved] + protected OsuColour Colours { get; private set; } = null!; [BackgroundDependencyLoader] private void load() @@ -93,7 +90,6 @@ namespace osu.Game.Users }; } - [NotNull] protected abstract Drawable CreateLayout(); protected OsuSpriteText CreateUsername() => new OsuSpriteText From 8e899c2e9278db719507918405e7d2c315ae93c9 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 25 Dec 2022 16:03:44 -0800 Subject: [PATCH 270/384] Use localisation parameters to find preferred string instead --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b55b943023..983277135d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -354,7 +354,7 @@ namespace osu.Game case LinkAction.SearchBeatmapSet: if (link.Argument is RomanisableString romanisable) - SearchBeatmapSet(romanisable.GetPreferred(frameworkConfig.GetBindable(FrameworkSetting.ShowUnicode).Value)); + SearchBeatmapSet(romanisable.GetPreferred(Localisation.CurrentParameters.Value.PreferOriginalScript)); else SearchBeatmapSet(argString); break; From f959b02dc85d750b2d05ec13183d548e66e4252c Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 25 Dec 2022 16:05:59 -0800 Subject: [PATCH 271/384] Use empty drawables for spacing badges instead --- .../BeatmapSet/BeatmapSetHeaderContent.cs | 31 +++++-------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 13506bfd1c..17836b558c 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -249,42 +249,27 @@ namespace osu.Game.Overlays.BeatmapSet title.AddLink(titleText, LinkAction.SearchBeatmapSet, titleText); - title.AddArbitraryDrawable(new Container - { - AutoSizeAxes = Axes.Both, - Child = externalLink = new ExternalLinkButton - { - Margin = new MarginPadding { Left = 5 }, - } - }); + title.AddArbitraryDrawable(Empty().With(d => d.Width = 5)); + title.AddArbitraryDrawable(externalLink = new ExternalLinkButton()); if (setInfo.NewValue.HasExplicitContent) { - title.AddArbitraryDrawable(new Container - { - AutoSizeAxes = Axes.Both, - Child = new ExplicitContentBeatmapBadge { Margin = new MarginPadding { Left = 10 } }, - }); + title.AddArbitraryDrawable(Empty().With(d => d.Width = 10)); + title.AddArbitraryDrawable(new ExplicitContentBeatmapBadge()); } if (setInfo.NewValue.FeaturedInSpotlight) { - title.AddArbitraryDrawable(new Container - { - AutoSizeAxes = Axes.Both, - Child = new SpotlightBeatmapBadge { Margin = new MarginPadding { Left = 10 } }, - }); + title.AddArbitraryDrawable(Empty().With(d => d.Width = 10)); + title.AddArbitraryDrawable(new SpotlightBeatmapBadge()); } artist.AddLink(artistText, LinkAction.SearchBeatmapSet, artistText); if (setInfo.NewValue.TrackId != null) { - artist.AddArbitraryDrawable(new Container - { - AutoSizeAxes = Axes.Both, - Child = new FeaturedArtistBeatmapBadge { Margin = new MarginPadding { Left = 10 } } - }); + artist.AddArbitraryDrawable(Empty().With(d => d.Width = 10)); + artist.AddArbitraryDrawable(new FeaturedArtistBeatmapBadge()); } updateExternalLink(); From 973fd90af2b334229e89f87aa31790daa6bef2f1 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 25 Dec 2022 16:15:02 -0800 Subject: [PATCH 272/384] Fix parameters with the same default value inspection --- osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs | 4 ++-- .../Visual/Gameplay/TestScenePlayerScoreSubmission.cs | 2 +- osu.Game/Graphics/OsuFont.cs | 2 +- osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs | 6 +++--- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 32d0cc8939..1e9f931b74 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -160,9 +160,9 @@ namespace osu.Game.Rulesets.Osu.Tests static bool assertSamples(HitObject hitObject) => hitObject.Samples.All(s => s.Name != HitSampleInfo.HIT_CLAP && s.Name != HitSampleInfo.HIT_WHISTLE); } - private Drawable testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats); + private Drawable testSimpleBig(int repeats = 0) => createSlider(repeats: repeats); - private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10); + private Drawable testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(repeats: repeats, stackHeight: 10); private Drawable testDistanceOverflow(int repeats = 0) { diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index 2fbdfbc198..d5031bc606 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -257,7 +257,7 @@ namespace osu.Game.Tests.Visual.Gameplay { prepareTestAPI(true); - createPlayerTest(false, createRuleset: () => new OsuRuleset + createPlayerTest(createRuleset: () => new OsuRuleset { RulesetInfo = { diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 038ea0f5d7..12e82469e8 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -21,7 +21,7 @@ namespace osu.Game.Graphics public static FontUsage Numeric => GetFont(Typeface.Venera, weight: FontWeight.Bold); - public static FontUsage Torus => GetFont(Typeface.Torus, weight: FontWeight.Regular); + public static FontUsage Torus => GetFont(weight: FontWeight.Regular); public static FontUsage TorusAlternate => GetFont(Typeface.TorusAlternate, weight: FontWeight.Regular); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 006eec2838..425f40258e 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -95,8 +95,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersScore, Anchor.CentreLeft, new Dimension(GridSizeMode.AutoSize)), new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersAccuracy, Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, minSize: 60, maxSize: 70)), new TableColumn("", Anchor.CentreLeft, new Dimension(GridSizeMode.Absolute, 25)), // flag - new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersPlayer, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 125)), - new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersCombo, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 70, maxSize: 120)) + new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersPlayer, Anchor.CentreLeft, new Dimension(minSize: 125)), + new TableColumn(BeatmapsetsStrings.ShowScoreboardHeadersCombo, Anchor.CentreLeft, new Dimension(minSize: 70, maxSize: 120)) }; // All statistics across all scores, unordered. @@ -116,7 +116,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var displayName = ruleset.GetDisplayNameForHitResult(result); - columns.Add(new TableColumn(displayName, Anchor.CentreLeft, new Dimension(GridSizeMode.Distributed, minSize: 35, maxSize: 60))); + columns.Add(new TableColumn(displayName, Anchor.CentreLeft, new Dimension(minSize: 35, maxSize: 60))); statisticResultTypes.Add((result, displayName)); } From a10628e2702a7dbda3d38b35c422928f70416a73 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 25 Dec 2022 20:46:59 -0800 Subject: [PATCH 273/384] Change severity of `RedundantArgumentDefaultValue` to hint --- osu.sln.DotSettings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index ef3b08e1f5..367dfccb71 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -146,7 +146,7 @@ HINT HINT WARNING - WARNING + HINT WARNING WARNING WARNING From 144144c40ce3954126c493dbc7e220eab620eed2 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 25 Dec 2022 20:44:13 -0800 Subject: [PATCH 274/384] Revert removing redundant font parameter --- osu.Game/Graphics/OsuFont.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index 12e82469e8..038ea0f5d7 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -21,7 +21,7 @@ namespace osu.Game.Graphics public static FontUsage Numeric => GetFont(Typeface.Venera, weight: FontWeight.Bold); - public static FontUsage Torus => GetFont(weight: FontWeight.Regular); + public static FontUsage Torus => GetFont(Typeface.Torus, weight: FontWeight.Regular); public static FontUsage TorusAlternate => GetFont(Typeface.TorusAlternate, weight: FontWeight.Regular); From 5dd03c6c60553d2d73455c89095a4839224eac3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Dec 2022 21:53:52 +0800 Subject: [PATCH 275/384] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index e934b2da6a..c6cf7812d1 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -10,7 +10,7 @@ true - + From fbff5d8d69892b255c78f47b32cdd71311bbaaa1 Mon Sep 17 00:00:00 2001 From: mk56-spn Date: Mon, 26 Dec 2022 16:16:52 +0100 Subject: [PATCH 276/384] Remove obsoleted "ForDifficultyRating" method --- osu.Game/Graphics/OsuColour.cs | 32 +------------------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/osu.Game/Graphics/OsuColour.cs b/osu.Game/Graphics/OsuColour.cs index 91161d5c71..c5659aaf57 100644 --- a/osu.Game/Graphics/OsuColour.cs +++ b/osu.Game/Graphics/OsuColour.cs @@ -22,38 +22,8 @@ namespace osu.Game.Graphics public static Color4 Gray(byte amt) => new Color4(amt, amt, amt, 255); /// - /// Retrieves the colour for a . + /// Retrieves the colour for a given point in the star range. /// - /// - /// Sourced from the @diff-{rating} variables in https://github.com/ppy/osu-web/blob/71fbab8936d79a7929d13854f5e854b4f383b236/resources/assets/less/variables.less. - /// - public Color4 ForDifficultyRating(DifficultyRating difficulty, bool useLighterColour = false) - { - switch (difficulty) - { - case DifficultyRating.Easy: - return Color4Extensions.FromHex("4ebfff"); - - case DifficultyRating.Normal: - return Color4Extensions.FromHex("66ff91"); - - case DifficultyRating.Hard: - return Color4Extensions.FromHex("f7e85d"); - - case DifficultyRating.Insane: - return Color4Extensions.FromHex("ff7e68"); - - case DifficultyRating.Expert: - return Color4Extensions.FromHex("fe3c71"); - - case DifficultyRating.ExpertPlus: - return Color4Extensions.FromHex("6662dd"); - - default: - throw new ArgumentOutOfRangeException(nameof(difficulty)); - } - } - public Color4 ForStarDifficulty(double starDifficulty) => ColourUtils.SampleFromLinearGradient(new[] { (0.1f, Color4Extensions.FromHex("aaaaaa")), From c7ca4bbba5a9fc9a6bc12efedb66cbc61531c416 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Mon, 26 Dec 2022 20:36:39 +0100 Subject: [PATCH 277/384] Use generic Enum methods --- .../Skinning/Legacy/LegacyCatcherNew.cs | 2 +- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs | 2 +- osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs | 2 +- osu.Game.Tournament/IPC/FileBasedIPC.cs | 2 +- osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs | 2 +- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 4 ++-- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 8 ++++---- osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs | 2 +- .../Online/API/Requests/Responses/APIRecentActivity.cs | 4 ++-- osu.Game/Online/API/Requests/Responses/APIUser.cs | 2 +- osu.Game/OsuGame.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs | 3 +-- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Rulesets/Scoring/HitResult.cs | 2 +- osu.Game/Screens/Edit/Setup/DesignSection.cs | 2 +- osu.Game/Screens/Utility/LatencyCertifierScreen.cs | 2 +- osu.Game/Skinning/Components/BeatmapAttributeText.cs | 2 +- osu.Game/Skinning/Skin.cs | 2 +- 20 files changed, 25 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs index b36d7f11cb..ab753d9c86 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy [BackgroundDependencyLoader] private void load(ISkinSource skin) { - foreach (var state in Enum.GetValues(typeof(CatcherAnimationState)).Cast()) + foreach (var state in Enum.GetValues()) { AddInternal(drawables[state] = getDrawableFor(state).With(d => { diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 122330d09b..ed02284a4b 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.UI HitPolicy = new StartTimeOrderedHitPolicy(); var hitWindows = new OsuHitWindows(); - foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) + foreach (var result in Enum.GetValues().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) poolDictionary.Add(result, new DrawableJudgementPool(result, onJudgementLoaded)); AddRangeInternal(poolDictionary.Values); diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs index 9493de624a..9f9debe7d7 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfield.cs @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Taiko.UI var hitWindows = new TaikoHitWindows(); - foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => hitWindows.IsHitResultAllowed(r))) + foreach (var result in Enum.GetValues().Where(r => hitWindows.IsHitResultAllowed(r))) { judgementPools.Add(result, new DrawablePool(15)); explosionPools.Add(result, new HitExplosionPool(result)); diff --git a/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs b/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs index b8a3828a64..ca5240a39d 100644 --- a/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs +++ b/osu.Game.Tests/Mods/MultiModIncompatibilityTest.cs @@ -60,6 +60,6 @@ namespace osu.Game.Tests.Mods /// This local helper is used rather than , because the aforementioned method flattens multi mods. /// > private static IEnumerable getMultiMods(Ruleset ruleset) - => Enum.GetValues(typeof(ModType)).Cast().SelectMany(ruleset.GetModsFor).OfType(); + => Enum.GetValues().SelectMany(ruleset.GetModsFor).OfType(); } } diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 2d47560947..7babb3ea5a 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -127,7 +127,7 @@ namespace osu.Game.Tournament.IPC using (var stream = IPCStorage.GetStream(file_ipc_state_filename)) using (var sr = new StreamReader(stream)) { - State.Value = (TourneyState)Enum.Parse(typeof(TourneyState), sr.ReadLine().AsNonNull()); + State.Value = Enum.Parse(sr.ReadLine().AsNonNull()); } } catch (Exception) diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index eb5c9a879a..c9d897ca11 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tournament.Screens.Editors { var countries = new List(); - foreach (var country in Enum.GetValues(typeof(CountryCode)).Cast().Skip(1)) + foreach (var country in Enum.GetValues().Skip(1)) { countries.Add(new TournamentTeam { diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 5f0a2a0824..e865fe7575 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -160,7 +160,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"SampleSet": - defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value); + defaultSampleBank = Enum.Parse(pair.Value); break; case @"SampleVolume": @@ -218,7 +218,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"Countdown": - beatmap.BeatmapInfo.Countdown = (CountdownType)Enum.Parse(typeof(CountdownType), pair.Value); + beatmap.BeatmapInfo.Countdown = Enum.Parse(tpair.Value); break; case @"CountdownOffset": diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 2b4f377ab6..491fee642b 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -301,11 +301,11 @@ namespace osu.Game.Beatmaps.Formats } } - private string parseLayer(string value) => Enum.Parse(typeof(LegacyStoryLayer), value).ToString(); + private string parseLayer(string value) => Enum.Parse(value).ToString(); private Anchor parseOrigin(string value) { - var origin = (LegacyOrigins)Enum.Parse(typeof(LegacyOrigins), value); + var origin = Enum.Parse(value); switch (origin) { @@ -343,8 +343,8 @@ namespace osu.Game.Beatmaps.Formats private AnimationLoopType parseAnimationLoopType(string value) { - var parsed = (AnimationLoopType)Enum.Parse(typeof(AnimationLoopType), value); - return Enum.IsDefined(typeof(AnimationLoopType), parsed) ? parsed : AnimationLoopType.LoopForever; + var parsed = Enum.Parse(value); + return Enum.IsDefined(parsed) ? parsed : AnimationLoopType.LoopForever; } private void handleVariables(string line) diff --git a/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs index 9ef58f4c49..dc089e3410 100644 --- a/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuEnumDropdown.cs @@ -12,7 +12,7 @@ namespace osu.Game.Graphics.UserInterface { public OsuEnumDropdown() { - Items = (T[])Enum.GetValues(typeof(T)); + Items = Enum.GetValues(); } } } diff --git a/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs b/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs index 2def18926f..c6a8a85407 100644 --- a/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs +++ b/osu.Game/Online/API/Requests/Responses/APIRecentActivity.cs @@ -21,7 +21,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty] private string type { - set => Type = (RecentActivityType)Enum.Parse(typeof(RecentActivityType), value.ToPascalCase()); + set => Type = Enum.Parse(value.ToPascalCase()); } public RecentActivityType Type; @@ -29,7 +29,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty] private string scoreRank { - set => ScoreRank = (ScoreRank)Enum.Parse(typeof(ScoreRank), value); + set => ScoreRank = Enum.Parse(value); } public ScoreRank ScoreRank; diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 2b6193f661..37a1586e49 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -185,7 +185,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"playstyle")] private string[] playStyle { - set => PlayStyles = value?.Select(str => Enum.Parse(typeof(APIPlayStyle), str, true)).Cast().ToArray(); + set => PlayStyles = value?.Select(str => Enum.Parse(str, true)).Cast().ToArray(); } public APIPlayStyle[] PlayStyles; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 983277135d..a81aa38911 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -726,7 +726,7 @@ namespace osu.Game { base.LoadComplete(); - var languages = Enum.GetValues(typeof(Language)).OfType(); + var languages = Enum.GetValues(); var mappings = languages.Select(language => { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 36fd5a4177..36e248c1f2 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -607,7 +607,7 @@ namespace osu.Game try { - foreach (ModType type in Enum.GetValues(typeof(ModType))) + foreach (ModType type in Enum.GetValues()) { dict[type] = instance.GetModsFor(type) // Rulesets should never return null mods, but let's be defensive just in case. diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs index 4af40e5ad6..b8d802ad4b 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenWelcome.cs @@ -79,8 +79,7 @@ namespace osu.Game.Overlays.FirstRunSetup Direction = FillDirection.Full; Spacing = new Vector2(5); - ChildrenEnumerable = Enum.GetValues(typeof(Language)) - .Cast() + ChildrenEnumerable = Enum.GetValues() .Select(l => new LanguageButton(l) { Action = () => frameworkLocale.Value = l.ToCultureCode() diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index a73151362b..fcf7a78090 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets /// This comes with considerable allocation overhead. If only accessing for reference purposes (ie. not changing bindables / settings) /// use instead. /// - public IEnumerable CreateAllMods() => Enum.GetValues(typeof(ModType)).Cast() + public IEnumerable CreateAllMods() => Enum.GetValues() // Confine all mods of each mod type into a single IEnumerable .SelectMany(GetModsFor) // Filter out all null mods diff --git a/osu.Game/Rulesets/Scoring/HitResult.cs b/osu.Game/Rulesets/Scoring/HitResult.cs index 96e13e5861..83ed98768c 100644 --- a/osu.Game/Rulesets/Scoring/HitResult.cs +++ b/osu.Game/Rulesets/Scoring/HitResult.cs @@ -264,7 +264,7 @@ namespace osu.Game.Rulesets.Scoring /// /// An array of all scorable s. /// - public static readonly HitResult[] ALL_TYPES = ((HitResult[])Enum.GetValues(typeof(HitResult))).Except(new[] { HitResult.LegacyComboIncrease }).ToArray(); + public static readonly HitResult[] ALL_TYPES = Enum.GetValues().Except(new[] { HitResult.LegacyComboIncrease }).ToArray(); /// /// Whether a is valid within a given range. diff --git a/osu.Game/Screens/Edit/Setup/DesignSection.cs b/osu.Game/Screens/Edit/Setup/DesignSection.cs index 3428366510..fd70b0c142 100644 --- a/osu.Game/Screens/Edit/Setup/DesignSection.cs +++ b/osu.Game/Screens/Edit/Setup/DesignSection.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Edit.Setup { Label = EditorSetupStrings.CountdownSpeed, Current = { Value = Beatmap.BeatmapInfo.Countdown != CountdownType.None ? Beatmap.BeatmapInfo.Countdown : CountdownType.Normal }, - Items = Enum.GetValues(typeof(CountdownType)).Cast().Where(type => type != CountdownType.None) + Items = Enum.GetValues().Where(type => type != CountdownType.None) }, CountdownOffset = new LabelledNumberBox { diff --git a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs index 212cebeaf5..5c8e448931 100644 --- a/osu.Game/Screens/Utility/LatencyCertifierScreen.cs +++ b/osu.Game/Screens/Utility/LatencyCertifierScreen.cs @@ -237,7 +237,7 @@ namespace osu.Game.Screens.Utility switch (e.Key) { case Key.Space: - int availableModes = Enum.GetValues(typeof(LatencyVisualMode)).Length; + int availableModes = Enum.GetValues().Length; VisualMode.Value = (LatencyVisualMode)(((int)VisualMode.Value + 1) % availableModes); return true; diff --git a/osu.Game/Skinning/Components/BeatmapAttributeText.cs b/osu.Game/Skinning/Components/BeatmapAttributeText.cs index 0a5f0d22cb..8361619574 100644 --- a/osu.Game/Skinning/Components/BeatmapAttributeText.cs +++ b/osu.Game/Skinning/Components/BeatmapAttributeText.cs @@ -115,7 +115,7 @@ namespace osu.Game.Skinning.Components .Cast() .ToArray(); - foreach (var type in Enum.GetValues(typeof(BeatmapAttribute)).Cast()) + foreach (var type in Enum.GetValues()) { numberedTemplate = numberedTemplate.Replace($"{{{{{type}}}}}", $"{{{1 + (int)type}}}"); } diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index 25d1dc903c..19e8bc7092 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -97,7 +97,7 @@ namespace osu.Game.Skinning Configuration = new SkinConfiguration(); // skininfo files may be null for default skin. - foreach (GlobalSkinComponentLookup.LookupType skinnableTarget in Enum.GetValues(typeof(GlobalSkinComponentLookup.LookupType))) + foreach (GlobalSkinComponentLookup.LookupType skinnableTarget in Enum.GetValues()) { string filename = $"{skinnableTarget}.json"; From fcbb21c75eb35a1313f9e998bed1d335f09a40a2 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Mon, 26 Dec 2022 20:38:35 +0100 Subject: [PATCH 278/384] Fix typo --- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index e865fe7575..9c710b690e 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -218,7 +218,7 @@ namespace osu.Game.Beatmaps.Formats break; case @"Countdown": - beatmap.BeatmapInfo.Countdown = Enum.Parse(tpair.Value); + beatmap.BeatmapInfo.Countdown = Enum.Parse(pair.Value); break; case @"CountdownOffset": From 335cb0205fb0729afb0c54744dd4992f7510bae7 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Mon, 26 Dec 2022 22:50:36 +0100 Subject: [PATCH 279/384] Remove now unnecessary using --- osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs index ab753d9c86..f6b2c52498 100644 --- a/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs +++ b/osu.Game.Rulesets.Catch/Skinning/Legacy/LegacyCatcherNew.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; From cb2b0d41788e03e0cad4830ca6d1c854a300d0e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Dec 2022 23:12:53 +0100 Subject: [PATCH 280/384] Remove redundant type specs --- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 2 +- osu.Game/Online/API/Requests/Responses/APIUser.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 491fee642b..44dbb3cc9f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -344,7 +344,7 @@ namespace osu.Game.Beatmaps.Formats private AnimationLoopType parseAnimationLoopType(string value) { var parsed = Enum.Parse(value); - return Enum.IsDefined(parsed) ? parsed : AnimationLoopType.LoopForever; + return Enum.IsDefined(parsed) ? parsed : AnimationLoopType.LoopForever; } private void handleVariables(string line) diff --git a/osu.Game/Online/API/Requests/Responses/APIUser.cs b/osu.Game/Online/API/Requests/Responses/APIUser.cs index 37a1586e49..9cb0c0704d 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUser.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUser.cs @@ -185,7 +185,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"playstyle")] private string[] playStyle { - set => PlayStyles = value?.Select(str => Enum.Parse(str, true)).Cast().ToArray(); + set => PlayStyles = value?.Select(str => Enum.Parse(str, true)).ToArray(); } public APIPlayStyle[] PlayStyles; From 01cf96e240d8a9d1ea99ec072797bdf10d0f6292 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 26 Dec 2022 23:25:54 +0100 Subject: [PATCH 281/384] Only show global rankings on results screen when progressing from gameplay --- osu.Game/Screens/Play/Player.cs | 5 +++- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 23 +++++++++++++++---- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4306d13ac2..05133fba35 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1159,7 +1159,10 @@ namespace osu.Game.Screens.Play /// /// The to be displayed in the results screen. /// The . - protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, true); + protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, true) + { + ShowUserStatistics = true + }; private void fadeOut(bool instant = false) { diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 6d4feeb0db..110e813e04 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -20,6 +20,12 @@ namespace osu.Game.Screens.Ranking { public partial class SoloResultsScreen : ResultsScreen { + /// + /// Whether the user's personal statistics should be shown on the extended statistics panel + /// after clicking the score panel associated with the being presented. + /// + public bool ShowUserStatistics { get; init; } + private GetScoresRequest getScoreRequest; [Resolved] @@ -39,13 +45,22 @@ namespace osu.Game.Screens.Ranking { base.LoadComplete(); - soloStatisticsWatcher.RegisterForStatisticsUpdateAfter(Score, update => statisticsUpdate.Value = update); + if (ShowUserStatistics) + soloStatisticsWatcher.RegisterForStatisticsUpdateAfter(Score, update => statisticsUpdate.Value = update); } - protected override StatisticsPanel CreateStatisticsPanel() => new SoloStatisticsPanel(Score) + protected override StatisticsPanel CreateStatisticsPanel() { - StatisticsUpdate = { BindTarget = statisticsUpdate } - }; + if (ShowUserStatistics) + { + return new SoloStatisticsPanel(Score) + { + StatisticsUpdate = { BindTarget = statisticsUpdate } + }; + } + + return base.CreateStatisticsPanel(); + } protected override APIRequest FetchScores(Action> scoresCallback) { From 84e3858a86bf6a613456ab37e8e73f406287a602 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 26 Dec 2022 15:37:46 -0800 Subject: [PATCH 282/384] Move context menu / popover container one level to account for header --- osu.Game/Overlays/OnlineOverlay.cs | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game/Overlays/OnlineOverlay.cs b/osu.Game/Overlays/OnlineOverlay.cs index ff225bd4ab..4fdf7cb2b6 100644 --- a/osu.Game/Overlays/OnlineOverlay.cs +++ b/osu.Game/Overlays/OnlineOverlay.cs @@ -39,25 +39,30 @@ namespace osu.Game.Overlays { RelativeSizeAxes = Axes.Both, ScrollbarVisible = false, - Child = new FillFlowContainer + Child = new OsuContextMenuContainer { - AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, - Direction = FillDirection.Vertical, - Children = new Drawable[] + AutoSizeAxes = Axes.Y, + Child = new PopoverContainer { - Header.With(h => h.Depth = float.MinValue), - new OsuContextMenuContainer + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Child = content = new PopoverContainer + Direction = FillDirection.Vertical, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y + Header.With(h => h.Depth = float.MinValue), + content = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } } } - } + }, } }, Loading = new LoadingLayer(true) From 8bc8b0d0af3f2f8b8e043e4ffc85a660c64f6bac Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 26 Dec 2022 15:38:07 -0800 Subject: [PATCH 283/384] Remove redundant context menu container --- .../BeatmapSet/BeatmapSetHeaderContent.cs | 182 +++++++++--------- 1 file changed, 88 insertions(+), 94 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 0318dad0e3..3eec4b4731 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -9,7 +9,6 @@ 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; @@ -91,118 +90,113 @@ namespace osu.Game.Overlays.BeatmapSet }, }, }, - new OsuContextMenuContainer + new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Child = new Container + Padding = new MarginPadding { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding + Vertical = BeatmapSetOverlay.Y_PADDING, + Left = BeatmapSetOverlay.X_PADDING, + Right = BeatmapSetOverlay.X_PADDING + BeatmapSetOverlay.RIGHT_WIDTH, + }, + Children = new Drawable[] + { + fadeContent = new FillFlowContainer { - 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, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] + new Container { - new Container + 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[] { - 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[] + title = new OsuSpriteText { - 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 }, - } + 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 + } + }, + new FillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding { Bottom = 20 }, + Children = new Drawable[] { - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding { Bottom = 20 }, - Children = new Drawable[] + artist = new OsuSpriteText { - 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 } - } + 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 + } + }, + 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[] { - 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[] + favouriteButton = new FavouriteButton { - 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), - }, + 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 777ffcf805f07954377832a869472afee8c09a0a Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 26 Dec 2022 20:45:32 -0800 Subject: [PATCH 284/384] Highlight "open" option on external link button context menu --- 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 efbbaaca85..4eccb37613 100644 --- a/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs +++ b/osu.Game/Graphics/UserInterface/ExternalLinkButton.cs @@ -82,7 +82,7 @@ namespace osu.Game.Graphics.UserInterface if (Link != null) { - items.Add(new OsuMenuItem("Open", MenuItemType.Standard, () => host.OpenUrlExternally(Link))); + items.Add(new OsuMenuItem("Open", MenuItemType.Highlighted, () => host.OpenUrlExternally(Link))); items.Add(new OsuMenuItem("Copy URL", MenuItemType.Standard, copyUrl)); } From 182f36c434bd2b60895403cf07a1adde88912067 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Tue, 27 Dec 2022 09:41:58 +0100 Subject: [PATCH 285/384] Use StringSplitOptions.TrimEntries for string.Split() when possible --- .../Screens/Drawings/Components/StorageBackedTeamList.cs | 8 ++++---- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 8 +------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs index c230607343..74afb42c1a 100644 --- a/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs +++ b/osu.Game.Tournament/Screens/Drawings/Components/StorageBackedTeamList.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components continue; // ReSharper disable once PossibleNullReferenceException - string[] split = line.Split(':'); + string[] split = line.Split(':', StringSplitOptions.TrimEntries); if (split.Length < 2) { @@ -55,9 +55,9 @@ namespace osu.Game.Tournament.Screens.Drawings.Components teams.Add(new TournamentTeam { - FullName = { Value = split[1].Trim(), }, - Acronym = { Value = split.Length >= 3 ? split[2].Trim() : null, }, - FlagName = { Value = split[0].Trim() } + FullName = { Value = split[1], }, + Acronym = { Value = split.Length >= 3 ? split[2] : null, }, + FlagName = { Value = split[0] } }); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index a4e15f790a..704756e3dd 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -132,13 +132,7 @@ namespace osu.Game.Beatmaps.Formats protected KeyValuePair SplitKeyVal(string line, char separator = ':', bool shouldTrim = true) { - string[] split = line.Split(separator, 2); - - if (shouldTrim) - { - for (int i = 0; i < split.Length; i++) - split[i] = split[i].Trim(); - } + string[] split = line.Split(separator, 2, shouldTrim ? StringSplitOptions.TrimEntries : StringSplitOptions.None); return new KeyValuePair ( From b3e44f20bca94f78599500027eaaa7ce164f8e65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 26 Dec 2022 21:53:41 +0800 Subject: [PATCH 286/384] Use new lazer API endpoint This is a temporary change to target the new experimental/next deploy. The main change that should result from this is having the user profile show the pp^next values from the new domain. --- osu.Game/Online/ProductionEndpointConfiguration.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/ProductionEndpointConfiguration.cs b/osu.Game/Online/ProductionEndpointConfiguration.cs index 316452280d..3a74d24b58 100644 --- a/osu.Game/Online/ProductionEndpointConfiguration.cs +++ b/osu.Game/Online/ProductionEndpointConfiguration.cs @@ -9,7 +9,7 @@ namespace osu.Game.Online { public ProductionEndpointConfiguration() { - WebsiteRootUrl = APIEndpointUrl = @"https://osu.ppy.sh"; + WebsiteRootUrl = APIEndpointUrl = @"https://lazer.ppy.sh"; APIClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"; APIClientID = "5"; SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator"; From 61029b126d03f00bea9b7ecfa3862452fcf7c7e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 27 Dec 2022 17:50:30 +0800 Subject: [PATCH 287/384] Add link to hard link explanation wiki page --- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 8b85bb49a5..0f8a78453d 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -22,6 +22,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Localisation; +using osu.Game.Online.Chat; using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections.Maintenance; using osu.Game.Screens.Edit.Setup; @@ -127,7 +128,9 @@ namespace osu.Game.Overlays.FirstRunSetup if (available) { copyInformation.Text = - "Data migration will use \"hard links\". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation."; + "Data migration will use \"hard links\". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation. "; + + copyInformation.AddLink("Learn more about how \"hard links\" work", LinkAction.OpenWiki, @"Client/Release_stream/Lazer/File_storage#via-hard-links"); } else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; From 1a4489edb28c98907fda40551bd7f42f66174d2e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 27 Dec 2022 14:55:51 +0300 Subject: [PATCH 288/384] Move version pinning of system packages to `osu.Game` --- osu.Game/osu.Game.csproj | 5 +++++ osu.iOS.props | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index da14ed123f..cce3e42be4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -43,5 +43,10 @@ + + + + + diff --git a/osu.iOS.props b/osu.iOS.props index 9b9abfc37b..6201022da1 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -17,10 +17,5 @@ - - - - - From e2703bba188b606760c74023f4cf7d62fdd8792a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Dec 2022 19:48:18 +0100 Subject: [PATCH 289/384] Fix invalid data in test scene --- .../Visual/Ranking/TestSceneOverallRanking.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs index 2edc577a95..355a572f95 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneOverallRanking.cs @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Ranking new UserStatistics { GlobalRank = 12_345, - Accuracy = 0.9899, + Accuracy = 98.99, MaxCombo = 2_322, RankedScore = 23_123_543_456, TotalScore = 123_123_543_456, @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Ranking new UserStatistics { GlobalRank = 1_234, - Accuracy = 0.9907, + Accuracy = 99.07, MaxCombo = 2_352, RankedScore = 23_124_231_435, TotalScore = 123_124_231_435, @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Ranking new UserStatistics { GlobalRank = 1_234, - Accuracy = 0.9907, + Accuracy = 99.07, MaxCombo = 2_352, RankedScore = 23_124_231_435, TotalScore = 123_124_231_435, @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Ranking new UserStatistics { GlobalRank = 12_345, - Accuracy = 0.9899, + Accuracy = 98.99, MaxCombo = 2_322, RankedScore = 23_123_543_456, TotalScore = 123_123_543_456, @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.Ranking var statistics = new UserStatistics { GlobalRank = 12_345, - Accuracy = 0.9899, + Accuracy = 98.99, MaxCombo = 2_322, RankedScore = 23_123_543_456, TotalScore = 123_123_543_456, @@ -93,7 +93,7 @@ namespace osu.Game.Tests.Visual.Ranking var statistics = new UserStatistics { GlobalRank = null, - Accuracy = 0.9899, + Accuracy = 98.99, MaxCombo = 2_322, RankedScore = 23_123_543_456, TotalScore = 123_123_543_456, From e90619244da7ff3151c329193c4e80a4f602daa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 27 Dec 2022 19:51:51 +0100 Subject: [PATCH 290/384] Fix incorrect accuracy display on overall ranking view --- osu.Game/Screens/Ranking/Statistics/User/AccuracyChangeRow.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/User/AccuracyChangeRow.cs b/osu.Game/Screens/Ranking/Statistics/User/AccuracyChangeRow.cs index 0f5dd9074a..0fd666e9d0 100644 --- a/osu.Game/Screens/Ranking/Statistics/User/AccuracyChangeRow.cs +++ b/osu.Game/Screens/Ranking/Statistics/User/AccuracyChangeRow.cs @@ -16,11 +16,11 @@ namespace osu.Game.Screens.Ranking.Statistics.User protected override LocalisableString Label => UsersStrings.ShowStatsHitAccuracy; - protected override LocalisableString FormatCurrentValue(double current) => current.FormatAccuracy(); + protected override LocalisableString FormatCurrentValue(double current) => (current / 100).FormatAccuracy(); protected override int CalculateDifference(double previous, double current, out LocalisableString formattedDifference) { - double difference = current - previous; + double difference = (current - previous) / 100; if (difference < 0) formattedDifference = difference.FormatAccuracy(); From 0d78bc224826fc70deaf961ba0c4b5a997cb8e71 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Dec 2022 06:42:32 +0800 Subject: [PATCH 291/384] Fix `osu.ppy.sh` links no longer opening in-game Addresses https://github.com/ppy/osu/discussions/21838. --- osu.Game/Online/ProductionEndpointConfiguration.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/ProductionEndpointConfiguration.cs b/osu.Game/Online/ProductionEndpointConfiguration.cs index 3a74d24b58..003ec50afd 100644 --- a/osu.Game/Online/ProductionEndpointConfiguration.cs +++ b/osu.Game/Online/ProductionEndpointConfiguration.cs @@ -9,7 +9,8 @@ namespace osu.Game.Online { public ProductionEndpointConfiguration() { - WebsiteRootUrl = APIEndpointUrl = @"https://lazer.ppy.sh"; + WebsiteRootUrl = @"https://osu.ppy.sh"; + APIEndpointUrl = @"https://lazer.ppy.sh"; APIClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"; APIClientID = "5"; SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator"; From e9d32fca18151036b7d2ba95fc7bae8f771671d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 07:28:18 +0100 Subject: [PATCH 292/384] Fix various failures in initial statistics fetch - If the local user is restricted, then attempting to fetch their data from the `/users` endpoint would result in an empty response. - Even if the user was successfully fetched, their `RulesetsStatistics` may not be populated (and instead be `null`). Curiously this was not picked up by static analysis until the first issue was fixed. Closes #21839. --- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 33344044b9..b2f371fd74 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -75,15 +75,27 @@ namespace osu.Game.Online.Solo return; var userRequest = new GetUsersRequest(new[] { localUser.OnlineID }); - userRequest.Success += response => Schedule(() => - { - latestStatistics = new Dictionary(); - foreach (var rulesetStats in response.Users.Single().RulesetsStatistics) - latestStatistics.Add(rulesetStats.Key, rulesetStats.Value); - }); + userRequest.Success += initialiseUserStatistics; api.Queue(userRequest); }); + private void initialiseUserStatistics(GetUsersResponse response) => Schedule(() => + { + var user = response.Users.SingleOrDefault(); + + // possible if the user is restricted or similar. + if (user == null) + return; + + latestStatistics = new Dictionary(); + + if (user.RulesetsStatistics != null) + { + foreach (var rulesetStats in user.RulesetsStatistics) + latestStatistics.Add(rulesetStats.Key, rulesetStats.Value); + } + }); + private void userScoreProcessed(int userId, long scoreId) { if (userId != api.LocalUser.Value?.OnlineID) From a0a26b1e8c257b40dc66ad62be754afcd8a7edda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 07:37:52 +0100 Subject: [PATCH 293/384] Ignore statistics update subscriptions with invalid score ID If score submission fails, the score will not receive a correct online ID from web, but will still be passed on to the solo statistics watcher on the results screen. This could lead to the watcher subscribing to changes with score ID equal to the default of -1. If this happened more than once, that would cause a crash due to duplicate keys in the `callbacks` dictionary. Closes #21837. --- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index b2f371fd74..235abde068 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -51,7 +51,7 @@ namespace osu.Game.Online.Solo if (!api.IsLoggedIn) return; - if (!score.Ruleset.IsLegacyRuleset()) + if (!score.Ruleset.IsLegacyRuleset() || score.OnlineID <= 0) return; var callback = new StatisticsUpdateCallback(score, onUpdateReady); From 04f9a354c3fb6e695e4cad4bfddd31b5ac8c03e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 07:49:09 +0100 Subject: [PATCH 294/384] Convert `SoloResultsScreen` to NRT --- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 110e813e04..94d333c2da 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) 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; @@ -26,15 +24,15 @@ namespace osu.Game.Screens.Ranking /// public bool ShowUserStatistics { get; init; } - private GetScoresRequest getScoreRequest; + private GetScoresRequest? getScoreRequest; [Resolved] - private RulesetStore rulesets { get; set; } + private RulesetStore rulesets { get; set; } = null!; [Resolved] - private SoloStatisticsWatcher soloStatisticsWatcher { get; set; } + private SoloStatisticsWatcher soloStatisticsWatcher { get; set; } = null!; - private readonly Bindable statisticsUpdate = new Bindable(); + private readonly Bindable statisticsUpdate = new Bindable(); public SoloResultsScreen(ScoreInfo score, bool allowRetry) : base(score, allowRetry) @@ -62,7 +60,7 @@ namespace osu.Game.Screens.Ranking return base.CreateStatisticsPanel(); } - protected override APIRequest FetchScores(Action> scoresCallback) + protected override APIRequest? FetchScores(Action>? scoresCallback) { if (Score.BeatmapInfo.OnlineID <= 0 || Score.BeatmapInfo.Status <= BeatmapOnlineStatus.Pending) return null; From 3c0b8af8f1dbf19dbd9496c85d3555354fff7e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 07:50:10 +0100 Subject: [PATCH 295/384] Allow unsubscribing from solo statistics updates This is more of a safety item. To avoid potential duplicate key in dictionary errors (and also avoid being slightly memory-leaky), allow `SoloStatisticsWatcher` consumers to dispose of the subscriptions they take out. --- .../Online/TestSceneSoloStatisticsWatcher.cs | 24 ++++++++++++- osu.Game/Online/Solo/SoloStatisticsWatcher.cs | 36 +++++++++++-------- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 4 ++- 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs index b1badc6282..e62e53bd02 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneSoloStatisticsWatcher.cs @@ -35,6 +35,8 @@ namespace osu.Game.Tests.Visual.Online private Action? handleGetUsersRequest; private Action? handleGetUserRequest; + private IDisposable? subscription; + private readonly Dictionary<(int userId, string rulesetName), UserStatistics> serverSideStatistics = new Dictionary<(int userId, string rulesetName), UserStatistics>(); [SetUpSteps] @@ -246,6 +248,26 @@ namespace osu.Game.Tests.Visual.Online AddAssert("values after are correct", () => update!.After.TotalScore, () => Is.EqualTo(6_000_000)); } + [Test] + public void TestStatisticsUpdateNotFiredAfterSubscriptionDisposal() + { + int userId = getUserId(); + setUpUser(userId); + + long scoreId = getScoreId(); + var ruleset = new OsuRuleset().RulesetInfo; + + SoloStatisticsUpdate? update = null; + registerForUpdates(scoreId, ruleset, receivedUpdate => update = receivedUpdate); + AddStep("unsubscribe", () => subscription!.Dispose()); + + feignScoreProcessing(userId, ruleset, 5_000_000); + + AddStep("signal score processed", () => ((ISpectatorClient)spectatorClient).UserScoreProcessed(userId, scoreId)); + AddWaitStep("wait a bit", 5); + AddAssert("update not received", () => update == null); + } + private int nextUserId = 2000; private long nextScoreId = 50000; @@ -266,7 +288,7 @@ namespace osu.Game.Tests.Visual.Online } private void registerForUpdates(long scoreId, RulesetInfo rulesetInfo, Action onUpdateReady) => - AddStep("register for updates", () => watcher.RegisterForStatisticsUpdateAfter( + AddStep("register for updates", () => subscription = watcher.RegisterForStatisticsUpdateAfter( new ScoreInfo(Beatmap.Value.BeatmapInfo, new OsuRuleset().RulesetInfo, new RealmUser()) { Ruleset = rulesetInfo, diff --git a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs index 235abde068..46449fea73 100644 --- a/osu.Game/Online/Solo/SoloStatisticsWatcher.cs +++ b/osu.Game/Online/Solo/SoloStatisticsWatcher.cs @@ -46,24 +46,30 @@ namespace osu.Game.Online.Solo /// /// The score to listen for the statistics update for. /// The callback to be invoked once the statistics update has been prepared. - public void RegisterForStatisticsUpdateAfter(ScoreInfo score, Action onUpdateReady) => Schedule(() => + /// An representing the subscription. Disposing it is equivalent to unsubscribing from future notifications. + public IDisposable RegisterForStatisticsUpdateAfter(ScoreInfo score, Action onUpdateReady) { - if (!api.IsLoggedIn) - return; - - if (!score.Ruleset.IsLegacyRuleset() || score.OnlineID <= 0) - return; - - var callback = new StatisticsUpdateCallback(score, onUpdateReady); - - if (lastProcessedScoreId == score.OnlineID) + Schedule(() => { - requestStatisticsUpdate(api.LocalUser.Value.Id, callback); - return; - } + if (!api.IsLoggedIn) + return; - callbacks.Add(score.OnlineID, callback); - }); + if (!score.Ruleset.IsLegacyRuleset() || score.OnlineID <= 0) + return; + + var callback = new StatisticsUpdateCallback(score, onUpdateReady); + + if (lastProcessedScoreId == score.OnlineID) + { + requestStatisticsUpdate(api.LocalUser.Value.Id, callback); + return; + } + + callbacks.Add(score.OnlineID, callback); + }); + + return new InvokeOnDisposal(() => Schedule(() => callbacks.Remove(score.OnlineID))); + } private void onUserChanged(APIUser? localUser) => Schedule(() => { diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 94d333c2da..c8920a734d 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -32,6 +32,7 @@ namespace osu.Game.Screens.Ranking [Resolved] private SoloStatisticsWatcher soloStatisticsWatcher { get; set; } = null!; + private IDisposable? statisticsSubscription; private readonly Bindable statisticsUpdate = new Bindable(); public SoloResultsScreen(ScoreInfo score, bool allowRetry) @@ -44,7 +45,7 @@ namespace osu.Game.Screens.Ranking base.LoadComplete(); if (ShowUserStatistics) - soloStatisticsWatcher.RegisterForStatisticsUpdateAfter(Score, update => statisticsUpdate.Value = update); + statisticsSubscription = soloStatisticsWatcher.RegisterForStatisticsUpdateAfter(Score, update => statisticsUpdate.Value = update); } protected override StatisticsPanel CreateStatisticsPanel() @@ -75,6 +76,7 @@ namespace osu.Game.Screens.Ranking base.Dispose(isDisposing); getScoreRequest?.Cancel(); + statisticsSubscription?.Dispose(); } } } From 76367444cbebff0d893ae941df3e0179d9d9c3d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 10:36:54 +0100 Subject: [PATCH 296/384] Adjust Android package versioning to .NET 6 With .NET 6, the way Xamarin package versioning works has changed. - The `ApplicationVersion` MSBuild property aims to replace `android:versionCode` in the manifest. - The `ApplicationDisplayVersion` MSBuild property aims to replace `android:versionName` in the manifest. More about this can be read in Xamarin docs: https://github.com/xamarin/xamarin-android/blob/ec712da8c1ce03f71578e08cafb6a767cdb90cd5/Documentation/guides/OneDotNetSingleProject.md To this end: - Manual `version{Code,Name}` specs are removed from `AndroidManifest.xml`, as they were preventing MSBuild properties from functioning properly. - `Version` now defaults to 0.0.0, so that local builds don't appear like they were deployed (see `OsuGameBase.IsDeployedBuild`). - `ApplicationDisplayVersion` now defaults to `Version`. This addresses the Android portion of #21498. - `ApplicationVersion` can now be specified by command line, but still needs to be supplied manually for version detection to work correctly. See `OsuGameAndroid.AssemblyVersion` for more info. Putting the pieces together, the complete publish command to deploy a new build should look something like so: dotnet publish -f net6.0-android \ -r android-arm64 \ -c Release \ -p:Version=2022.1228.0 \ -p:ApplicationVersion=202212280 --- osu.Android/AndroidManifest.xml | 2 +- osu.Android/osu.Android.csproj | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Android/AndroidManifest.xml b/osu.Android/AndroidManifest.xml index be326be5eb..bc2f49b1a9 100644 --- a/osu.Android/AndroidManifest.xml +++ b/osu.Android/AndroidManifest.xml @@ -1,5 +1,5 @@  - + \ No newline at end of file diff --git a/osu.Android/osu.Android.csproj b/osu.Android/osu.Android.csproj index de53e5dd59..1507bfaa29 100644 --- a/osu.Android/osu.Android.csproj +++ b/osu.Android/osu.Android.csproj @@ -8,6 +8,9 @@ true false + 0.0.0 + 1 + $(Version) From b4c5e18da0347872e068086d6954d3321721ad1a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 28 Dec 2022 13:23:46 +0300 Subject: [PATCH 297/384] Add keywords to ease search of "first object visibility" setting --- osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs index 69e24bc616..f6b3c12487 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/ModsSettings.cs @@ -27,6 +27,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = GameplaySettingsStrings.IncreaseFirstObjectVisibility, Current = config.GetBindable(OsuSetting.IncreaseFirstObjectVisibility), + Keywords = new[] { @"approach", @"circle", @"hidden" }, }, }; } From 9021cf7be6b4b6f271f7159c215e13878f95c3ec Mon Sep 17 00:00:00 2001 From: sw1tchbl4d3 Date: Wed, 28 Dec 2022 11:42:34 +0100 Subject: [PATCH 298/384] Allow aspect ratios smaller than the default in taiko --- osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs index 79c5c36e08..d42aaddf9e 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.UI float height = default_relative_height; - if (LockPlayfieldAspect.Value) + if (LockPlayfieldAspect.Value && Parent.ChildSize.X / Parent.ChildSize.Y > default_aspect) height *= Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect; Height = height; From b2aa2e16029d984109049fce98c58466be867709 Mon Sep 17 00:00:00 2001 From: BlauFx Date: Wed, 28 Dec 2022 13:23:07 +0100 Subject: [PATCH 299/384] Add hardlink support for Linux --- osu.Game/Database/RealmFileStore.cs | 16 +++++++++++---- osu.Game/IO/HardLinkHelper.cs | 20 ++++++++++++++----- .../FirstRunSetup/ScreenImportFromStable.cs | 2 +- 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index 04b503b808..49102d81e5 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -63,11 +63,19 @@ namespace osu.Game.Database private void copyToStore(RealmFile file, Stream data, bool preferHardLinks) { - if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows && data is FileStream fs && preferHardLinks) + // attempt to do a fast hard link rather than copy. + if (data is FileStream fs && preferHardLinks) { - // attempt to do a fast hard link rather than copy. - if (HardLinkHelper.CreateHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name, IntPtr.Zero)) - return; + if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) + { + if (HardLinkHelper.CreateHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name, IntPtr.Zero)) + return; + } + else if (RuntimeInfo.OS == RuntimeInfo.Platform.Linux) + { + if (HardLinkHelper.link(fs.Name, Storage.GetFullPath(file.GetStoragePath(), true)) == 0) + return; + } } data.Seek(0, SeekOrigin.Begin); diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index 1393bf26fd..8d521bab9f 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -14,9 +14,8 @@ namespace osu.Game.IO { public static bool CheckAvailability(string testDestinationPath, string testSourcePath) { - // We can support other operating systems quite easily in the future. - // Let's handle the most common one for now, though. - if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) + // TODO: Add macOS support for hardlinks. + if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows || RuntimeInfo.OS != RuntimeInfo.Platform.Linux) return false; const string test_filename = "_hard_link_test"; @@ -26,12 +25,20 @@ namespace osu.Game.IO cleanupFiles(); + try { File.WriteAllText(testSourcePath, string.Empty); - // Test availability by creating an arbitrary hard link between the source and destination paths. - return CreateHardLink(testDestinationPath, testSourcePath, IntPtr.Zero); + + bool isHardLinkAvailable = false; + + if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) + isHardLinkAvailable = CreateHardLink(testDestinationPath, testSourcePath, IntPtr.Zero); + else if (RuntimeInfo.OS == RuntimeInfo.Platform.Linux) + isHardLinkAvailable = link(testSourcePath, testDestinationPath) == 0; + + return isHardLinkAvailable; } catch { @@ -70,6 +77,9 @@ namespace osu.Game.IO return result; } + [DllImport("libc", SetLastError = true)] + public static extern int link(string oldpath, string newpath); + [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 0f8a78453d..2603a6c6c4 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -132,7 +132,7 @@ namespace osu.Game.Overlays.FirstRunSetup copyInformation.AddLink("Learn more about how \"hard links\" work", LinkAction.OpenWiki, @"Client/Release_stream/Lazer/File_storage#via-hard-links"); } - else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows) + else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows || RuntimeInfo.OS != RuntimeInfo.Platform.Linux) copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; else { From 16165b1f67fc51ea01b906b020dc195788f99a2b Mon Sep 17 00:00:00 2001 From: BlauFx Date: Wed, 28 Dec 2022 13:58:52 +0100 Subject: [PATCH 300/384] Remove blank line --- osu.Game/IO/HardLinkHelper.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index 8d521bab9f..b3eb528bbb 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -25,7 +25,6 @@ namespace osu.Game.IO cleanupFiles(); - try { File.WriteAllText(testSourcePath, string.Empty); From 5c5e84f931a45b5c35261d2e38aabc857730bc47 Mon Sep 17 00:00:00 2001 From: BlauFx Date: Wed, 28 Dec 2022 14:06:50 +0100 Subject: [PATCH 301/384] Fix formatiing --- osu.Game/IO/HardLinkHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index b3eb528bbb..d059e8b710 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -76,7 +76,7 @@ namespace osu.Game.IO return result; } - [DllImport("libc", SetLastError = true)] + [DllImport("libc", SetLastError = true)] public static extern int link(string oldpath, string newpath); [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] From f32564652b3d07224c10cce449ba7dd28b7fecf4 Mon Sep 17 00:00:00 2001 From: BlauFx Date: Wed, 28 Dec 2022 14:33:38 +0100 Subject: [PATCH 302/384] Mention the filesystem should be NTFS on Windows --- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 2603a6c6c4..6e9b724774 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -136,8 +136,10 @@ namespace osu.Game.Overlays.FirstRunSetup copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; else { + string mentionNTFS = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? " (and the file system is NTFS)." : "."; + copyInformation.Text = - "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS). "; + $"A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install{mentionNTFS}"; copyInformation.AddLink(GeneralSettingsStrings.ChangeFolderLocation, () => { game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())); From 53bca947d13fe267260e3767c67cd6e30efd2154 Mon Sep 17 00:00:00 2001 From: BlauFx Date: Wed, 28 Dec 2022 14:34:27 +0100 Subject: [PATCH 303/384] Move duplicated code into its own method --- osu.Game/Database/RealmFileStore.cs | 15 ++------------- osu.Game/IO/HardLinkHelper.cs | 23 ++++++++++++++--------- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index 49102d81e5..e5ace5b346 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -64,19 +64,8 @@ namespace osu.Game.Database private void copyToStore(RealmFile file, Stream data, bool preferHardLinks) { // attempt to do a fast hard link rather than copy. - if (data is FileStream fs && preferHardLinks) - { - if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) - { - if (HardLinkHelper.CreateHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name, IntPtr.Zero)) - return; - } - else if (RuntimeInfo.OS == RuntimeInfo.Platform.Linux) - { - if (HardLinkHelper.link(fs.Name, Storage.GetFullPath(file.GetStoragePath(), true)) == 0) - return; - } - } + if (data is FileStream fs && preferHardLinks && HardLinkHelper.AttemptHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name)) + return; data.Seek(0, SeekOrigin.Begin); diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index d059e8b710..c8fc6d9f49 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -28,16 +28,9 @@ namespace osu.Game.IO try { File.WriteAllText(testSourcePath, string.Empty); + // Test availability by creating an arbitrary hard link between the source and destination paths. - - bool isHardLinkAvailable = false; - - if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) - isHardLinkAvailable = CreateHardLink(testDestinationPath, testSourcePath, IntPtr.Zero); - else if (RuntimeInfo.OS == RuntimeInfo.Platform.Linux) - isHardLinkAvailable = link(testSourcePath, testDestinationPath) == 0; - - return isHardLinkAvailable; + return AttemptHardLink(testDestinationPath, testSourcePath); } catch { @@ -61,6 +54,18 @@ namespace osu.Game.IO } } + public static bool AttemptHardLink(string testDestinationPath, string testSourcePath) + { + bool isHardLinkAvailable = false; + + if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) + isHardLinkAvailable = CreateHardLink(testDestinationPath, testSourcePath, IntPtr.Zero); + else if (RuntimeInfo.OS == RuntimeInfo.Platform.Linux) + isHardLinkAvailable = link(testSourcePath, testDestinationPath) == 0; + + return isHardLinkAvailable; + } + // For future use (to detect if a file is a hard link with other references existing on disk). public static int GetFileLinkCount(string filePath) { From c6da7248bacc02b4879c329d68238d8865b46cd6 Mon Sep 17 00:00:00 2001 From: BlauFx Date: Wed, 28 Dec 2022 14:40:32 +0100 Subject: [PATCH 304/384] Remove unnecessary directive --- osu.Game/Database/RealmFileStore.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index e5ace5b346..df276adbaf 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -4,7 +4,6 @@ using System; using System.IO; using System.Linq; -using osu.Framework; using osu.Framework.Extensions; using osu.Framework.IO.Stores; using osu.Framework.Logging; From d63be3ff17b8eaee6e39d757db32c8f124e4e3a6 Mon Sep 17 00:00:00 2001 From: BlauFx Date: Wed, 28 Dec 2022 15:02:44 +0100 Subject: [PATCH 305/384] Change name of variable --- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 6e9b724774..74c1bf955c 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -136,10 +136,10 @@ namespace osu.Game.Overlays.FirstRunSetup copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; else { - string mentionNTFS = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? " (and the file system is NTFS)." : "."; + string mentionNtfs = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? " (and the file system is NTFS)." : "."; copyInformation.Text = - $"A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install{mentionNTFS}"; + $"A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install{mentionNtfs}"; copyInformation.AddLink(GeneralSettingsStrings.ChangeFolderLocation, () => { game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())); From 2c346eae0d827abb91a05a3c0300a3c9e02076d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 21:19:28 +0100 Subject: [PATCH 306/384] Revert inlining of hard link creation into condition Just feels bad. Mixing data access with actual underlying logic. --- osu.Game/Database/RealmFileStore.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index df276adbaf..4f429cb20c 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -62,9 +62,12 @@ namespace osu.Game.Database private void copyToStore(RealmFile file, Stream data, bool preferHardLinks) { - // attempt to do a fast hard link rather than copy. - if (data is FileStream fs && preferHardLinks && HardLinkHelper.AttemptHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name)) - return; + if (data is FileStream fs && preferHardLinks) + { + // attempt to do a fast hard link rather than copy. + if (HardLinkHelper.AttemptHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name)) + return; + } data.Seek(0, SeekOrigin.Begin); From cadd487c75c0876ec15388f55577cbac97d2df2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 21:20:49 +0100 Subject: [PATCH 307/384] Use switch statement in `AttemptHardLink()` --- osu.Game/IO/HardLinkHelper.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index c8fc6d9f49..d6ca11a68f 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -56,14 +56,17 @@ namespace osu.Game.IO public static bool AttemptHardLink(string testDestinationPath, string testSourcePath) { - bool isHardLinkAvailable = false; + switch (RuntimeInfo.OS) + { + case RuntimeInfo.Platform.Windows: + return CreateHardLink(testDestinationPath, testSourcePath, IntPtr.Zero); - if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows) - isHardLinkAvailable = CreateHardLink(testDestinationPath, testSourcePath, IntPtr.Zero); - else if (RuntimeInfo.OS == RuntimeInfo.Platform.Linux) - isHardLinkAvailable = link(testSourcePath, testDestinationPath) == 0; + case RuntimeInfo.Platform.Linux: + return link(testSourcePath, testDestinationPath) == 0; - return isHardLinkAvailable; + default: + return false; + } } // For future use (to detect if a file is a hard link with other references existing on disk). From 04d4b4a6ce2a7cb5aff2bea79e8dd6378f34a833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 21:23:06 +0100 Subject: [PATCH 308/384] Rename and xmldoc hard link creation method --- osu.Game/Database/RealmFileStore.cs | 2 +- osu.Game/IO/HardLinkHelper.cs | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/RealmFileStore.cs b/osu.Game/Database/RealmFileStore.cs index 4f429cb20c..f75d3be725 100644 --- a/osu.Game/Database/RealmFileStore.cs +++ b/osu.Game/Database/RealmFileStore.cs @@ -65,7 +65,7 @@ namespace osu.Game.Database if (data is FileStream fs && preferHardLinks) { // attempt to do a fast hard link rather than copy. - if (HardLinkHelper.AttemptHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name)) + if (HardLinkHelper.TryCreateHardLink(Storage.GetFullPath(file.GetStoragePath(), true), fs.Name)) return; } diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index d6ca11a68f..9c0a8fbba1 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -30,7 +30,7 @@ namespace osu.Game.IO File.WriteAllText(testSourcePath, string.Empty); // Test availability by creating an arbitrary hard link between the source and destination paths. - return AttemptHardLink(testDestinationPath, testSourcePath); + return TryCreateHardLink(testDestinationPath, testSourcePath); } catch { @@ -54,15 +54,23 @@ namespace osu.Game.IO } } - public static bool AttemptHardLink(string testDestinationPath, string testSourcePath) + /// + /// Attempts to create a hard link from to , + /// using platform-specific native methods. + /// + /// + /// Hard links are only available on Windows and Linux. + /// + /// Whether the hard link was successfully created. + public static bool TryCreateHardLink(string destinationPath, string sourcePath) { switch (RuntimeInfo.OS) { case RuntimeInfo.Platform.Windows: - return CreateHardLink(testDestinationPath, testSourcePath, IntPtr.Zero); + return CreateHardLink(destinationPath, sourcePath, IntPtr.Zero); case RuntimeInfo.Platform.Linux: - return link(testSourcePath, testDestinationPath) == 0; + return link(sourcePath, destinationPath) == 0; default: return false; From d4b3965967ea155e94975409de1a5883c3981523 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 21:25:24 +0100 Subject: [PATCH 309/384] Change warning message about file duplication - It was being glued in an ugly way that would have prevented sanely localising it. - Even on Linux, the filesystem (whichever one the user has chosen out of the multitude available) still needs to support hard links for them to have a chance of working. --- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 74c1bf955c..8b1b50ed98 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -136,10 +136,9 @@ namespace osu.Game.Overlays.FirstRunSetup copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; else { - string mentionNtfs = RuntimeInfo.OS == RuntimeInfo.Platform.Windows ? " (and the file system is NTFS)." : "."; - - copyInformation.Text = - $"A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install{mentionNtfs}"; + copyInformation.Text = RuntimeInfo.OS == RuntimeInfo.Platform.Windows + ? "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS)." + : "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system supports hard links)."; copyInformation.AddLink(GeneralSettingsStrings.ChangeFolderLocation, () => { game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())); From 8d79fa93ac06a5913628a0eb2ba847a869fd7e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 21:58:30 +0100 Subject: [PATCH 310/384] Implement `GetFileLinkCount()` for Linux --- osu.Game/IO/HardLinkHelper.cs | 65 +++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index 9c0a8fbba1..03cb043cca 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -81,19 +81,30 @@ namespace osu.Game.IO public static int GetFileLinkCount(string filePath) { int result = 0; - SafeFileHandle handle = CreateFile(filePath, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Archive, IntPtr.Zero); - ByHandleFileInformation fileInfo; + switch (RuntimeInfo.OS) + { + case RuntimeInfo.Platform.Windows: + SafeFileHandle handle = CreateFile(filePath, FileAccess.Read, FileShare.Read, IntPtr.Zero, FileMode.Open, FileAttributes.Archive, IntPtr.Zero); - if (GetFileInformationByHandle(handle, out fileInfo)) - result = (int)fileInfo.NumberOfLinks; - CloseHandle(handle); + ByHandleFileInformation fileInfo; + + if (GetFileInformationByHandle(handle, out fileInfo)) + result = (int)fileInfo.NumberOfLinks; + CloseHandle(handle); + break; + + case RuntimeInfo.Platform.Linux: + if (stat(filePath, out var statbuf) == 0) + result = (int)statbuf.st_nlink; + + break; + } return result; } - [DllImport("libc", SetLastError = true)] - public static extern int link(string oldpath, string newpath); + #region Windows native methods [DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] public static extern bool CreateHardLink(string lpFileName, string lpExistingFileName, IntPtr lpSecurityAttributes); @@ -129,5 +140,45 @@ namespace osu.Game.IO public readonly uint FileIndexHigh; public readonly uint FileIndexLow; } + + #endregion + + #region Linux native methods + + [DllImport("libc", SetLastError = true)] + public static extern int link(string oldpath, string newpath); + + [DllImport("libc", SetLastError = true)] + private static extern int stat(string pathname, out struct_stat statbuf); + + // ReSharper disable once InconsistentNaming + // Struct layout is likely non-portable across unices. Tread with caution. + [StructLayout(LayoutKind.Sequential)] + private struct struct_stat + { + public readonly long st_dev; + public readonly long st_ino; + public readonly long st_nlink; + public readonly int st_mode; + public readonly int st_uid; + public readonly int st_gid; + public readonly long st_rdev; + public readonly long st_size; + public readonly long st_blksize; + public readonly long st_blocks; + public readonly timespec st_atim; + public readonly timespec st_mtim; + public readonly timespec st_ctim; + } + + // ReSharper disable once InconsistentNaming + [StructLayout(LayoutKind.Sequential)] + private struct timespec + { + public readonly long tv_sec; + public readonly long tv_nsec; + } + + #endregion } } From 49b0ec9ddb43d0f507ba8155d7bec69ed7876bb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 22:18:27 +0100 Subject: [PATCH 311/384] Fix broken condition --- osu.Game/IO/HardLinkHelper.cs | 2 +- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index 03cb043cca..fef084f5f0 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -15,7 +15,7 @@ namespace osu.Game.IO public static bool CheckAvailability(string testDestinationPath, string testSourcePath) { // TODO: Add macOS support for hardlinks. - if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows || RuntimeInfo.OS != RuntimeInfo.Platform.Linux) + if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows && RuntimeInfo.OS != RuntimeInfo.Platform.Linux) return false; const string test_filename = "_hard_link_test"; diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 8b1b50ed98..0e7514a7dc 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -132,7 +132,7 @@ namespace osu.Game.Overlays.FirstRunSetup copyInformation.AddLink("Learn more about how \"hard links\" work", LinkAction.OpenWiki, @"Client/Release_stream/Lazer/File_storage#via-hard-links"); } - else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows || RuntimeInfo.OS != RuntimeInfo.Platform.Linux) + else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows && RuntimeInfo.OS != RuntimeInfo.Platform.Linux) copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; else { From 10c11e974de9c2533059bcd76b7abf248f497ab4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 22:19:49 +0100 Subject: [PATCH 312/384] Fix broken spacing --- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 0e7514a7dc..2e3ab823f7 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -137,8 +137,8 @@ namespace osu.Game.Overlays.FirstRunSetup else { copyInformation.Text = RuntimeInfo.OS == RuntimeInfo.Platform.Windows - ? "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS)." - : "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system supports hard links)."; + ? "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system is NTFS). " + : "A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install (and the file system supports hard links). "; copyInformation.AddLink(GeneralSettingsStrings.ChangeFolderLocation, () => { game?.PerformFromScreen(menu => menu.Push(new MigrationSelectScreen())); From 74bc5d46666403f54f9f6e5336c028e00cf1ff03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 28 Dec 2022 22:38:42 +0100 Subject: [PATCH 313/384] Disable naming rule inspection on struct stat definition --- osu.Game/IO/HardLinkHelper.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index fef084f5f0..ebf2d2bd71 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -145,6 +145,8 @@ namespace osu.Game.IO #region Linux native methods +#pragma warning disable IDE1006 // Naming rule violation + [DllImport("libc", SetLastError = true)] public static extern int link(string oldpath, string newpath); @@ -179,6 +181,8 @@ namespace osu.Game.IO public readonly long tv_nsec; } +#pragma warning restore IDE1006 + #endregion } } From b40d114e76dd447438e105f4b91aa9c3441b1496 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 28 Dec 2022 15:00:57 -0800 Subject: [PATCH 314/384] Remove context menu from news card for now --- osu.Game/Overlays/News/NewsCard.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs index f18ce7ff06..e0be5cc4a9 100644 --- a/osu.Game/Overlays/News/NewsCard.cs +++ b/osu.Game/Overlays/News/NewsCard.cs @@ -12,18 +12,16 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.News { - public partial class NewsCard : OsuHoverContainer, IHasContextMenu + public partial class NewsCard : OsuHoverContainer { protected override IEnumerable EffectTargets => new[] { background }; @@ -163,10 +161,5 @@ namespace osu.Game.Overlays.News DateTimeOffset IHasCustomTooltip.TooltipContent => date; } - - public MenuItem[] ContextMenuItems => new MenuItem[] - { - new OsuMenuItem("View news in browser", MenuItemType.Highlighted, Action), - }; } } From ffd9359f4a6c9ce223d889b87b6b936b9b8de333 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 28 Dec 2022 14:50:40 -0800 Subject: [PATCH 315/384] Add tests for chat text box saving / syncing --- .../Visual/Online/TestSceneChatOverlay.cs | 46 +++++++++++++++++++ .../Online/TestSceneStandAloneChatDisplay.cs | 32 +++++++++++-- 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 8cc4eabcd7..a8369dd6d9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -530,6 +530,52 @@ namespace osu.Game.Tests.Visual.Online }); } + [Test] + public void TestTextBoxSavePerChannel() + { + var testPMChannel = new Channel(testUser); + + AddStep("show overlay", () => chatOverlay.Show()); + joinTestChannel(0); + joinChannel(testPMChannel); + + AddAssert("listing is visible", () => listingIsVisible); + AddStep("search for 'number 2'", () => chatOverlayTextBox.Text = "number 2"); + AddAssert("'number 2' saved to selector", () => channelManager.CurrentChannel.Value.TextBoxMessage.Value == "number 2"); + + AddStep("select normal channel", () => clickDrawable(getChannelListItem(testChannel1))); + AddAssert("text box cleared on normal channel", () => chatOverlayTextBox.Text == string.Empty); + AddAssert("nothing saved on normal channel", () => channelManager.CurrentChannel.Value.TextBoxMessage.Value == string.Empty); + AddStep("type '727'", () => chatOverlayTextBox.Text = "727"); + AddAssert("'727' saved to normal channel", () => channelManager.CurrentChannel.Value.TextBoxMessage.Value == "727"); + + AddStep("select PM channel", () => clickDrawable(getChannelListItem(testPMChannel))); + AddAssert("text box cleared on PM channel", () => chatOverlayTextBox.Text == string.Empty); + AddAssert("nothing saved on PM channel", () => channelManager.CurrentChannel.Value.TextBoxMessage.Value == string.Empty); + AddStep("type 'hello'", () => chatOverlayTextBox.Text = "hello"); + AddAssert("'hello' saved to PM channel", () => channelManager.CurrentChannel.Value.TextBoxMessage.Value == "hello"); + + AddStep("select normal channel", () => clickDrawable(getChannelListItem(testChannel1))); + AddAssert("text box contains '727'", () => chatOverlayTextBox.Text == "727"); + + AddStep("select PM channel", () => clickDrawable(getChannelListItem(testPMChannel))); + AddAssert("text box contains 'hello'", () => chatOverlayTextBox.Text == "hello"); + AddStep("click close button", () => + { + ChannelListItemCloseButton closeButton = getChannelListItem(testPMChannel).ChildrenOfType().Single(); + clickDrawable(closeButton); + }); + + AddAssert("listing is visible", () => listingIsVisible); + AddAssert("text box contains 'channel 2'", () => chatOverlayTextBox.Text == "number 2"); + AddUntilStep("only channel 2 visible", () => + { + IEnumerable listingItems = chatOverlay.ChildrenOfType() + .Where(item => item.IsPresent); + return listingItems.Count() == 1 && listingItems.Single().Channel == testChannel2; + }); + } + private void joinTestChannel(int i) { AddStep($"Join test channel {i}", () => channelManager.JoinChannel(testChannels[i])); diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index ebd5e12acb..d7f79d3e30 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -50,6 +50,8 @@ namespace osu.Game.Tests.Visual.Online private ChannelManager channelManager; private TestStandAloneChatDisplay chatDisplay; + private TestStandAloneChatDisplay chatWithTextBox; + private TestStandAloneChatDisplay chatWithTextBox2; private int messageIdSequence; private Channel testChannel; @@ -78,7 +80,7 @@ namespace osu.Game.Tests.Visual.Online private void reinitialiseDrawableDisplay() { - Children = new[] + Children = new Drawable[] { chatDisplay = new TestStandAloneChatDisplay { @@ -88,13 +90,28 @@ namespace osu.Game.Tests.Visual.Online Size = new Vector2(400, 80), Channel = { Value = testChannel }, }, - new TestStandAloneChatDisplay(true) + new FillFlowContainer { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, Margin = new MarginPadding(20), - Size = new Vector2(400, 150), - Channel = { Value = testChannel }, + Children = new[] + { + chatWithTextBox = new TestStandAloneChatDisplay(true) + { + Margin = new MarginPadding(20), + Size = new Vector2(400, 150), + Channel = { Value = testChannel }, + }, + chatWithTextBox2 = new TestStandAloneChatDisplay(true) + { + Margin = new MarginPadding(20), + Size = new Vector2(400, 150), + Channel = { Value = testChannel }, + }, + } } }; } @@ -351,6 +368,13 @@ namespace osu.Game.Tests.Visual.Online checkScrolledToBottom(); } + [Test] + public void TestTextBoxSync() + { + AddStep("type 'hello' to text box 1", () => chatWithTextBox.ChildrenOfType().Single().Text = "hello"); + AddAssert("text box 2 contains 'hello'", () => chatWithTextBox2.ChildrenOfType().Single().Text == "hello"); + } + private void fillChat(int count = 10) { AddStep("fill chat", () => From c326745f96747453453f49f5d3e51e494f4682fd Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Wed, 28 Dec 2022 15:12:57 -0800 Subject: [PATCH 316/384] Save / sync chat text box messages per channel --- osu.Game/Online/Chat/Channel.cs | 5 +++++ osu.Game/Online/Chat/StandAloneChatDisplay.cs | 8 ++++++++ osu.Game/Overlays/Chat/ChatTextBar.cs | 11 ++++++++--- osu.Game/Overlays/Chat/ChatTextBox.cs | 1 - 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 24b384b1d4..761e8aba8d 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -98,6 +98,11 @@ namespace osu.Game.Online.Chat /// public Bindable HighlightedMessage = new Bindable(); + /// + /// The current text box message while in this . + /// + public Bindable TextBoxMessage = new Bindable(string.Empty); + [JsonConstructor] public Channel() { diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 7fd6f99102..43da9a1d43 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -87,6 +87,14 @@ namespace osu.Game.Online.Chat channelManager ??= manager; } + protected override void LoadComplete() + { + base.LoadComplete(); + + if (channelManager != null) + TextBox?.Current.BindTo(channelManager.CurrentChannel.Value.TextBoxMessage); + } + protected virtual StandAloneDrawableChannel CreateDrawableChannel(Channel channel) => new StandAloneDrawableChannel(channel); diff --git a/osu.Game/Overlays/Chat/ChatTextBar.cs b/osu.Game/Overlays/Chat/ChatTextBar.cs index bcf5c1a409..682c96a695 100644 --- a/osu.Game/Overlays/Chat/ChatTextBar.cs +++ b/osu.Game/Overlays/Chat/ChatTextBar.cs @@ -128,9 +128,8 @@ namespace osu.Game.Overlays.Chat chattingTextContainer.FadeTo(showSearch ? 0 : 1); searchIconContainer.FadeTo(showSearch ? 1 : 0); - // Clear search terms if any exist when switching back to chat mode - if (!showSearch) - OnSearchTermsChanged?.Invoke(string.Empty); + if (showSearch) + OnSearchTermsChanged?.Invoke(chatTextBox.Current.Value); }, true); currentChannel.BindValueChanged(change => @@ -151,6 +150,12 @@ namespace osu.Game.Overlays.Chat chattingText.Text = ChatStrings.TalkingIn(newChannel.Name); break; } + + if (change.OldValue != null) + chatTextBox.Current.UnbindFrom(change.OldValue.TextBoxMessage); + + if (newChannel != null) + chatTextBox.Current.BindTo(newChannel.TextBoxMessage); }, true); } diff --git a/osu.Game/Overlays/Chat/ChatTextBox.cs b/osu.Game/Overlays/Chat/ChatTextBox.cs index 780c85a9c1..7cd005698e 100644 --- a/osu.Game/Overlays/Chat/ChatTextBox.cs +++ b/osu.Game/Overlays/Chat/ChatTextBox.cs @@ -24,7 +24,6 @@ namespace osu.Game.Overlays.Chat bool showSearch = change.NewValue; PlaceholderText = showSearch ? HomeStrings.SearchPlaceholder : ChatStrings.InputPlaceholder; - Text = string.Empty; }, true); } From 70dbb8edac4a2f7ed7513ade3e186cddfb95adfd Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 29 Dec 2022 01:37:37 -0800 Subject: [PATCH 317/384] Fix stand alone chat display textbox not binding to local channel --- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 43da9a1d43..0a5434822b 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -87,14 +87,6 @@ namespace osu.Game.Online.Chat channelManager ??= manager; } - protected override void LoadComplete() - { - base.LoadComplete(); - - if (channelManager != null) - TextBox?.Current.BindTo(channelManager.CurrentChannel.Value.TextBoxMessage); - } - protected virtual StandAloneDrawableChannel CreateDrawableChannel(Channel channel) => new StandAloneDrawableChannel(channel); @@ -119,8 +111,13 @@ namespace osu.Game.Online.Chat { drawableChannel?.Expire(); + if (e.OldValue != null) + TextBox?.Current.UnbindFrom(e.OldValue.TextBoxMessage); + if (e.NewValue == null) return; + TextBox?.Current.BindTo(e.NewValue.TextBoxMessage); + drawableChannel = CreateDrawableChannel(e.NewValue); drawableChannel.CreateChatLineAction = CreateMessage; drawableChannel.Padding = new MarginPadding { Bottom = postingTextBox ? text_box_height : 0 }; From cacc23204d2a94d79e947a495444d95506ce27f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 29 Dec 2022 13:16:13 +0100 Subject: [PATCH 318/384] Add failing test coverage --- osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index 0bc42b06dd..acb68fc9cd 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -201,6 +201,22 @@ namespace osu.Game.Tests.Visual.Menus AddAssert("volume not changed", () => Audio.Volume.Value == 0.5); } + [Test] + public void TestRulesetSelectorOverflow() + { + AddStep("set toolbar width", () => + { + toolbar.RelativeSizeAxes = Axes.None; + toolbar.Width = 0; + }); + AddStep("move mouse over news toggle button", () => + { + var button = toolbar.ChildrenOfType().Single(); + InputManager.MoveMouseTo(button); + }); + AddAssert("no ruleset toggle buttons hovered", () => !toolbar.ChildrenOfType().Any(button => button.IsHovered)); + } + public partial class TestToolbar : Toolbar { public new Bindable OverlayActivationMode => base.OverlayActivationMode as Bindable; From c5f7da9a4ecb6f9d1810653d09adacbae0d877a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 29 Dec 2022 13:18:59 +0100 Subject: [PATCH 319/384] Fix hover propagating through toolbar buttons Closes #21920. Weirdly enough this was semeingly fixed once before in ancient times in 3891f467a34562d2d921fe996654802d0b29ab0b, but then unfixed again in 566e09083f06b87591c7745d82adf0d831a6066f. The second change is no longer needed since the toolbar became opaque in #9447. --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index ea5fc5bb38..4193e52584 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -179,7 +179,7 @@ namespace osu.Game.Overlays.Toolbar HoverBackground.FadeIn(200); tooltipContainer.FadeIn(100); - return base.OnHover(e); + return true; } protected override void OnHoverLost(HoverLostEvent e) From 0fcf10e10a0bdfd121c47b81649769a3e70a92f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Dec 2022 22:35:13 +0800 Subject: [PATCH 320/384] Also support hard links on macOS --- osu.Game/IO/HardLinkHelper.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/IO/HardLinkHelper.cs b/osu.Game/IO/HardLinkHelper.cs index ebf2d2bd71..619bfdad6e 100644 --- a/osu.Game/IO/HardLinkHelper.cs +++ b/osu.Game/IO/HardLinkHelper.cs @@ -14,8 +14,8 @@ namespace osu.Game.IO { public static bool CheckAvailability(string testDestinationPath, string testSourcePath) { - // TODO: Add macOS support for hardlinks. - if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows && RuntimeInfo.OS != RuntimeInfo.Platform.Linux) + // For simplicity, only support desktop operating systems for now. + if (!RuntimeInfo.IsDesktop) return false; const string test_filename = "_hard_link_test"; @@ -59,7 +59,7 @@ namespace osu.Game.IO /// using platform-specific native methods. /// /// - /// Hard links are only available on Windows and Linux. + /// Hard links are only available on desktop platforms. /// /// Whether the hard link was successfully created. public static bool TryCreateHardLink(string destinationPath, string sourcePath) @@ -70,6 +70,7 @@ namespace osu.Game.IO return CreateHardLink(destinationPath, sourcePath, IntPtr.Zero); case RuntimeInfo.Platform.Linux: + case RuntimeInfo.Platform.macOS: return link(sourcePath, destinationPath) == 0; default: @@ -95,6 +96,7 @@ namespace osu.Game.IO break; case RuntimeInfo.Platform.Linux: + case RuntimeInfo.Platform.macOS: if (stat(filePath, out var statbuf) == 0) result = (int)statbuf.st_nlink; From ccf713c885c1dd77155a22d95db00158f45ae6d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 29 Dec 2022 23:18:12 +0800 Subject: [PATCH 321/384] Fix incorrect hard link validity check in stable import screen --- osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs index 2e3ab823f7..0a2274575f 100644 --- a/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs +++ b/osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs @@ -132,7 +132,7 @@ namespace osu.Game.Overlays.FirstRunSetup copyInformation.AddLink("Learn more about how \"hard links\" work", LinkAction.OpenWiki, @"Client/Release_stream/Lazer/File_storage#via-hard-links"); } - else if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows && RuntimeInfo.OS != RuntimeInfo.Platform.Linux) + else if (!RuntimeInfo.IsDesktop) copyInformation.Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import."; else { From f7febdac5e20ba71eb1dee4c64dd1916a558135a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 29 Dec 2022 22:48:33 +0100 Subject: [PATCH 322/384] Add failing assertion --- osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs index acb68fc9cd..aef6f9ade0 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneToolbar.cs @@ -207,7 +207,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("set toolbar width", () => { toolbar.RelativeSizeAxes = Axes.None; - toolbar.Width = 0; + toolbar.Width = 400; }); AddStep("move mouse over news toggle button", () => { @@ -215,6 +215,7 @@ namespace osu.Game.Tests.Visual.Menus InputManager.MoveMouseTo(button); }); AddAssert("no ruleset toggle buttons hovered", () => !toolbar.ChildrenOfType().Any(button => button.IsHovered)); + AddUntilStep("toolbar gradient visible", () => toolbar.ChildrenOfType().Single().Children.All(d => d.Alpha > 0)); } public partial class TestToolbar : Toolbar From bf975eb48a89707d69b62ff1e2bebd434464ad2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 29 Dec 2022 23:02:45 +0100 Subject: [PATCH 323/384] Fix toolbar gradient not showing when mouse is hovered over buttons --- osu.Game/Overlays/Toolbar/Toolbar.cs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index ac0f822f68..a559363bbf 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -13,6 +13,7 @@ using osuTK; using osu.Framework.Graphics.Shapes; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets; using osu.Framework.Input.Bindings; @@ -196,6 +197,9 @@ namespace osu.Game.Overlays.Toolbar public partial class ToolbarBackground : Container { + private InputManager inputManager; + private Bindable showGradient { get; } = new BindableBool(); + private readonly Box gradientBackground; public ToolbarBackground() @@ -220,15 +224,26 @@ namespace osu.Game.Overlays.Toolbar }; } - protected override bool OnHover(HoverEvent e) + protected override void LoadComplete() { - gradientBackground.FadeIn(transition_time, Easing.OutQuint); - return true; + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + showGradient.BindValueChanged(_ => updateState(), true); } - protected override void OnHoverLost(HoverLostEvent e) + protected override void Update() { - gradientBackground.FadeOut(transition_time, Easing.OutQuint); + base.Update(); + showGradient.Value = Contains(inputManager.CurrentState.Mouse.Position); + } + + private void updateState() + { + if (showGradient.Value) + gradientBackground.FadeIn(transition_time, Easing.OutQuint); + else + gradientBackground.FadeOut(transition_time, Easing.OutQuint); } } From f625c5d7446144455f2366a252392467de2a1038 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 29 Dec 2022 23:34:33 +0100 Subject: [PATCH 324/384] Fix gradient showing when toggling toolbar with mouse above window --- osu.Game/Overlays/Toolbar/Toolbar.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index a559363bbf..dbad866f74 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -235,7 +235,11 @@ namespace osu.Game.Overlays.Toolbar protected override void Update() { base.Update(); - showGradient.Value = Contains(inputManager.CurrentState.Mouse.Position); + + var currentMousePosition = inputManager.CurrentState.Mouse.Position; + // ensure that the gradient is not shown if the mouse is above the window. + // this is relevant when the background is moving due to the toolbar being toggled. + showGradient.Value = currentMousePosition.Y >= 0 && Contains(inputManager.CurrentState.Mouse.Position); } private void updateState() From 0d70f2c0fd3d070b49c6ce4225a88bed63932392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 09:07:48 +0100 Subject: [PATCH 325/384] Use alternative workaround --- osu.Game/Overlays/Toolbar/Toolbar.cs | 54 +++++++++++++++++++--------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index dbad866f74..f21ef0ee98 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -13,7 +13,6 @@ using osuTK; using osu.Framework.Graphics.Shapes; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets; using osu.Framework.Input.Bindings; @@ -66,9 +65,12 @@ namespace osu.Game.Overlays.Toolbar [BackgroundDependencyLoader(true)] private void load(OsuGame osuGame) { + ToolbarBackground background; + HoverInterceptor interceptor; + Children = new Drawable[] { - new ToolbarBackground(), + background = new ToolbarBackground(), new GridContainer { RelativeSizeAxes = Axes.Both, @@ -181,9 +183,15 @@ namespace osu.Game.Overlays.Toolbar }, }, } + }, + interceptor = new HoverInterceptor + { + RelativeSizeAxes = Axes.Both } }; + ((IBindable)background.ShowGradient).BindTo(interceptor.ReceivedHover); + if (osuGame != null) OverlayActivationMode.BindTo(osuGame.OverlayActivationMode); } @@ -197,8 +205,7 @@ namespace osu.Game.Overlays.Toolbar public partial class ToolbarBackground : Container { - private InputManager inputManager; - private Bindable showGradient { get; } = new BindableBool(); + public Bindable ShowGradient { get; } = new BindableBool(); private readonly Box gradientBackground; @@ -228,29 +235,42 @@ namespace osu.Game.Overlays.Toolbar { base.LoadComplete(); - inputManager = GetContainingInputManager(); - showGradient.BindValueChanged(_ => updateState(), true); - } - - protected override void Update() - { - base.Update(); - - var currentMousePosition = inputManager.CurrentState.Mouse.Position; - // ensure that the gradient is not shown if the mouse is above the window. - // this is relevant when the background is moving due to the toolbar being toggled. - showGradient.Value = currentMousePosition.Y >= 0 && Contains(inputManager.CurrentState.Mouse.Position); + ShowGradient.BindValueChanged(_ => updateState(), true); } private void updateState() { - if (showGradient.Value) + if (ShowGradient.Value) gradientBackground.FadeIn(transition_time, Easing.OutQuint); else gradientBackground.FadeOut(transition_time, Easing.OutQuint); } } + /// + /// Whenever the mouse cursor is within the bounds of the toolbar, we want the background gradient to show, for toolbar button descriptions to be legible. + /// Unfortunately we also need to ensure that the toolbar buttons handle hover, to prevent the possibility of multiple descriptions being shown + /// due to hover events passing through multiple buttons. + /// This drawable is a workaround, that when placed front-most in the toolbar, allows to see whether hover events have been propagated through it without handling them. + /// + private partial class HoverInterceptor : Drawable + { + public IBindable ReceivedHover => receivedHover; + private readonly Bindable receivedHover = new BindableBool(); + + protected override bool OnHover(HoverEvent e) + { + receivedHover.Value = true; + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + receivedHover.Value = false; + base.OnHoverLost(e); + } + } + protected override void UpdateState(ValueChangedEvent state) { bool blockShow = hiddenByUser || OverlayActivationMode.Value == OverlayActivation.Disabled; From 3c32a50c125bf4e9b28b26298277bb80cc38cb0f Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 30 Dec 2022 21:19:46 +0900 Subject: [PATCH 326/384] add new accuracy counter display --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 14 ++++++++ .../Play/HUD/GameplayAccuracyCounter.cs | 32 ++++++++++++++++++- 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 4228840461..cec2b7e6b9 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -39,6 +39,16 @@ namespace osu.Game.Rulesets.Scoring /// public readonly BindableDouble Accuracy = new BindableDouble(1) { MinValue = 0, MaxValue = 1 }; + /// + /// The accuracy which increase from 0%. + /// + public readonly BindableDouble IncreaseAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; + + /// + /// The accuracy which Decrease from 100%. + /// + public readonly BindableDouble DecreaseAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; + /// /// The current combo. /// @@ -264,6 +274,10 @@ namespace osu.Game.Rulesets.Scoring private void updateScore() { Accuracy.Value = currentMaximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / currentMaximumScoringValues.BaseScore : 1; + IncreaseAccuracy.Value = maximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / maximumScoringValues.BaseScore : 0; + DecreaseAccuracy.Value = maximumScoringValues.BaseScore > 0 + ? (double)(maximumScoringValues.BaseScore - (currentMaximumScoringValues.BaseScore - currentScoringValues.BaseScore)) / maximumScoringValues.BaseScore + : 1; TotalScore.Value = computeScore(Mode.Value, currentScoringValues, maximumScoringValues); } diff --git a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs index 1933193515..f8848f57b9 100644 --- a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs @@ -4,6 +4,8 @@ #nullable disable using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Scoring; @@ -11,10 +13,38 @@ namespace osu.Game.Screens.Play.HUD { public abstract partial class GameplayAccuracyCounter : PercentageCounter { + [SettingSource("Accuracy Display Mode")] + public Bindable AccType { get; } = new Bindable(); + [BackgroundDependencyLoader] private void load(ScoreProcessor scoreProcessor) { - Current.BindTo(scoreProcessor.Accuracy); + AccType.BindValueChanged(mod => + { + Current.UnbindBindings(); + + switch (mod.NewValue) + { + case AccuracyType.Current: + Current.BindTo(scoreProcessor.Accuracy); + break; + + case AccuracyType.Increase: + Current.BindTo(scoreProcessor.IncreaseAccuracy); + break; + + case AccuracyType.Decrease: + Current.BindTo(scoreProcessor.DecreaseAccuracy); + break; + } + }, true); + } + + public enum AccuracyType + { + Current, + Increase, + Decrease } } } From a91da2284d27e5dc84c98b724cbbfae5cc4c2fad Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 30 Dec 2022 22:58:46 +0900 Subject: [PATCH 327/384] safe way to pass bindable --- .../Components/Timelines/Summary/Parts/PreviewTimePart.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs index 6149900fdc..b20f971086 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs @@ -22,10 +22,13 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts private partial class PreviewTimeVisualisation : PointVisualisation { + private BindableInt previewTime = new BindableInt(); + public PreviewTimeVisualisation(BindableInt time) : base(time.Value) { - time.BindValueChanged(s => X = s.NewValue); + previewTime.BindTo(time); + previewTime.BindValueChanged(s => X = s.NewValue); } [BackgroundDependencyLoader] From 23c485c763e91b4e7352138a0a94ec698afbb09c Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 30 Dec 2022 22:59:56 +0900 Subject: [PATCH 328/384] readonly --- .../Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs index b20f971086..bd34662969 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts private partial class PreviewTimeVisualisation : PointVisualisation { - private BindableInt previewTime = new BindableInt(); + private readonly BindableInt previewTime = new BindableInt(); public PreviewTimeVisualisation(BindableInt time) : base(time.Value) From 784fe7ecf2278bf112f4a101d37a290ad028b67f Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 30 Dec 2022 23:06:10 +0900 Subject: [PATCH 329/384] rename `AccType` to `AccuracyDisplay` --- osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs index f8848f57b9..a862e5e230 100644 --- a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs @@ -14,12 +14,12 @@ namespace osu.Game.Screens.Play.HUD public abstract partial class GameplayAccuracyCounter : PercentageCounter { [SettingSource("Accuracy Display Mode")] - public Bindable AccType { get; } = new Bindable(); + public Bindable AccuracyDisplay { get; } = new Bindable(); [BackgroundDependencyLoader] private void load(ScoreProcessor scoreProcessor) { - AccType.BindValueChanged(mod => + AccuracyDisplay.BindValueChanged(mod => { Current.UnbindBindings(); From 8beb168be9775b93947af36ab8d2ca54cf22a536 Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 30 Dec 2022 23:24:20 +0900 Subject: [PATCH 330/384] remove nullable disabled --- osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs index a862e5e230..f7916d833a 100644 --- a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs @@ -1,8 +1,6 @@ // Copyright (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.Configuration; From d60349c7c606ba8a6b27a9b38d3448b609b89c0b Mon Sep 17 00:00:00 2001 From: cdwcgt Date: Fri, 30 Dec 2022 23:24:41 +0900 Subject: [PATCH 331/384] add description --- osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs index f7916d833a..ba96260f51 100644 --- a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.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.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Configuration; @@ -11,7 +12,7 @@ namespace osu.Game.Screens.Play.HUD { public abstract partial class GameplayAccuracyCounter : PercentageCounter { - [SettingSource("Accuracy Display Mode")] + [SettingSource("Accuracy Display Mode", "Which Accuracy will display")] public Bindable AccuracyDisplay { get; } = new Bindable(); [BackgroundDependencyLoader] @@ -23,7 +24,7 @@ namespace osu.Game.Screens.Play.HUD switch (mod.NewValue) { - case AccuracyType.Current: + case AccuracyType.Rolling: Current.BindTo(scoreProcessor.Accuracy); break; @@ -40,8 +41,13 @@ namespace osu.Game.Screens.Play.HUD public enum AccuracyType { - Current, + [Description("Rolling")] + Rolling, + + [Description("Best achievable")] Increase, + + [Description("Worst achievable")] Decrease } } From 6d42cc5a360c4907357802e1720c2b563c0ac3d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 20:30:58 +0100 Subject: [PATCH 332/384] Naming pass --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 8 +++--- .../Play/HUD/GameplayAccuracyCounter.cs | 28 +++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index cec2b7e6b9..c379691c52 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -42,12 +42,12 @@ namespace osu.Game.Rulesets.Scoring /// /// The accuracy which increase from 0%. /// - public readonly BindableDouble IncreaseAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; + public readonly BindableDouble MinimumAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; /// /// The accuracy which Decrease from 100%. /// - public readonly BindableDouble DecreaseAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; + public readonly BindableDouble MaximumAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; /// /// The current combo. @@ -274,8 +274,8 @@ namespace osu.Game.Rulesets.Scoring private void updateScore() { Accuracy.Value = currentMaximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / currentMaximumScoringValues.BaseScore : 1; - IncreaseAccuracy.Value = maximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / maximumScoringValues.BaseScore : 0; - DecreaseAccuracy.Value = maximumScoringValues.BaseScore > 0 + MinimumAccuracy.Value = maximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / maximumScoringValues.BaseScore : 0; + MaximumAccuracy.Value = maximumScoringValues.BaseScore > 0 ? (double)(maximumScoringValues.BaseScore - (currentMaximumScoringValues.BaseScore - currentScoringValues.BaseScore)) / maximumScoringValues.BaseScore : 1; TotalScore.Value = computeScore(Mode.Value, currentScoringValues, maximumScoringValues); diff --git a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs index ba96260f51..32e7827be8 100644 --- a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs @@ -12,8 +12,8 @@ namespace osu.Game.Screens.Play.HUD { public abstract partial class GameplayAccuracyCounter : PercentageCounter { - [SettingSource("Accuracy Display Mode", "Which Accuracy will display")] - public Bindable AccuracyDisplay { get; } = new Bindable(); + [SettingSource("Accuracy display mode", "Which accuracy mode should be displayed.")] + public Bindable AccuracyDisplay { get; } = new Bindable(); [BackgroundDependencyLoader] private void load(ScoreProcessor scoreProcessor) @@ -24,31 +24,31 @@ namespace osu.Game.Screens.Play.HUD switch (mod.NewValue) { - case AccuracyType.Rolling: + case AccuracyDisplayMode.Standard: Current.BindTo(scoreProcessor.Accuracy); break; - case AccuracyType.Increase: - Current.BindTo(scoreProcessor.IncreaseAccuracy); + case AccuracyDisplayMode.MinimumAchievable: + Current.BindTo(scoreProcessor.MinimumAccuracy); break; - case AccuracyType.Decrease: - Current.BindTo(scoreProcessor.DecreaseAccuracy); + case AccuracyDisplayMode.MaximumAchievable: + Current.BindTo(scoreProcessor.MaximumAccuracy); break; } }, true); } - public enum AccuracyType + public enum AccuracyDisplayMode { - [Description("Rolling")] - Rolling, + [Description("Standard")] + Standard, - [Description("Best achievable")] - Increase, + [Description("Maximum achievable")] + MaximumAchievable, - [Description("Worst achievable")] - Decrease + [Description("Minimum achievable")] + MinimumAchievable } } } From bb2822a175a44604a4bd2b08b04ff63c30bad788 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 20:43:23 +0100 Subject: [PATCH 333/384] xmldoc pass --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index c379691c52..ab4422b2ef 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -40,12 +40,14 @@ namespace osu.Game.Rulesets.Scoring public readonly BindableDouble Accuracy = new BindableDouble(1) { MinValue = 0, MaxValue = 1 }; /// - /// The accuracy which increase from 0%. + /// The minimum achievable accuracy for the whole beatmap at this stage of gameplay. + /// Assumes that all objects that have not been judged yet will receive the minimum hit result. /// public readonly BindableDouble MinimumAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; /// - /// The accuracy which Decrease from 100%. + /// The maximum achievable accuracy for the whole beatmap at this stage of gameplay. + /// Assumes that all objects that have not been judged yet will receive the maximum hit result. /// public readonly BindableDouble MaximumAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; From 8ace635249edccfc3eae7ac2f1519a821d518674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 20:43:47 +0100 Subject: [PATCH 334/384] Adjust minimum values --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index ab4422b2ef..2fec68f0b0 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -43,13 +43,13 @@ namespace osu.Game.Rulesets.Scoring /// The minimum achievable accuracy for the whole beatmap at this stage of gameplay. /// Assumes that all objects that have not been judged yet will receive the minimum hit result. /// - public readonly BindableDouble MinimumAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; + public readonly BindableDouble MinimumAccuracy = new BindableDouble { MinValue = 0, MaxValue = 1 }; /// /// The maximum achievable accuracy for the whole beatmap at this stage of gameplay. /// Assumes that all objects that have not been judged yet will receive the maximum hit result. /// - public readonly BindableDouble MaximumAccuracy = new BindableDouble(0) { MinValue = 0, MaxValue = 1 }; + public readonly BindableDouble MaximumAccuracy = new BindableDouble(1) { MinValue = 0, MaxValue = 1 }; /// /// The current combo. From ae026e2d2db216ee35aeeb776f677af84fad512e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 20:52:05 +0100 Subject: [PATCH 335/384] Add test coverage --- .../Gameplay/TestSceneScoreProcessor.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs index 3ce7aa72d9..90c7688443 100644 --- a/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneScoreProcessor.cs @@ -8,6 +8,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Online.Spectator; using osu.Game.Rulesets.Judgements; @@ -139,6 +140,29 @@ namespace osu.Game.Tests.Gameplay Assert.That(score.MaximumStatistics[HitResult.LargeBonus], Is.EqualTo(1)); } + [Test] + public void TestAccuracyModes() + { + var beatmap = new Beatmap + { + HitObjects = Enumerable.Range(0, 4).Select(_ => new TestHitObject(HitResult.Great)).ToList() + }; + + var scoreProcessor = new ScoreProcessor(new OsuRuleset()); + scoreProcessor.ApplyBeatmap(beatmap); + + Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo(1)); + Assert.That(scoreProcessor.MinimumAccuracy.Value, Is.EqualTo(0)); + Assert.That(scoreProcessor.MaximumAccuracy.Value, Is.EqualTo(1)); + + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], beatmap.HitObjects[0].CreateJudgement()) { Type = HitResult.Ok }); + scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[1], beatmap.HitObjects[1].CreateJudgement()) { Type = HitResult.Great }); + + Assert.That(scoreProcessor.Accuracy.Value, Is.EqualTo((double)(100 + 300) / (2 * 300)).Within(Precision.DOUBLE_EPSILON)); + Assert.That(scoreProcessor.MinimumAccuracy.Value, Is.EqualTo((double)(100 + 300) / (4 * 300)).Within(Precision.DOUBLE_EPSILON)); + Assert.That(scoreProcessor.MaximumAccuracy.Value, Is.EqualTo((double)(100 + 3 * 300) / (4 * 300)).Within(Precision.DOUBLE_EPSILON)); + } + private class TestJudgement : Judgement { public override HitResult MaxResult { get; } From 6ed474d4fbb1be6523bbf809273dee21748bf926 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 20:56:54 +0100 Subject: [PATCH 336/384] Rearrange formula for maximum accuracy Feels like it's easier to understand this way. The difference of the maximum scoring values for the entire beatmap and the max values for the part of the beatmap that has already been played represents the act of filling the rest of the unjudged objects with maximum results. --- 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 2fec68f0b0..13276a8748 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -278,7 +278,7 @@ namespace osu.Game.Rulesets.Scoring Accuracy.Value = currentMaximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / currentMaximumScoringValues.BaseScore : 1; MinimumAccuracy.Value = maximumScoringValues.BaseScore > 0 ? (double)currentScoringValues.BaseScore / maximumScoringValues.BaseScore : 0; MaximumAccuracy.Value = maximumScoringValues.BaseScore > 0 - ? (double)(maximumScoringValues.BaseScore - (currentMaximumScoringValues.BaseScore - currentScoringValues.BaseScore)) / maximumScoringValues.BaseScore + ? (double)(currentScoringValues.BaseScore + (maximumScoringValues.BaseScore - currentMaximumScoringValues.BaseScore)) / maximumScoringValues.BaseScore : 1; TotalScore.Value = computeScore(Mode.Value, currentScoringValues, maximumScoringValues); } From 7580ab78be796e9bf80b2b8dd8a3a96dbca2a904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 21:08:48 +0100 Subject: [PATCH 337/384] Move binding to `LoadComplete()` --- osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs index 32e7827be8..210f96ce46 100644 --- a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs @@ -15,9 +15,13 @@ namespace osu.Game.Screens.Play.HUD [SettingSource("Accuracy display mode", "Which accuracy mode should be displayed.")] public Bindable AccuracyDisplay { get; } = new Bindable(); - [BackgroundDependencyLoader] - private void load(ScoreProcessor scoreProcessor) + [Resolved] + private ScoreProcessor scoreProcessor { get; set; } = null!; + + protected override void LoadComplete() { + base.LoadComplete(); + AccuracyDisplay.BindValueChanged(mod => { Current.UnbindBindings(); From 6509d3538c203f41049218899b1dd0471dcfa385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 21:12:29 +0100 Subject: [PATCH 338/384] Fix counter initially rolling down from 100% to 0% in minimum achievable mode --- osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs index 210f96ce46..ca7fef8f73 100644 --- a/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs +++ b/osu.Game/Screens/Play/HUD/GameplayAccuracyCounter.cs @@ -41,6 +41,12 @@ namespace osu.Game.Screens.Play.HUD break; } }, true); + + // if the accuracy counter is using the "minimum achievable" mode, + // then its initial value is 0%, rather than the 100% that the base PercentageCounter assumes. + // to counteract this, manually finish transforms on DisplayedCount once after the initial callback above + // to stop it from rolling down from 100% to 0%. + FinishTransforms(targetMember: nameof(DisplayedCount)); } public enum AccuracyDisplayMode From 87250ad84707e5b7b1a5041af9fc102b9da81f43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 1 Jan 2023 14:32:28 +0800 Subject: [PATCH 339/384] Add search keywords for beatmap colours / hitsound overrides --- osu.Game/Overlays/Settings/Sections/Gameplay/BeatmapSettings.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/BeatmapSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/BeatmapSettings.cs index e8db5fe58a..da5fc519e6 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/BeatmapSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/BeatmapSettings.cs @@ -32,11 +32,13 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay }, new SettingsCheckbox { + Keywords = new[] { "combo", "override" }, LabelText = SkinSettingsStrings.BeatmapColours, Current = config.GetBindable(OsuSetting.BeatmapColours) }, new SettingsCheckbox { + Keywords = new[] { "samples", "override" }, LabelText = SkinSettingsStrings.BeatmapHitsounds, Current = config.GetBindable(OsuSetting.BeatmapHitsounds) }, From 9a4f0cad2c5306b6c3076b7ab5262002386f9992 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 1 Jan 2023 17:48:04 +0800 Subject: [PATCH 340/384] Fix incorrect domain root being used for recent activity entries on profile overlay Closes https://github.com/ppy/osu/issues/21980. --- .../Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs index 4a8b020e33..c3fa467e5f 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs @@ -224,7 +224,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent private void addBeatmapsetLink() => content.AddLink(activity.Beatmapset?.Title, LinkAction.OpenBeatmapSet, getLinkArgument(activity.Beatmapset?.Url), creationParameters: t => t.Font = getLinkFont()); - private string getLinkArgument(string url) => MessageFormatter.GetLinkDetails($"{api.APIEndpointUrl}{url}").Argument.ToString(); + private string getLinkArgument(string url) => MessageFormatter.GetLinkDetails($"{api.WebsiteRootUrl}{url}").Argument.ToString(); private FontUsage getLinkFont(FontWeight fontWeight = FontWeight.Regular) => OsuFont.GetFont(size: font_size, weight: fontWeight, italics: true); From a82f1a6abd19bc8f79f1b3e72a45806d4c2c23ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 1 Jan 2023 18:48:56 +0100 Subject: [PATCH 341/384] Adjust method naming and copy --- osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 4 ++-- osu.Game/Tests/Visual/EditorTestScene.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs b/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs index ad49f3ac0a..d5aa71095b 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Editing { AddStep("seek to 1000", () => EditorClock.Seek(1000)); AddAssert("time is 1000", () => EditorClock.CurrentTime == 1000); - AddStep("set current time as preview point", () => Editor.SetCurrentTimeAsPreview()); + AddStep("set current time as preview point", () => Editor.SetPreviewPointToCurrentTime()); AddAssert("preview time is 1000", () => EditorBeatmap.PreviewTime.Value == 1000); } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 62bb7d3133..be4e2f9628 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -327,7 +327,7 @@ namespace osu.Game.Screens.Edit { Items = new MenuItem[] { - new EditorMenuItem("Set Current Position as Preview Point", MenuItemType.Standard, SetCurrentTimeAsPreview) + new EditorMenuItem("Set preview point to current time", MenuItemType.Standard, SetPreviewPointToCurrentTime) } } } @@ -808,7 +808,7 @@ namespace osu.Game.Screens.Edit protected void Redo() => changeHandler?.RestoreState(1); - protected void SetCurrentTimeAsPreview() + protected void SetPreviewPointToCurrentTime() { editorBeatmap.PreviewTime.Value = (int)clock.CurrentTime; } diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 9c8ac65add..6e2f1e99cd 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual public new void Redo() => base.Redo(); - public new void SetCurrentTimeAsPreview() => base.SetCurrentTimeAsPreview(); + public new void SetPreviewPointToCurrentTime() => base.SetPreviewPointToCurrentTime(); public new bool Save() => base.Save(); From 656bf12b3d43d15d38873b9993c6531fcfb2eb1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 1 Jan 2023 19:37:19 +0100 Subject: [PATCH 342/384] Add all possible timeline elements to test for demonstrative purposes --- .../Visual/Editing/TestSceneEditorSummaryTimeline.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs index ccd2feef9c..f255dd08a8 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSummaryTimeline.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.Timelines.Summary; @@ -21,7 +22,13 @@ namespace osu.Game.Tests.Visual.Editing public TestSceneEditorSummaryTimeline() { - editorBeatmap = new EditorBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); + var beatmap = CreateBeatmap(new OsuRuleset().RulesetInfo); + + beatmap.ControlPointInfo.Add(100000, new TimingControlPoint { BeatLength = 100 }); + beatmap.ControlPointInfo.Add(50000, new DifficultyControlPoint { SliderVelocity = 2 }); + beatmap.BeatmapInfo.Bookmarks = new[] { 75000, 125000 }; + + editorBeatmap = new EditorBeatmap(beatmap); } protected override void LoadComplete() From 452ebddfd28b8740e9c268a8ee41fc48de701d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 1 Jan 2023 19:39:21 +0100 Subject: [PATCH 343/384] Adjust visual appearance of preview time part - Use slightly different hue of green to distinguish from difficulty control points. The colour is still not ideal, but picking a distinctive enough hue is pretty hard. - Place the preview time part at the bottom rather at the top. Not sure why it was at the top; not only could it overlap with the control points, but it also looked quite badly misaligned there when bookmarks were displayed at the bottom. --- .../Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs | 2 +- .../Edit/Components/Timelines/Summary/SummaryTimeline.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs index bd34662969..9d3bbe8bff 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts } [BackgroundDependencyLoader] - private void load(OsuColour colours) => Colour = colours.Lime; + private void load(OsuColour colours) => Colour = colours.Green1; } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index 41377bcb18..075d47d82e 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -44,9 +44,8 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary new PreviewTimePart { Anchor = Anchor.Centre, - Origin = Anchor.BottomCentre, + Origin = Anchor.TopCentre, RelativeSizeAxes = Axes.Both, - Y = -10, Height = 0.35f }, new Container From efdd557f3b7b9a503b189d1aa864d6bd4f15421f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 1 Jan 2023 19:45:23 +0100 Subject: [PATCH 344/384] Adjust binding logic --- .../Timelines/Summary/Parts/PreviewTimePart.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs index 9d3bbe8bff..de7f611424 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs @@ -13,7 +13,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts protected override void LoadBeatmap(EditorBeatmap beatmap) { base.LoadBeatmap(beatmap); - Add(new PreviewTimeVisualisation(beatmap.PreviewTime)); + Add(new PreviewTimeVisualisation(beatmap)); beatmap.PreviewTime.BindValueChanged(s => { Alpha = s.NewValue == -1 ? 0 : 1; @@ -24,11 +24,10 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { private readonly BindableInt previewTime = new BindableInt(); - public PreviewTimeVisualisation(BindableInt time) - : base(time.Value) + public PreviewTimeVisualisation(EditorBeatmap editorBeatmap) { - previewTime.BindTo(time); - previewTime.BindValueChanged(s => X = s.NewValue); + previewTime.BindTo(editorBeatmap.PreviewTime); + previewTime.BindValueChanged(s => X = s.NewValue, true); } [BackgroundDependencyLoader] From b689ad6d80e177734a470fc0d30e6497dc9af09b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 1 Jan 2023 19:54:26 +0100 Subject: [PATCH 345/384] Fix changing preview point not prompting for save --- osu.Game/Screens/Edit/EditorBeatmap.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index dfd7328d11..1684dcf0cd 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -111,7 +111,12 @@ namespace osu.Game.Screens.Edit trackStartTime(obj); PreviewTime = new BindableInt(playableBeatmap.Metadata.PreviewTime); - PreviewTime.BindValueChanged(s => this.beatmapInfo.Metadata.PreviewTime = s.NewValue); + PreviewTime.BindValueChanged(s => + { + BeginChange(); + this.beatmapInfo.Metadata.PreviewTime = s.NewValue; + EndChange(); + }); } /// From 88e90d5fa098f7f19fa2986d44aa9a02336ab0b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 13:17:59 +0100 Subject: [PATCH 346/384] Enable NRT in user profile overlay --- .../Profile/Header/BottomHeaderContainer.cs | 14 ++++---- .../Profile/Header/CentreHeaderContainer.cs | 10 +++--- .../Header/Components/ExpandDetailsButton.cs | 8 ++--- .../Header/Components/FollowersButton.cs | 4 +-- .../Profile/Header/Components/LevelBadge.cs | 13 ++++--- .../Header/Components/LevelProgressBar.cs | 10 +++--- .../Components/MappingSubscribersButton.cs | 4 +-- .../Header/Components/MessageUserButton.cs | 24 ++++++------- .../Components/OverlinedInfoContainer.cs | 2 -- .../Components/OverlinedTotalPlayTime.cs | 8 ++--- .../Header/Components/PreviousUsernames.cs | 10 +++--- .../Header/Components/ProfileHeaderButton.cs | 2 -- .../ProfileHeaderStatisticsButton.cs | 2 -- .../Components/ProfileRulesetSelector.cs | 7 ++-- .../Components/ProfileRulesetTabItem.cs | 2 -- .../Profile/Header/Components/RankGraph.cs | 8 ++--- .../Header/Components/SupporterIcon.cs | 8 ++--- .../Profile/Header/DetailHeaderContainer.cs | 18 +++++----- .../Profile/Header/MedalHeaderContainer.cs | 12 +++---- .../Profile/Header/TopHeaderContainer.cs | 24 ++++++------- osu.Game/Overlays/Profile/ProfileHeader.cs | 8 ++--- osu.Game/Overlays/Profile/ProfileSection.cs | 4 +-- .../Overlays/Profile/Sections/AboutSection.cs | 2 -- .../Sections/BeatmapMetadataContainer.cs | 6 ++-- .../Beatmaps/PaginatedBeatmapContainer.cs | 10 +++--- .../Profile/Sections/BeatmapsSection.cs | 2 -- .../Overlays/Profile/Sections/CounterPill.cs | 4 +-- .../Historical/ChartProfileSubsection.cs | 10 +++--- .../Historical/DrawableMostPlayedBeatmap.cs | 2 -- .../PaginatedMostPlayedBeatmapContainer.cs | 8 ++--- .../Historical/PlayHistorySubsection.cs | 6 ++-- .../Sections/Historical/ProfileLineChart.cs | 6 +--- .../Sections/Historical/ReplaysSubsection.cs | 6 ++-- .../Sections/Historical/UserHistoryGraph.cs | 6 +--- .../Profile/Sections/HistoricalSection.cs | 2 -- .../Kudosu/DrawableKudosuHistoryItem.cs | 4 +-- .../Profile/Sections/Kudosu/KudosuInfo.cs | 6 ++-- .../Kudosu/PaginatedKudosuHistoryContainer.cs | 8 ++--- .../Profile/Sections/KudosuSection.cs | 2 -- .../Profile/Sections/MedalsSection.cs | 2 -- .../Sections/PaginatedProfileSubsection.cs | 35 ++++++++++--------- .../Profile/Sections/ProfileItemContainer.cs | 2 -- .../Profile/Sections/ProfileSubsection.cs | 10 ++---- .../Sections/ProfileSubsectionHeader.cs | 4 +-- .../Sections/Ranks/DrawableProfileScore.cs | 13 +++---- .../Ranks/DrawableProfileWeightedScore.cs | 2 -- .../Sections/Ranks/PaginatedScoreContainer.cs | 8 ++--- .../Overlays/Profile/Sections/RanksSection.cs | 2 -- .../Sections/Recent/DrawableRecentActivity.cs | 17 +++++---- .../Profile/Sections/Recent/MedalIcon.cs | 2 -- .../PaginatedRecentActivityContainer.cs | 8 ++--- .../Profile/Sections/RecentSection.cs | 2 -- osu.Game/Overlays/Profile/UserGraph.cs | 25 +++++++------ osu.Game/Overlays/UserProfileOverlay.cs | 15 ++++---- osu.Game/Users/UserCoverBackground.cs | 12 +++---- 55 files changed, 172 insertions(+), 279 deletions(-) diff --git a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs index 5ba3963a45..f5c7a3f87a 100644 --- a/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/BottomHeaderContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) 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.Allocation; @@ -25,15 +23,15 @@ namespace osu.Game.Overlays.Profile.Header { public partial class BottomHeaderContainer : CompositeDrawable { - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); - private LinkFlowContainer topLinkContainer; - private LinkFlowContainer bottomLinkContainer; + private LinkFlowContainer topLinkContainer = null!; + private LinkFlowContainer bottomLinkContainer = null!; private Color4 iconColour; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; public BottomHeaderContainer() { @@ -78,7 +76,7 @@ namespace osu.Game.Overlays.Profile.Header User.BindValueChanged(user => updateDisplay(user.NewValue)); } - private void updateDisplay(APIUser user) + private void updateDisplay(APIUser? user) { topLinkContainer.Clear(); bottomLinkContainer.Clear(); @@ -164,7 +162,7 @@ namespace osu.Game.Overlays.Profile.Header private void addSpacer(OsuTextFlowContainer textFlow) => textFlow.AddArbitraryDrawable(new Container { Width = 15 }); - private bool tryAddInfo(IconUsage icon, string content, string link = null) + private bool tryAddInfo(IconUsage icon, string content, string? link = null) { if (string.IsNullOrEmpty(content)) return false; diff --git a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs index 5f6af7a6e2..5e4e1184a4 100644 --- a/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/CentreHeaderContainer.cs @@ -1,8 +1,6 @@ // Copyright (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.Extensions.LocalisationExtensions; @@ -20,10 +18,10 @@ namespace osu.Game.Overlays.Profile.Header public partial class CentreHeaderContainer : CompositeDrawable { public readonly BindableBool DetailsVisible = new BindableBool(true); - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); - private OverlinedInfoContainer hiddenDetailGlobal; - private OverlinedInfoContainer hiddenDetailCountry; + private OverlinedInfoContainer hiddenDetailGlobal = null!; + private OverlinedInfoContainer hiddenDetailCountry = null!; public CentreHeaderContainer() { @@ -146,7 +144,7 @@ namespace osu.Game.Overlays.Profile.Header User.BindValueChanged(user => updateDisplay(user.NewValue)); } - private void updateDisplay(APIUser user) + private void updateDisplay(APIUser? user) { hiddenDetailGlobal.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; hiddenDetailCountry.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-"; diff --git a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs index a778ddd2b1..e7a83c95d8 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ExpandDetailsButton.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -23,9 +21,9 @@ namespace osu.Game.Overlays.Profile.Header.Components public override LocalisableString TooltipText => DetailsVisible.Value ? CommonStrings.ButtonsCollapse : CommonStrings.ButtonsExpand; - private SpriteIcon icon; - private Sample sampleOpen; - private Sample sampleClose; + private SpriteIcon icon = null!; + private Sample? sampleOpen; + private Sample? sampleClose; protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverClickSounds(); diff --git a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs index c278a6c48b..c0bcec622a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/FollowersButton.cs @@ -1,8 +1,6 @@ // Copyright (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.Sprites; @@ -14,7 +12,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class FollowersButton : ProfileHeaderStatisticsButton { - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); public override LocalisableString TooltipText => FriendsStrings.ButtonsDisabled; diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs index ef2f35e9a8..8f84e69bac 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelBadge.cs @@ -1,8 +1,6 @@ // Copyright (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; @@ -20,11 +18,11 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class LevelBadge : CompositeDrawable, IHasTooltip { - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); public LocalisableString TooltipText { get; private set; } - private OsuSpriteText levelText; + private OsuSpriteText levelText = null!; public LevelBadge() { @@ -53,10 +51,11 @@ namespace osu.Game.Overlays.Profile.Header.Components User.BindValueChanged(user => updateLevel(user.NewValue)); } - private void updateLevel(APIUser user) + private void updateLevel(APIUser? user) { - levelText.Text = user?.Statistics?.Level.Current.ToString() ?? "0"; - TooltipText = UsersStrings.ShowStatsLevel(user?.Statistics?.Level.Current.ToString()); + string level = user?.Statistics?.Level.Current.ToString() ?? "0"; + levelText.Text = level; + TooltipText = UsersStrings.ShowStatsLevel(level); } } } diff --git a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs index 0351230fb3..ea5ca0c8bd 100644 --- a/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs +++ b/osu.Game/Overlays/Profile/Header/Components/LevelProgressBar.cs @@ -1,8 +1,6 @@ // Copyright (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.Extensions.LocalisationExtensions; @@ -21,12 +19,12 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class LevelProgressBar : CompositeDrawable, IHasTooltip { - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); public LocalisableString TooltipText { get; } - private Bar levelProgressBar; - private OsuSpriteText levelProgressText; + private Bar levelProgressBar = null!; + private OsuSpriteText levelProgressText = null!; public LevelProgressBar() { @@ -61,7 +59,7 @@ namespace osu.Game.Overlays.Profile.Header.Components User.BindValueChanged(user => updateProgress(user.NewValue)); } - private void updateProgress(APIUser user) + private void updateProgress(APIUser? user) { levelProgressBar.Length = user?.Statistics?.Level.Progress / 100f ?? 0; levelProgressText.Text = user?.Statistics?.Level.Progress.ToLocalisableString("0'%'") ?? default; diff --git a/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs b/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs index 887cf10cce..c600f7622a 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MappingSubscribersButton.cs @@ -1,8 +1,6 @@ // Copyright (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.Sprites; @@ -14,7 +12,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class MappingSubscribersButton : ProfileHeaderStatisticsButton { - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); public override LocalisableString TooltipText => FollowsStrings.MappingFollowers; diff --git a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs index 4886324a22..3266e3c308 100644 --- a/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/MessageUserButton.cs @@ -1,8 +1,6 @@ // Copyright (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; @@ -18,21 +16,21 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class MessageUserButton : ProfileHeaderButton { - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); public override LocalisableString TooltipText => UsersStrings.CardSendMessage; - [Resolved(CanBeNull = true)] - private ChannelManager channelManager { get; set; } - - [Resolved(CanBeNull = true)] - private UserProfileOverlay userOverlay { get; set; } - - [Resolved(CanBeNull = true)] - private ChatOverlay chatOverlay { get; set; } + [Resolved] + private ChannelManager? channelManager { get; set; } [Resolved] - private IAPIProvider apiProvider { get; set; } + private UserProfileOverlay? userOverlay { get; set; } + + [Resolved] + private ChatOverlay? chatOverlay { get; set; } + + [Resolved] + private IAPIProvider apiProvider { get; set; } = null!; public MessageUserButton() { @@ -56,7 +54,7 @@ namespace osu.Game.Overlays.Profile.Header.Components chatOverlay?.Show(); }; - User.ValueChanged += e => Content.Alpha = !e.NewValue.PMFriendsOnly && apiProvider.LocalUser.Value.Id != e.NewValue.Id ? 1 : 0; + User.ValueChanged += e => Content.Alpha = e.NewValue != null && !e.NewValue.PMFriendsOnly && apiProvider.LocalUser.Value.Id != e.NewValue.Id ? 1 : 0; } } } diff --git a/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs index 244f185e28..42b67865a0 100644 --- a/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedInfoContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs index c040f5a787..e9e277acd3 100644 --- a/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs +++ b/osu.Game/Overlays/Profile/Header/Components/OverlinedTotalPlayTime.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -16,11 +14,11 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class OverlinedTotalPlayTime : CompositeDrawable, IHasTooltip { - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); public LocalisableString TooltipText { get; set; } - private OverlinedInfoContainer info; + private OverlinedInfoContainer info = null!; public OverlinedTotalPlayTime() { @@ -41,7 +39,7 @@ namespace osu.Game.Overlays.Profile.Header.Components User.BindValueChanged(updateTime, true); } - private void updateTime(ValueChangedEvent user) + private void updateTime(ValueChangedEvent user) { TooltipText = (user.NewValue?.Statistics?.PlayTime ?? 0) / 3600 + " hours"; info.Content = formatTime(user.NewValue?.Statistics?.PlayTime); diff --git a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs index 0abc4de5ef..b722fe92e0 100644 --- a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs +++ b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs @@ -1,8 +1,6 @@ // Copyright (c) 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; @@ -27,7 +25,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private const int width = 310; private const int move_offset = 15; - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); private readonly TextFlowContainer text; private readonly Box background; @@ -109,11 +107,11 @@ namespace osu.Game.Overlays.Profile.Header.Components User.BindValueChanged(onUserChanged, true); } - private void onUserChanged(ValueChangedEvent user) + private void onUserChanged(ValueChangedEvent user) { text.Text = string.Empty; - string[] usernames = user.NewValue?.PreviousUsernames; + string[]? usernames = user.NewValue?.PreviousUsernames; if (usernames?.Any() ?? false) { @@ -149,7 +147,7 @@ namespace osu.Game.Overlays.Profile.Header.Components private partial class HoverIconContainer : Container { - public Action ActivateHover; + public Action? ActivateHover; public HoverIconContainer() { diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs index c4a46440d2..414ca4d077 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderButton.cs @@ -1,8 +1,6 @@ // Copyright (c) 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; diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs index 5ad726a3ea..32c5ebee2c 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileHeaderStatisticsButton.cs @@ -1,8 +1,6 @@ // Copyright (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.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs index 72446bde3a..684ce9088e 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetSelector.cs @@ -1,9 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Bindables; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; @@ -12,12 +11,12 @@ namespace osu.Game.Overlays.Profile.Header.Components { public partial class ProfileRulesetSelector : OverlayRulesetSelector { - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); protected override void LoadComplete() { base.LoadComplete(); - User.BindValueChanged(u => SetDefaultRuleset(Rulesets.GetRuleset(u.NewValue?.PlayMode ?? "osu")), true); + User.BindValueChanged(u => SetDefaultRuleset(Rulesets.GetRuleset(u.NewValue?.PlayMode ?? "osu").AsNonNull()), true); } public void SetDefaultRuleset(RulesetInfo ruleset) diff --git a/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetTabItem.cs b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetTabItem.cs index 72adc96f9a..9caa7dd1bc 100644 --- a/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetTabItem.cs +++ b/osu.Game/Overlays/Profile/Header/Components/ProfileRulesetTabItem.cs @@ -1,8 +1,6 @@ // Copyright (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; diff --git a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs index 51531977d2..fe1069eea1 100644 --- a/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs +++ b/osu.Game/Overlays/Profile/Header/Components/RankGraph.cs @@ -1,8 +1,6 @@ // Copyright (c) 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; @@ -21,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Header.Components { private const int ranked_days = 88; - public readonly Bindable Statistics = new Bindable(); + public readonly Bindable Statistics = new Bindable(); private readonly OsuSpriteText placeholder; @@ -42,11 +40,11 @@ namespace osu.Game.Overlays.Profile.Header.Components Statistics.BindValueChanged(statistics => updateStatistics(statistics.NewValue), true); } - private void updateStatistics(UserStatistics statistics) + private void updateStatistics(UserStatistics? statistics) { // checking both IsRanked and RankHistory is required. // see https://github.com/ppy/osu-web/blob/154ceafba0f35a1dd935df53ec98ae2ea5615f9f/resources/assets/lib/profile-page/rank-chart.tsx#L46 - int[] userRanks = statistics?.IsRanked == true ? statistics.RankHistory?.Data : null; + int[]? userRanks = statistics?.IsRanked == true ? statistics.RankHistory?.Data : null; Data = userRanks?.Select((x, index) => new KeyValuePair(index, x)).Where(x => x.Value != 0).ToArray(); } diff --git a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs index 4028eb1389..92e2017659 100644 --- a/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs +++ b/osu.Game/Overlays/Profile/Header/Components/SupporterIcon.cs @@ -1,8 +1,6 @@ // Copyright (c) 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; @@ -82,10 +80,10 @@ namespace osu.Game.Overlays.Profile.Header.Components } [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; - [BackgroundDependencyLoader(true)] - private void load(OsuGame game) + [BackgroundDependencyLoader] + private void load(OsuGame? game) { background.Colour = colours.Pink; diff --git a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs index 44986dc178..95fead1246 100644 --- a/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/DetailHeaderContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) 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.Bindables; @@ -25,14 +23,14 @@ namespace osu.Game.Overlays.Profile.Header public partial class DetailHeaderContainer : CompositeDrawable { private readonly Dictionary scoreRankInfos = new Dictionary(); - private OverlinedInfoContainer medalInfo; - private OverlinedInfoContainer ppInfo; - private OverlinedInfoContainer detailGlobalRank; - private OverlinedInfoContainer detailCountryRank; - private FillFlowContainer fillFlow; - private RankGraph rankGraph; + private OverlinedInfoContainer medalInfo = null!; + private OverlinedInfoContainer ppInfo = null!; + private OverlinedInfoContainer detailGlobalRank = null!; + private OverlinedInfoContainer detailCountryRank = null!; + private FillFlowContainer? fillFlow; + private RankGraph rankGraph = null!; - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); private bool expanded = true; @@ -173,7 +171,7 @@ namespace osu.Game.Overlays.Profile.Header }; } - private void updateDisplay(APIUser user) + private void updateDisplay(APIUser? user) { medalInfo.Content = user?.Achievements?.Length.ToString() ?? "0"; ppInfo.Content = user?.Statistics?.PP?.ToLocalisableString("#,##0") ?? (LocalisableString)"0"; diff --git a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs index ab800d006d..79f9eba57d 100644 --- a/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/MedalHeaderContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -20,9 +18,9 @@ namespace osu.Game.Overlays.Profile.Header { public partial class MedalHeaderContainer : CompositeDrawable { - private FillFlowContainer badgeFlowContainer; + private FillFlowContainer badgeFlowContainer = null!; - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -66,16 +64,16 @@ namespace osu.Game.Overlays.Profile.Header }; } - private CancellationTokenSource cancellationTokenSource; + private CancellationTokenSource? cancellationTokenSource; - private void updateDisplay(APIUser user) + private void updateDisplay(APIUser? user) { cancellationTokenSource?.Cancel(); cancellationTokenSource = new CancellationTokenSource(); badgeFlowContainer.Clear(); - var badges = user.Badges; + var badges = user?.Badges; if (badges?.Length > 0) { diff --git a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs index 8826dd982d..a6501f567f 100644 --- a/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs +++ b/osu.Game/Overlays/Profile/Header/TopHeaderContainer.cs @@ -1,8 +1,6 @@ // Copyright (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.Extensions; @@ -29,19 +27,19 @@ namespace osu.Game.Overlays.Profile.Header { private const float avatar_size = 110; - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; - private SupporterIcon supporterTag; - private UpdateableAvatar avatar; - private OsuSpriteText usernameText; - private ExternalLinkButton openUserExternally; - private OsuSpriteText titleText; - private UpdateableFlag userFlag; - private OsuSpriteText userCountryText; - private FillFlowContainer userStats; + private SupporterIcon supporterTag = null!; + private UpdateableAvatar avatar = null!; + private OsuSpriteText usernameText = null!; + private ExternalLinkButton openUserExternally = null!; + private OsuSpriteText titleText = null!; + private UpdateableFlag userFlag = null!; + private OsuSpriteText userCountryText = null!; + private FillFlowContainer userStats = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) @@ -176,7 +174,7 @@ namespace osu.Game.Overlays.Profile.Header User.BindValueChanged(user => updateUser(user.NewValue)); } - private void updateUser(APIUser user) + private void updateUser(APIUser? user) { avatar.User = user; usernameText.Text = user?.Username ?? string.Empty; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 8443678989..2b8d2503e0 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Diagnostics; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -20,9 +18,9 @@ namespace osu.Game.Overlays.Profile { public partial class ProfileHeader : TabControlOverlayHeader { - private UserCoverBackground coverContainer; + private UserCoverBackground coverContainer = null!; - public Bindable User = new Bindable(); + public Bindable User = new Bindable(); private CentreHeaderContainer centreHeaderContainer; private DetailHeaderContainer detailHeaderContainer; @@ -102,7 +100,7 @@ namespace osu.Game.Overlays.Profile protected override OverlayTitle CreateTitle() => new ProfileHeaderTitle(); - private void updateDisplay(APIUser user) => coverContainer.User = user; + private void updateDisplay(APIUser? user) => coverContainer.User = user; private partial class ProfileHeaderTitle : OverlayTitle { diff --git a/osu.Game/Overlays/Profile/ProfileSection.cs b/osu.Game/Overlays/Profile/ProfileSection.cs index fc99050f73..62b40545df 100644 --- a/osu.Game/Overlays/Profile/ProfileSection.cs +++ b/osu.Game/Overlays/Profile/ProfileSection.cs @@ -1,8 +1,6 @@ // Copyright (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.Extensions.Color4Extensions; @@ -31,7 +29,7 @@ namespace osu.Game.Overlays.Profile protected override Container Content => content; - public readonly Bindable User = new Bindable(); + public readonly Bindable User = new Bindable(); protected ProfileSection() { diff --git a/osu.Game/Overlays/Profile/Sections/AboutSection.cs b/osu.Game/Overlays/Profile/Sections/AboutSection.cs index ac3dca5107..69a8b23412 100644 --- a/osu.Game/Overlays/Profile/Sections/AboutSection.cs +++ b/osu.Game/Overlays/Profile/Sections/AboutSection.cs @@ -1,8 +1,6 @@ // Copyright (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.Localisation; using osu.Game.Resources.Localisation.Web; diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs index 5a80a4d444..499c572eac 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs @@ -1,8 +1,6 @@ // Copyright (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; @@ -25,8 +23,8 @@ namespace osu.Game.Overlays.Profile.Sections AutoSizeAxes = Axes.Both; } - [BackgroundDependencyLoader(true)] - private void load(BeatmapSetOverlay beatmapSetOverlay) + [BackgroundDependencyLoader] + private void load(BeatmapSetOverlay? beatmapSetOverlay) { Action = () => { diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index 56d9fb9ec6..e327d71d25 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) 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.Bindables; @@ -24,7 +22,7 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps protected override int InitialItemsCount => type == BeatmapSetType.Graveyard ? 2 : 6; - public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, LocalisableString headerText) + public PaginatedBeatmapContainer(BeatmapSetType type, Bindable user, LocalisableString headerText) : base(user, headerText) { this.type = type; @@ -66,10 +64,10 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps } } - protected override APIRequest> CreateRequest(PaginationParameters pagination) => - new GetUserBeatmapsRequest(User.Value.Id, type, pagination); + protected override APIRequest> CreateRequest(APIUser user, PaginationParameters pagination) => + new GetUserBeatmapsRequest(user.Id, type, pagination); - protected override Drawable CreateDrawableItem(APIBeatmapSet model) => model.OnlineID > 0 + protected override Drawable? CreateDrawableItem(APIBeatmapSet model) => model.OnlineID > 0 ? new BeatmapCardNormal(model) { Anchor = Anchor.TopCentre, diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs index cf80ebd66f..3b304a79ef 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapsSection.cs @@ -1,8 +1,6 @@ // Copyright (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.Localisation; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Profile.Sections.Beatmaps; diff --git a/osu.Game/Overlays/Profile/Sections/CounterPill.cs b/osu.Game/Overlays/Profile/Sections/CounterPill.cs index c93cdb84f2..74cd4b218b 100644 --- a/osu.Game/Overlays/Profile/Sections/CounterPill.cs +++ b/osu.Game/Overlays/Profile/Sections/CounterPill.cs @@ -1,8 +1,6 @@ // Copyright (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.Containers; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; @@ -18,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections { public readonly BindableInt Current = new BindableInt(); - private OsuSpriteText counter; + private OsuSpriteText counter = null!; [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs index e0837320b2..c532c693ec 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ChartProfileSubsection.cs @@ -1,8 +1,6 @@ // Copyright (c) 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.Bindables; @@ -15,14 +13,14 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { public abstract partial class ChartProfileSubsection : ProfileSubsection { - private ProfileLineChart chart; + private ProfileLineChart chart = null!; /// /// Text describing the value being plotted on the graph, which will be displayed as a prefix to the value in the history graph tooltip. /// protected abstract LocalisableString GraphCounterName { get; } - protected ChartProfileSubsection(Bindable user, LocalisableString headerText) + protected ChartProfileSubsection(Bindable user, LocalisableString headerText) : base(user, headerText) { } @@ -46,7 +44,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical User.BindValueChanged(onUserChanged, true); } - private void onUserChanged(ValueChangedEvent e) + private void onUserChanged(ValueChangedEvent e) { var values = GetValues(e.NewValue); @@ -86,6 +84,6 @@ namespace osu.Game.Overlays.Profile.Sections.Historical return filledHistoryEntries.ToArray(); } - protected abstract APIUserHistoryCount[] GetValues(APIUser user); + protected abstract APIUserHistoryCount[]? GetValues(APIUser? user); } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index a31ee88f3b..53447a971b 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs index 222969bdfd..515c1fd146 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PaginatedMostPlayedBeatmapContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) 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.Bindables; @@ -18,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { public partial class PaginatedMostPlayedBeatmapContainer : PaginatedProfileSubsection { - public PaginatedMostPlayedBeatmapContainer(Bindable user) + public PaginatedMostPlayedBeatmapContainer(Bindable user) : base(user, UsersStrings.ShowExtraHistoricalMostPlayedTitle) { } @@ -31,8 +29,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical protected override int GetCount(APIUser user) => user.BeatmapPlayCountsCount; - protected override APIRequest> CreateRequest(PaginationParameters pagination) => - new GetUserMostPlayedBeatmapsRequest(User.Value.Id, pagination); + protected override APIRequest> CreateRequest(APIUser user, PaginationParameters pagination) => + new GetUserMostPlayedBeatmapsRequest(user.Id, pagination); protected override Drawable CreateDrawableItem(APIUserMostPlayedBeatmap mostPlayed) => new DrawableMostPlayedBeatmap(mostPlayed); diff --git a/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs index 186bf73898..c8a6b1da31 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/PlayHistorySubsection.cs @@ -1,8 +1,6 @@ // Copyright (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.Localisation; using osu.Game.Online.API.Requests.Responses; @@ -14,11 +12,11 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { protected override LocalisableString GraphCounterName => UsersStrings.ShowExtraHistoricalMonthlyPlaycountsCountLabel; - public PlayHistorySubsection(Bindable user) + public PlayHistorySubsection(Bindable user) : base(user, UsersStrings.ShowExtraHistoricalMonthlyPlaycountsTitle) { } - protected override APIUserHistoryCount[] GetValues(APIUser user) => user?.MonthlyPlayCounts; + protected override APIUserHistoryCount[]? GetValues(APIUser? user) => user?.MonthlyPlayCounts; } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs index da86d870fc..5711bfc046 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ProfileLineChart.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 osu.Framework.Graphics.Containers; using osu.Framework.Graphics; -using JetBrains.Annotations; using System; using System.Linq; using osu.Game.Graphics.Sprites; @@ -22,9 +19,8 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { public partial class ProfileLineChart : CompositeDrawable { - private APIUserHistoryCount[] values; + private APIUserHistoryCount[] values = Array.Empty(); - [NotNull] public APIUserHistoryCount[] Values { get => values; diff --git a/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs b/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs index d1d1e76e14..ac7c616a64 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/ReplaysSubsection.cs @@ -1,8 +1,6 @@ // Copyright (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.Localisation; using osu.Game.Online.API.Requests.Responses; @@ -14,11 +12,11 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { protected override LocalisableString GraphCounterName => UsersStrings.ShowExtraHistoricalReplaysWatchedCountsCountLabel; - public ReplaysSubsection(Bindable user) + public ReplaysSubsection(Bindable user) : base(user, UsersStrings.ShowExtraHistoricalReplaysWatchedCountsTitle) { } - protected override APIUserHistoryCount[] GetValues(APIUser user) => user?.ReplaysWatchedCounts; + protected override APIUserHistoryCount[]? GetValues(APIUser? user) => user?.ReplaysWatchedCounts; } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs index 3f68ffdd0e..310607e757 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/UserHistoryGraph.cs @@ -1,12 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Localisation; using osu.Game.Online.API.Requests.Responses; @@ -17,8 +14,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { private readonly LocalisableString tooltipCounterName; - [CanBeNull] - public APIUserHistoryCount[] Values + public APIUserHistoryCount[]? Values { set => Data = value?.Select(v => new KeyValuePair(v.Date, v.Count)).ToArray(); } diff --git a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs index 13e0aefc65..19f7a32d4d 100644 --- a/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs +++ b/osu.Game/Overlays/Profile/Sections/HistoricalSection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Online.API.Requests; diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs index 122b20cbfc..e7991acb89 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/DrawableKudosuHistoryItem.cs @@ -1,8 +1,6 @@ // Copyright (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,7 +18,7 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu private const int height = 25; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; private readonly APIKudosuHistory historyItem; private readonly LinkFlowContainer linkFlowContainer; diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs index 2b4d58b845..acf4827dfd 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/KudosuInfo.cs @@ -1,8 +1,6 @@ // Copyright (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 osuTK; using osu.Framework.Graphics; @@ -22,9 +20,9 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu { public partial class KudosuInfo : Container { - private readonly Bindable user = new Bindable(); + private readonly Bindable user = new Bindable(); - public KudosuInfo(Bindable user) + public KudosuInfo(Bindable user) { this.user.BindTo(user); CountSection total; diff --git a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs index c082c634a7..c5de810f22 100644 --- a/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Kudosu/PaginatedKudosuHistoryContainer.cs @@ -1,8 +1,6 @@ // Copyright (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.Online.API.Requests; using osu.Framework.Bindables; @@ -16,13 +14,13 @@ namespace osu.Game.Overlays.Profile.Sections.Kudosu { public partial class PaginatedKudosuHistoryContainer : PaginatedProfileSubsection { - public PaginatedKudosuHistoryContainer(Bindable user) + public PaginatedKudosuHistoryContainer(Bindable user) : base(user, missingText: UsersStrings.ShowExtraKudosuEntryEmpty) { } - protected override APIRequest> CreateRequest(PaginationParameters pagination) - => new GetUserKudosuHistoryRequest(User.Value.Id, pagination); + protected override APIRequest> CreateRequest(APIUser user, PaginationParameters pagination) + => new GetUserKudosuHistoryRequest(user.Id, pagination); protected override Drawable CreateDrawableItem(APIKudosuHistory item) => new DrawableKudosuHistoryItem(item); } diff --git a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs index 26cf78c537..482a853c44 100644 --- a/osu.Game/Overlays/Profile/Sections/KudosuSection.cs +++ b/osu.Game/Overlays/Profile/Sections/KudosuSection.cs @@ -1,8 +1,6 @@ // Copyright (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.Overlays.Profile.Sections.Kudosu; using osu.Framework.Localisation; diff --git a/osu.Game/Overlays/Profile/Sections/MedalsSection.cs b/osu.Game/Overlays/Profile/Sections/MedalsSection.cs index a511dc95a0..42f241d662 100644 --- a/osu.Game/Overlays/Profile/Sections/MedalsSection.cs +++ b/osu.Game/Overlays/Profile/Sections/MedalsSection.cs @@ -1,8 +1,6 @@ // Copyright (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.Localisation; using osu.Game.Resources.Localisation.Web; diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index 530391466a..f32221747c 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.Collections.Generic; using System.Linq; using System.Threading; @@ -35,20 +33,20 @@ namespace osu.Game.Overlays.Profile.Sections protected virtual int InitialItemsCount => 5; [Resolved] - private IAPIProvider api { get; set; } + private IAPIProvider api { get; set; } = null!; protected PaginationParameters? CurrentPage { get; private set; } - protected ReverseChildIDFillFlowContainer ItemsContainer { get; private set; } + protected ReverseChildIDFillFlowContainer ItemsContainer { get; private set; } = null!; - private APIRequest> retrievalRequest; - private CancellationTokenSource loadCancellation; + private APIRequest>? retrievalRequest; + private CancellationTokenSource? loadCancellation; - private ShowMoreButton moreButton; - private OsuSpriteText missing; + private ShowMoreButton moreButton = null!; + private OsuSpriteText missing = null!; private readonly LocalisableString? missingText; - protected PaginatedProfileSubsection(Bindable user, LocalisableString? headerText = null, LocalisableString? missingText = null) + protected PaginatedProfileSubsection(Bindable user, LocalisableString? headerText = null, LocalisableString? missingText = null) : base(user, headerText, CounterVisibilityState.AlwaysVisible) { this.missingText = missingText; @@ -94,7 +92,7 @@ namespace osu.Game.Overlays.Profile.Sections User.BindValueChanged(onUserChanged, true); } - private void onUserChanged(ValueChangedEvent e) + private void onUserChanged(ValueChangedEvent e) { loadCancellation?.Cancel(); retrievalRequest?.Cancel(); @@ -111,17 +109,20 @@ namespace osu.Game.Overlays.Profile.Sections private void showMore() { + if (User.Value == null) + return; + loadCancellation = new CancellationTokenSource(); CurrentPage = CurrentPage?.TakeNext(ItemsPerPage) ?? new PaginationParameters(InitialItemsCount); - retrievalRequest = CreateRequest(CurrentPage.Value); - retrievalRequest.Success += UpdateItems; + retrievalRequest = CreateRequest(User.Value, CurrentPage.Value); + retrievalRequest.Success += items => UpdateItems(items, loadCancellation); api.Queue(retrievalRequest); } - protected virtual void UpdateItems(List items) => Schedule(() => + protected virtual void UpdateItems(List items, CancellationTokenSource cancellationTokenSource) => Schedule(() => { OnItemsReceived(items); @@ -136,7 +137,7 @@ namespace osu.Game.Overlays.Profile.Sections return; } - LoadComponentsAsync(items.Select(CreateDrawableItem).Where(d => d != null), drawables => + LoadComponentsAsync(items.Select(CreateDrawableItem).Where(d => d != null).Cast(), drawables => { missing.Hide(); @@ -144,7 +145,7 @@ namespace osu.Game.Overlays.Profile.Sections moreButton.IsLoading = false; ItemsContainer.AddRange(drawables); - }, loadCancellation.Token); + }, cancellationTokenSource.Token); }); protected virtual int GetCount(APIUser user) => 0; @@ -153,9 +154,9 @@ namespace osu.Game.Overlays.Profile.Sections { } - protected abstract APIRequest> CreateRequest(PaginationParameters pagination); + protected abstract APIRequest> CreateRequest(APIUser user, PaginationParameters pagination); - protected abstract Drawable CreateDrawableItem(TModel model); + protected abstract Drawable? CreateDrawableItem(TModel model); protected override void Dispose(bool isDisposing) { diff --git a/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs b/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs index c81a08fa20..b30faee380 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileItemContainer.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs index 9d88645c9a..0a0e1a9298 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsection.cs @@ -1,13 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using JetBrains.Annotations; using osu.Framework.Localisation; using osu.Game.Online.API.Requests.Responses; @@ -15,14 +12,14 @@ namespace osu.Game.Overlays.Profile.Sections { public abstract partial class ProfileSubsection : FillFlowContainer { - protected readonly Bindable User = new Bindable(); + protected readonly Bindable User = new Bindable(); private readonly LocalisableString headerText; private readonly CounterVisibilityState counterVisibilityState; - private ProfileSubsectionHeader header; + private ProfileSubsectionHeader header = null!; - protected ProfileSubsection(Bindable user, LocalisableString? headerText = null, CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) + protected ProfileSubsection(Bindable user, LocalisableString? headerText = null, CounterVisibilityState counterVisibilityState = CounterVisibilityState.AlwaysHidden) { this.headerText = headerText ?? string.Empty; this.counterVisibilityState = counterVisibilityState; @@ -46,7 +43,6 @@ namespace osu.Game.Overlays.Profile.Sections }; } - [NotNull] protected abstract Drawable CreateContent(); protected void SetCount(int value) => header.Current.Value = value; diff --git a/osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs b/osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs index 598ac19059..fa21535d02 100644 --- a/osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs +++ b/osu.Game/Overlays/Profile/Sections/ProfileSubsectionHeader.cs @@ -1,8 +1,6 @@ // Copyright (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.Containers; using osu.Framework.Graphics; @@ -30,7 +28,7 @@ namespace osu.Game.Overlays.Profile.Sections private readonly LocalisableString text; private readonly CounterVisibilityState counterState; - private CounterPill counterPill; + private CounterPill counterPill = null!; public ProfileSubsectionHeader(LocalisableString text, CounterVisibilityState counterState) { diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 67df4825cc..0f3e0bc6b2 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.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.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -34,10 +32,10 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks protected readonly SoloScoreInfo Score; [Resolved] - private OsuColour colours { get; set; } + private OsuColour colours { get; set; } = null!; [Resolved] - private OverlayColourProvider colourProvider { get; set; } + private OverlayColourProvider colourProvider { get; set; } = null!; public DrawableProfileScore(SoloScoreInfo score) { @@ -84,7 +82,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Spacing = new Vector2(0, 2), Children = new Drawable[] { - new ScoreBeatmapMetadataContainer(Score.Beatmap), + new ScoreBeatmapMetadataContainer(Score.Beatmap.AsNonNull()), new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -94,7 +92,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { new OsuSpriteText { - Text = $"{Score.Beatmap?.DifficultyName}", + Text = $"{Score.Beatmap.AsNonNull().DifficultyName}", Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), Colour = colours.Yellow }, @@ -199,7 +197,6 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks }); } - [NotNull] protected virtual Drawable CreateRightContent() => CreateDrawableAccuracy(); protected Drawable CreateDrawableAccuracy() => new Container diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs index f04d7d9733..6cfe34ec6f 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs @@ -1,8 +1,6 @@ // Copyright (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.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index 38fac075fb..3ef0275f50 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -1,8 +1,6 @@ // Copyright (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.Containers; using osu.Game.Online.API.Requests; using System; @@ -21,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { private readonly ScoreType type; - public PaginatedScoreContainer(ScoreType type, Bindable user, LocalisableString headerText) + public PaginatedScoreContainer(ScoreType type, Bindable user, LocalisableString headerText) : base(user, headerText) { this.type = type; @@ -62,8 +60,8 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks base.OnItemsReceived(items); } - protected override APIRequest> CreateRequest(PaginationParameters pagination) => - new GetUserScoresRequest(User.Value.Id, type, pagination); + protected override APIRequest> CreateRequest(APIUser user, PaginationParameters pagination) => + new GetUserScoresRequest(user.Id, type, pagination); private int drawableItemIndex; diff --git a/osu.Game/Overlays/Profile/Sections/RanksSection.cs b/osu.Game/Overlays/Profile/Sections/RanksSection.cs index ca41bff2f9..ce831b30a8 100644 --- a/osu.Game/Overlays/Profile/Sections/RanksSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RanksSection.cs @@ -1,8 +1,6 @@ // Copyright (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.Overlays.Profile.Sections.Ranks; using osu.Game.Online.API.Requests; using osu.Framework.Localisation; diff --git a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs index c3fa467e5f..0479ab7c16 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/DrawableRecentActivity.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 osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; @@ -24,14 +23,14 @@ namespace osu.Game.Overlays.Profile.Sections.Recent private const int font_size = 14; [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 readonly APIRecentActivity activity; - private LinkFlowContainer content; + private LinkFlowContainer content = null!; public DrawableRecentActivity(APIRecentActivity activity) { @@ -216,15 +215,15 @@ namespace osu.Game.Overlays.Profile.Sections.Recent rulesets.AvailableRulesets.FirstOrDefault(r => r.ShortName == activity.Mode)?.Name ?? activity.Mode; private void addUserLink() - => content.AddLink(activity.User?.Username, LinkAction.OpenUserProfile, getLinkArgument(activity.User?.Url), creationParameters: t => t.Font = getLinkFont(FontWeight.Bold)); + => content.AddLink(activity.User.AsNonNull().Username, LinkAction.OpenUserProfile, getLinkArgument(activity.User.AsNonNull().Url), creationParameters: t => t.Font = getLinkFont(FontWeight.Bold)); private void addBeatmapLink() - => content.AddLink(activity.Beatmap?.Title, LinkAction.OpenBeatmap, getLinkArgument(activity.Beatmap?.Url), creationParameters: t => t.Font = getLinkFont()); + => content.AddLink(activity.Beatmap.AsNonNull().Title, LinkAction.OpenBeatmap, getLinkArgument(activity.Beatmap.AsNonNull().Url), creationParameters: t => t.Font = getLinkFont()); private void addBeatmapsetLink() - => content.AddLink(activity.Beatmapset?.Title, LinkAction.OpenBeatmapSet, getLinkArgument(activity.Beatmapset?.Url), creationParameters: t => t.Font = getLinkFont()); + => content.AddLink(activity.Beatmapset.AsNonNull().Title, LinkAction.OpenBeatmapSet, getLinkArgument(activity.Beatmapset.AsNonNull().Url), creationParameters: t => t.Font = getLinkFont()); - private string getLinkArgument(string url) => MessageFormatter.GetLinkDetails($"{api.WebsiteRootUrl}{url}").Argument.ToString(); + private string getLinkArgument(string url) => MessageFormatter.GetLinkDetails($"{api.WebsiteRootUrl}{url}").Argument.ToString().AsNonNull(); private FontUsage getLinkFont(FontWeight fontWeight = FontWeight.Regular) => OsuFont.GetFont(size: font_size, weight: fontWeight, italics: true); diff --git a/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs b/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs index 5eeed24469..6cb439ab33 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/MedalIcon.cs @@ -1,8 +1,6 @@ // Copyright (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.Containers; using osu.Framework.Graphics; diff --git a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs index b07dfc154f..4e5b11d79f 100644 --- a/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Recent/PaginatedRecentActivityContainer.cs @@ -1,8 +1,6 @@ // Copyright (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.Online.API.Requests; using osu.Framework.Bindables; @@ -18,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Recent { public partial class PaginatedRecentActivityContainer : PaginatedProfileSubsection { - public PaginatedRecentActivityContainer(Bindable user) + public PaginatedRecentActivityContainer(Bindable user) : base(user, missingText: EventsStrings.Empty) { } @@ -29,8 +27,8 @@ namespace osu.Game.Overlays.Profile.Sections.Recent ItemsContainer.Spacing = new Vector2(0, 8); } - protected override APIRequest> CreateRequest(PaginationParameters pagination) => - new GetUserRecentActivitiesRequest(User.Value.Id, pagination); + protected override APIRequest> CreateRequest(APIUser user, PaginationParameters pagination) => + new GetUserRecentActivitiesRequest(user.Id, pagination); protected override Drawable CreateDrawableItem(APIRecentActivity model) => new DrawableRecentActivity(model); } diff --git a/osu.Game/Overlays/Profile/Sections/RecentSection.cs b/osu.Game/Overlays/Profile/Sections/RecentSection.cs index f2ebf81504..e29dc7f635 100644 --- a/osu.Game/Overlays/Profile/Sections/RecentSection.cs +++ b/osu.Game/Overlays/Profile/Sections/RecentSection.cs @@ -1,8 +1,6 @@ // Copyright (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.Localisation; using osu.Game.Overlays.Profile.Sections.Recent; using osu.Game.Resources.Localisation.Web; diff --git a/osu.Game/Overlays/Profile/UserGraph.cs b/osu.Game/Overlays/Profile/UserGraph.cs index fa9cbe0449..63cdbea5a4 100644 --- a/osu.Game/Overlays/Profile/UserGraph.cs +++ b/osu.Game/Overlays/Profile/UserGraph.cs @@ -1,12 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -26,12 +23,12 @@ namespace osu.Game.Overlays.Profile /// /// Type of data to be used for X-axis of the graph. /// Type of data to be used for Y-axis of the graph. - public abstract partial class UserGraph : Container, IHasCustomTooltip + public abstract partial class UserGraph : Container, IHasCustomTooltip { protected const float FADE_DURATION = 150; private readonly UserLineGraph graph; - private KeyValuePair[] data; + private KeyValuePair[]? data; private int hoveredIndex = -1; protected UserGraph() @@ -83,8 +80,7 @@ namespace osu.Game.Overlays.Profile /// /// Set of values which will be used to create a graph. /// - [CanBeNull] - protected KeyValuePair[] Data + protected KeyValuePair[]? Data { set { @@ -120,9 +116,9 @@ namespace osu.Game.Overlays.Profile protected virtual void ShowGraph() => graph.FadeIn(FADE_DURATION, Easing.Out); protected virtual void HideGraph() => graph.FadeOut(FADE_DURATION, Easing.Out); - public ITooltip GetCustomTooltip() => new UserGraphTooltip(); + public ITooltip GetCustomTooltip() => new UserGraphTooltip(); - public UserGraphTooltipContent TooltipContent + public UserGraphTooltipContent? TooltipContent { get { @@ -143,7 +139,7 @@ namespace osu.Game.Overlays.Profile private readonly Box ballBg; private readonly Box line; - public Action OnBallMove; + public Action? OnBallMove; public UserLineGraph() { @@ -191,7 +187,7 @@ namespace osu.Game.Overlays.Profile Vector2 position = calculateBallPosition(index); movingBall.MoveToY(position.Y, duration, Easing.OutQuint); bar.MoveToX(position.X, duration, Easing.OutQuint); - OnBallMove.Invoke(index); + OnBallMove?.Invoke(index); } public void ShowBar() => bar.FadeIn(FADE_DURATION); @@ -207,7 +203,7 @@ namespace osu.Game.Overlays.Profile } } - private partial class UserGraphTooltip : VisibilityContainer, ITooltip + private partial class UserGraphTooltip : VisibilityContainer, ITooltip { protected readonly OsuSpriteText Label, Counter, BottomText; private readonly Box background; @@ -267,8 +263,11 @@ namespace osu.Game.Overlays.Profile background.Colour = colours.Gray1; } - public void SetContent(UserGraphTooltipContent content) + public void SetContent(UserGraphTooltipContent? content) { + if (content == null) + return; + Label.Text = content.Name; Counter.Text = content.Count; BottomText.Text = content.Time; diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index 386f95cf0a..1a7c4173ab 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -1,9 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -24,11 +23,11 @@ namespace osu.Game.Overlays { public partial class UserProfileOverlay : FullscreenOverlay { - private ProfileSection lastSection; - private ProfileSection[] sections; - private GetUserRequest userReq; - private ProfileSectionsContainer sectionsContainer; - private ProfileSectionTabControl tabs; + private ProfileSection? lastSection; + private ProfileSection[]? sections; + private GetUserRequest? userReq; + private ProfileSectionsContainer? sectionsContainer; + private ProfileSectionTabControl? tabs; public const float CONTENT_X_MARGIN = 70; @@ -133,6 +132,8 @@ namespace osu.Game.Overlays private void userLoadComplete(APIUser user) { + Debug.Assert(sections != null && sectionsContainer != null && tabs != null); + Header.User.Value = user; if (user.ProfileOrder != null) diff --git a/osu.Game/Users/UserCoverBackground.cs b/osu.Game/Users/UserCoverBackground.cs index 69a5fba876..de6a306b2a 100644 --- a/osu.Game/Users/UserCoverBackground.cs +++ b/osu.Game/Users/UserCoverBackground.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -17,15 +15,15 @@ using osuTK.Graphics; namespace osu.Game.Users { - public partial class UserCoverBackground : ModelBackedDrawable + public partial class UserCoverBackground : ModelBackedDrawable { - public APIUser User + public APIUser? User { get => Model; set => Model = value; } - protected override Drawable CreateDrawable(APIUser user) => new Cover(user); + protected override Drawable CreateDrawable(APIUser? user) => new Cover(user); protected override double LoadDelay => 300; @@ -40,9 +38,9 @@ namespace osu.Game.Users [LongRunningLoad] private partial class Cover : CompositeDrawable { - private readonly APIUser user; + private readonly APIUser? user; - public Cover(APIUser user) + public Cover(APIUser? user) { this.user = user; From 73f14bd14fb76791d44365d33d782d519a897177 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 30 Dec 2022 15:05:04 +0100 Subject: [PATCH 347/384] Enable NRT in user profile overlay test scenes --- osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs | 2 -- osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs | 2 -- .../Visual/Online/TestSceneProfileRulesetSelector.cs | 4 +--- osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs | 2 -- osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs | 4 +--- osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs | 2 -- .../Visual/Online/TestSceneUserProfilePreviousUsernames.cs | 6 ++---- .../Visual/Online/TestSceneUserProfileRecentSection.cs | 2 -- osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs | 2 -- osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs | 2 -- .../UserInterface/TestSceneProfileSubsectionHeader.cs | 4 +--- 11 files changed, 5 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs index d884c0cf14..28f0e6ff9c 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneHistoricalSection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs index e753632474..84497245db 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneKudosuHistory.cs @@ -1,8 +1,6 @@ // Copyright (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.Overlays.Profile.Sections.Kudosu; using System.Collections.Generic; using System; diff --git a/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs index e81b7a2ac8..a4d8238fa3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneProfileRulesetSelector.cs @@ -1,8 +1,6 @@ // Copyright (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.Overlays.Profile.Header.Components; using osu.Game.Rulesets.Catch; @@ -24,7 +22,7 @@ namespace osu.Game.Tests.Visual.Online public TestSceneProfileRulesetSelector() { ProfileRulesetSelector selector; - var user = new Bindable(); + var user = new Bindable(); Child = selector = new ProfileRulesetSelector { diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs index d93bf059dd..454242270d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserHistoryGraph.cs @@ -1,8 +1,6 @@ // Copyright (c) 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.Tests/Visual/Online/TestSceneUserProfileHeader.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs index 75743d788a..bfd6372e6f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileHeader.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using NUnit.Framework; @@ -20,7 +18,7 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); - private ProfileHeader header; + private ProfileHeader header = null!; [SetUpSteps] public void SetUpSteps() diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs index 02d01b4a46..35c7e7bf28 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileOverlay.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using System.Linq; using NUnit.Framework; diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs index fcefb31716..921738d331 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using NUnit.Framework; using osu.Framework.Graphics; @@ -14,7 +12,7 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public partial class TestSceneUserProfilePreviousUsernames : OsuTestScene { - private PreviousUsernames container; + private PreviousUsernames container = null!; [SetUp] public void SetUp() => Schedule(() => @@ -50,7 +48,7 @@ namespace osu.Game.Tests.Visual.Online AddUntilStep("Is hidden", () => container.Alpha == 0); } - private static readonly APIUser[] users = + private static readonly APIUser?[] users = { new APIUser { Id = 1, PreviousUsernames = new[] { "username1" } }, new APIUser { Id = 2, PreviousUsernames = new[] { "longusername", "longerusername" } }, diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs index f8432118d4..9d0ea80533 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileRecentSection.cs @@ -1,8 +1,6 @@ // Copyright (c) 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.Tests/Visual/Online/TestSceneUserProfileScores.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs index 6f0ef10911..5249e8694d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs @@ -1,8 +1,6 @@ // Copyright (c) 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.Tests/Visual/Online/TestSceneUserRanks.cs b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs index ef3a677efc..fdbd8a4325 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserRanks.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs index b4b45da133..c51095f360 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneProfileSubsectionHeader.cs @@ -1,8 +1,6 @@ // Copyright (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.Overlays.Profile.Sections; using osu.Framework.Testing; @@ -18,7 +16,7 @@ namespace osu.Game.Tests.Visual.UserInterface [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); - private ProfileSubsectionHeader header; + private ProfileSubsectionHeader header = null!; [Test] public void TestHiddenCounter() From 8f7ae0395a082eda458009ccdaf3504675dca9fa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Jan 2023 00:55:05 +0800 Subject: [PATCH 348/384] Fix song select leaderboard potentially showing wrong scores on quick beatmap changes Closes #22002. --- .../Select/Leaderboards/BeatmapLeaderboard.cs | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index b8a2eec526..9e8247059d 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -154,10 +154,17 @@ namespace osu.Game.Screens.Select.Leaderboards scoreRetrievalRequest = new GetScoresRequest(fetchBeatmapInfo, fetchRuleset, Scope, requestMods); - scoreRetrievalRequest.Success += response => SetScores( - scoreManager.OrderByTotalScore(response.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo))), - response.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo) - ); + scoreRetrievalRequest.Success += response => Schedule(() => + { + // Beatmap may have changed since fetch request. Can't rely on request cancellation due to Schedule inside SetScores. + if (!fetchBeatmapInfo.Equals(BeatmapInfo)) + return; + + SetScores( + scoreManager.OrderByTotalScore(response.Scores.Select(s => s.ToScoreInfo(rulesets, fetchBeatmapInfo))), + response.UserScore?.CreateScoreInfo(rulesets, fetchBeatmapInfo) + ); + }); return scoreRetrievalRequest; } From ac85433178f9f61992e6a2239a3e783fca9f76ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Jan 2023 09:44:34 +0800 Subject: [PATCH 349/384] Fix default volume control keys not working when chat textbox is focused Closes #22004. --- osu.Game/Graphics/UserInterface/HistoryTextBox.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs index d74a4f2cdb..b6dc1fcc9b 100644 --- a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs +++ b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs @@ -45,6 +45,9 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnKeyDown(KeyDownEvent e) { + if (e.ControlPressed || e.AltPressed || e.SuperPressed || e.ShiftPressed) + return false; + switch (e.Key) { case Key.Up: From 4491a5ba9f7c38010600bcc4db4c358cdc6c7c9e Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 3 Jan 2023 13:41:08 +0300 Subject: [PATCH 350/384] Fix editor exporting beatmap combo colours in wrong order --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 03c63ff4f2..7e058d755e 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -300,7 +300,7 @@ namespace osu.Game.Beatmaps.Formats { var comboColour = colours[i]; - writer.Write(FormattableString.Invariant($"Combo{i}: ")); + writer.Write(FormattableString.Invariant($"Combo{1 + i}: ")); writer.Write(FormattableString.Invariant($"{(byte)(comboColour.R * byte.MaxValue)},")); writer.Write(FormattableString.Invariant($"{(byte)(comboColour.G * byte.MaxValue)},")); writer.Write(FormattableString.Invariant($"{(byte)(comboColour.B * byte.MaxValue)},")); From efbd9ba4b90946909faa4a07024484e38cd15001 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 3 Jan 2023 15:28:05 +0300 Subject: [PATCH 351/384] Fix catcher not moving when fully hidden in "No Scope" mod --- osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs index 19b4a39f97..ddeea51ecb 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModNoScope.cs @@ -26,6 +26,9 @@ namespace osu.Game.Rulesets.Catch.Mods var catchPlayfield = (CatchPlayfield)playfield; bool shouldAlwaysShowCatcher = IsBreakTime.Value; float targetAlpha = shouldAlwaysShowCatcher ? 1 : ComboBasedAlpha; + + // AlwaysPresent required for catcher to still act on input when fully hidden. + catchPlayfield.CatcherArea.AlwaysPresent = true; catchPlayfield.CatcherArea.Alpha = (float)Interpolation.Lerp(catchPlayfield.CatcherArea.Alpha, targetAlpha, Math.Clamp(catchPlayfield.Time.Elapsed / TRANSITION_DURATION, 0, 1)); } } From 71125afcb45365a09424a7348ef20d51dc2a2392 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 3 Jan 2023 15:43:36 +0300 Subject: [PATCH 352/384] Add test coverage --- .../Mods/TestSceneCatchModNoScope.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs index 50b928d509..c48bf7adc9 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModNoScope.cs @@ -18,6 +18,36 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods { protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); + [Test] + public void TestAlwaysHidden() + { + CreateModTest(new ModTestData + { + Mod = new CatchModNoScope + { + HiddenComboCount = { Value = 0 }, + }, + Autoplay = true, + PassCondition = () => Player.ScoreProcessor.Combo.Value == 2, + Beatmap = new Beatmap + { + HitObjects = new List + { + new Fruit + { + X = CatchPlayfield.CENTER_X * 0.5f, + StartTime = 1000, + }, + new Fruit + { + X = CatchPlayfield.CENTER_X * 1.5f, + StartTime = 2000, + } + } + } + }); + } + [Test] public void TestVisibleDuringBreak() { From 96e81e7f41e825e2ed631a97ab212f13ec30b259 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Jan 2023 01:41:21 +0800 Subject: [PATCH 353/384] Switch on NRT and add `IEquatable` to `GetScoresRequest` --- .../Online/API/Requests/GetScoresRequest.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 7d1d26b75d..f2a2daccb5 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System; using osu.Game.Beatmaps; using osu.Game.Rulesets; @@ -11,10 +9,11 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Mods; using System.Text; using System.Collections.Generic; +using System.Linq; namespace osu.Game.Online.API.Requests { - public class GetScoresRequest : APIRequest + public class GetScoresRequest : APIRequest, IEquatable { public const int MAX_SCORES_PER_REQUEST = 50; @@ -23,7 +22,7 @@ namespace osu.Game.Online.API.Requests private readonly IRulesetInfo ruleset; private readonly IEnumerable mods; - public GetScoresRequest(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null) + public GetScoresRequest(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable? mods = null) { if (beatmapInfo.OnlineID <= 0) throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(IBeatmapInfo.OnlineID)}."); @@ -51,5 +50,16 @@ namespace osu.Game.Online.API.Requests return query.ToString(); } + + public bool Equals(GetScoresRequest? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return beatmapInfo.Equals(other.beatmapInfo) + && scope == other.scope + && ruleset.Equals(other.ruleset) + && mods.SequenceEqual(other.mods); + } } } From beb3b96aca7366fa812d436103ffbdfa5b682eee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Jan 2023 01:44:00 +0800 Subject: [PATCH 354/384] Harden request equality checks --- .../Select/Leaderboards/BeatmapLeaderboard.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 9e8247059d..c419501add 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -152,12 +152,14 @@ namespace osu.Game.Screens.Select.Leaderboards else if (filterMods) requestMods = mods.Value; - scoreRetrievalRequest = new GetScoresRequest(fetchBeatmapInfo, fetchRuleset, Scope, requestMods); + scoreRetrievalRequest?.Cancel(); - scoreRetrievalRequest.Success += response => Schedule(() => + var newRequest = new GetScoresRequest(fetchBeatmapInfo, fetchRuleset, Scope, requestMods); + newRequest.Success += response => Schedule(() => { - // Beatmap may have changed since fetch request. Can't rely on request cancellation due to Schedule inside SetScores. - if (!fetchBeatmapInfo.Equals(BeatmapInfo)) + // Request may have changed since fetch request. + // Can't rely on request cancellation due to Schedule inside SetScores so let's play it safe. + if (!newRequest.Equals(scoreRetrievalRequest)) return; SetScores( @@ -166,7 +168,7 @@ namespace osu.Game.Screens.Select.Leaderboards ); }); - return scoreRetrievalRequest; + return scoreRetrievalRequest = newRequest; } protected override LeaderboardScore CreateDrawableScore(ScoreInfo model, int index) => new LeaderboardScore(model, index, IsOnlineScope) From 49b098407983ec0e39a2b8e19c2df469fe0ba703 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 2 Jan 2023 21:53:58 -0800 Subject: [PATCH 355/384] Add failing playlist no selection after deleted test --- .../TestScenePlaylistsRoomSettingsPlaylist.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs index 91e9ce5ea2..ae27db0dd1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -46,10 +47,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("item removed", () => !playlist.Items.Contains(selectedItem)); } - [Test] - public void TestNextItemSelectedAfterDeletion() + [TestCase(true)] + [TestCase(false)] + public void TestNextItemSelectedAfterDeletion(bool allowSelection) { - createPlaylist(); + createPlaylist(p => + { + p.AllowSelection = allowSelection; + }); moveToItem(0); AddStep("click", () => InputManager.Click(MouseButton.Left)); @@ -57,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer moveToDeleteButton(0); AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); - AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]); + AddAssert("item 0 is " + (allowSelection ? "selected" : "not selected"), () => playlist.SelectedItem.Value == (allowSelection ? playlist.Items[0] : null)); } [Test] @@ -117,7 +122,7 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset); }); - private void createPlaylist() + private void createPlaylist(Action setupPlaylist = null) { AddStep("create playlist", () => { @@ -154,6 +159,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); } + + setupPlaylist?.Invoke(playlist); }); AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); From 5dfd4180c84c09bce3ef25803d66ab0168377842 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 2 Jan 2023 21:22:24 -0800 Subject: [PATCH 356/384] Fix playlist selecting random item when not allowed after deleting --- .../OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs index df502ae09c..736f09584b 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs @@ -24,7 +24,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Items.Remove(item); - SelectedItem.Value = nextItem ?? Items.LastOrDefault(); + if (AllowSelection && SelectedItem.Value == item) + SelectedItem.Value = nextItem ?? Items.LastOrDefault(); }; } } From 5fb6f220e69437c87224e4ae13c4da6205c1f87d Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Mon, 2 Jan 2023 21:21:27 -0800 Subject: [PATCH 357/384] Fix playlist items not animating when rearranging --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index f5477837b0..8abdec9ade 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -156,6 +156,8 @@ namespace osu.Game.Screens.OnlinePlay protected override FillFlowContainer> CreateListFillFlowContainer() => new FillFlowContainer> { + LayoutDuration = 200, + LayoutEasing = Easing.OutQuint, Spacing = new Vector2(0, 2) }; From d70df08f45bb4573bb28ba866036a3cf9527589a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 4 Jan 2023 16:18:33 +0300 Subject: [PATCH 358/384] Tint "argon" slider ball with combo colour --- .../Skinning/Argon/ArgonSliderBall.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs index 48b43f359d..d6ce793c7e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonSliderBall.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; @@ -21,6 +23,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon private readonly Vector2 defaultIconScale = new Vector2(0.6f, 0.8f); + private readonly IBindable accentColour = new Bindable(); + [Resolved(canBeNull: true)] private DrawableHitObject? parentObject { get; set; } @@ -37,7 +41,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon { fill = new Box { - Colour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")), RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -53,10 +56,22 @@ namespace osu.Game.Rulesets.Osu.Skinning.Argon }; } + [BackgroundDependencyLoader] + private void load() + { + if (parentObject != null) + accentColour.BindTo(parentObject.AccentColour); + } + protected override void LoadComplete() { base.LoadComplete(); + accentColour.BindValueChanged(colour => + { + fill.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.5f)); + }, true); + if (parentObject != null) { parentObject.ApplyCustomUpdateState += updateStateTransforms; From 760b2d98df1908064a89b54cffcec2befecff943 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 4 Jan 2023 16:19:30 +0300 Subject: [PATCH 359/384] Tint "argon" slider follow circle with combo colour --- .../Skinning/Argon/ArgonFollowCircle.cs | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs index 95c75164aa..fca3e70236 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Argon/ArgonFollowCircle.cs @@ -1,35 +1,64 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.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.Utils; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Skinning.Argon { public partial class ArgonFollowCircle : FollowCircle { + private readonly CircularContainer circleContainer; + private readonly Box circleFill; + + private readonly IBindable accentColour = new Bindable(); + + [Resolved(canBeNull: true)] + private DrawableHitObject? parentObject { get; set; } + public ArgonFollowCircle() { - InternalChild = new CircularContainer + InternalChild = circleContainer = new CircularContainer { RelativeSizeAxes = Axes.Both, Masking = true, BorderThickness = 4, - BorderColour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")), Blending = BlendingParameters.Additive, - Child = new Box + Child = circleFill = new Box { - Colour = ColourInfo.GradientVertical(Colour4.FromHex("FC618F"), Colour4.FromHex("BB1A41")), RelativeSizeAxes = Axes.Both, Alpha = 0.3f, } }; } + [BackgroundDependencyLoader] + private void load() + { + if (parentObject != null) + accentColour.BindTo(parentObject.AccentColour); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + accentColour.BindValueChanged(colour => + { + circleContainer.BorderColour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.5f)); + circleFill.Colour = ColourInfo.GradientVertical(colour.NewValue, colour.NewValue.Darken(0.5f)); + }, true); + } + protected override void OnSliderPress() { const float duration = 300f; From 8da7667b0b84392004fe23ada51191b69d6ebb7a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 5 Jan 2023 14:06:24 +0300 Subject: [PATCH 360/384] Decrease transition duration of extended difficulty list during hide --- .../Drawables/Cards/BeatmapCardContent.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs index d4cbe6ddd0..7deb5f768c 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs @@ -138,9 +138,18 @@ namespace osu.Game.Beatmaps.Drawables.Cards // This avoids depth issues where a hovered (scaled) card to the right of another card would be beneath the card to the left. this.ScaleTo(Expanded.Value ? 1.03f : 1, 500, Easing.OutQuint); - background.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - dropdownContent.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - borderContainer.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + if (Expanded.Value) + { + background.FadeIn(BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + dropdownContent.FadeIn(BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + borderContainer.FadeIn(BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + } + else + { + background.FadeOut(BeatmapCard.TRANSITION_DURATION / 3f, Easing.OutQuint); + dropdownContent.FadeOut(BeatmapCard.TRANSITION_DURATION / 3f, Easing.OutQuint); + borderContainer.FadeOut(BeatmapCard.TRANSITION_DURATION / 3f, Easing.OutQuint); + } content.TweenEdgeEffectTo(new EdgeEffectParameters { From 7d8aff8f7e75b6f5517eeb91a97cf09a401a8587 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 5 Jan 2023 14:35:57 +0300 Subject: [PATCH 361/384] Fix settings toolbox group not animating on expansion --- osu.Game/Overlays/SettingsToolboxGroup.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/SettingsToolboxGroup.cs b/osu.Game/Overlays/SettingsToolboxGroup.cs index 56c890e9e2..86964e7ed4 100644 --- a/osu.Game/Overlays/SettingsToolboxGroup.cs +++ b/osu.Game/Overlays/SettingsToolboxGroup.cs @@ -126,7 +126,8 @@ namespace osu.Game.Overlays { base.LoadComplete(); - Expanded.BindValueChanged(updateExpandedState, true); + Expanded.BindValueChanged(_ => updateExpandedState(true)); + updateExpandedState(false); this.Delay(600).Schedule(updateFadeState); } @@ -161,21 +162,25 @@ namespace osu.Game.Overlays return base.OnInvalidate(invalidation, source); } - private void updateExpandedState(ValueChangedEvent expanded) + private void updateExpandedState(bool animate) { // clearing transforms is necessary to avoid a previous height transform // potentially continuing to get processed while content has changed to autosize. content.ClearTransforms(); - if (expanded.NewValue) + if (Expanded.Value) + { content.AutoSizeAxes = Axes.Y; + content.AutoSizeDuration = animate ? transition_duration : 0; + content.AutoSizeEasing = Easing.OutQuint; + } else { content.AutoSizeAxes = Axes.None; - content.ResizeHeightTo(0, transition_duration, Easing.OutQuint); + content.ResizeHeightTo(0, animate ? transition_duration : 0, Easing.OutQuint); } - headerContent.FadeColour(expanded.NewValue ? Color4.White : OsuColour.Gray(0.5f), 200, Easing.OutQuint); + headerContent.FadeColour(Expanded.Value ? Color4.White : OsuColour.Gray(0.5f), 200, Easing.OutQuint); } private void updateFadeState() From 86b1164e28b5f33fd0f0f11a03bf6b854ac1235f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 5 Jan 2023 14:41:26 +0300 Subject: [PATCH 362/384] Add visual test case --- .../UserInterface/TestSceneSettingsToolboxGroup.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsToolboxGroup.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsToolboxGroup.cs index 71b98ed9af..f96d2feba8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsToolboxGroup.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneSettingsToolboxGroup.cs @@ -55,6 +55,16 @@ namespace osu.Game.Tests.Visual.UserInterface }; }); + [Test] + public void TestDisplay() + { + AddRepeatStep("toggle expanded state", () => + { + InputManager.MoveMouseTo(group.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }, 5); + } + [Test] public void TestClickExpandButtonMultipleTimes() { From c6e2104ec298647d6a0bb48b86846bbdf6ea7707 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Fri, 6 Jan 2023 03:46:49 +0300 Subject: [PATCH 363/384] Refresh waveforms instead of recreating the whole component --- .../Screens/Edit/Timing/TapTimingControl.cs | 19 ++----------------- .../Edit/Timing/WaveformComparisonDisplay.cs | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs index 0d29b69d96..bb7a3b8be3 100644 --- a/osu.Game/Screens/Edit/Timing/TapTimingControl.cs +++ b/osu.Game/Screens/Edit/Timing/TapTimingControl.cs @@ -3,7 +3,6 @@ using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -30,15 +29,12 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private Bindable selectedGroup { get; set; } = null!; - private readonly IBindable track = new Bindable(); - private readonly BindableBool isHandlingTapping = new BindableBool(); private MetronomeDisplay metronome = null!; - private Container waveformContainer = null!; [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider, OsuColour colours, EditorClock clock) + private void load(OverlayColourProvider colourProvider, OsuColour colours) { const float padding = 10; @@ -92,11 +88,7 @@ namespace osu.Game.Screens.Edit.Timing Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - waveformContainer = new Container - { - RelativeSizeAxes = Axes.Both, - Child = new WaveformComparisonDisplay(), - } + new WaveformComparisonDisplay() } }, } @@ -187,13 +179,6 @@ namespace osu.Game.Screens.Edit.Timing if (handling.NewValue) start(); }, true); - - track.BindTo(clock.Track); - } - - protected override void LoadComplete() - { - track.ValueChanged += _ => waveformContainer.Child = new WaveformComparisonDisplay(); } private void start() diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index 6c17aeed54..b5a1ab29d2 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; @@ -294,13 +295,18 @@ namespace osu.Game.Screens.Edit.Timing [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; + [Resolved] + private IBindable beatmap { get; set; } = null!; + + private readonly IBindable track = new Bindable(); + public WaveformRow(bool isMainRow) { this.isMainRow = isMainRow; } [BackgroundDependencyLoader] - private void load(IBindable beatmap) + private void load(EditorClock clock) { InternalChildren = new Drawable[] { @@ -330,6 +336,13 @@ namespace osu.Game.Screens.Edit.Timing Colour = colourProvider.Content2 } }; + + track.BindTo(clock.Track); + } + + protected override void LoadComplete() + { + track.ValueChanged += _ => waveformGraph.Waveform = beatmap.Value.Waveform; } public int BeatIndex { set => beatIndexText.Text = value.ToString(); } From 408356d05e0d2f7a1178c99dc34b66e2b944324d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 6 Jan 2023 11:58:38 +0300 Subject: [PATCH 364/384] Fix gameplay leaderboard showing "-" on non-tracked scores --- osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs | 4 ++-- osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs index f6f289db55..d990af32e7 100644 --- a/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/GameplayLeaderboard.cs @@ -171,13 +171,13 @@ namespace osu.Game.Screens.Play.HUD for (int i = 0; i < Flow.Count; i++) { Flow.SetLayoutPosition(orderedByScore[i], i); - orderedByScore[i].ScorePosition = CheckValidScorePosition(i + 1) ? i + 1 : null; + orderedByScore[i].ScorePosition = CheckValidScorePosition(orderedByScore[i], i + 1) ? i + 1 : null; } sorting.Validate(); } - protected virtual bool CheckValidScorePosition(int i) => true; + protected virtual bool CheckValidScorePosition(GameplayLeaderboardScore score, int position) => true; private partial class InputDisabledScrollContainer : OsuScrollContainer { diff --git a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs index 0f3e54ecdd..9f92880919 100644 --- a/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs +++ b/osu.Game/Screens/Play/HUD/SoloGameplayLeaderboard.cs @@ -100,16 +100,16 @@ namespace osu.Game.Screens.Play.HUD local.DisplayOrder.Value = long.MaxValue; } - protected override bool CheckValidScorePosition(int i) + protected override bool CheckValidScorePosition(GameplayLeaderboardScore score, int position) { // change displayed position to '-' when there are 50 already submitted scores and tracked score is last - if (scoreSource.Value != PlayBeatmapDetailArea.TabType.Local) + if (score.Tracked && scoreSource.Value != PlayBeatmapDetailArea.TabType.Local) { - if (i == Flow.Count && Flow.Count > GetScoresRequest.MAX_SCORES_PER_REQUEST) + if (position == Flow.Count && Flow.Count > GetScoresRequest.MAX_SCORES_PER_REQUEST) return false; } - return base.CheckValidScorePosition(i); + return base.CheckValidScorePosition(score, position); } private void updateVisibility() => From 458fe382ed51b110fc476013c7db32fe8b69926b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Jan 2023 20:06:41 +0900 Subject: [PATCH 365/384] Make selected tab items more bold --- osu.Game/Overlays/BeatmapListing/FilterTabItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 7b95ae8ea8..042efb18fb 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -81,7 +81,7 @@ namespace osu.Game.Overlays.BeatmapListing private void updateState() { text.FadeColour(IsHovered ? colourProvider.Light1 : GetStateColour(), 200, Easing.OutQuint); - text.Font = text.Font.With(weight: Active.Value ? FontWeight.SemiBold : FontWeight.Regular); + text.Font = text.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Regular); } protected virtual Color4 GetStateColour() => Active.Value ? colourProvider.Content1 : colourProvider.Light2; From 3c74d27deb556d93c52f7b10f33f7d38c7b5848f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Jan 2023 20:35:58 +0900 Subject: [PATCH 366/384] Also add an underline to better accent current filters --- ...BeatmapSearchMultipleSelectionFilterRow.cs | 23 +++++++++++++++++++ .../Overlays/BeatmapListing/FilterTabItem.cs | 12 +++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index 5d1ccbd58b..c6cd6591a9 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -12,6 +12,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osuTK; @@ -100,9 +101,31 @@ namespace osu.Game.Overlays.BeatmapListing protected partial class MultipleSelectionFilterTabItem : FilterTabItem { + private readonly Box selectedUnderline; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + public MultipleSelectionFilterTabItem(T value) : base(value) { + // This doesn't match any actual design, but should make it easier for the user to understand + // that filters are applied until we settle on a final design. + AddInternal(selectedUnderline = new Box + { + Depth = float.MaxValue, + RelativeSizeAxes = Axes.X, + Height = 1.5f, + Anchor = Anchor.BottomLeft, + Origin = Anchor.CentreLeft, + }); + } + + protected override void UpdateState() + { + base.UpdateState(); + selectedUnderline.FadeTo(Active.Value ? 1 : 0, 200, Easing.OutQuint); + selectedUnderline.FadeColour(IsHovered ? colourProvider.Light1 : GetStateColour(), 200, Easing.OutQuint); } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 7b95ae8ea8..34ade0596d 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -52,33 +52,33 @@ namespace osu.Game.Overlays.BeatmapListing { base.LoadComplete(); - updateState(); + UpdateState(); FinishTransforms(true); } protected override bool OnHover(HoverEvent e) { base.OnHover(e); - updateState(); + UpdateState(); return true; } protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - updateState(); + UpdateState(); } - protected override void OnActivated() => updateState(); + protected override void OnActivated() => UpdateState(); - protected override void OnDeactivated() => updateState(); + protected override void OnDeactivated() => UpdateState(); /// /// Returns the label text to be used for the supplied . /// protected virtual LocalisableString LabelFor(T value) => (value as Enum)?.GetLocalisableDescription() ?? value.ToString(); - private void updateState() + protected virtual void UpdateState() { text.FadeColour(IsHovered ? colourProvider.Light1 : GetStateColour(), 200, Easing.OutQuint); text.Font = text.Font.With(weight: Active.Value ? FontWeight.SemiBold : FontWeight.Regular); From 4319937bc79a1eabc6ffbba40566087938c14323 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 6 Jan 2023 20:35:58 +0900 Subject: [PATCH 367/384] Also add an underline to better accent current filters --- ...BeatmapSearchMultipleSelectionFilterRow.cs | 23 +++++++++++++++++++ .../Overlays/BeatmapListing/FilterTabItem.cs | 12 +++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index 5d1ccbd58b..c6cd6591a9 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -12,6 +12,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osuTK; @@ -100,9 +101,31 @@ namespace osu.Game.Overlays.BeatmapListing protected partial class MultipleSelectionFilterTabItem : FilterTabItem { + private readonly Box selectedUnderline; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + public MultipleSelectionFilterTabItem(T value) : base(value) { + // This doesn't match any actual design, but should make it easier for the user to understand + // that filters are applied until we settle on a final design. + AddInternal(selectedUnderline = new Box + { + Depth = float.MaxValue, + RelativeSizeAxes = Axes.X, + Height = 1.5f, + Anchor = Anchor.BottomLeft, + Origin = Anchor.CentreLeft, + }); + } + + protected override void UpdateState() + { + base.UpdateState(); + selectedUnderline.FadeTo(Active.Value ? 1 : 0, 200, Easing.OutQuint); + selectedUnderline.FadeColour(IsHovered ? colourProvider.Light1 : GetStateColour(), 200, Easing.OutQuint); } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 042efb18fb..9a697890f3 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -52,33 +52,33 @@ namespace osu.Game.Overlays.BeatmapListing { base.LoadComplete(); - updateState(); + UpdateState(); FinishTransforms(true); } protected override bool OnHover(HoverEvent e) { base.OnHover(e); - updateState(); + UpdateState(); return true; } protected override void OnHoverLost(HoverLostEvent e) { base.OnHoverLost(e); - updateState(); + UpdateState(); } - protected override void OnActivated() => updateState(); + protected override void OnActivated() => UpdateState(); - protected override void OnDeactivated() => updateState(); + protected override void OnDeactivated() => UpdateState(); /// /// Returns the label text to be used for the supplied . /// protected virtual LocalisableString LabelFor(T value) => (value as Enum)?.GetLocalisableDescription() ?? value.ToString(); - private void updateState() + protected virtual void UpdateState() { text.FadeColour(IsHovered ? colourProvider.Light1 : GetStateColour(), 200, Easing.OutQuint); text.Font = text.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Regular); From 0ade4d92d1f70a4943c49e06643996c70bc0c010 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 6 Jan 2023 15:13:31 +0300 Subject: [PATCH 368/384] Fix multiple highlighting issues with beatmap listing tab items --- .../BeatmapSearchMultipleSelectionFilterRow.cs | 5 ++--- osu.Game/Overlays/BeatmapListing/FilterTabItem.cs | 10 +++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs index c6cd6591a9..abd2643a41 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchMultipleSelectionFilterRow.cs @@ -103,8 +103,7 @@ namespace osu.Game.Overlays.BeatmapListing { private readonly Box selectedUnderline; - [Resolved] - private OverlayColourProvider colourProvider { get; set; } + protected override bool HighlightOnHoverWhenActive => true; public MultipleSelectionFilterTabItem(T value) : base(value) @@ -125,7 +124,7 @@ namespace osu.Game.Overlays.BeatmapListing { base.UpdateState(); selectedUnderline.FadeTo(Active.Value ? 1 : 0, 200, Easing.OutQuint); - selectedUnderline.FadeColour(IsHovered ? colourProvider.Light1 : GetStateColour(), 200, Easing.OutQuint); + selectedUnderline.FadeColour(IsHovered ? ColourProvider.Content2 : GetStateColour(), 200, Easing.OutQuint); } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs index 34ade0596d..a97b2e16c3 100644 --- a/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs +++ b/osu.Game/Overlays/BeatmapListing/FilterTabItem.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.BeatmapListing public partial class FilterTabItem : TabItem { [Resolved] - private OverlayColourProvider colourProvider { get; set; } + protected OverlayColourProvider ColourProvider { get; private set; } private OsuSpriteText text; @@ -78,12 +78,16 @@ namespace osu.Game.Overlays.BeatmapListing /// protected virtual LocalisableString LabelFor(T value) => (value as Enum)?.GetLocalisableDescription() ?? value.ToString(); + protected virtual bool HighlightOnHoverWhenActive => false; + protected virtual void UpdateState() { - text.FadeColour(IsHovered ? colourProvider.Light1 : GetStateColour(), 200, Easing.OutQuint); + bool highlightHover = IsHovered && (!Active.Value || HighlightOnHoverWhenActive); + + text.FadeColour(highlightHover ? ColourProvider.Content2 : GetStateColour(), 200, Easing.OutQuint); text.Font = text.Font.With(weight: Active.Value ? FontWeight.SemiBold : FontWeight.Regular); } - protected virtual Color4 GetStateColour() => Active.Value ? colourProvider.Content1 : colourProvider.Light2; + protected virtual Color4 GetStateColour() => Active.Value ? ColourProvider.Content1 : ColourProvider.Light2; } } From e90c698e62974d630865fe8962f4926e321ca345 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Fri, 6 Jan 2023 16:35:39 +0300 Subject: [PATCH 369/384] Fix WCD does not take into account start time of control points --- osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs index b5a1ab29d2..3b3acea935 100644 --- a/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs +++ b/osu.Game/Screens/Edit/Timing/WaveformComparisonDisplay.cs @@ -151,7 +151,7 @@ namespace osu.Game.Screens.Edit.Timing if (!displayLocked.Value) { float trackLength = (float)beatmap.Value.Track.Length; - int totalBeatsAvailable = (int)(trackLength / timingPoint.BeatLength); + int totalBeatsAvailable = (int)((trackLength - timingPoint.Time) / timingPoint.BeatLength); Scheduler.AddOnce(showFromBeat, (int)(e.MousePosition.X / DrawWidth * totalBeatsAvailable)); } From 9364c7775d57e18e1abea1b7d5fe0d1416c45f03 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Fri, 6 Jan 2023 19:26:30 +0300 Subject: [PATCH 370/384] Refresh background on file change in editor --- .../Screens/Backgrounds/BackgroundScreenBeatmap.cs | 12 ++++++++++++ osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs index 42a81ad3fa..312fd496a1 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBeatmap.cs @@ -99,6 +99,18 @@ namespace osu.Game.Screens.Backgrounds } } + /// + /// Reloads beatmap's background. + /// + public void RefreshBackground() + { + Schedule(() => + { + cancellationSource?.Cancel(); + LoadComponentAsync(new BeatmapBackground(beatmap), switchBackground, (cancellationSource = new CancellationTokenSource()).Token); + }); + } + private void switchBackground(BeatmapBackground b) { float newDepth = 0; diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index ca0f50cd34..c79d9d6632 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -33,6 +33,9 @@ namespace osu.Game.Screens.Edit.Setup [Resolved] private EditorBeatmap editorBeatmap { get; set; } + [Resolved] + private Editor editor { get; set; } + [Resolved] private SetupScreenHeader header { get; set; } @@ -93,6 +96,8 @@ namespace osu.Game.Screens.Edit.Setup working.Value.Metadata.BackgroundFile = destination.Name; header.Background.UpdateBackground(); + editor.ApplyToBackground(bg => bg.RefreshBackground()); + return true; } From 53d7dcefe5c4d4da6c0a642bf9556e419a3cb2ce Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 5 Jan 2023 14:40:18 -0800 Subject: [PATCH 371/384] Fix osu! logo not being draggable on player loader --- osu.Game/Screens/Menu/OsuLogo.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 2d6a0736e9..7a5a26226b 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -282,7 +282,7 @@ namespace osu.Game.Screens.Menu if (beatIndex < 0) return; - if (IsHovered) + if (Action != null && IsHovered) { this.Delay(early_activation).Schedule(() => { @@ -361,11 +361,11 @@ namespace osu.Game.Screens.Menu } } - public override bool HandlePositionalInput => base.HandlePositionalInput && Action != null && Alpha > 0.2f; + public override bool HandlePositionalInput => base.HandlePositionalInput && Alpha > 0.2f; protected override bool OnMouseDown(MouseDownEvent e) { - if (e.Button != MouseButton.Left) return false; + if (Action == null || e.Button != MouseButton.Left) return false; logoBounceContainer.ScaleTo(0.9f, 1000, Easing.Out); return true; @@ -380,7 +380,9 @@ namespace osu.Game.Screens.Menu protected override bool OnClick(ClickEvent e) { - if (Action?.Invoke() ?? true) + if (Action == null) return false; + + if (Action.Invoke()) sampleClick.Play(); flashLayer.ClearTransforms(); @@ -391,6 +393,8 @@ namespace osu.Game.Screens.Menu protected override bool OnHover(HoverEvent e) { + if (Action == null) return false; + logoHoverContainer.ScaleTo(1.1f, 500, Easing.OutElastic); return true; } From 7f970f3cd846068bb775586f398e8fce73329089 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Thu, 29 Dec 2022 21:17:24 -0800 Subject: [PATCH 372/384] Display nominators on beatmap set overlay --- .../API/Requests/Responses/APIBeatmapSet.cs | 6 ++ .../Responses/BeatmapSetOnlineNominations.cs | 22 +++++++ osu.Game/Overlays/BeatmapSet/Info.cs | 4 ++ .../BeatmapSet/MetadataSectionNominators.cs | 62 +++++++++++++++++++ osu.Game/Overlays/BeatmapSet/MetadataType.cs | 5 +- 5 files changed, 98 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Online/API/Requests/Responses/BeatmapSetOnlineNominations.cs create mode 100644 osu.Game/Overlays/BeatmapSet/MetadataSectionNominators.cs diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index 717a1de6b5..eb39b7489d 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -111,6 +111,12 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"language")] public BeatmapSetOnlineLanguage Language { get; set; } + [JsonProperty(@"current_nominations")] + public BeatmapSetOnlineNominations[] CurrentNominations { get; set; } = Array.Empty(); + + [JsonProperty(@"related_users")] + public APIUser[] RelatedUsers { get; set; } = Array.Empty(); + public string Source { get; set; } = string.Empty; [JsonProperty(@"tags")] diff --git a/osu.Game/Online/API/Requests/Responses/BeatmapSetOnlineNominations.cs b/osu.Game/Online/API/Requests/Responses/BeatmapSetOnlineNominations.cs new file mode 100644 index 0000000000..5d0a3ea38b --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/BeatmapSetOnlineNominations.cs @@ -0,0 +1,22 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Online.API.Requests.Responses +{ + public struct BeatmapSetOnlineNominations + { + [JsonProperty(@"beatmapset_id")] + public int BeatmapsetId { get; set; } + + [JsonProperty(@"reset")] + public bool Reset { get; set; } + + [JsonProperty(@"rulesets")] + public string[]? Rulesets { get; set; } + + [JsonProperty(@"user_id")] + public int UserId { get; set; } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index d184f0d0fd..dbeea74f6f 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -36,6 +37,7 @@ namespace osu.Game.Overlays.BeatmapSet public Info() { + MetadataSectionNominators nominators; MetadataSection source, tags; MetadataSectionGenre genre; MetadataSectionLanguage language; @@ -82,6 +84,7 @@ namespace osu.Game.Overlays.BeatmapSet Direction = FillDirection.Full, Children = new Drawable[] { + nominators = new MetadataSectionNominators(), source = new MetadataSectionSource(), genre = new MetadataSectionGenre { Width = 0.5f }, language = new MetadataSectionLanguage { Width = 0.5f }, @@ -122,6 +125,7 @@ namespace osu.Game.Overlays.BeatmapSet BeatmapSet.ValueChanged += b => { + nominators.Metadata = (b.NewValue?.CurrentNominations ?? Array.Empty(), b.NewValue?.RelatedUsers ?? Array.Empty()); source.Metadata = b.NewValue?.Source ?? string.Empty; tags.Metadata = b.NewValue?.Tags ?? string.Empty; genre.Metadata = b.NewValue?.Genre ?? new BeatmapSetOnlineGenre { Id = (int)SearchGenre.Unspecified }; diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSectionNominators.cs b/osu.Game/Overlays/BeatmapSet/MetadataSectionNominators.cs new file mode 100644 index 0000000000..78a3fe7cd5 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/MetadataSectionNominators.cs @@ -0,0 +1,62 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Resources.Localisation.Web; + +namespace osu.Game.Overlays.BeatmapSet +{ + public partial class MetadataSectionNominators : MetadataSection<(BeatmapSetOnlineNominations[] CurrentNominations, APIUser[] RelatedUsers)> + { + public override (BeatmapSetOnlineNominations[] CurrentNominations, APIUser[] RelatedUsers) Metadata + { + set + { + if (value.CurrentNominations.Length == 0) + { + this.FadeOut(TRANSITION_DURATION); + return; + } + + base.Metadata = value; + } + } + + public MetadataSectionNominators(Action<(BeatmapSetOnlineNominations[] CurrentNominations, APIUser[] RelatedUsers)>? searchAction = null) + : base(MetadataType.Nominators, searchAction) + { + } + + protected override void AddMetadata((BeatmapSetOnlineNominations[] CurrentNominations, APIUser[] RelatedUsers) metadata, LinkFlowContainer loaded) + { + int[] nominatorIds = metadata.CurrentNominations.Select(n => n.UserId).ToArray(); + + int nominatorsFound = 0; + + foreach (int nominatorId in nominatorIds) + { + foreach (var user in metadata.RelatedUsers) + { + if (nominatorId != user.OnlineID) continue; + + nominatorsFound++; + + loaded.AddUserLink(new APIUser + { + Username = user.Username, + Id = nominatorId, + }); + + if (nominatorsFound < nominatorIds.Length) + loaded.AddText(CommonStrings.ArrayAndWordsConnector); + + break; + } + } + } + } +} diff --git a/osu.Game/Overlays/BeatmapSet/MetadataType.cs b/osu.Game/Overlays/BeatmapSet/MetadataType.cs index 924e020641..dc96ce99e9 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataType.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataType.cs @@ -23,6 +23,9 @@ namespace osu.Game.Overlays.BeatmapSet Genre, [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoLanguage))] - Language + Language, + + [LocalisableDescription(typeof(BeatmapsetsStrings), nameof(BeatmapsetsStrings.ShowInfoNominators))] + Nominators, } } From e449d8dda067cc671b648330e6089d3e5f53cf9c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 6 Jan 2023 22:39:46 +0300 Subject: [PATCH 373/384] Fix friends list duplicating on connection failure --- osu.Game/Online/API/APIAccess.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index f2b9b6e968..757f6598e7 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -259,7 +259,11 @@ namespace osu.Game.Online.API var friendsReq = new GetFriendsRequest(); friendsReq.Failure += _ => state.Value = APIState.Failing; - friendsReq.Success += res => friends.AddRange(res); + friendsReq.Success += res => + { + friends.Clear(); + friends.AddRange(res); + }; if (!handleRequest(friendsReq)) { From 167ac8b5dda3b0b4d3bf42512fd1234547c525ef Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 7 Jan 2023 03:03:52 +0300 Subject: [PATCH 374/384] Fix editor object being requered --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index c79d9d6632..8565843170 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using System.IO; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -34,7 +32,7 @@ namespace osu.Game.Screens.Edit.Setup private EditorBeatmap editorBeatmap { get; set; } [Resolved] - private Editor editor { get; set; } + private Editor? editor { get; set; } [Resolved] private SetupScreenHeader header { get; set; } @@ -96,7 +94,7 @@ namespace osu.Game.Screens.Edit.Setup working.Value.Metadata.BackgroundFile = destination.Name; header.Background.UpdateBackground(); - editor.ApplyToBackground(bg => bg.RefreshBackground()); + editor?.ApplyToBackground(bg => bg.RefreshBackground()); return true; } From b47cef838c689f551abbd5bcf1dc1f9b132c6173 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 7 Jan 2023 03:08:02 +0300 Subject: [PATCH 375/384] Nullability --- .../Screens/Edit/Setup/ResourcesSection.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 8565843170..901101dcec 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -14,28 +14,28 @@ namespace osu.Game.Screens.Edit.Setup { internal partial class ResourcesSection : SetupSection { - private LabelledFileChooser audioTrackChooser; - private LabelledFileChooser backgroundChooser; + private LabelledFileChooser audioTrackChooser = null!; + private LabelledFileChooser backgroundChooser = null!; public override LocalisableString Title => EditorSetupStrings.ResourcesHeader; [Resolved] - private MusicController music { get; set; } + private MusicController music { get; set; } = null!; [Resolved] - private BeatmapManager beatmaps { get; set; } + private BeatmapManager beatmaps { get; set; } = null!; [Resolved] - private IBindable working { get; set; } + private IBindable working { get; set; } = null!; [Resolved] - private EditorBeatmap editorBeatmap { get; set; } + private EditorBeatmap editorBeatmap { get; set; } = null!; [Resolved] private Editor? editor { get; set; } [Resolved] - private SetupScreenHeader header { get; set; } + private SetupScreenHeader header { get; set; } = null!; [BackgroundDependencyLoader] private void load() @@ -128,17 +128,17 @@ namespace osu.Game.Screens.Edit.Setup return true; } - private void backgroundChanged(ValueChangedEvent file) + private void backgroundChanged(ValueChangedEvent file) { - if (!ChangeBackgroundImage(file.NewValue)) + if (!ChangeBackgroundImage(file.NewValue!)) backgroundChooser.Current.Value = file.OldValue; updatePlaceholderText(); } - private void audioTrackChanged(ValueChangedEvent file) + private void audioTrackChanged(ValueChangedEvent file) { - if (!ChangeAudioTrack(file.NewValue)) + if (!ChangeAudioTrack(file.NewValue!)) audioTrackChooser.Current.Value = file.OldValue; updatePlaceholderText(); From 904c76e437114a214ec5fcc649066ad4daadf30f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 7 Jan 2023 14:23:31 +0300 Subject: [PATCH 376/384] Use sane `BeatmapInfo` for preview time mutation `EditorBeatmap.BeatmapInfo` is usually the correct instance for mutating properties that should persist in the database. --- osu.Game/Screens/Edit/EditorBeatmap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 1684dcf0cd..dc1fda13f4 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -110,11 +110,11 @@ namespace osu.Game.Screens.Edit foreach (var obj in HitObjects) trackStartTime(obj); - PreviewTime = new BindableInt(playableBeatmap.Metadata.PreviewTime); + PreviewTime = new BindableInt(BeatmapInfo.Metadata.PreviewTime); PreviewTime.BindValueChanged(s => { BeginChange(); - this.beatmapInfo.Metadata.PreviewTime = s.NewValue; + BeatmapInfo.Metadata.PreviewTime = s.NewValue; EndChange(); }); } From abca13eb6cb934dc81f3791f8352ee4ac1ef0a95 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 7 Jan 2023 14:30:01 +0300 Subject: [PATCH 377/384] Rewrite visualisation piece to bind once and without potential event leak --- .../Visual/Editing/TestScenePreviewTime.cs | 4 ++-- .../Summary/Parts/PreviewTimePart.cs | 20 +++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs b/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs index d5aa71095b..3319788c8a 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePreviewTime.cs @@ -27,9 +27,9 @@ namespace osu.Game.Tests.Visual.Editing public void TestScenePreviewTimeline() { AddStep("set preview time to -1", () => EditorBeatmap.PreviewTime.Value = -1); - AddAssert("preview time line should not show", () => Editor.ChildrenOfType().Single().Alpha == 0); + AddAssert("preview time line should not show", () => !Editor.ChildrenOfType().Single().Children.Any()); AddStep("set preview time to 1000", () => EditorBeatmap.PreviewTime.Value = 1000); - AddAssert("preview time line should show", () => Editor.ChildrenOfType().Single().Alpha == 1); + AddAssert("preview time line should show", () => Editor.ChildrenOfType().Single().Children.Single().Alpha == 1); } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs index de7f611424..c63bb7ac24 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/PreviewTimePart.cs @@ -10,24 +10,28 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { public partial class PreviewTimePart : TimelinePart { + private readonly BindableInt previewTime = new BindableInt(); + protected override void LoadBeatmap(EditorBeatmap beatmap) { base.LoadBeatmap(beatmap); - Add(new PreviewTimeVisualisation(beatmap)); - beatmap.PreviewTime.BindValueChanged(s => + + previewTime.UnbindAll(); + previewTime.BindTo(beatmap.PreviewTime); + previewTime.BindValueChanged(t => { - Alpha = s.NewValue == -1 ? 0 : 1; + Clear(); + + if (t.NewValue >= 0) + Add(new PreviewTimeVisualisation(t.NewValue)); }, true); } private partial class PreviewTimeVisualisation : PointVisualisation { - private readonly BindableInt previewTime = new BindableInt(); - - public PreviewTimeVisualisation(EditorBeatmap editorBeatmap) + public PreviewTimeVisualisation(double time) + : base(time) { - previewTime.BindTo(editorBeatmap.PreviewTime); - previewTime.BindValueChanged(s => X = s.NewValue, true); } [BackgroundDependencyLoader] From aaeb43fbb2f48ce1f364c9ffd267abddcdcb3898 Mon Sep 17 00:00:00 2001 From: ansel <79257300125@ya.ru> Date: Sat, 7 Jan 2023 18:15:57 +0300 Subject: [PATCH 378/384] Return older file if new one is null --- osu.Game/Screens/Edit/Setup/ResourcesSection.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs index 901101dcec..8c84ad90ba 100644 --- a/osu.Game/Screens/Edit/Setup/ResourcesSection.cs +++ b/osu.Game/Screens/Edit/Setup/ResourcesSection.cs @@ -130,7 +130,7 @@ namespace osu.Game.Screens.Edit.Setup private void backgroundChanged(ValueChangedEvent file) { - if (!ChangeBackgroundImage(file.NewValue!)) + if (file.NewValue == null || !ChangeBackgroundImage(file.NewValue)) backgroundChooser.Current.Value = file.OldValue; updatePlaceholderText(); @@ -138,7 +138,7 @@ namespace osu.Game.Screens.Edit.Setup private void audioTrackChanged(ValueChangedEvent file) { - if (!ChangeAudioTrack(file.NewValue!)) + if (file.NewValue == null || !ChangeAudioTrack(file.NewValue)) audioTrackChooser.Current.Value = file.OldValue; updatePlaceholderText(); From 0f6735564eeefca5ea9ef14dd85771da117b3366 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 7 Jan 2023 10:54:48 -0800 Subject: [PATCH 379/384] Move and rename nomination response model to singular --- .../BeatmapSetOnlineNomination.cs} | 4 ++-- osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs | 2 +- osu.Game/Overlays/BeatmapSet/Info.cs | 2 +- .../Overlays/BeatmapSet/MetadataSectionNominators.cs | 9 +++++---- 4 files changed, 9 insertions(+), 8 deletions(-) rename osu.Game/{Online/API/Requests/Responses/BeatmapSetOnlineNominations.cs => Beatmaps/BeatmapSetOnlineNomination.cs} (84%) diff --git a/osu.Game/Online/API/Requests/Responses/BeatmapSetOnlineNominations.cs b/osu.Game/Beatmaps/BeatmapSetOnlineNomination.cs similarity index 84% rename from osu.Game/Online/API/Requests/Responses/BeatmapSetOnlineNominations.cs rename to osu.Game/Beatmaps/BeatmapSetOnlineNomination.cs index 5d0a3ea38b..f6de414628 100644 --- a/osu.Game/Online/API/Requests/Responses/BeatmapSetOnlineNominations.cs +++ b/osu.Game/Beatmaps/BeatmapSetOnlineNomination.cs @@ -3,9 +3,9 @@ using Newtonsoft.Json; -namespace osu.Game.Online.API.Requests.Responses +namespace osu.Game.Beatmaps { - public struct BeatmapSetOnlineNominations + public struct BeatmapSetOnlineNomination { [JsonProperty(@"beatmapset_id")] public int BeatmapsetId { get; set; } diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index eb39b7489d..fc740f1eec 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -112,7 +112,7 @@ namespace osu.Game.Online.API.Requests.Responses public BeatmapSetOnlineLanguage Language { get; set; } [JsonProperty(@"current_nominations")] - public BeatmapSetOnlineNominations[] CurrentNominations { get; set; } = Array.Empty(); + public BeatmapSetOnlineNomination[] CurrentNominations { get; set; } = Array.Empty(); [JsonProperty(@"related_users")] public APIUser[] RelatedUsers { get; set; } = Array.Empty(); diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index dbeea74f6f..58739eb471 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -125,7 +125,7 @@ namespace osu.Game.Overlays.BeatmapSet BeatmapSet.ValueChanged += b => { - nominators.Metadata = (b.NewValue?.CurrentNominations ?? Array.Empty(), b.NewValue?.RelatedUsers ?? Array.Empty()); + nominators.Metadata = (b.NewValue?.CurrentNominations ?? Array.Empty(), b.NewValue?.RelatedUsers ?? Array.Empty()); source.Metadata = b.NewValue?.Source ?? string.Empty; tags.Metadata = b.NewValue?.Tags ?? string.Empty; genre.Metadata = b.NewValue?.Genre ?? new BeatmapSetOnlineGenre { Id = (int)SearchGenre.Unspecified }; diff --git a/osu.Game/Overlays/BeatmapSet/MetadataSectionNominators.cs b/osu.Game/Overlays/BeatmapSet/MetadataSectionNominators.cs index 78a3fe7cd5..76dbda3d5e 100644 --- a/osu.Game/Overlays/BeatmapSet/MetadataSectionNominators.cs +++ b/osu.Game/Overlays/BeatmapSet/MetadataSectionNominators.cs @@ -4,15 +4,16 @@ using System; using System.Linq; using osu.Framework.Graphics; +using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.BeatmapSet { - public partial class MetadataSectionNominators : MetadataSection<(BeatmapSetOnlineNominations[] CurrentNominations, APIUser[] RelatedUsers)> + public partial class MetadataSectionNominators : MetadataSection<(BeatmapSetOnlineNomination[] CurrentNominations, APIUser[] RelatedUsers)> { - public override (BeatmapSetOnlineNominations[] CurrentNominations, APIUser[] RelatedUsers) Metadata + public override (BeatmapSetOnlineNomination[] CurrentNominations, APIUser[] RelatedUsers) Metadata { set { @@ -26,12 +27,12 @@ namespace osu.Game.Overlays.BeatmapSet } } - public MetadataSectionNominators(Action<(BeatmapSetOnlineNominations[] CurrentNominations, APIUser[] RelatedUsers)>? searchAction = null) + public MetadataSectionNominators(Action<(BeatmapSetOnlineNomination[] CurrentNominations, APIUser[] RelatedUsers)>? searchAction = null) : base(MetadataType.Nominators, searchAction) { } - protected override void AddMetadata((BeatmapSetOnlineNominations[] CurrentNominations, APIUser[] RelatedUsers) metadata, LinkFlowContainer loaded) + protected override void AddMetadata((BeatmapSetOnlineNomination[] CurrentNominations, APIUser[] RelatedUsers) metadata, LinkFlowContainer loaded) { int[] nominatorIds = metadata.CurrentNominations.Select(n => n.UserId).ToArray(); From 9d32fde5928f1c77804090c3c1802397b58fa036 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 7 Jan 2023 11:04:42 -0800 Subject: [PATCH 380/384] Mark current nominations and related users as nullable --- osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index fc740f1eec..aeae3edde2 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -112,10 +112,10 @@ namespace osu.Game.Online.API.Requests.Responses public BeatmapSetOnlineLanguage Language { get; set; } [JsonProperty(@"current_nominations")] - public BeatmapSetOnlineNomination[] CurrentNominations { get; set; } = Array.Empty(); + public BeatmapSetOnlineNomination[]? CurrentNominations { get; set; } [JsonProperty(@"related_users")] - public APIUser[] RelatedUsers { get; set; } = Array.Empty(); + public APIUser[]? RelatedUsers { get; set; } public string Source { get; set; } = string.Empty; From bae3a6851bf256040aad4f4ab07af15437aec0ca Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 7 Jan 2023 11:15:51 -0800 Subject: [PATCH 381/384] Block hover events behind logo even if action is null --- osu.Game/Screens/Menu/OsuLogo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 7a5a26226b..e95d0384ba 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -393,7 +393,7 @@ namespace osu.Game.Screens.Menu protected override bool OnHover(HoverEvent e) { - if (Action == null) return false; + if (Action == null) return true; logoHoverContainer.ScaleTo(1.1f, 500, Easing.OutElastic); return true; From 750b55d9b7101e22473249688c7ff41d4971abb0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 8 Jan 2023 22:28:19 +0900 Subject: [PATCH 382/384] Update other events to have matching return types (and animate more) --- osu.Game/Screens/Menu/OsuLogo.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index e95d0384ba..9430a1cda8 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -365,7 +365,7 @@ namespace osu.Game.Screens.Menu protected override bool OnMouseDown(MouseDownEvent e) { - if (Action == null || e.Button != MouseButton.Left) return false; + if (e.Button != MouseButton.Left) return true; logoBounceContainer.ScaleTo(0.9f, 1000, Easing.Out); return true; @@ -380,22 +380,21 @@ namespace osu.Game.Screens.Menu protected override bool OnClick(ClickEvent e) { - if (Action == null) return false; - - if (Action.Invoke()) - sampleClick.Play(); - flashLayer.ClearTransforms(); flashLayer.Alpha = 0.4f; flashLayer.FadeOut(1500, Easing.OutExpo); + + if (Action?.Invoke() == true) + sampleClick.Play(); + return true; } protected override bool OnHover(HoverEvent e) { - if (Action == null) return true; + if (Action != null) + logoHoverContainer.ScaleTo(1.1f, 500, Easing.OutElastic); - logoHoverContainer.ScaleTo(1.1f, 500, Easing.OutElastic); return true; } From f6b1dfc7b0f0f390e9fa9733aa528847a7fa648d Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sat, 28 May 2022 21:41:23 -0700 Subject: [PATCH 383/384] Fix channel listing items overflowing at high ui scaling --- .../Chat/Listing/ChannelListingItem.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs index 22a3bdc06f..9c85c73ee4 100644 --- a/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs +++ b/osu.Game/Overlays/Chat/Listing/ChannelListingItem.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Chat.Listing private Box hoverBox = null!; private SpriteIcon checkbox = null!; private OsuSpriteText channelText = null!; - private OsuSpriteText topicText = null!; + private OsuTextFlowContainer topicText = null!; private IBindable channelJoined = null!; [Resolved] @@ -65,8 +65,8 @@ namespace osu.Game.Overlays.Chat.Listing Masking = true; CornerRadius = 5; - RelativeSizeAxes = Axes.X; - Height = 20 + (vertical_margin * 2); + RelativeSizeAxes = Content.RelativeSizeAxes = Axes.X; + AutoSizeAxes = Content.AutoSizeAxes = Axes.Y; Children = new Drawable[] { @@ -79,14 +79,19 @@ namespace osu.Game.Overlays.Chat.Listing }, new GridContainer { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, ColumnDimensions = new[] { new Dimension(GridSizeMode.Absolute, 40), new Dimension(GridSizeMode.Absolute, 200), - new Dimension(GridSizeMode.Absolute, 400), + new Dimension(maxSize: 400), new Dimension(GridSizeMode.AutoSize), - new Dimension(), + new Dimension(GridSizeMode.Absolute, 50), // enough for any 5 digit user count + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize, minSize: 20 + (vertical_margin * 2)), }, Content = new[] { @@ -108,12 +113,13 @@ namespace osu.Game.Overlays.Chat.Listing Font = OsuFont.Torus.With(size: text_size, weight: FontWeight.SemiBold), Margin = new MarginPadding { Bottom = 2 }, }, - topicText = new OsuSpriteText + topicText = new OsuTextFlowContainer(t => t.Font = OsuFont.Torus.With(size: text_size)) { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Text = Channel.Topic, - Font = OsuFont.Torus.With(size: text_size), Margin = new MarginPadding { Bottom = 2 }, }, new SpriteIcon From 4a77105e78f15b1a72d1b10a61b6dc5e8346ddc1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 9 Jan 2023 16:28:08 +0900 Subject: [PATCH 384/384] Rename `LockPlayfieldAspect` and add comment explaining the change --- osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs | 2 +- osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs | 4 ++-- .../UI/TaikoPlayfieldAdjustmentContainer.cs | 9 +++++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs index f7fdd447d6..3b3b3e606c 100644 --- a/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs +++ b/osu.Game.Rulesets.Taiko/Mods/TaikoModClassic.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Taiko.Mods public void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { drawableTaikoRuleset = (DrawableTaikoRuleset)drawableRuleset; - drawableTaikoRuleset.LockPlayfieldAspect.Value = false; + drawableTaikoRuleset.LockPlayfieldMaxAspect.Value = false; var playfield = (TaikoPlayfield)drawableRuleset.Playfield; playfield.ClassicHitTargetPosition.Value = true; diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs index 32a83d87b8..146daa8c27 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoRuleset.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.UI { public new BindableDouble TimeRange => base.TimeRange; - public readonly BindableBool LockPlayfieldAspect = new BindableBool(true); + public readonly BindableBool LockPlayfieldMaxAspect = new BindableBool(true); protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping; @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Taiko.UI public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new TaikoPlayfieldAdjustmentContainer { - LockPlayfieldAspect = { BindTarget = LockPlayfieldAspect } + LockPlayfieldMaxAspect = { BindTarget = LockPlayfieldMaxAspect } }; protected override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs index d42aaddf9e..42732d90e4 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoPlayfieldAdjustmentContainer.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Taiko.UI private const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768; private const float default_aspect = 16f / 9f; - public readonly IBindable LockPlayfieldAspect = new BindableBool(true); + public readonly IBindable LockPlayfieldMaxAspect = new BindableBool(true); protected override void Update() { @@ -21,7 +21,12 @@ namespace osu.Game.Rulesets.Taiko.UI float height = default_relative_height; - if (LockPlayfieldAspect.Value && Parent.ChildSize.X / Parent.ChildSize.Y > default_aspect) + // Players coming from stable expect to be able to change the aspect ratio regardless of the window size. + // We originally wanted to limit this more, but there was considerable pushback from the community. + // + // As a middle-ground, the aspect ratio can still be adjusted in the downwards direction but has a maximum limit. + // This is still a bit weird, because readability changes with window size, but it is what it is. + if (LockPlayfieldMaxAspect.Value && Parent.ChildSize.X / Parent.ChildSize.Y > default_aspect) height *= Math.Clamp(Parent.ChildSize.X / Parent.ChildSize.Y, 0.4f, 4) / default_aspect; Height = height;