From eecf6ad558983786890f5905370142fcff4d5a75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Nov 2021 21:53:40 +0900 Subject: [PATCH 001/117] Add `IsManaged` helper method to EF classes to match realm implementation --- osu.Game/Beatmaps/BeatmapDifficulty.cs | 2 ++ osu.Game/Beatmaps/BeatmapInfo.cs | 2 ++ osu.Game/Beatmaps/BeatmapMetadata.cs | 2 ++ osu.Game/Beatmaps/BeatmapSetFileInfo.cs | 2 ++ osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 ++ osu.Game/Configuration/DatabasedSetting.cs | 2 ++ osu.Game/Database/IHasPrimaryKey.cs | 2 ++ osu.Game/IO/FileInfo.cs | 2 ++ osu.Game/Scoring/ScoreFileInfo.cs | 2 ++ osu.Game/Scoring/ScoreInfo.cs | 2 ++ osu.Game/Skinning/SkinFileInfo.cs | 2 ++ osu.Game/Skinning/SkinInfo.cs | 2 ++ 12 files changed, 24 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index dfd21469fa..65d1fb8286 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -15,6 +15,8 @@ namespace osu.Game.Beatmaps public int ID { get; set; } + public bool IsManaged => ID > 0; + public float DrainRate { get; set; } = DEFAULT_DIFFICULTY; public float CircleSize { get; set; } = DEFAULT_DIFFICULTY; public float OverallDifficulty { get; set; } = DEFAULT_DIFFICULTY; diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index d2b322a843..7359de0cd7 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -21,6 +21,8 @@ namespace osu.Game.Beatmaps { public int ID { get; set; } + public bool IsManaged => ID > 0; + public int BeatmapVersion; private int? onlineID; diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index b395f16c24..5da0264893 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -20,6 +20,8 @@ namespace osu.Game.Beatmaps { public int ID { get; set; } + public bool IsManaged => ID > 0; + public string Title { get; set; } = string.Empty; [JsonProperty("title_unicode")] diff --git a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs index ce50463f05..29dcf4d6aa 100644 --- a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs @@ -11,6 +11,8 @@ namespace osu.Game.Beatmaps { public int ID { get; set; } + public bool IsManaged => ID > 0; + public int BeatmapSetInfoID { get; set; } public int FileInfoID { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index a0de50a311..db5c3bf15a 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -16,6 +16,8 @@ namespace osu.Game.Beatmaps { public int ID { get; set; } + public bool IsManaged => ID > 0; + private int? onlineID; [Column("OnlineBeatmapSetID")] diff --git a/osu.Game/Configuration/DatabasedSetting.cs b/osu.Game/Configuration/DatabasedSetting.cs index fe1d51d57f..65d9f7799d 100644 --- a/osu.Game/Configuration/DatabasedSetting.cs +++ b/osu.Game/Configuration/DatabasedSetting.cs @@ -11,6 +11,8 @@ namespace osu.Game.Configuration { public int ID { get; set; } + public bool IsManaged => ID > 0; + public int? RulesetID { get; set; } public int? Variant { get; set; } diff --git a/osu.Game/Database/IHasPrimaryKey.cs b/osu.Game/Database/IHasPrimaryKey.cs index 3c0fc94418..51a49948fe 100644 --- a/osu.Game/Database/IHasPrimaryKey.cs +++ b/osu.Game/Database/IHasPrimaryKey.cs @@ -11,5 +11,7 @@ namespace osu.Game.Database [JsonIgnore] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] int ID { get; set; } + + bool IsManaged { get; } } } diff --git a/osu.Game/IO/FileInfo.cs b/osu.Game/IO/FileInfo.cs index 331546f9f8..360f8440f1 100644 --- a/osu.Game/IO/FileInfo.cs +++ b/osu.Game/IO/FileInfo.cs @@ -10,6 +10,8 @@ namespace osu.Game.IO { public int ID { get; set; } + public bool IsManaged => ID > 0; + public string Hash { get; set; } public string StoragePath => Path.Combine(Hash.Remove(1), Hash.Remove(2), Hash); diff --git a/osu.Game/Scoring/ScoreFileInfo.cs b/osu.Game/Scoring/ScoreFileInfo.cs index 9075fdec5b..d98ef9fdc6 100644 --- a/osu.Game/Scoring/ScoreFileInfo.cs +++ b/osu.Game/Scoring/ScoreFileInfo.cs @@ -11,6 +11,8 @@ namespace osu.Game.Scoring { public int ID { get; set; } + public bool IsManaged => ID > 0; + public int FileInfoID { get; set; } public FileInfo FileInfo { get; set; } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 736a939a59..2b02dfef9f 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -22,6 +22,8 @@ namespace osu.Game.Scoring { public int ID { get; set; } + public bool IsManaged => ID > 0; + public ScoreRank Rank { get; set; } public long TotalScore { get; set; } diff --git a/osu.Game/Skinning/SkinFileInfo.cs b/osu.Game/Skinning/SkinFileInfo.cs index 8a7019e1a3..06d0d5e82e 100644 --- a/osu.Game/Skinning/SkinFileInfo.cs +++ b/osu.Game/Skinning/SkinFileInfo.cs @@ -11,6 +11,8 @@ namespace osu.Game.Skinning { public int ID { get; set; } + public bool IsManaged => ID > 0; + public int SkinInfoID { get; set; } public int FileInfoID { get; set; } diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 3b34e23d57..307bb2f9cd 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -18,6 +18,8 @@ namespace osu.Game.Skinning public int ID { get; set; } + public bool IsManaged => ID > 0; + public string Name { get; set; } = string.Empty; public string Creator { get; set; } = string.Empty; From 83b4625bd54cff98c758e82daf4fd8fc5ec27e46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Nov 2021 22:13:07 +0900 Subject: [PATCH 002/117] Replace existing cases with new helper method --- .../Visual/Editing/TestSceneEditorBeatmapCreation.cs | 2 +- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 2 +- osu.Game/Database/ArchiveModelManager.cs | 6 +++--- osu.Game/Overlays/Settings/Sections/SkinSection.cs | 4 ++-- osu.Game/Screens/Select/SongSelect.cs | 4 ++-- osu.Game/Skinning/SkinManager.cs | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index 92c8131568..db20d3c7ba 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.Editing public void TestCreateNewBeatmap() { AddStep("save beatmap", () => Editor.Save()); - AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.ID > 0); + AddAssert("new beatmap persisted", () => EditorBeatmap.BeatmapInfo.IsManaged); AddAssert("new beatmap in database", () => beatmapManager.QueryBeatmapSet(s => s.ID == EditorBeatmap.BeatmapInfo.BeatmapSet.ID)?.DeletePending == false); } diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 5f20bf49b9..17d64bce4d 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -131,7 +131,7 @@ namespace osu.Game.Beatmaps var localRulesetInfo = rulesetInfo as RulesetInfo; // Difficulty can only be computed if the beatmap and ruleset are locally available. - if (localBeatmapInfo == null || localBeatmapInfo.ID == 0 || localRulesetInfo == null) + if (localBeatmapInfo?.IsManaged != true || localRulesetInfo == null) { // If not, fall back to the existing star difficulty (e.g. from an online source). return Task.FromResult(new StarDifficulty(beatmapInfo.StarRating, (beatmapInfo as IBeatmapOnlineInfo)?.MaxCombo ?? 0)); diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index e8b6996869..5f9304448a 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -516,7 +516,7 @@ namespace osu.Game.Database { Files.Dereference(file.FileInfo); - if (file.ID > 0) + if (file.IsManaged) { // This shouldn't be required, but here for safety in case the provided TModel is not being change tracked // Definitely can be removed once we rework the database backend. @@ -545,7 +545,7 @@ namespace osu.Game.Database }); } - if (model.ID > 0) + if (model.IsManaged) Update(model); } @@ -811,7 +811,7 @@ namespace osu.Game.Database /// The usable items present in the store. /// Whether the exists. protected virtual bool CheckLocalAvailability(TModel model, IQueryable items) - => model.ID > 0 && items.Any(i => i.ID == model.ID && i.Files.Any()); + => model.IsManaged && items.Any(i => i.ID == model.ID && i.Files.Any()); /// /// Whether import can be skipped after finding an existing import early in the process. diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 0714b28b47..e17abb5936 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -45,7 +45,7 @@ namespace osu.Game.Overlays.Settings.Sections { get { - int index = skinItems.FindIndex(s => s.ID > 0); + int index = skinItems.FindIndex(s => s.IsManaged); if (index < 0) index = skinItems.Count; @@ -176,7 +176,7 @@ namespace osu.Game.Overlays.Settings.Sections Action = export; currentSkin = skins.CurrentSkin.GetBoundCopy(); - currentSkin.BindValueChanged(skin => Enabled.Value = skin.NewValue.SkinInfo.ID > 0, true); + currentSkin.BindValueChanged(skin => Enabled.Value = skin.NewValue.SkinInfo.IsManaged, true); } private void export() diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 2c36bf5fc8..969a6e0290 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -805,14 +805,14 @@ namespace osu.Game.Screens.Select private void delete(BeatmapSetInfo beatmap) { - if (beatmap == null || beatmap.ID <= 0) return; + if (beatmap == null || !beatmap.IsManaged) return; dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap)); } private void clearScores(BeatmapInfo beatmapInfo) { - if (beatmapInfo == null || beatmapInfo.ID <= 0) return; + if (beatmapInfo == null || !beatmapInfo.IsManaged) return; dialogOverlay?.Push(new BeatmapClearScoresDialog(beatmapInfo, () => // schedule done here rather than inside the dialog as the dialog may fade out and never callback. diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 0739026544..2887d03029 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -307,7 +307,7 @@ namespace osu.Game.Skinning public void Save(Skin skin) { - if (skin.SkinInfo.ID <= 0) + if (!skin.SkinInfo.IsManaged) throw new InvalidOperationException($"Attempting to save a skin which is not yet tracked. Call {nameof(EnsureMutableSkin)} first."); foreach (var drawableInfo in skin.DrawableComponentInfo) From 9157b91e5f2aebb4fcbee2052227a27c781041fa Mon Sep 17 00:00:00 2001 From: GoldenMine0502 Date: Tue, 23 Nov 2021 16:41:20 +0900 Subject: [PATCH 003/117] fix adding wrong values --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 24881d9c47..4f87767fa7 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (strainTime < min_speed_bonus) speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2); - double distance = Math.Min(single_spacing_threshold, osuCurrObj.TravelDistance + osuCurrObj.JumpDistance); + double distance = Math.Min(single_spacing_threshold, osuCurrObj.TravelDistance + osuCurrObj.MovementDistance); return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / strainTime; } From e67d9b1c2153c068a762820b866372818a2c5405 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 24 Nov 2021 12:14:52 +0900 Subject: [PATCH 004/117] Reorder members a bit --- .../Preprocessing/OsuDifficultyHitObject.cs | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index d073d751d0..5c163eeb76 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -18,6 +18,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject; + /// + /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms. + /// + public readonly double StrainTime; + /// /// Normalized distance from the end position of the previous to the start position of this . /// @@ -28,31 +33,26 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing /// public double MovementDistance { get; private set; } - /// - /// Normalized distance between the start and end position of the previous . - /// - public double TravelDistance { get; private set; } - - /// - /// Angle the player has to take to hit this . - /// Calculated as the angle between the circles (current-2, current-1, current). - /// - public double? Angle { get; private set; } - /// /// Milliseconds elapsed since the end time of the previous , with a minimum of 25ms. /// public double MovementTime { get; private set; } + /// + /// Normalized distance between the start and end position of the previous . + /// + public double TravelDistance { get; private set; } + /// /// Milliseconds elapsed since the start time of the previous to the end time of the same previous , with a minimum of 25ms. /// public double TravelTime { get; private set; } /// - /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms. + /// Angle the player has to take to hit this . + /// Calculated as the angle between the circles (current-2, current-1, current). /// - public readonly double StrainTime; + public double? Angle { get; private set; } private readonly OsuHitObject lastLastObject; private readonly OsuHitObject lastObject; From 402de754f7f459fc48fd0d86a88c5f708a03a2c9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 24 Nov 2021 12:37:16 +0900 Subject: [PATCH 005/117] Make TravelDistance/TravelTime apply to the current object --- .../Preprocessing/OsuDifficultyHitObject.cs | 27 ++++++++++--------- .../Difficulty/Skills/Aim.cs | 12 ++++----- .../Difficulty/Skills/Speed.cs | 3 ++- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 5c163eeb76..eb36e96995 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -39,12 +39,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public double MovementTime { get; private set; } /// - /// Normalized distance between the start and end position of the previous . + /// Normalized distance between the start and end position of this . /// public double TravelDistance { get; private set; } /// - /// Milliseconds elapsed since the start time of the previous to the end time of the same previous , with a minimum of 25ms. + /// Milliseconds elapsed between the start and end time of this , with a minimum of 25ms. /// public double TravelTime { get; private set; } @@ -84,15 +84,23 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing scalingFactor *= 1 + smallCircleBonus; } + if (BaseObject is Slider currentSlider) + { + computeSliderCursorPosition(currentSlider); + TravelDistance = currentSlider.LazyTravelDistance; + TravelTime = Math.Max(currentSlider.LazyTravelTime / clockRate, min_delta_time); + } + Vector2 lastCursorPosition = getEndCursorPosition(lastObject); + JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; + MovementTime = StrainTime; + MovementDistance = JumpDistance; if (lastObject is Slider lastSlider) { - computeSliderCursorPosition(lastSlider); - TravelDistance = lastSlider.LazyTravelDistance; - TravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time); - MovementTime = Math.Max(StrainTime - TravelTime, min_delta_time); + double lastTravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time); + MovementTime = Math.Max(MovementTime - lastTravelTime, min_delta_time); // Jump distance from the slider tail to the next object, as opposed to the lazy position of JumpDistance. float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; @@ -102,12 +110,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // In such cases, a leniency is applied by also considering the jump distance from the tail of the slider, and taking the minimum jump distance. // Additional distance is removed based on position of jump relative to slider follow circle radius. // JumpDistance is the leniency distance beyond the assumed_slider_radius. tailJumpDistance is maximum_slider_radius since the full distance of radial leniency is still possible. - MovementDistance = Math.Max(0, Math.Min(JumpDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius)); - } - else - { - MovementTime = StrainTime; - MovementDistance = JumpDistance; + MovementDistance = Math.Max(0, Math.Min(MovementDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius)); } if (lastLastObject != null && !(lastLastObject is Spinner)) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 2a8d2ce759..4ddcbb7770 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (osuLastObj.BaseObject is Slider && withSliders) { double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object - double travelVelocity = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // calculate the slider velocity from slider head to slider end. + double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; // calculate the slider velocity from slider head to slider end. currVelocity = Math.Max(currVelocity, movementVelocity + travelVelocity); // take the larger total combined velocity. } @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (osuLastLastObj.BaseObject is Slider && withSliders) { double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime; - double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; + double travelVelocity = osuLastLastObj.TravelDistance / osuLastLastObj.TravelTime; prevVelocity = Math.Max(prevVelocity, movementVelocity + travelVelocity); } @@ -107,8 +107,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (Math.Max(prevVelocity, currVelocity) != 0) { // We want to use the average velocity over the whole object when awarding differences, not the individual jump and slider path velocities. - prevVelocity = (osuLastObj.JumpDistance + osuLastObj.TravelDistance) / osuLastObj.StrainTime; - currVelocity = (osuCurrObj.JumpDistance + osuCurrObj.TravelDistance) / osuCurrObj.StrainTime; + prevVelocity = (osuLastObj.JumpDistance + osuLastLastObj.TravelDistance) / osuLastObj.StrainTime; + currVelocity = (osuCurrObj.JumpDistance + osuLastObj.TravelDistance) / osuCurrObj.StrainTime; // Scale with ratio of difference compared to 0.5 * max dist. double distRatio = Math.Pow(Math.Sin(Math.PI / 2 * Math.Abs(prevVelocity - currVelocity) / Math.Max(prevVelocity, currVelocity)), 2); @@ -128,10 +128,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills velocityChangeBonus *= Math.Pow(Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime) / Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime), 2); } - if (osuCurrObj.TravelTime != 0) + if (osuLastObj.TravelTime != 0) { // Reward sliders based on velocity. - sliderBonus = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; + sliderBonus = osuLastObj.TravelDistance / osuLastObj.TravelTime; } // Add in acute angle bonus or wide angle bonus + velocity change bonus, whichever is larger. diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 24881d9c47..b53d287ee6 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -154,7 +154,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (strainTime < min_speed_bonus) speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2); - double distance = Math.Min(single_spacing_threshold, osuCurrObj.TravelDistance + osuCurrObj.JumpDistance); + double travelDistance = osuPrevObj?.TravelDistance ?? 0; + double distance = Math.Min(single_spacing_threshold, travelDistance + osuCurrObj.JumpDistance); return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / strainTime; } From e07c44d79a02028f0d944b51e86a053b3940e307 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 24 Nov 2021 13:01:15 +0900 Subject: [PATCH 006/117] Reword comment with a more diagrammatical explanation --- .../Preprocessing/OsuDifficultyHitObject.cs | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index eb36e96995..ecba2500d0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -102,14 +102,25 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing double lastTravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time); MovementTime = Math.Max(MovementTime - lastTravelTime, min_delta_time); - // Jump distance from the slider tail to the next object, as opposed to the lazy position of JumpDistance. - float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; + // + // We'll try to better approximate the real movements a player will take in patterns following on from sliders. Consider the following slider-to-object patterns: + // + // 1. <======o==> + // | / + // o + // + // 2. <======o==>---o + // |______| + // + // Where "<==>" represents a slider, and "o" represents where the cursor needs to be for either hitobject (for a slider, this is the lazy cursor position). + // + // Case (1) is an anti-flow pattern, where players will cut the slider short in order to move to the next object. The jump pattern is (o--o). + // Case (2) is a flow pattern, where players will follow the slider through to its visual extent. The jump pattern is (>--o). + // + // A lenience is applied by assuming that the player jumps the minimum of these two distances in all cases. + // - // For hitobjects which continue in the direction of the slider, the player will normally follow through the slider, - // such that they're not jumping from the lazy position but rather from very close to (or the end of) the slider. - // In such cases, a leniency is applied by also considering the jump distance from the tail of the slider, and taking the minimum jump distance. - // Additional distance is removed based on position of jump relative to slider follow circle radius. - // JumpDistance is the leniency distance beyond the assumed_slider_radius. tailJumpDistance is maximum_slider_radius since the full distance of radial leniency is still possible. + float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; MovementDistance = Math.Max(0, Math.Min(MovementDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius)); } From a081038076f647b6fa7632135f71766b8a97514c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 24 Nov 2021 13:01:53 +0900 Subject: [PATCH 007/117] Normalized -> Normalised --- .../Preprocessing/OsuDifficultyHitObject.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index ecba2500d0..5c20851e23 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -11,10 +11,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing { public class OsuDifficultyHitObject : DifficultyHitObject { - private const int normalized_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths. + private const int normalised_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths. private const int min_delta_time = 25; - private const float maximum_slider_radius = normalized_radius * 2.4f; - private const float assumed_slider_radius = normalized_radius * 1.8f; + private const float maximum_slider_radius = normalised_radius * 2.4f; + private const float assumed_slider_radius = normalised_radius * 1.8f; protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject; @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public readonly double StrainTime; /// - /// Normalized distance from the end position of the previous to the start position of this . + /// Normalised distance from the end position of the previous to the start position of this . /// public double JumpDistance { get; private set; } @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public double MovementTime { get; private set; } /// - /// Normalized distance between the start and end position of this . + /// Normalised distance between the start and end position of this . /// public double TravelDistance { get; private set; } @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing return; // We will scale distances by this factor, so we can assume a uniform CircleSize among beatmaps. - float scalingFactor = normalized_radius / (float)BaseObject.Radius; + float scalingFactor = normalised_radius / (float)BaseObject.Radius; if (BaseObject.Radius < 30) { @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing slider.LazyEndPosition = slider.StackedPosition + slider.Path.PositionAt(endTimeMin); // temporary lazy end position until a real result can be derived. var currCursorPosition = slider.StackedPosition; - double scalingFactor = normalized_radius / slider.Radius; // lazySliderDistance is coded to be sensitive to scaling, this makes the maths easier with the thresholds being used. + double scalingFactor = normalised_radius / slider.Radius; // lazySliderDistance is coded to be sensitive to scaling, this makes the maths easier with the thresholds being used. for (int i = 1; i < slider.NestedHitObjects.Count; i++) { @@ -181,7 +181,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing else if (currMovementObj is SliderRepeat) { // For a slider repeat, assume a tighter movement threshold to better assess repeat sliders. - requiredMovement = normalized_radius; + requiredMovement = normalised_radius; } if (currMovementLength > requiredMovement) From b5747f351dac23d33f63b3b804ea1052a7c5a5de Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 24 Nov 2021 13:11:44 +0900 Subject: [PATCH 008/117] Reword xmldocs --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 5c20851e23..52108f85f9 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -29,12 +29,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public double JumpDistance { get; private set; } /// - /// Minimum distance from the end position of the previous to the start position of this . + /// Normalised minimum distance from the end position of the previous to the start position of this . /// + /// + /// This is bounded by , but may be smaller if a more natural path is able to be taken through a preceding slider. + /// public double MovementDistance { get; private set; } /// - /// Milliseconds elapsed since the end time of the previous , with a minimum of 25ms. + /// The time taken to travel through , with a minimum value of 25ms. /// public double MovementTime { get; private set; } @@ -44,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public double TravelDistance { get; private set; } /// - /// Milliseconds elapsed between the start and end time of this , with a minimum of 25ms. + /// The time taken to travel through , with a minimum value of 25ms for a non-zero distance. /// public double TravelTime { get; private set; } From 274444ed6725345e3b4f0588fc046d1bba95a79f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 24 Nov 2021 13:22:52 +0900 Subject: [PATCH 009/117] Add additional information to diagram --- .../Preprocessing/OsuDifficultyHitObject.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 52108f85f9..36a1053317 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing if (lastObject is Slider lastSlider) { double lastTravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time); - MovementTime = Math.Max(MovementTime - lastTravelTime, min_delta_time); + MovementTime = Math.Max(StrainTime - lastTravelTime, min_delta_time); // // We'll try to better approximate the real movements a player will take in patterns following on from sliders. Consider the following slider-to-object patterns: @@ -117,14 +117,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // // Where "<==>" represents a slider, and "o" represents where the cursor needs to be for either hitobject (for a slider, this is the lazy cursor position). // - // Case (1) is an anti-flow pattern, where players will cut the slider short in order to move to the next object. The jump pattern is (o--o). - // Case (2) is a flow pattern, where players will follow the slider through to its visual extent. The jump pattern is (>--o). + // The pattern (o--o) has distance JumpDistance. + // The pattern (>--o) is a new distance we'll call "tailJumpDistance". + // + // Case (1) is an anti-flow pattern, where players will cut the slider short in order to move to the next object. The most natural jump pattern is (o--o). + // Case (2) is a flow pattern, where players will follow the slider through to its visual extent. The most natural jump pattern is (>--o). // // A lenience is applied by assuming that the player jumps the minimum of these two distances in all cases. // float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; - MovementDistance = Math.Max(0, Math.Min(MovementDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius)); + MovementDistance = Math.Max(0, Math.Min(JumpDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius)); } if (lastLastObject != null && !(lastLastObject is Spinner)) From b20ff22af0c41294dfffedb6717e6fc78c43ec3b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 24 Nov 2021 16:50:33 +0900 Subject: [PATCH 010/117] Ensure travel distance is calculated for all sliders --- .../Preprocessing/OsuDifficultyHitObject.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 36a1053317..f1f246359a 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -74,6 +74,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing private void setDistances(double clockRate) { + if (BaseObject is Slider currentSlider) + { + computeSliderCursorPosition(currentSlider); + TravelDistance = currentSlider.LazyTravelDistance; + TravelTime = Math.Max(currentSlider.LazyTravelTime / clockRate, min_delta_time); + } + // We don't need to calculate either angle or distance when one of the last->curr objects is a spinner if (BaseObject is Spinner || lastObject is Spinner) return; @@ -87,13 +94,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing scalingFactor *= 1 + smallCircleBonus; } - if (BaseObject is Slider currentSlider) - { - computeSliderCursorPosition(currentSlider); - TravelDistance = currentSlider.LazyTravelDistance; - TravelTime = Math.Max(currentSlider.LazyTravelTime / clockRate, min_delta_time); - } - Vector2 lastCursorPosition = getEndCursorPosition(lastObject); JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; From 3e4b774992b7aa811d60bc75e5b7979ba4889f07 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 25 Nov 2021 14:08:08 +0900 Subject: [PATCH 011/117] Invert lines for better chronological order --- osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 4ddcbb7770..d2a1083f29 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -49,8 +49,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // But if the last object is a slider, then we extend the travel velocity through the slider into the current object. if (osuLastObj.BaseObject is Slider && withSliders) { - double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; // calculate the slider velocity from slider head to slider end. + double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object currVelocity = Math.Max(currVelocity, movementVelocity + travelVelocity); // take the larger total combined velocity. } @@ -60,8 +60,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (osuLastLastObj.BaseObject is Slider && withSliders) { - double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime; double travelVelocity = osuLastLastObj.TravelDistance / osuLastLastObj.TravelTime; + double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime; prevVelocity = Math.Max(prevVelocity, movementVelocity + travelVelocity); } From 23dd21339ddf8fd973817e6c19fe18fcdd8b3156 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Nov 2021 13:39:09 +0100 Subject: [PATCH 012/117] Delay online fetch of non-current playlist item on room join --- .../Online/Multiplayer/MultiplayerClient.cs | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 478da983cd..4b44d5724f 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -139,7 +139,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(joinedRoom != null); // Populate playlist items. - var playlistItems = await Task.WhenAll(joinedRoom.Playlist.Select(createPlaylistItem)).ConfigureAwait(false); + var playlistItems = await Task.WhenAll(joinedRoom.Playlist.Select(item => createPlaylistItem(item, item.ID == joinedRoom.Settings.PlaylistItemId))).ConfigureAwait(false); // Populate users. Debug.Assert(joinedRoom.Users != null); @@ -605,7 +605,7 @@ namespace osu.Game.Online.Multiplayer if (Room == null) return; - var playlistItem = await createPlaylistItem(item).ConfigureAwait(false); + var playlistItem = await createPlaylistItem(item, true).ConfigureAwait(false); Scheduler.Add(() => { @@ -647,7 +647,7 @@ namespace osu.Game.Online.Multiplayer if (Room == null) return; - var playlistItem = await createPlaylistItem(item).ConfigureAwait(false); + var playlistItem = await createPlaylistItem(item, true).ConfigureAwait(false); Scheduler.Add(() => { @@ -700,10 +700,8 @@ namespace osu.Game.Online.Multiplayer CurrentMatchPlayingItem.Value = APIRoom.Playlist.SingleOrDefault(p => p.ID == settings.PlaylistItemId); } - private async Task createPlaylistItem(MultiplayerPlaylistItem item) + private async Task createPlaylistItem(MultiplayerPlaylistItem item, bool populateImmediately) { - var apiBeatmap = await GetAPIBeatmap(item.BeatmapID).ConfigureAwait(false); - var ruleset = Rulesets.GetRuleset(item.RulesetID); var rulesetInstance = ruleset.CreateInstance(); @@ -711,7 +709,6 @@ namespace osu.Game.Online.Multiplayer { ID = item.ID, OwnerID = item.OwnerID, - Beatmap = { Value = apiBeatmap }, Ruleset = { Value = ruleset }, Expired = item.Expired }; @@ -719,9 +716,25 @@ namespace osu.Game.Online.Multiplayer playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))); playlistItem.AllowedMods.AddRange(item.AllowedMods.Select(m => m.ToMod(rulesetInstance))); + if (populateImmediately) + { + await populateFromOnline(item, playlistItem).ConfigureAwait(false); + } + else + { + // to avoid blocking other operations (like the initial room join), schedule online population to happen in the background. + // ReSharper disable once AsyncVoidLambda + Schedule(async () => await populateFromOnline(item, playlistItem).ConfigureAwait(false)); + } + return playlistItem; } + private async Task populateFromOnline(MultiplayerPlaylistItem item, PlaylistItem playlistItem) + { + playlistItem.Beatmap.Value = await GetAPIBeatmap(item.BeatmapID).ConfigureAwait(false); + } + /// /// Retrieves a from an online source. /// From 695167a7493e305e0e1f011de1750105561d65a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Nov 2021 13:45:29 +0100 Subject: [PATCH 013/117] Add support for null item display in `DrawableRoomPlaylistItem` --- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 2dbe2df82c..79a624b884 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -50,6 +50,7 @@ namespace osu.Game.Screens.OnlinePlay private LinkFlowContainer authorText; private ExplicitContentBeatmapPill explicitContentPill; private ModDisplay modDisplay; + private FillFlowContainer buttonsFlow; private UpdateableAvatar ownerAvatar; private readonly IBindable valid = new Bindable(); @@ -150,15 +151,22 @@ namespace osu.Game.Screens.OnlinePlay .ContinueWith(u => Schedule(() => ownerAvatar.User = u.Result), TaskContinuationOptions.OnlyOnRanToCompletion); } - difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(ICON_HEIGHT) }; + if (Item.Beatmap.Value != null) + difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(ICON_HEIGHT) }; + else + difficultyIconContainer.Clear(); panelBackground.Beatmap.Value = Item.Beatmap.Value; beatmapText.Clear(); - beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineID.ToString(), null, text => + + if (Item.Beatmap.Value != null) { - text.Truncate = true; - }); + beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineID.ToString(), null, text => + { + text.Truncate = true; + }); + } authorText.Clear(); @@ -168,10 +176,13 @@ namespace osu.Game.Screens.OnlinePlay authorText.AddUserLink(Item.Beatmap.Value.Metadata.Author); } - bool hasExplicitContent = (Item.Beatmap.Value.BeatmapSet as IBeatmapSetOnlineInfo)?.HasExplicitContent == true; + bool hasExplicitContent = (Item.Beatmap.Value?.BeatmapSet as IBeatmapSetOnlineInfo)?.HasExplicitContent == true; explicitContentPill.Alpha = hasExplicitContent ? 1 : 0; modDisplay.Current.Value = requiredMods.ToArray(); + + buttonsFlow.Clear(); + buttonsFlow.ChildrenEnumerable = CreateButtons(); } protected override Drawable CreateContent() @@ -273,7 +284,7 @@ namespace osu.Game.Screens.OnlinePlay } } }, - new FillFlowContainer + buttonsFlow = new FillFlowContainer { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, @@ -305,9 +316,9 @@ namespace osu.Game.Screens.OnlinePlay } protected virtual IEnumerable CreateButtons() => - new Drawable[] + new[] { - new PlaylistDownloadButton(Item), + Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), new PlaylistRemoveButton { Size = new Vector2(30, 30), From 95373649a4440067f424bb30226153a5f48aa378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Nov 2021 14:02:47 +0100 Subject: [PATCH 014/117] Skip null items in star rating range display --- .../Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index fc029543bb..edf9c5d155 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.OnlinePlay.Components private void updateRange(object sender, NotifyCollectionChangedEventArgs e) { - var orderedDifficulties = Playlist.Select(p => p.Beatmap.Value).OrderBy(b => b.StarRating).ToArray(); + var orderedDifficulties = Playlist.Where(p => p.Beatmap.Value != null).Select(p => p.Beatmap.Value).OrderBy(b => b.StarRating).ToArray(); StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0); StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0); From fe119da044a3290df438908da7d82a7ecfe879b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Nov 2021 19:52:36 +0900 Subject: [PATCH 015/117] Add fetching of beatmap and user data when playlist panels come on screen --- .../Online/Multiplayer/MultiplayerClient.cs | 34 +++++++++-------- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 37 ++++++++++++++----- 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 4b44d5724f..78bf2c4db3 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -444,10 +444,18 @@ namespace osu.Game.Online.Multiplayer return Task.CompletedTask; } - Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings) + async Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings) { + Debug.Assert(APIRoom != null); + Debug.Assert(Room != null); + + // ensure the new selected item is populated immediately. + var playlistItem = APIRoom.Playlist.SingleOrDefault(p => p.ID == newSettings.PlaylistItemId); + + if (playlistItem != null) + await PopulateBeatmap(playlistItem).ConfigureAwait(false); + Scheduler.Add(() => updateLocalRoomSettings(newSettings)); - return Task.CompletedTask; } Task IMultiplayerClient.UserStateChanged(int userId, MultiplayerUserState state) @@ -700,7 +708,7 @@ namespace osu.Game.Online.Multiplayer CurrentMatchPlayingItem.Value = APIRoom.Playlist.SingleOrDefault(p => p.ID == settings.PlaylistItemId); } - private async Task createPlaylistItem(MultiplayerPlaylistItem item, bool populateImmediately) + private async Task createPlaylistItem(MultiplayerPlaylistItem item, bool populateBeatmapImmediately) { var ruleset = Rulesets.GetRuleset(item.RulesetID); var rulesetInstance = ruleset.CreateInstance(); @@ -708,6 +716,7 @@ namespace osu.Game.Online.Multiplayer var playlistItem = new PlaylistItem { ID = item.ID, + BeatmapID = item.BeatmapID, OwnerID = item.OwnerID, Ruleset = { Value = ruleset }, Expired = item.Expired @@ -716,23 +725,18 @@ namespace osu.Game.Online.Multiplayer playlistItem.RequiredMods.AddRange(item.RequiredMods.Select(m => m.ToMod(rulesetInstance))); playlistItem.AllowedMods.AddRange(item.AllowedMods.Select(m => m.ToMod(rulesetInstance))); - if (populateImmediately) - { - await populateFromOnline(item, playlistItem).ConfigureAwait(false); - } - else - { - // to avoid blocking other operations (like the initial room join), schedule online population to happen in the background. - // ReSharper disable once AsyncVoidLambda - Schedule(async () => await populateFromOnline(item, playlistItem).ConfigureAwait(false)); - } + if (populateBeatmapImmediately) + await PopulateBeatmap(playlistItem).ConfigureAwait(false); return playlistItem; } - private async Task populateFromOnline(MultiplayerPlaylistItem item, PlaylistItem playlistItem) + public async Task PopulateBeatmap(PlaylistItem item) { - playlistItem.Beatmap.Value = await GetAPIBeatmap(item.BeatmapID).ConfigureAwait(false); + if (item.Beatmap.Value != null) + return; + + item.Beatmap.Value = await GetAPIBeatmap(item.BeatmapID).ConfigureAwait(false); } /// diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 79a624b884..737c331d2f 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -16,6 +16,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Database; @@ -24,6 +25,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.Chat; +using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Overlays.BeatmapSet; using osu.Game.Rulesets; @@ -67,6 +69,13 @@ namespace osu.Game.Screens.OnlinePlay [Resolved] private UserLookupCache userLookupCache { get; set; } + [Resolved] + private MultiplayerClient multiplayerClient { get; set; } + + private PanelBackground panelBackground; + + private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty); + private readonly bool allowEdit; private readonly bool allowSelection; private readonly bool showItemOwner; @@ -131,11 +140,27 @@ namespace osu.Game.Screens.OnlinePlay valid.BindValueChanged(_ => Scheduler.AddOnce(refresh)); requiredMods.CollectionChanged += (_, __) => Scheduler.AddOnce(refresh); + onScreenLoader.DelayedLoadStarted += _ => + { + Task.Run(async () => + { + try + { + var user = await userLookupCache.GetUserAsync(Item.OwnerID).ConfigureAwait(false); + Schedule(() => ownerAvatar.User = user); + + await multiplayerClient.PopulateBeatmap(Item).ConfigureAwait(false); + } + catch (Exception e) + { + Logger.Log($"Error while populating playlist item {e}"); + } + }); + }; + refresh(); } - private PanelBackground panelBackground; - private void refresh() { if (!valid.Value) @@ -144,13 +169,6 @@ namespace osu.Game.Screens.OnlinePlay maskingContainer.BorderColour = colours.Red; } - if (showItemOwner) - { - ownerAvatar.Show(); - userLookupCache.GetUserAsync(Item.OwnerID) - .ContinueWith(u => Schedule(() => ownerAvatar.User = u.Result), TaskContinuationOptions.OnlyOnRanToCompletion); - } - if (Item.Beatmap.Value != null) difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(ICON_HEIGHT) }; else @@ -203,6 +221,7 @@ namespace osu.Game.Screens.OnlinePlay Alpha = 0, AlwaysPresent = true }, + onScreenLoader, panelBackground = new PanelBackground { RelativeSizeAxes = Axes.Both, From e4ba66877d95c6ed45d2fd9f9204ee4a7058ed7c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Nov 2021 19:54:23 +0900 Subject: [PATCH 016/117] Improve transitions when loading new data into a playlist panel --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 737c331d2f..e4b0d9647e 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -80,6 +80,8 @@ namespace osu.Game.Screens.OnlinePlay private readonly bool allowSelection; private readonly bool showItemOwner; + private FillFlowContainer mainFillFlow; + protected override bool ShouldBeConsideredForInput(Drawable child) => allowEdit || !allowSelection || SelectedItem.Value == Model; public DrawableRoomPlaylistItem(PlaylistItem item, bool allowEdit, bool allowSelection, bool showItemOwner) @@ -201,6 +203,9 @@ namespace osu.Game.Screens.OnlinePlay buttonsFlow.Clear(); buttonsFlow.ChildrenEnumerable = CreateButtons(); + + difficultyIconContainer.FadeInFromZero(500, Easing.OutQuint); + mainFillFlow.FadeInFromZero(500, Easing.OutQuint); } protected override Drawable CreateContent() @@ -247,7 +252,7 @@ namespace osu.Game.Screens.OnlinePlay AutoSizeAxes = Axes.Both, Margin = new MarginPadding { Left = 8, Right = 8 }, }, - new FillFlowContainer + mainFillFlow = new FillFlowContainer { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, From a0ff86f5e80055ac91cb63eb456899b6d7b4a139 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Dec 2021 22:43:03 +0900 Subject: [PATCH 017/117] Ensure all read and write operations on `APIRoom` are done on the update thread --- .../Online/Multiplayer/MultiplayerClient.cs | 32 ++++++++++--------- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 10 ++++-- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 60a7dda961..82844af3c5 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -444,18 +444,28 @@ namespace osu.Game.Online.Multiplayer return Task.CompletedTask; } - async Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings) + Task IMultiplayerClient.SettingsChanged(MultiplayerRoomSettings newSettings) { Debug.Assert(APIRoom != null); Debug.Assert(Room != null); - // ensure the new selected item is populated immediately. - var playlistItem = APIRoom.Playlist.SingleOrDefault(p => p.ID == newSettings.PlaylistItemId); + Scheduler.Add(() => + { + // ensure the new selected item is populated immediately. + var playlistItem = APIRoom.Playlist.SingleOrDefault(p => p.ID == newSettings.PlaylistItemId); - if (playlistItem != null) - await PopulateBeatmap(playlistItem).ConfigureAwait(false); + if (playlistItem != null) + { + GetAPIBeatmap(playlistItem.BeatmapID).ContinueWith(b => + { + Scheduler.Add(() => playlistItem.Beatmap.Value = b.Result); + }, TaskContinuationOptions.OnlyOnRanToCompletion); + } - Scheduler.Add(() => updateLocalRoomSettings(newSettings)); + updateLocalRoomSettings(newSettings); + }); + + return Task.CompletedTask; } Task IMultiplayerClient.UserStateChanged(int userId, MultiplayerUserState state) @@ -727,19 +737,11 @@ namespace osu.Game.Online.Multiplayer playlistItem.AllowedMods.AddRange(item.AllowedMods.Select(m => m.ToMod(rulesetInstance))); if (populateBeatmapImmediately) - await PopulateBeatmap(playlistItem).ConfigureAwait(false); + playlistItem.Beatmap.Value = await GetAPIBeatmap(item.BeatmapID).ConfigureAwait(false); return playlistItem; } - public async Task PopulateBeatmap(PlaylistItem item) - { - if (item.Beatmap.Value != null) - return; - - item.Beatmap.Value = await GetAPIBeatmap(item.BeatmapID).ConfigureAwait(false); - } - /// /// Retrieves a from an online source. /// diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index e4b0d9647e..2e630262f3 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -69,6 +69,9 @@ namespace osu.Game.Screens.OnlinePlay [Resolved] private UserLookupCache userLookupCache { get; set; } + [Resolved] + private BeatmapLookupCache beatmapLookupCache { get; set; } + [Resolved] private MultiplayerClient multiplayerClient { get; set; } @@ -148,10 +151,11 @@ namespace osu.Game.Screens.OnlinePlay { try { - var user = await userLookupCache.GetUserAsync(Item.OwnerID).ConfigureAwait(false); - Schedule(() => ownerAvatar.User = user); + var foundUser = await userLookupCache.GetUserAsync(Item.OwnerID).ConfigureAwait(false); + Schedule(() => ownerAvatar.User = foundUser); - await multiplayerClient.PopulateBeatmap(Item).ConfigureAwait(false); + var foundBeatmap = await beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ConfigureAwait(false); + Schedule(() => Item.Beatmap.Value = foundBeatmap); } catch (Exception e) { From d262baefada800347338f6422739baa1b50bc45a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Dec 2021 22:43:32 +0900 Subject: [PATCH 018/117] Only query for the owner user metadata in the case it is actually required --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 2e630262f3..e2471d71d6 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -151,8 +151,11 @@ namespace osu.Game.Screens.OnlinePlay { try { - var foundUser = await userLookupCache.GetUserAsync(Item.OwnerID).ConfigureAwait(false); - Schedule(() => ownerAvatar.User = foundUser); + if (showItemOwner) + { + var foundUser = await userLookupCache.GetUserAsync(Item.OwnerID).ConfigureAwait(false); + Schedule(() => ownerAvatar.User = foundUser); + } var foundBeatmap = await beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ConfigureAwait(false); Schedule(() => Item.Beatmap.Value = foundBeatmap); From a8e17cb3a5bad201deb8d2df3a07412e613d7282 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Dec 2021 22:46:32 +0900 Subject: [PATCH 019/117] Add relative size specs so partially on-screen panels still start loading Without this, panels at the top of the list but not fully on-screen wouldn't begin their metadata loading process. --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index e2471d71d6..dae7b611f2 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.OnlinePlay private PanelBackground panelBackground; - private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty); + private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; private readonly bool allowEdit; private readonly bool allowSelection; From 624ec4580a1b5347224b073323cb6a39adb6ea5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Dec 2021 14:31:59 +0900 Subject: [PATCH 020/117] Ensure `updateLocalRoomSettings` is only called after full population --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 82844af3c5..c2b9727309 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -458,11 +458,17 @@ namespace osu.Game.Online.Multiplayer { GetAPIBeatmap(playlistItem.BeatmapID).ContinueWith(b => { - Scheduler.Add(() => playlistItem.Beatmap.Value = b.Result); - }, TaskContinuationOptions.OnlyOnRanToCompletion); - } + bool success = b.IsCompletedSuccessfully; - updateLocalRoomSettings(newSettings); + Scheduler.Add(() => + { + if (success) + playlistItem.Beatmap.Value = b.Result; + + updateLocalRoomSettings(newSettings); + }); + }); + } }); return Task.CompletedTask; From bdddaba3521f524a150d41505efcbf8ee6fb0020 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 2 Dec 2021 14:31:08 +0900 Subject: [PATCH 021/117] Remove unnecessary test request handling --- .../Visual/OnlinePlay/TestRoomRequestsHandler.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs index abcf31c007..af2202b05a 100644 --- a/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs +++ b/osu.Game/Tests/Visual/OnlinePlay/TestRoomRequestsHandler.cs @@ -4,9 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Scoring; @@ -89,15 +87,6 @@ namespace osu.Game.Tests.Visual.OnlinePlay getRoomRequest.TriggerSuccess(createResponseRoom(ServerSideRooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId), true)); return true; - case GetBeatmapSetRequest getBeatmapSetRequest: - var onlineReq = new GetBeatmapSetRequest(getBeatmapSetRequest.ID, getBeatmapSetRequest.Type); - onlineReq.Success += res => getBeatmapSetRequest.TriggerSuccess(res); - onlineReq.Failure += e => getBeatmapSetRequest.TriggerFailure(e); - - // Get the online API from the game's dependencies. - game.Dependencies.Get().Queue(onlineReq); - return true; - case CreateRoomScoreRequest createRoomScoreRequest: createRoomScoreRequest.TriggerSuccess(new APIScoreToken { ID = 1 }); return true; From b3b239c9a138067cfdc6d0907f7194bd959aa959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 4 Dec 2021 13:57:39 +0100 Subject: [PATCH 022/117] Fix test failures due to beatmap lookup logic being active even when model is populated --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index dae7b611f2..de82c463b3 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -157,8 +157,11 @@ namespace osu.Game.Screens.OnlinePlay Schedule(() => ownerAvatar.User = foundUser); } - var foundBeatmap = await beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ConfigureAwait(false); - Schedule(() => Item.Beatmap.Value = foundBeatmap); + if (Item.Beatmap.Value == null) + { + var foundBeatmap = await beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ConfigureAwait(false); + Schedule(() => Item.Beatmap.Value = foundBeatmap); + } } catch (Exception e) { From a07f8c74dc454de38c1ca4b54323e29345747bb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Dec 2021 15:04:44 +0100 Subject: [PATCH 023/117] Add basic structure for composable card dropdown --- .../Visual/Beatmaps/TestSceneBeatmapCard.cs | 10 +- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 413 +++++++++--------- .../Drawables/Cards/BeatmapCardDropdown.cs | 85 ++++ 3 files changed, 307 insertions(+), 201 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index 5effc1f215..d3ce5028b7 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; @@ -227,7 +228,7 @@ namespace osu.Game.Tests.Visual.Beatmaps new BasicScrollContainer { RelativeSizeAxes = Axes.Both, - Child = new FillFlowContainer + Child = new ReverseChildIDFillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -248,6 +249,11 @@ namespace osu.Game.Tests.Visual.Beatmaps } [Test] - public void TestNormal() => createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo)); + public void TestNormal() + { + createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo)); + + AddToggleStep("toggle expanded state", expanded => this.ChildrenOfType().Last().Expanded.Value = expanded); + } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 37c1bacda4..46ee9afb86 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -31,10 +31,12 @@ namespace osu.Game.Beatmaps.Drawables.Cards public class BeatmapCard : OsuClickableContainer { public const float TRANSITION_DURATION = 400; + public const float CORNER_RADIUS = 10; + + public Bindable Expanded { get; } = new BindableBool(); private const float width = 408; private const float height = 100; - private const float corner_radius = 10; private const float icon_area_width = 30; private readonly APIBeatmapSet beatmapSet; @@ -73,242 +75,255 @@ namespace osu.Game.Beatmaps.Drawables.Cards { Width = width; Height = height; - CornerRadius = corner_radius; - Masking = true; FillFlowContainer leftIconArea; GridContainer titleContainer; GridContainer artistContainer; - InternalChildren = new Drawable[] + InternalChild = new BeatmapCardDropdown(height) { - downloadTracker, - rightAreaBackground = new Container + Body = new Container { - RelativeSizeAxes = Axes.Y, - Width = icon_area_width + 2 * corner_radius, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - // workaround for masking artifacts at the top & bottom of card, - // which become especially visible on downloaded beatmaps (when the icon area has a lime background). - Padding = new MarginPadding { Vertical = 1 }, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.White - }, - }, - thumbnail = new BeatmapCardThumbnail(beatmapSet) - { - Name = @"Left (icon) area", - Size = new Vector2(height), - Padding = new MarginPadding { Right = corner_radius }, - Child = leftIconArea = new FillFlowContainer - { - Margin = new MarginPadding(5), - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(1) - } - }, - new Container - { - Name = @"Right (button) area", - Width = 30, - RelativeSizeAxes = Axes.Y, - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Padding = new MarginPadding { Vertical = 17.5f }, - Child = rightAreaButtons = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new BeatmapCardIconButton[] - { - new FavouriteButton(beatmapSet) - { - Current = favouriteState, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre - }, - new DownloadButton(beatmapSet) - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - State = { BindTarget = downloadTracker.State } - }, - new GoToBeatmapButton(beatmapSet) - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - State = { BindTarget = downloadTracker.State } - } - } - } - }, - mainContent = new Container - { - Name = @"Main content", - X = height - corner_radius, - Height = height, - CornerRadius = corner_radius, - Masking = true, + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - mainContentBackground = new BeatmapCardContentBackground(beatmapSet) + downloadTracker, + rightAreaBackground = new Container { - RelativeSizeAxes = Axes.Both, - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding + RelativeSizeAxes = Axes.Y, + Width = icon_area_width + 2 * CORNER_RADIUS, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + // workaround for masking artifacts at the top & bottom of card, + // which become especially visible on downloaded beatmaps (when the icon area has a lime background). + Padding = new MarginPadding { Vertical = 1 }, + Child = new Box { - Horizontal = 10, - Vertical = 4 + RelativeSizeAxes = Axes.Both, + Colour = Colour4.White }, - Direction = FillDirection.Vertical, - Children = new Drawable[] + }, + thumbnail = new BeatmapCardThumbnail(beatmapSet) + { + Name = @"Left (icon) area", + Size = new Vector2(height), + Padding = new MarginPadding { Right = CORNER_RADIUS }, + Child = leftIconArea = new FillFlowContainer { - titleContainer = new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.AutoSize) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new[] - { - new OsuSpriteText - { - Text = new RomanisableString(beatmapSet.TitleUnicode, beatmapSet.Title), - Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold), - RelativeSizeAxes = Axes.X, - Truncate = true - }, - Empty() - } - } - }, - artistContainer = new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.AutoSize) - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize) - }, - Content = new[] - { - new[] - { - new OsuSpriteText - { - Text = createArtistText(), - Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold), - RelativeSizeAxes = Axes.X, - Truncate = true - }, - Empty() - }, - } - }, - new LinkFlowContainer(s => - { - s.Shadow = false; - s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold); - }).With(d => - { - d.AutoSizeAxes = Axes.Both; - d.Margin = new MarginPadding { Top = 2 }; - d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); - d.AddUserLink(beatmapSet.Author); - }), + Margin = new MarginPadding(5), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(1) } }, new Container { - Name = @"Bottom content", - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Padding = new MarginPadding + Name = @"Right (button) area", + Width = 30, + RelativeSizeAxes = Axes.Y, + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + Padding = new MarginPadding { Vertical = 17.5f }, + Child = rightAreaButtons = new Container { - Horizontal = 10, - Vertical = 4 - }, + RelativeSizeAxes = Axes.Both, + Children = new BeatmapCardIconButton[] + { + new FavouriteButton(beatmapSet) + { + Current = favouriteState, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }, + new DownloadButton(beatmapSet) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + State = { BindTarget = downloadTracker.State } + }, + new GoToBeatmapButton(beatmapSet) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + State = { BindTarget = downloadTracker.State } + } + } + } + }, + mainContent = new Container + { + Name = @"Main content", + X = height - CORNER_RADIUS, + Height = height, + CornerRadius = CORNER_RADIUS, + Masking = true, Children = new Drawable[] { - idleBottomContent = new FillFlowContainer + mainContentBackground = new BeatmapCardContentBackground(beatmapSet) { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Horizontal = 10, + Vertical = 4 + }, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 3), - AlwaysPresent = true, Children = new Drawable[] { - statisticsContainer = new FillFlowContainer + titleContainer = new GridContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Alpha = 0, - AlwaysPresent = true, - ChildrenEnumerable = createStatistics() - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(4, 0), - Children = new Drawable[] + ColumnDimensions = new[] { - new BeatmapSetOnlineStatusPill + new Dimension(), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new[] { - AutoSizeAxes = Axes.Both, - Status = beatmapSet.Status, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft - }, - new DifficultySpectrumDisplay(beatmapSet) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - DotSize = new Vector2(6, 12) + new OsuSpriteText + { + Text = new RomanisableString(beatmapSet.TitleUnicode, beatmapSet.Title), + Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold), + RelativeSizeAxes = Axes.X, + Truncate = true + }, + Empty() } } - } + }, + artistContainer = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new[] + { + new OsuSpriteText + { + Text = createArtistText(), + Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold), + RelativeSizeAxes = Axes.X, + Truncate = true + }, + Empty() + }, + } + }, + new LinkFlowContainer(s => + { + s.Shadow = false; + s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold); + }).With(d => + { + d.AutoSizeAxes = Axes.Both; + d.Margin = new MarginPadding { Top = 2 }; + d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); + d.AddUserLink(beatmapSet.Author); + }), } }, - downloadProgressBar = new BeatmapCardDownloadProgressBar + new Container { + Name = @"Bottom content", RelativeSizeAxes = Axes.X, - Height = 6, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - State = { BindTarget = downloadTracker.State }, - Progress = { BindTarget = downloadTracker.Progress } + AutoSizeAxes = Axes.Y, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Padding = new MarginPadding + { + Horizontal = 10, + Vertical = 4 + }, + Children = new Drawable[] + { + idleBottomContent = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 3), + AlwaysPresent = true, + Children = new Drawable[] + { + statisticsContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Alpha = 0, + AlwaysPresent = true, + ChildrenEnumerable = createStatistics() + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(4, 0), + Children = new Drawable[] + { + new BeatmapSetOnlineStatusPill + { + AutoSizeAxes = Axes.Both, + Status = beatmapSet.Status, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }, + new DifficultySpectrumDisplay(beatmapSet) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + DotSize = new Vector2(6, 12) + } + } + } + } + }, + downloadProgressBar = new BeatmapCardDownloadProgressBar + { + RelativeSizeAxes = Axes.X, + Height = 6, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + State = { BindTarget = downloadTracker.State }, + Progress = { BindTarget = downloadTracker.Progress } + } + } } } } } - } + }, + Dropdown = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 10, Vertical = 13 }, + Child = new BeatmapCardDifficultyList(beatmapSet) + }, + Expanded = { BindTarget = Expanded } }; if (beatmapSet.HasVideo) @@ -388,7 +403,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { float targetWidth = width - height; if (IsHovered) - targetWidth = targetWidth - icon_area_width + corner_radius; + targetWidth = targetWidth - icon_area_width + CORNER_RADIUS; thumbnail.Dimmed.Value = IsHovered; diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs new file mode 100644 index 0000000000..29f12de947 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs @@ -0,0 +1,85 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Overlays; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public class BeatmapCardDropdown : CompositeDrawable + { + public Drawable Body + { + set => bodyContent.Child = value; + } + + public Drawable Dropdown + { + set => dropdownContent.Child = value; + } + + public Bindable Expanded { get; } = new BindableBool(); + + private readonly Box background; + private readonly Container bodyContent; + private readonly Container dropdownContent; + + public BeatmapCardDropdown(float height) + { + RelativeSizeAxes = Axes.X; + Height = height; + + InternalChild = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + CornerRadius = BeatmapCard.CORNER_RADIUS, + Masking = true, + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + bodyContent = new Container + { + RelativeSizeAxes = Axes.X, + Height = height, + CornerRadius = BeatmapCard.CORNER_RADIUS, + Masking = true, + }, + dropdownContent = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = height }, + Alpha = 0 + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + background.Colour = colourProvider.Background2; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Expanded.BindValueChanged(_ => updateState()); + } + + private void updateState() + { + background.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + dropdownContent.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + } + } +} From 3fea8d5e62e709ce46bc480e32dc10b33c6c251f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Dec 2021 15:48:02 +0100 Subject: [PATCH 024/117] Implement visual behaviour of expanded card state --- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 15 +++++---- .../Drawables/Cards/BeatmapCardDropdown.cs | 33 +++++++++++++++++-- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 46ee9afb86..f0425794be 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -359,7 +359,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards { base.LoadComplete(); - downloadTracker.State.BindValueChanged(_ => updateState(), true); + downloadTracker.State.BindValueChanged(_ => updateState()); + Expanded.BindValueChanged(_ => updateState(), true); FinishTransforms(true); } @@ -401,19 +402,21 @@ namespace osu.Game.Beatmaps.Drawables.Cards private void updateState() { + bool showDetails = IsHovered || Expanded.Value; + float targetWidth = width - height; - if (IsHovered) + if (showDetails) targetWidth = targetWidth - icon_area_width + CORNER_RADIUS; - thumbnail.Dimmed.Value = IsHovered; + thumbnail.Dimmed.Value = showDetails; mainContent.ResizeWidthTo(targetWidth, TRANSITION_DURATION, Easing.OutQuint); - mainContentBackground.Dimmed.Value = IsHovered; + mainContentBackground.Dimmed.Value = showDetails; - statisticsContainer.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); + statisticsContainer.FadeTo(showDetails ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); rightAreaBackground.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, TRANSITION_DURATION, Easing.OutQuint); - rightAreaButtons.FadeTo(IsHovered ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); + rightAreaButtons.FadeTo(showDetails ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); foreach (var button in rightAreaButtons) { diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs index 29f12de947..877338ecb0 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs @@ -5,8 +5,10 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Overlays; +using osuTK; namespace osu.Game.Beatmaps.Drawables.Cards { @@ -25,15 +27,17 @@ namespace osu.Game.Beatmaps.Drawables.Cards public Bindable Expanded { get; } = new BindableBool(); private readonly Box background; + private readonly Container content; private readonly Container bodyContent; private readonly Container dropdownContent; + private readonly Container borderContainer; public BeatmapCardDropdown(float height) { RelativeSizeAxes = Axes.X; Height = height; - InternalChild = new Container + InternalChild = content = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -59,6 +63,19 @@ namespace osu.Game.Beatmaps.Drawables.Cards AutoSizeAxes = Axes.Y, Margin = new MarginPadding { Top = height }, Alpha = 0 + }, + borderContainer = new Container + { + RelativeSizeAxes = Axes.Both, + CornerRadius = BeatmapCard.CORNER_RADIUS, + Masking = true, + BorderThickness = 3, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } } } }; @@ -68,18 +85,30 @@ namespace osu.Game.Beatmaps.Drawables.Cards private void load(OverlayColourProvider colourProvider) { background.Colour = colourProvider.Background2; + borderContainer.BorderColour = colourProvider.Highlight1; } protected override void LoadComplete() { base.LoadComplete(); - Expanded.BindValueChanged(_ => updateState()); + Expanded.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); } private void updateState() { 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); + + content.TweenEdgeEffectTo(new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Offset = new Vector2(0, 2), + Radius = 10, + Colour = Colour4.Black.Opacity(Expanded.Value ? 0.3f : 0f), + Hollow = true, + }, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } } } From 250e5b47b7b1f9e7c50c0f552529f9b0bc47b4a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 28 Nov 2021 20:25:23 +0100 Subject: [PATCH 025/117] Move "extra info" beatmap card row to separate component --- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 24 +---------- .../Cards/BeatmapCardExtraInfoRow.cs | 43 +++++++++++++++++++ 2 files changed, 44 insertions(+), 23 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index f0425794be..1a05607074 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -276,29 +276,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards AlwaysPresent = true, ChildrenEnumerable = createStatistics() }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(4, 0), - Children = new Drawable[] - { - new BeatmapSetOnlineStatusPill - { - AutoSizeAxes = Axes.Both, - Status = beatmapSet.Status, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft - }, - new DifficultySpectrumDisplay(beatmapSet) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - DotSize = new Vector2(6, 12) - } - } - } + new BeatmapCardExtraInfoRow(beatmapSet) } }, downloadProgressBar = new BeatmapCardDownloadProgressBar diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs new file mode 100644 index 0000000000..c64e5b83d8 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs @@ -0,0 +1,43 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using osuTK; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public class BeatmapCardExtraInfoRow : CompositeDrawable + { + public BeatmapCardExtraInfoRow(APIBeatmapSet beatmapSet) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(4, 0), + Children = new Drawable[] + { + new BeatmapSetOnlineStatusPill + { + AutoSizeAxes = Axes.Both, + Status = beatmapSet.Status, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }, + new DifficultySpectrumDisplay(beatmapSet) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + DotSize = new Vector2(6, 12) + } + } + }; + } + } +} From e451e43b9022542bc28c6f96e2263c44b27a1213 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Dec 2021 16:31:45 +0100 Subject: [PATCH 026/117] Implement input handling behaviour of beatmap card dropdown --- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 12 ++++- .../Drawables/Cards/BeatmapCardDropdown.cs | 52 +++++++++++++++++-- .../Cards/BeatmapCardExtraInfoRow.cs | 4 +- .../Drawables/Cards/HoverHandlingContainer.cs | 27 ++++++++++ 4 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/HoverHandlingContainer.cs diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 1a05607074..2ab77539b3 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -44,6 +44,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly BeatmapDownloadTracker downloadTracker; + private BeatmapCardDropdown dropdown = null!; + private BeatmapCardThumbnail thumbnail = null!; private Container rightAreaBackground = null!; @@ -80,7 +82,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards GridContainer titleContainer; GridContainer artistContainer; - InternalChild = new BeatmapCardDropdown(height) + InternalChild = dropdown = new BeatmapCardDropdown(height) { Body = new Container { @@ -277,6 +279,14 @@ namespace osu.Game.Beatmaps.Drawables.Cards ChildrenEnumerable = createStatistics() }, new BeatmapCardExtraInfoRow(beatmapSet) + { + Hovered = _ => + { + dropdown.ScheduleShow(); + return false; + }, + Unhovered = _ => dropdown.ScheduleHide() + } } }, downloadProgressBar = new BeatmapCardDownloadProgressBar diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs index 877338ecb0..ef4ba67ab7 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs @@ -1,12 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Threading; using osu.Game.Overlays; using osuTK; @@ -37,13 +40,13 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.X; Height = height; - InternalChild = content = new Container + InternalChild = content = new HoverHandlingContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, CornerRadius = BeatmapCard.CORNER_RADIUS, Masking = true, - + Unhovered = _ => checkForHide(), Children = new Drawable[] { background = new Box @@ -57,12 +60,18 @@ namespace osu.Game.Beatmaps.Drawables.Cards CornerRadius = BeatmapCard.CORNER_RADIUS, Masking = true, }, - dropdownContent = new Container + dropdownContent = new HoverHandlingContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Margin = new MarginPadding { Top = height }, - Alpha = 0 + Alpha = 0, + Hovered = _ => + { + keep(); + return true; + }, + Unhovered = _ => checkForHide() }, borderContainer = new Container { @@ -95,6 +104,41 @@ namespace osu.Game.Beatmaps.Drawables.Cards FinishTransforms(true); } + private ScheduledDelegate? scheduledExpandedChange; + + public void ScheduleShow() + { + scheduledExpandedChange?.Cancel(); + if (Expanded.Value) + return; + + scheduledExpandedChange = Scheduler.AddDelayed(() => Expanded.Value = true, 100); + } + + public void ScheduleHide() + { + scheduledExpandedChange?.Cancel(); + if (!Expanded.Value) + return; + + scheduledExpandedChange = Scheduler.AddDelayed(() => Expanded.Value = false, 500); + } + + private void checkForHide() + { + if (content.IsHovered || dropdownContent.IsHovered) + return; + + scheduledExpandedChange?.Cancel(); + Expanded.Value = false; + } + + private void keep() + { + scheduledExpandedChange?.Cancel(); + Expanded.Value = true; + } + private void updateState() { background.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs index c64e5b83d8..0a9d98e621 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs @@ -8,14 +8,14 @@ using osuTK; namespace osu.Game.Beatmaps.Drawables.Cards { - public class BeatmapCardExtraInfoRow : CompositeDrawable + public class BeatmapCardExtraInfoRow : HoverHandlingContainer { public BeatmapCardExtraInfoRow(APIBeatmapSet beatmapSet) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - InternalChild = new FillFlowContainer + Child = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Beatmaps/Drawables/Cards/HoverHandlingContainer.cs b/osu.Game/Beatmaps/Drawables/Cards/HoverHandlingContainer.cs new file mode 100644 index 0000000000..1e2c616332 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/HoverHandlingContainer.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public class HoverHandlingContainer : Container + { + public Func? Hovered { get; set; } + public Action? Unhovered { get; set; } + + protected override bool OnHover(HoverEvent e) => Hovered?.Invoke(e) ?? base.OnHover(e); + + protected override void OnHoverLost(HoverLostEvent e) + { + if (Unhovered != null) + Unhovered?.Invoke(e); + else + base.OnHoverLost(e); + } + } +} From af10223ac4d7841cd5fc7212a09d5498211fbc6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 29 Nov 2021 01:08:45 +0100 Subject: [PATCH 027/117] Add reverse fill flows & depth specs at usage sites for correct Z-ordering --- osu.Game/Overlays/BeatmapListingOverlay.cs | 2 +- osu.Game/Overlays/Profile/ProfileSection.cs | 5 +++-- .../Overlays/Profile/Sections/PaginatedProfileSubsection.cs | 6 ++++-- osu.Game/Overlays/Rankings/SpotlightsLayout.cs | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 49f2f5c211..a454af00c4 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -151,7 +151,7 @@ namespace osu.Game.Overlays } // spawn new children with the contained so we only clear old content at the last moment. - var content = new FillFlowContainer + var content = new ReverseChildIDFillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Overlays/Profile/ProfileSection.cs b/osu.Game/Overlays/Profile/ProfileSection.cs index 6223b32814..fdf3077bf0 100644 --- a/osu.Game/Overlays/Profile/ProfileSection.cs +++ b/osu.Game/Overlays/Profile/ProfileSection.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online.API.Requests.Responses; @@ -22,7 +23,7 @@ namespace osu.Game.Overlays.Profile public abstract string Identifier { get; } - private readonly FillFlowContainer content; + private readonly FillFlowContainer content; private readonly Box background; private readonly Box underscore; @@ -79,7 +80,7 @@ namespace osu.Game.Overlays.Profile } } }, - content = new FillFlowContainer + content = new ReverseChildIDFillFlowContainer { Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index 130ae44273..e7053ec4fa 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; +using osu.Game.Graphics.Containers; using osu.Game.Online.API.Requests.Responses; using osuTK; @@ -26,7 +27,7 @@ namespace osu.Game.Overlays.Profile.Sections protected int VisiblePages; protected int ItemsPerPage; - protected FillFlowContainer ItemsContainer { get; private set; } + protected ReverseChildIDFillFlowContainer ItemsContainer { get; private set; } private APIRequest> retrievalRequest; private CancellationTokenSource loadCancellation; @@ -48,11 +49,12 @@ namespace osu.Game.Overlays.Profile.Sections Direction = FillDirection.Vertical, Children = new Drawable[] { - ItemsContainer = new FillFlowContainer + ItemsContainer = new ReverseChildIDFillFlowContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Spacing = new Vector2(0, 2), + Depth = float.MinValue }, moreButton = new ShowMoreButton { diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs index a37f762532..61d68b7d29 100644 --- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs +++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs @@ -135,7 +135,7 @@ namespace osu.Game.Overlays.Rankings Children = new Drawable[] { new ScoresTable(1, response.Users), - new FillFlowContainer + new ReverseChildIDFillFlowContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, From af35652b8b9f97dc700f19040e161f8d477c2748 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Dec 2021 17:16:41 +0100 Subject: [PATCH 028/117] Disable beatmap card expansion on solo spectator screen --- .../Visual/Beatmaps/TestSceneBeatmapCard.cs | 9 +++++++- .../Drawables/Cards/BeatmapCardDropdown.cs | 22 +++++++++++++++---- osu.Game/Screens/Play/SoloSpectator.cs | 5 ++++- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index d3ce5028b7..f835d21603 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -253,7 +254,13 @@ namespace osu.Game.Tests.Visual.Beatmaps { createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo)); - AddToggleStep("toggle expanded state", expanded => this.ChildrenOfType().Last().Expanded.Value = expanded); + AddToggleStep("toggle expanded state", expanded => + { + var card = this.ChildrenOfType().Last(); + if (!card.Expanded.Disabled) + card.Expanded.Value = expanded; + }); + AddToggleStep("disable/enable expansion", disabled => this.ChildrenOfType().ForEach(card => card.Expanded.Disabled = disabled)); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs index ef4ba67ab7..366e5cb0fc 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs @@ -109,23 +109,34 @@ namespace osu.Game.Beatmaps.Drawables.Cards public void ScheduleShow() { scheduledExpandedChange?.Cancel(); - if (Expanded.Value) + if (Expanded.Disabled || Expanded.Value) return; - scheduledExpandedChange = Scheduler.AddDelayed(() => Expanded.Value = true, 100); + scheduledExpandedChange = Scheduler.AddDelayed(() => + { + if (!Expanded.Disabled) + Expanded.Value = true; + }, 100); } public void ScheduleHide() { scheduledExpandedChange?.Cancel(); - if (!Expanded.Value) + if (Expanded.Disabled || !Expanded.Value) return; - scheduledExpandedChange = Scheduler.AddDelayed(() => Expanded.Value = false, 500); + scheduledExpandedChange = Scheduler.AddDelayed(() => + { + if (!Expanded.Disabled) + Expanded.Value = false; + }, 500); } private void checkForHide() { + if (Expanded.Disabled) + return; + if (content.IsHovered || dropdownContent.IsHovered) return; @@ -135,6 +146,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards private void keep() { + if (Expanded.Disabled) + return; + scheduledExpandedChange?.Cancel(); Expanded.Value = true; } diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index 7fea44b3ea..3918dbe8fc 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -228,7 +228,10 @@ namespace osu.Game.Screens.Play onlineBeatmapRequest.Success += beatmapSet => Schedule(() => { this.beatmapSet = beatmapSet; - beatmapPanelContainer.Child = new BeatmapCard(this.beatmapSet); + beatmapPanelContainer.Child = new BeatmapCard(this.beatmapSet) + { + Expanded = { Disabled = true } + }; checkForAutomaticDownload(); }); From 0f743893890029d010b75a42d6472c79a12b69c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Dec 2021 17:35:10 +0100 Subject: [PATCH 029/117] Add scrolling for long difficulty lists in beatmap card --- .../Drawables/Cards/BeatmapCardDropdown.cs | 63 ++++++++++++++++++- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs index 366e5cb0fc..a24f09a9b5 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs @@ -3,13 +3,17 @@ #nullable enable +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Framework.Threading; +using osu.Framework.Utils; +using osu.Game.Graphics.Containers; using osu.Game.Overlays; using osuTK; @@ -24,7 +28,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards public Drawable Dropdown { - set => dropdownContent.Child = value; + set => dropdownScroll.Child = value; } public Bindable Expanded { get; } = new BindableBool(); @@ -33,6 +37,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly Container content; private readonly Container bodyContent; private readonly Container dropdownContent; + private readonly OsuScrollContainer dropdownScroll; private readonly Container borderContainer; public BeatmapCardDropdown(float height) @@ -71,7 +76,12 @@ namespace osu.Game.Beatmaps.Drawables.Cards keep(); return true; }, - Unhovered = _ => checkForHide() + Unhovered = _ => checkForHide(), + Child = dropdownScroll = new DropdownScrollContainer + { + RelativeSizeAxes = Axes.X, + ScrollbarVisible = false + } }, borderContainer = new Container { @@ -168,5 +178,54 @@ namespace osu.Game.Beatmaps.Drawables.Cards Hollow = true, }, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } + + private class DropdownScrollContainer : OsuScrollContainer + { + public DropdownScrollContainer() + { + ScrollbarVisible = false; + } + + protected override void Update() + { + base.Update(); + + Height = Math.Min(Content.DrawHeight, 400); + } + + private bool allowScroll => !Precision.AlmostEquals(DrawSize, Content.DrawSize); + + protected override bool OnDragStart(DragStartEvent e) + { + if (!allowScroll) + return false; + + return base.OnDragStart(e); + } + + protected override void OnDrag(DragEvent e) + { + if (!allowScroll) + return; + + base.OnDrag(e); + } + + protected override void OnDragEnd(DragEndEvent e) + { + if (!allowScroll) + return; + + base.OnDragEnd(e); + } + + protected override bool OnScroll(ScrollEvent e) + { + if (!allowScroll) + return false; + + return base.OnScroll(e); + } + } } } From 91aa38c4f6d60aa243c2af8d806e8606967b3b42 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Dec 2021 12:28:46 +0900 Subject: [PATCH 030/117] Change playlist lookup to fail hard when failing --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index c2b9727309..f8a25e0388 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -452,7 +452,7 @@ namespace osu.Game.Online.Multiplayer Scheduler.Add(() => { // ensure the new selected item is populated immediately. - var playlistItem = APIRoom.Playlist.SingleOrDefault(p => p.ID == newSettings.PlaylistItemId); + var playlistItem = APIRoom.Playlist.Single(p => p.ID == newSettings.PlaylistItemId); if (playlistItem != null) { From 5ff452cc9aa8e47be3a36a15facd7e148bb820dc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Dec 2021 12:29:11 +0900 Subject: [PATCH 031/117] Update success bool to access `Exception` to stop exceptions from firing outwards --- osu.Game/Online/Multiplayer/MultiplayerClient.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index f8a25e0388..2ddcc09e1b 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -458,7 +458,8 @@ namespace osu.Game.Online.Multiplayer { GetAPIBeatmap(playlistItem.BeatmapID).ContinueWith(b => { - bool success = b.IsCompletedSuccessfully; + // Should be called outside of the `Scheduler` logic (and specifically accessing `Exception`) to suppress an exception from firing outwards. + bool success = b.Exception == null; Scheduler.Add(() => { From 4278a320e46042c47de929500b559ed079dc20b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Dec 2021 20:12:02 +0100 Subject: [PATCH 032/117] Fix skin setting resetting every launch The reason this was happening was an unfortunate oversight in the migration logic. The code that was attempting to parse the skin settings as `int` was firing regardless of whether a skin migration from EF to realm had already occurred. If it had occurred, the skin setting would contain a GUID rather than an integer, and therefore fail to parse, and therefore implicitly fallback to a EF skin ID of 0 which would be the default skin. Fix by not running the setting migrating logic at all when there are no EF skins to migrate. --- osu.Game/Database/EFToRealmMigrator.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Database/EFToRealmMigrator.cs b/osu.Game/Database/EFToRealmMigrator.cs index 3790dc8ae9..b79a982460 100644 --- a/osu.Game/Database/EFToRealmMigrator.cs +++ b/osu.Game/Database/EFToRealmMigrator.cs @@ -35,6 +35,16 @@ namespace osu.Game.Database private void migrateSkins(DatabaseWriteUsage db) { + // can be removed 20220530. + var existingSkins = db.Context.SkinInfo + .Include(s => s.Files) + .ThenInclude(f => f.FileInfo) + .ToList(); + + // previous entries in EF are removed post migration. + if (!existingSkins.Any()) + return; + var userSkinChoice = config.GetBindable(OsuSetting.Skin); int.TryParse(userSkinChoice.Value, out int userSkinInt); @@ -49,16 +59,6 @@ namespace osu.Game.Database break; } - // migrate ruleset settings. can be removed 20220530. - var existingSkins = db.Context.SkinInfo - .Include(s => s.Files) - .ThenInclude(f => f.FileInfo) - .ToList(); - - // previous entries in EF are removed post migration. - if (!existingSkins.Any()) - return; - using (var realm = realmContextFactory.CreateContext()) using (var transaction = realm.BeginWrite()) { From ccfc361626415fe5993fae093bcfb35abcf980e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Dec 2021 20:49:29 +0100 Subject: [PATCH 033/117] Apply naming suggestions --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs | 12 ++++++------ ...eatmapCardDropdown.cs => BeatmapCardContent.cs} | 14 +++++++------- 2 files changed, 13 insertions(+), 13 deletions(-) rename osu.Game/Beatmaps/Drawables/Cards/{BeatmapCardDropdown.cs => BeatmapCardContent.cs} (94%) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 2ab77539b3..9031d6df1a 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -44,7 +44,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly BeatmapDownloadTracker downloadTracker; - private BeatmapCardDropdown dropdown = null!; + private BeatmapCardContent content = null!; private BeatmapCardThumbnail thumbnail = null!; @@ -82,9 +82,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards GridContainer titleContainer; GridContainer artistContainer; - InternalChild = dropdown = new BeatmapCardDropdown(height) + InternalChild = content = new BeatmapCardContent(height) { - Body = new Container + MainContent = new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] @@ -282,10 +282,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards { Hovered = _ => { - dropdown.ScheduleShow(); + content.ScheduleShow(); return false; }, - Unhovered = _ => dropdown.ScheduleHide() + Unhovered = _ => content.ScheduleHide() } } }, @@ -304,7 +304,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards } } }, - Dropdown = new Container + ExpandedContent = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs similarity index 94% rename from osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs rename to osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs index a24f09a9b5..7f94d0e3b7 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDropdown.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs @@ -19,14 +19,14 @@ using osuTK; namespace osu.Game.Beatmaps.Drawables.Cards { - public class BeatmapCardDropdown : CompositeDrawable + public class BeatmapCardContent : CompositeDrawable { - public Drawable Body + public Drawable MainContent { set => bodyContent.Child = value; } - public Drawable Dropdown + public Drawable ExpandedContent { set => dropdownScroll.Child = value; } @@ -40,7 +40,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly OsuScrollContainer dropdownScroll; private readonly Container borderContainer; - public BeatmapCardDropdown(float height) + public BeatmapCardContent(float height) { RelativeSizeAxes = Axes.X; Height = height; @@ -77,7 +77,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards return true; }, Unhovered = _ => checkForHide(), - Child = dropdownScroll = new DropdownScrollContainer + Child = dropdownScroll = new ExpandedContentScrollContainer { RelativeSizeAxes = Axes.X, ScrollbarVisible = false @@ -179,9 +179,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards }, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } - private class DropdownScrollContainer : OsuScrollContainer + private class ExpandedContentScrollContainer : OsuScrollContainer { - public DropdownScrollContainer() + public ExpandedContentScrollContainer() { ScrollbarVisible = false; } From 82ed8eae6bfa4f42ecb274f1138f414ad42f7d04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Dec 2021 20:52:06 +0100 Subject: [PATCH 034/117] Ensure hover handling container always calls base on hover events --- .../Drawables/Cards/HoverHandlingContainer.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/HoverHandlingContainer.cs b/osu.Game/Beatmaps/Drawables/Cards/HoverHandlingContainer.cs index 1e2c616332..fe37616755 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/HoverHandlingContainer.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/HoverHandlingContainer.cs @@ -14,14 +14,16 @@ namespace osu.Game.Beatmaps.Drawables.Cards public Func? Hovered { get; set; } public Action? Unhovered { get; set; } - protected override bool OnHover(HoverEvent e) => Hovered?.Invoke(e) ?? base.OnHover(e); + protected override bool OnHover(HoverEvent e) + { + bool handledByBase = base.OnHover(e); + return Hovered?.Invoke(e) ?? handledByBase; + } protected override void OnHoverLost(HoverLostEvent e) { - if (Unhovered != null) - Unhovered?.Invoke(e); - else - base.OnHoverLost(e); + base.OnHoverLost(e); + Unhovered?.Invoke(e); } } } From 999bba439f98bb31741acbfadd4b7a834be3a7a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 6 Dec 2021 21:00:26 +0100 Subject: [PATCH 035/117] Clarify usages of reverse child ID flow with inline comments --- osu.Game/Overlays/BeatmapListingOverlay.cs | 1 + osu.Game/Overlays/Profile/ProfileSection.cs | 2 ++ .../Overlays/Profile/Sections/PaginatedProfileSubsection.cs | 3 +++ osu.Game/Overlays/Rankings/SpotlightsLayout.cs | 1 + 4 files changed, 7 insertions(+) diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index a454af00c4..6b27dbf847 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -151,6 +151,7 @@ namespace osu.Game.Overlays } // spawn new children with the contained so we only clear old content at the last moment. + // reverse ID flow is required for correct Z-ordering of the cards' expandable content (last card should be front-most). var content = new ReverseChildIDFillFlowContainer { RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Overlays/Profile/ProfileSection.cs b/osu.Game/Overlays/Profile/ProfileSection.cs index fdf3077bf0..fc6fce0d8e 100644 --- a/osu.Game/Overlays/Profile/ProfileSection.cs +++ b/osu.Game/Overlays/Profile/ProfileSection.cs @@ -80,6 +80,8 @@ namespace osu.Game.Overlays.Profile } } }, + // reverse ID flow is required for correct Z-ordering of the content (last item should be front-most). + // particularly important in BeatmapsSection, as it uses beatmap cards, which have expandable overhanging content. content = new ReverseChildIDFillFlowContainer { Direction = FillDirection.Vertical, diff --git a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs index e7053ec4fa..9dcbf6142d 100644 --- a/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs +++ b/osu.Game/Overlays/Profile/Sections/PaginatedProfileSubsection.cs @@ -49,11 +49,14 @@ namespace osu.Game.Overlays.Profile.Sections Direction = FillDirection.Vertical, Children = new Drawable[] { + // reverse ID flow is required for correct Z-ordering of the items (last item should be front-most). + // particularly important in PaginatedBeatmapContainer, as it uses beatmap cards, which have expandable overhanging content. ItemsContainer = new ReverseChildIDFillFlowContainer { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Spacing = new Vector2(0, 2), + // ensure the container and its contents are in front of the "more" button. Depth = float.MinValue }, moreButton = new ShowMoreButton diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs index 61d68b7d29..bcfc2499b9 100644 --- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs +++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs @@ -135,6 +135,7 @@ namespace osu.Game.Overlays.Rankings Children = new Drawable[] { new ScoresTable(1, response.Users), + // reverse ID flow is required for correct Z-ordering of the cards' expandable content (last card should be front-most). new ReverseChildIDFillFlowContainer { AutoSizeAxes = Axes.Y, From 974987550ff53d9a628f6d6dbf6d9c265147e677 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 7 Dec 2021 04:01:53 +0300 Subject: [PATCH 036/117] Move API request response size log to correct logging target --- osu.Game/Online/API/APIRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index efb0b102d0..91148c177f 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -42,7 +42,7 @@ namespace osu.Game.Online.API if (WebRequest != null) { Response = ((OsuJsonWebRequest)WebRequest).ResponseObject; - Logger.Log($"{GetType()} finished with response size of {WebRequest.ResponseStream.Length:#,0} bytes"); + Logger.Log($"{GetType()} finished with response size of {WebRequest.ResponseStream.Length:#,0} bytes", LoggingTarget.Network); } } From f3e9fb76fcf98e7da97687b64846878af8bfc0b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Dec 2021 13:32:35 +0900 Subject: [PATCH 037/117] Add the ability to pass a `CancellationToken` through `DifficultyCalculator.CalculateAll` Was weirdly missing from this one method. --- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 01b4150030..6b6ea6fed5 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -120,14 +120,14 @@ namespace osu.Game.Rulesets.Difficulty /// Calculates the difficulty of the beatmap using all mod combinations applicable to the beatmap. /// /// A collection of structures describing the difficulty of the beatmap for each mod combination. - public IEnumerable CalculateAll() + public IEnumerable CalculateAll(CancellationToken cancellationToken = default) { foreach (var combination in CreateDifficultyAdjustmentModCombinations()) { if (combination is MultiMod multi) - yield return Calculate(multi.Mods); + yield return Calculate(multi.Mods, cancellationToken); else - yield return Calculate(combination.Yield()); + yield return Calculate(combination.Yield(), cancellationToken); } } From cfa712473d471862634941f559d5bbd614a15086 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Dec 2021 13:33:41 +0900 Subject: [PATCH 038/117] Use default timeout in `GetPlayableBeatmap` when provided `CancellationToken` is `default` --- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 6b6ea6fed5..6b61dd3efb 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -145,7 +145,11 @@ namespace osu.Game.Rulesets.Difficulty { playableMods = mods.Select(m => m.DeepClone()).ToArray(); - Beatmap = beatmap.GetPlayableBeatmap(ruleset, playableMods, cancellationToken); + // Only pass through the cancellation token if it's non-default. + // This allows for the default timeout to be applied for playable beatmap construction. + Beatmap = cancellationToken == default + ? beatmap.GetPlayableBeatmap(ruleset, playableMods) + : beatmap.GetPlayableBeatmap(ruleset, playableMods, cancellationToken); var track = new TrackVirtual(10000); playableMods.OfType().ForEach(m => m.ApplyToTrack(track)); From dea7f2308c00f673ce16d42a51d346d2d5dead5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Dec 2021 17:24:03 +0900 Subject: [PATCH 039/117] Fix participant panels potentially keeping a reference to an old user If a user leave and then rejoins a multiplayer match while another user is not at the lobby screen, there is a potential the `ParticipantPanel` tracking the user will not correctly be recreated to reference the new instance of the `MultiplayerUser`. This happens because the `OnRoomUpdated` call is scheduled, which means it is not running in the background, coupled with the local logic that relies on `IEquatable(MultiplayerRoomUser)` (which in turn falls back to a UserID comparison). Changing this to a reference comparison is the easiest way to resolve this. Whether we change the `IEquatable` implementation is up for discussion. Closes https://github.com/ppy/osu/issues/15970. --- .../Multiplayer/Participants/ParticipantsList.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs index 2ad64e115e..d36c556fac 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Participants/ParticipantsList.cs @@ -77,7 +77,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants else { // Remove panels for users no longer in the room. - panels.RemoveAll(p => !Room.Users.Contains(p.User)); + foreach (var p in panels) + { + // Note that we *must* use reference equality here, as this call is scheduled and a user may have left and joined since it was last run. + if (Room.Users.All(u => !ReferenceEquals(p.User, u))) + p.Expire(); + } // Add panels for all users new to the room. foreach (var user in Room.Users.Except(panels.Select(p => p.User))) From 9978caab127c3ce89ee7a77433ac2685c7f1fe55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Dec 2021 18:37:30 +0900 Subject: [PATCH 040/117] 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 ce23d36faa..0c922c09ac 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 788a5113ad..adb25f46fe 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 2aa9821e8f..db5d9af865 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From 5ffe702dd67e5e9444cd1f8fd9a19771011a70f3 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 7 Dec 2021 18:53:08 +0900 Subject: [PATCH 041/117] Add match type and queue mode to multiplayer room panels --- .../Multiplayer/TestSceneDrawableRoom.cs | 35 +++++++++++++ .../Lounge/Components/DrawableRoom.cs | 31 ++++++------ .../Lounge/Components/MatchTypePill.cs | 50 +++++++++++++++++++ .../Lounge/Components/QueueModePill.cs | 50 +++++++++++++++++++ .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 13 +++++ .../OnlinePlay/Match/DrawableMatchRoom.cs | 8 +++ 6 files changed, 173 insertions(+), 14 deletions(-) create mode 100644 osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs create mode 100644 osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 2c28a1752e..423822cbe4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -11,12 +11,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Overlays; using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.OnlinePlay.Lounge.Components; +using osu.Game.Screens.OnlinePlay.Match; using osu.Game.Tests.Beatmaps; using osuTK; @@ -172,6 +174,39 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType().Single().Alpha)); } + [Test] + public void TestMultiplayerRooms() + { + AddStep("create rooms", () => Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(5), + Children = new[] + { + new DrawableMatchRoom(new Room + { + Name = { Value = "A host-only room" }, + QueueMode = { Value = QueueMode.HostOnly }, + Type = { Value = MatchType.HeadToHead } + }), + new DrawableMatchRoom(new Room + { + Name = { Value = "An all-players, team-versus room" }, + QueueMode = { Value = QueueMode.AllPlayers }, + Type = { Value = MatchType.TeamVersus } + }), + new DrawableMatchRoom(new Room + { + Name = { Value = "A round-robin room" }, + QueueMode = { Value = QueueMode.AllPlayersRoundRobin }, + Type = { Value = MatchType.HeadToHead } + }), + } + }); + } + private DrawableRoom createLoungeRoom(Room room) { room.Host.Value ??= new APIUser { Username = "peppy", Id = 2 }; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 0502c4abe6..ba3866d734 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.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.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -184,20 +185,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, Spacing = new Vector2(5), - Children = new Drawable[] - { - new PlaylistCountPill - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - new StarRatingRangeDisplay - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Scale = new Vector2(0.8f) - } - } + ChildrenEnumerable = CreateBottomDetails() } } }, @@ -287,6 +275,21 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components protected virtual Drawable CreateBackground() => new OnlinePlayBackgroundSprite(); + protected virtual IEnumerable CreateBottomDetails() => new Drawable[] + { + new PlaylistCountPill + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + new StarRatingRangeDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Scale = new Vector2(0.8f) + } + }; + private class RoomNameText : OsuSpriteText { [Resolved(typeof(Room), nameof(Online.Rooms.Room.Name))] diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs new file mode 100644 index 0000000000..d104ede8f7 --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/MatchTypePill.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Rooms; + +namespace osu.Game.Screens.OnlinePlay.Lounge.Components +{ + public class MatchTypePill : OnlinePlayComposite + { + private OsuTextFlowContainer textFlow; + + public MatchTypePill() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new PillContainer + { + Child = textFlow = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12)) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Type.BindValueChanged(onMatchTypeChanged, true); + } + + private void onMatchTypeChanged(ValueChangedEvent type) + { + textFlow.Clear(); + textFlow.AddText(type.NewValue.GetLocalisableDescription()); + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs new file mode 100644 index 0000000000..7501f0237b --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/QueueModePill.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Multiplayer; + +namespace osu.Game.Screens.OnlinePlay.Lounge.Components +{ + public class QueueModePill : OnlinePlayComposite + { + private OsuTextFlowContainer textFlow; + + public QueueModePill() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new PillContainer + { + Child = textFlow = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12)) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + QueueMode.BindValueChanged(onQueueModeChanged, true); + } + + private void onQueueModeChanged(ValueChangedEvent mode) + { + textFlow.Clear(); + textFlow.AddText(mode.NewValue.GetLocalisableDescription()); + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 0d2b2249ef..529c056f4f 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -177,6 +178,18 @@ namespace osu.Game.Screens.OnlinePlay.Lounge return true; } + protected override IEnumerable CreateBottomDetails() + { + if (Room.Type.Value == MatchType.Playlists) + return base.CreateBottomDetails(); + + return new Drawable[] + { + new MatchTypePill(), + new QueueModePill(), + }.Concat(base.CreateBottomDetails()); + } + public class PasswordEntryPopover : OsuPopover { private readonly Room room; diff --git a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs index a7b907c7d2..ed457967e7 100644 --- a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -67,6 +69,12 @@ namespace osu.Game.Screens.OnlinePlay.Match protected override Drawable CreateBackground() => background = new BackgroundSprite(); + protected override IEnumerable CreateBottomDetails() => new Drawable[] + { + new MatchTypePill(), + new QueueModePill(), + }.Concat(base.CreateBottomDetails()); + private class BackgroundSprite : UpdateableBeatmapBackgroundSprite { protected override double LoadDelay => 0; From 4683193f098fcf640eb80514ecee3280514cc104 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 7 Dec 2021 20:36:39 +0900 Subject: [PATCH 042/117] Move implementation to base class --- .../Lounge/Components/DrawableRoom.cs | 38 +++++++++++++------ .../OnlinePlay/Lounge/DrawableLoungeRoom.cs | 13 ------- .../OnlinePlay/Match/DrawableMatchRoom.cs | 8 ---- 3 files changed, 27 insertions(+), 32 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index ba3866d734..a87f21630c 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -275,20 +275,36 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components protected virtual Drawable CreateBackground() => new OnlinePlayBackgroundSprite(); - protected virtual IEnumerable CreateBottomDetails() => new Drawable[] + protected virtual IEnumerable CreateBottomDetails() { - new PlaylistCountPill + var pills = new List(); + + if (Room.Type.Value != MatchType.Playlists) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - new StarRatingRangeDisplay - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Scale = new Vector2(0.8f) + pills.AddRange(new OnlinePlayComposite[] + { + new MatchTypePill(), + new QueueModePill(), + }); } - }; + + pills.AddRange(new Drawable[] + { + new PlaylistCountPill + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + new StarRatingRangeDisplay + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Scale = new Vector2(0.8f) + } + }); + + return pills; + } private class RoomNameText : OsuSpriteText { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs index 529c056f4f..0d2b2249ef 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/DrawableLoungeRoom.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -178,18 +177,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge return true; } - protected override IEnumerable CreateBottomDetails() - { - if (Room.Type.Value == MatchType.Playlists) - return base.CreateBottomDetails(); - - return new Drawable[] - { - new MatchTypePill(), - new QueueModePill(), - }.Concat(base.CreateBottomDetails()); - } - public class PasswordEntryPopover : OsuPopover { private readonly Room room; diff --git a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs index ed457967e7..a7b907c7d2 100644 --- a/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Match/DrawableMatchRoom.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -69,12 +67,6 @@ namespace osu.Game.Screens.OnlinePlay.Match protected override Drawable CreateBackground() => background = new BackgroundSprite(); - protected override IEnumerable CreateBottomDetails() => new Drawable[] - { - new MatchTypePill(), - new QueueModePill(), - }.Concat(base.CreateBottomDetails()); - private class BackgroundSprite : UpdateableBeatmapBackgroundSprite { protected override double LoadDelay => 0; From ded86282c1b44574b8f1fe1711766640b77b17b4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 7 Dec 2021 23:14:35 +0900 Subject: [PATCH 043/117] Rename + better documentation --- .../Preprocessing/OsuDifficultyHitObject.cs | 37 +++++++++++++------ .../Difficulty/Skills/Aim.cs | 16 ++++---- .../Difficulty/Skills/Flashlight.cs | 2 +- .../Difficulty/Skills/Speed.cs | 2 +- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index f1f246359a..8ecba7375c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -24,22 +24,35 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing public readonly double StrainTime; /// - /// Normalised distance from the end position of the previous to the start position of this . + /// Normalised distance from the "lazy" end position of the previous to the start position of this . + /// + /// The "lazy" end position is the position at which the cursor ends up if the previous hitobject is followed with as minimal movement as possible (i.e. on the edge of slider follow circles). + /// /// - public double JumpDistance { get; private set; } + public double LazyJumpDistance { get; private set; } /// - /// Normalised minimum distance from the end position of the previous to the start position of this . + /// Normalised shortest distance to consider for a jump between the previous and this . /// /// - /// This is bounded by , but may be smaller if a more natural path is able to be taken through a preceding slider. + /// This is bounded from above by , and is smaller than the former if a more natural path is able to be taken through the previous . /// - public double MovementDistance { get; private set; } + /// + /// Suppose a linear slider - circle pattern. + ///
+ /// Following the slider lazily (see: ) will result in underestimating the true end position of the slider as being closer towards the start position. + /// As a result, overestimates the jump distance because the player is able to take a more natural path by following through the slider to its end, + /// such that the jump is felt as only starting from the slider's true end position. + ///
+ /// Now consider a slider - circle pattern where the circle is stacked along the path inside the slider. + /// In this case, the lazy end position correctly estimates the true end position of the slider and provides the more natural movement path. + ///
+ public double MinimumJumpDistance { get; private set; } /// - /// The time taken to travel through , with a minimum value of 25ms. + /// The time taken to travel through , with a minimum value of 25ms. /// - public double MovementTime { get; private set; } + public double MinimumJumpTime { get; private set; } /// /// Normalised distance between the start and end position of this . @@ -96,14 +109,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing Vector2 lastCursorPosition = getEndCursorPosition(lastObject); - JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; - MovementTime = StrainTime; - MovementDistance = JumpDistance; + LazyJumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; + MinimumJumpTime = StrainTime; + MinimumJumpDistance = LazyJumpDistance; if (lastObject is Slider lastSlider) { double lastTravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time); - MovementTime = Math.Max(StrainTime - lastTravelTime, min_delta_time); + MinimumJumpTime = Math.Max(StrainTime - lastTravelTime, min_delta_time); // // We'll try to better approximate the real movements a player will take in patterns following on from sliders. Consider the following slider-to-object patterns: @@ -127,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; - MovementDistance = Math.Max(0, Math.Min(JumpDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius)); + MinimumJumpDistance = Math.Max(0, Math.Min(LazyJumpDistance - (maximum_slider_radius - assumed_slider_radius), tailJumpDistance - maximum_slider_radius)); } if (lastLastObject != null && !(lastLastObject is Spinner)) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index d2a1083f29..a6301aed6d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -44,24 +44,24 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills var osuLastLastObj = (OsuDifficultyHitObject)Previous[1]; // Calculate the velocity to the current hitobject, which starts with a base distance / time assuming the last object is a hitcircle. - double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime; + double currVelocity = osuCurrObj.LazyJumpDistance / osuCurrObj.StrainTime; // But if the last object is a slider, then we extend the travel velocity through the slider into the current object. if (osuLastObj.BaseObject is Slider && withSliders) { double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; // calculate the slider velocity from slider head to slider end. - double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object + double movementVelocity = osuCurrObj.MinimumJumpDistance / osuCurrObj.MinimumJumpTime; // calculate the movement velocity from slider end to current object currVelocity = Math.Max(currVelocity, movementVelocity + travelVelocity); // take the larger total combined velocity. } // As above, do the same for the previous hitobject. - double prevVelocity = osuLastObj.JumpDistance / osuLastObj.StrainTime; + double prevVelocity = osuLastObj.LazyJumpDistance / osuLastObj.StrainTime; if (osuLastLastObj.BaseObject is Slider && withSliders) { double travelVelocity = osuLastLastObj.TravelDistance / osuLastLastObj.TravelTime; - double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime; + double movementVelocity = osuLastObj.MinimumJumpDistance / osuLastObj.MinimumJumpTime; prevVelocity = Math.Max(prevVelocity, movementVelocity + travelVelocity); } @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills acuteAngleBonus *= calcAcuteAngleBonus(lastAngle) // Multiply by previous angle, we don't want to buff unless this is a wiggle type pattern. * Math.Min(angleBonus, 125 / osuCurrObj.StrainTime) // The maximum velocity we buff is equal to 125 / strainTime * Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, (100 - osuCurrObj.StrainTime) / 25)), 2) // scale buff from 150 bpm 1/4 to 200 bpm 1/4 - * Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.JumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter). + * Math.Pow(Math.Sin(Math.PI / 2 * (Math.Clamp(osuCurrObj.LazyJumpDistance, 50, 100) - 50) / 50), 2); // Buff distance exceeding 50 (radius) up to 100 (diameter). } // Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute. @@ -107,8 +107,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills if (Math.Max(prevVelocity, currVelocity) != 0) { // We want to use the average velocity over the whole object when awarding differences, not the individual jump and slider path velocities. - prevVelocity = (osuLastObj.JumpDistance + osuLastLastObj.TravelDistance) / osuLastObj.StrainTime; - currVelocity = (osuCurrObj.JumpDistance + osuLastObj.TravelDistance) / osuCurrObj.StrainTime; + prevVelocity = (osuLastObj.LazyJumpDistance + osuLastLastObj.TravelDistance) / osuLastObj.StrainTime; + currVelocity = (osuCurrObj.LazyJumpDistance + osuLastObj.TravelDistance) / osuCurrObj.StrainTime; // Scale with ratio of difference compared to 0.5 * max dist. double distRatio = Math.Pow(Math.Sin(Math.PI / 2 * Math.Abs(prevVelocity - currVelocity) / Math.Max(prevVelocity, currVelocity)), 2); @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // Reward for % distance slowed down compared to previous, paying attention to not award overlap double nonOverlapVelocityBuff = Math.Abs(prevVelocity - currVelocity) // do not award overlap - * Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, Math.Min(osuCurrObj.JumpDistance, osuLastObj.JumpDistance) / 100)), 2); + * Math.Pow(Math.Sin(Math.PI / 2 * Math.Min(1, Math.Min(osuCurrObj.LazyJumpDistance, osuLastObj.LazyJumpDistance) / 100)), 2); // Choose the largest bonus, multiplied by ratio. velocityChangeBonus = Math.Max(overlapVelocityBuff, nonOverlapVelocityBuff) * distRatio; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 466f0556ab..44ba0e2057 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills smallDistNerf = Math.Min(1.0, jumpDistance / 75.0); // We also want to nerf stacks so that only the first object of the stack is accounted for. - double stackNerf = Math.Min(1.0, (osuPrevious.JumpDistance / scalingFactor) / 25.0); + double stackNerf = Math.Min(1.0, (osuPrevious.LazyJumpDistance / scalingFactor) / 25.0); result += Math.Pow(0.8, i) * stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index b53d287ee6..75a9b13bdf 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills speedBonus = 1 + 0.75 * Math.Pow((min_speed_bonus - strainTime) / speed_balancing_factor, 2); double travelDistance = osuPrevObj?.TravelDistance ?? 0; - double distance = Math.Min(single_spacing_threshold, travelDistance + osuCurrObj.JumpDistance); + double distance = Math.Min(single_spacing_threshold, travelDistance + osuCurrObj.LazyJumpDistance); return (speedBonus + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) / strainTime; } From 7e236c3a4140f0023628dfe00e5b8966f2425827 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 7 Dec 2021 23:36:48 +0900 Subject: [PATCH 044/117] Remove unused dependency --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index de82c463b3..c291bddeeb 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -25,7 +25,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.Chat; -using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Overlays.BeatmapSet; using osu.Game.Rulesets; @@ -72,9 +71,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved] private BeatmapLookupCache beatmapLookupCache { get; set; } - [Resolved] - private MultiplayerClient multiplayerClient { get; set; } - private PanelBackground panelBackground; private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; From 25a0505c97df49a825d874551afc69daf0972c47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Dec 2021 14:26:11 +0900 Subject: [PATCH 045/117] Scale card when expanding to better distinguish hovered card from other cards in listing --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs | 14 +++++++++++++- .../Beatmaps/Drawables/Cards/BeatmapCardContent.cs | 3 +++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 9031d6df1a..d93ac841ab 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -285,7 +285,13 @@ namespace osu.Game.Beatmaps.Drawables.Cards content.ScheduleShow(); return false; }, - Unhovered = _ => content.ScheduleHide() + Unhovered = _ => + { + // This hide should only trigger if the expanded content has not shown yet. + // ie. if the user has not shown intent to want to see it (quickly moved over the info row area). + if (!Expanded.Value) + content.ScheduleHide(); + } } } }, @@ -360,6 +366,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards protected override void OnHoverLost(HoverLostEvent e) { + content.ScheduleHide(); + updateState(); base.OnHoverLost(e); } @@ -398,6 +406,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards thumbnail.Dimmed.Value = showDetails; + // Scale value is intentionally chosen to fit in the spacing of listing displays, as to not overlap horizontally with adjacent cards. + // This avoids depth issues where a hovered (scaled) card to the right of another card would be beneath the card to the left. + content.ScaleTo(Expanded.Value ? 1.03f : 1, 500, Easing.OutQuint); + mainContent.ResizeWidthTo(targetWidth, TRANSITION_DURATION, Easing.OutQuint); mainContentBackground.Dimmed.Value = showDetails; diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs index 7f94d0e3b7..681f09c658 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs @@ -45,6 +45,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.X; Height = height; + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + InternalChild = content = new HoverHandlingContainer { RelativeSizeAxes = Axes.X, From e9694dc74e0514dae02e0ba65538ac2f8e76f613 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 7 Dec 2021 20:40:10 +0100 Subject: [PATCH 046/117] Wait for match type changes in team versus test --- osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index 981989c28a..ccce26ad31 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddAssert("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus); + AddUntilStep("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus); AddAssert("user state arrived", () => client.Room?.Users.FirstOrDefault()?.MatchState is TeamVersusUserState); } @@ -162,13 +162,13 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddAssert("room type is head to head", () => client.Room?.Settings.MatchType == MatchType.HeadToHead); + AddUntilStep("room type is head to head", () => client.Room?.Settings.MatchType == MatchType.HeadToHead); AddUntilStep("team displays are not displaying teams", () => multiplayerScreenStack.ChildrenOfType().All(d => d.DisplayedTeam == null)); AddStep("change to team vs", () => client.ChangeSettings(matchType: MatchType.TeamVersus)); - AddAssert("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus); + AddUntilStep("room type is team vs", () => client.Room?.Settings.MatchType == MatchType.TeamVersus); AddUntilStep("team displays are displaying teams", () => multiplayerScreenStack.ChildrenOfType().All(d => d.DisplayedTeam != null)); } From 10dd64e07c89165ee5b437f13c36cab09c9eec2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 7 Dec 2021 21:00:25 +0100 Subject: [PATCH 047/117] Fix being able to paste objects while composer is loading Would lead to exceptions due to modification of `Beatmap.HitObjects` during its enumeration by `DrawableRuleset`, which was happening as an async load via `EditorScreenWithTimeline.CreateMainContent()`. --- osu.Game/Screens/Edit/Compose/ComposeScreen.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs index 3b02d42b41..9386538a78 100644 --- a/osu.Game/Screens/Edit/Compose/ComposeScreen.cs +++ b/osu.Game/Screens/Edit/Compose/ComposeScreen.cs @@ -83,7 +83,9 @@ namespace osu.Game.Screens.Edit.Compose { base.LoadComplete(); EditorBeatmap.SelectedHitObjects.BindCollectionChanged((_, __) => updateClipboardActionAvailability()); - clipboard.BindValueChanged(_ => updateClipboardActionAvailability(), true); + clipboard.BindValueChanged(_ => updateClipboardActionAvailability()); + composer.OnLoadComplete += _ => updateClipboardActionAvailability(); + updateClipboardActionAvailability(); } #region Clipboard operations @@ -131,7 +133,7 @@ namespace osu.Game.Screens.Edit.Compose private void updateClipboardActionAvailability() { CanCut.Value = CanCopy.Value = EditorBeatmap.SelectedHitObjects.Any(); - CanPaste.Value = !string.IsNullOrEmpty(clipboard.Value); + CanPaste.Value = composer.IsLoaded && !string.IsNullOrEmpty(clipboard.Value); } private string formatSelectionAsString() From 7720a1b69b87f85b09440d1f5e62a4b8ab14fda7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 7 Dec 2021 21:10:45 +0100 Subject: [PATCH 048/117] Fix test to wait for drawable ruleset load before attempting paste --- osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs index c81a1abfbc..c23db5e440 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDifficultySwitching.cs @@ -10,6 +10,7 @@ using osu.Game.Beatmaps; using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit; using osu.Game.Tests.Beatmaps.IO; @@ -89,6 +90,7 @@ namespace osu.Game.Tests.Visual.Editing confirmEditingBeatmap(() => targetDifficulty); AddAssert("no objects selected", () => !EditorBeatmap.SelectedHitObjects.Any()); + AddUntilStep("wait for drawable ruleset", () => Editor.ChildrenOfType().SingleOrDefault()?.IsLoaded == true); AddStep("paste object", () => Editor.Paste()); if (sameRuleset) From fea3b9d7a9c1a0cc87e3473dec0af49c25007fb1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 8 Dec 2021 04:43:01 +0300 Subject: [PATCH 049/117] Disable minimum frame durations on osu! for iOS --- osu.Game.Rulesets.Catch.Tests.iOS/Info.plist | 2 ++ osu.Game.Rulesets.Mania.Tests.iOS/Info.plist | 2 ++ osu.Game.Rulesets.Osu.Tests.iOS/Info.plist | 2 ++ osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist | 2 ++ osu.Game.Tests.iOS/Info.plist | 2 ++ osu.iOS/Info.plist | 2 ++ 6 files changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist index 5115746cbb..3ba1886d98 100644 --- a/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Catch.Tests.iOS/Info.plist @@ -32,5 +32,7 @@ XSAppIconAssets Assets.xcassets/AppIcon.appiconset + CADisableMinimumFrameDurationOnPhone + diff --git a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist index 8780204d5b..09ed2dd007 100644 --- a/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Mania.Tests.iOS/Info.plist @@ -32,5 +32,7 @@ XSAppIconAssets Assets.xcassets/AppIcon.appiconset + CADisableMinimumFrameDurationOnPhone + diff --git a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist index f79215cf54..dd032ef1c1 100644 --- a/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Osu.Tests.iOS/Info.plist @@ -32,5 +32,7 @@ XSAppIconAssets Assets.xcassets/AppIcon.appiconset + CADisableMinimumFrameDurationOnPhone + diff --git a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist index 5fe822946a..ac658cd14e 100644 --- a/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist +++ b/osu.Game.Rulesets.Taiko.Tests.iOS/Info.plist @@ -32,5 +32,7 @@ XSAppIconAssets Assets.xcassets/AppIcon.appiconset + CADisableMinimumFrameDurationOnPhone + diff --git a/osu.Game.Tests.iOS/Info.plist b/osu.Game.Tests.iOS/Info.plist index 98a4223116..1a89345bc5 100644 --- a/osu.Game.Tests.iOS/Info.plist +++ b/osu.Game.Tests.iOS/Info.plist @@ -32,5 +32,7 @@ XSAppIconAssets Assets.xcassets/AppIcon.appiconset + CADisableMinimumFrameDurationOnPhone + diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index 249474b1d7..2592f909ce 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -33,6 +33,8 @@ UIStatusBarHidden + CADisableMinimumFrameDurationOnPhone + NSCameraUsageDescription We don't really use the camera. NSMicrophoneUsageDescription From a969fe3ef811a64c0e05a70342645782dd9d4f77 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Dec 2021 13:37:46 +0900 Subject: [PATCH 050/117] Add test coverage showing intended UX of user's volume levels being retained when unmuting --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 06eaa726c9..958d617d63 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -251,7 +251,12 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestMutedNotificationMuteButton() { - addVolumeSteps("mute button", () => volumeOverlay.IsMuted.Value = true, () => !volumeOverlay.IsMuted.Value); + addVolumeSteps("mute button", () => + { + // Importantly, in the case the volume is muted but the user has a volume level set, it should be retained. + audioManager.VolumeTrack.Value = 0.5f; + volumeOverlay.IsMuted.Value = true; + }, () => !volumeOverlay.IsMuted.Value && audioManager.VolumeTrack.Value == 0.5f); } /// From 0775053a18a711c8eb9fff57a26ff80c8b98caca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Dec 2021 13:38:13 +0900 Subject: [PATCH 051/117] Fix the unmute notification potentially overwriting user's volume levels unnecessarily I've also changed the cutoffs to 5% rather than zero, as this seems like a saner method of showing this dialog. With levels 5% or less, the game is basically inaudible. Arguably, the cutoff can be increased to 10%. --- osu.Game/Screens/Play/PlayerLoader.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 57db411571..dfc3c2b61d 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -468,12 +468,14 @@ namespace osu.Game.Screens.Play private int restartCount; + private const double volume_requirement = 0.05; + private void showMuteWarningIfNeeded() { if (!muteWarningShownOnce.Value) { // Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted. - if (volumeOverlay?.IsMuted.Value == true || audioManager.Volume.Value <= audioManager.Volume.MinValue || audioManager.VolumeTrack.Value <= audioManager.VolumeTrack.MinValue) + if (volumeOverlay?.IsMuted.Value == true || audioManager.Volume.Value <= volume_requirement || audioManager.VolumeTrack.Value <= volume_requirement) { notificationOverlay?.Post(new MutedNotification()); muteWarningShownOnce.Value = true; @@ -487,7 +489,7 @@ namespace osu.Game.Screens.Play public MutedNotification() { - Text = "Your music volume is set to 0%! Click here to restore it."; + Text = "Your game volume is too low to hear anything! Click here to restore it."; } [BackgroundDependencyLoader] @@ -501,8 +503,12 @@ namespace osu.Game.Screens.Play notificationOverlay.Hide(); volumeOverlay.IsMuted.Value = false; - audioManager.Volume.SetDefault(); - audioManager.VolumeTrack.SetDefault(); + + // Check values before resetting, as the user may have only had mute enabled, in which case we might not need to adjust volumes. + if (audioManager.Volume.Value < volume_requirement) + audioManager.Volume.SetDefault(); + if (audioManager.VolumeTrack.Value < volume_requirement) + audioManager.VolumeTrack.SetDefault(); return true; }; From 7c0f7b1baac4fbc16791f2208605f7a77ec78920 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 8 Dec 2021 14:57:21 +0900 Subject: [PATCH 052/117] Use "x" for cursor position in diagrams --- .../Preprocessing/OsuDifficultyHitObject.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 8ecba7375c..1b6914bfaf 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -121,20 +121,20 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // // We'll try to better approximate the real movements a player will take in patterns following on from sliders. Consider the following slider-to-object patterns: // - // 1. <======o==> + // 1. <======x==> // | / - // o + // x // - // 2. <======o==>---o + // 2. <======x==>---x // |______| // - // Where "<==>" represents a slider, and "o" represents where the cursor needs to be for either hitobject (for a slider, this is the lazy cursor position). + // Where "<==>" represents a slider, and "x" represents where the cursor needs to be for either hitobject (for a slider, this is the lazy cursor position). // - // The pattern (o--o) has distance JumpDistance. - // The pattern (>--o) is a new distance we'll call "tailJumpDistance". + // The pattern (x--x) has distance JumpDistance. + // The pattern (>--x) is a new distance we'll call "tailJumpDistance". // - // Case (1) is an anti-flow pattern, where players will cut the slider short in order to move to the next object. The most natural jump pattern is (o--o). - // Case (2) is a flow pattern, where players will follow the slider through to its visual extent. The most natural jump pattern is (>--o). + // Case (1) is an anti-flow pattern, where players will cut the slider short in order to move to the next object. The most natural jump pattern is (x--x). + // Case (2) is a flow pattern, where players will follow the slider through to its visual extent. The most natural jump pattern is (>--x). // // A lenience is applied by assuming that the player jumps the minimum of these two distances in all cases. // From 6ec3f41839a6c8a3a6cca8c75162d7c100336605 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Dec 2021 14:56:38 +0900 Subject: [PATCH 053/117] Fix `LegacyComboCounter` not handling non-default anchor/origin specifications correctly --- .../Screens/Play/HUD/LegacyComboCounter.cs | 43 ++++++++++--------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs index 4859f1b977..f1078c5d55 100644 --- a/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs +++ b/osu.Game/Screens/Play/HUD/LegacyComboCounter.cs @@ -67,22 +67,32 @@ namespace osu.Game.Screens.Play.HUD Scale = new Vector2(1.2f); - InternalChild = counterContainer = new Container + InternalChildren = new[] { - AutoSizeAxes = Axes.Both, - AlwaysPresent = true, - Children = new[] + popOutCount = new LegacySpriteText(LegacyFont.Combo) { - popOutCount = new LegacySpriteText(LegacyFont.Combo) + Alpha = 0, + Margin = new MarginPadding(0.05f), + Blending = BlendingParameters.Additive, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + BypassAutoSizeAxes = Axes.Both, + }, + counterContainer = new Container + { + AutoSizeAxes = Axes.Both, + AlwaysPresent = true, + Children = new[] { - Alpha = 0, - Margin = new MarginPadding(0.05f), - Blending = BlendingParameters.Additive, - }, - displayedCountSpriteText = new LegacySpriteText(LegacyFont.Combo) - { - Alpha = 0, - }, + displayedCountSpriteText = new LegacySpriteText(LegacyFont.Combo) + { + // Initial text and AlwaysPresent allow the counter to have a size before it first displays a combo. + // This is useful for display in the skin editor. + Text = formatCount(0), + AlwaysPresent = true, + Alpha = 0, + }, + } } }; } @@ -121,13 +131,6 @@ namespace osu.Game.Screens.Play.HUD ((IHasText)displayedCountSpriteText).Text = formatCount(Current.Value); - counterContainer.Anchor = Anchor; - counterContainer.Origin = Origin; - displayedCountSpriteText.Anchor = Anchor; - displayedCountSpriteText.Origin = Origin; - popOutCount.Anchor = Anchor; - popOutCount.Origin = Origin; - Current.BindValueChanged(combo => updateCount(combo.NewValue == 0), true); } From 814f072767f52ceb576eb8d7ecfdeceb14340ae7 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 8 Dec 2021 15:17:56 +0900 Subject: [PATCH 054/117] Use new LazyJumpDistance terminology in documentation --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 1b6914bfaf..6c354cfa99 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -130,7 +130,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // // Where "<==>" represents a slider, and "x" represents where the cursor needs to be for either hitobject (for a slider, this is the lazy cursor position). // - // The pattern (x--x) has distance JumpDistance. + // The pattern (x--x) has distance LazyJumpDistance. // The pattern (>--x) is a new distance we'll call "tailJumpDistance". // // Case (1) is an anti-flow pattern, where players will cut the slider short in order to move to the next object. The most natural jump pattern is (x--x). From 872e0884c0a0997a16c23fcb1b394688409d5803 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Dec 2021 15:22:02 +0900 Subject: [PATCH 055/117] Fix the local user's rank not showing on multiplayer/playlist results screen Applying the simple solution for now. Not sure how this will evolve over time, but seems sane enough. --- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index 76411c8c6b..c07cfa9c4d 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -157,6 +157,8 @@ namespace osu.Game.Screens.Play request.Success += s => { score.ScoreInfo.OnlineScoreID = s.ID; + score.ScoreInfo.Position = s.Position; + scoreSubmissionSource.SetResult(true); }; From 11104124f1f923fa4c1f6bf06ac2441a7ac3cc0c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 8 Dec 2021 15:52:59 +0900 Subject: [PATCH 056/117] Restructure doc for easier readability --- .../Preprocessing/OsuDifficultyHitObject.cs | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 6c354cfa99..2c81b42e6c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -119,24 +119,25 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing MinimumJumpTime = Math.Max(StrainTime - lastTravelTime, min_delta_time); // - // We'll try to better approximate the real movements a player will take in patterns following on from sliders. Consider the following slider-to-object patterns: + // There are two types of slider-to-object patterns to consider in order to better approximate the real movements a player will take. // - // 1. <======x==> - // | / - // x + // 1. The anti-flow pattern, where players cut the slider short in order to move to the next hitobject. // - // 2. <======x==>---x - // |______| + // <======o==> ← slider + // | ← most natural jump path + // o ← a follow-up hitcircle // - // Where "<==>" represents a slider, and "x" represents where the cursor needs to be for either hitobject (for a slider, this is the lazy cursor position). + // In this case the most natural jump path (o--o) is approximated by LazyJumpDistance. // - // The pattern (x--x) has distance LazyJumpDistance. - // The pattern (>--x) is a new distance we'll call "tailJumpDistance". + // 2. The flow pattern, where players follow through the slider to its visual extent into the next hitobject. // - // Case (1) is an anti-flow pattern, where players will cut the slider short in order to move to the next object. The most natural jump pattern is (x--x). - // Case (2) is a flow pattern, where players will follow the slider through to its visual extent. The most natural jump pattern is (>--x). + // <======o==>---o + // ↑ + // most natural jump path // - // A lenience is applied by assuming that the player jumps the minimum of these two distances in all cases. + // In this case the most natural movement path is better approximated by a new distance called "tailJumpDistance" - the distance between the slider's tail and the next hitobject. + // + // Thus, the player is assumed to jump the minimum of these two distances in all cases. // float tailJumpDistance = Vector2.Subtract(lastSlider.TailCircle.StackedPosition, BaseObject.StackedPosition).Length * scalingFactor; From 99991a6703887ffa1b95b4217005bf81db15be4d Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 8 Dec 2021 15:59:15 +0900 Subject: [PATCH 057/117] Minor cleanups, unifying wording a bit more --- .../Difficulty/Preprocessing/OsuDifficultyHitObject.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 2c81b42e6c..4df8ff0b12 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -119,7 +119,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing MinimumJumpTime = Math.Max(StrainTime - lastTravelTime, min_delta_time); // - // There are two types of slider-to-object patterns to consider in order to better approximate the real movements a player will take. + // There are two types of slider-to-object patterns to consider in order to better approximate the real movement a player will take to jump between the hitobjects. // // 1. The anti-flow pattern, where players cut the slider short in order to move to the next hitobject. // @@ -127,7 +127,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // | ← most natural jump path // o ← a follow-up hitcircle // - // In this case the most natural jump path (o--o) is approximated by LazyJumpDistance. + // In this case the most natural jump path is approximated by LazyJumpDistance. // // 2. The flow pattern, where players follow through the slider to its visual extent into the next hitobject. // @@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing // ↑ // most natural jump path // - // In this case the most natural movement path is better approximated by a new distance called "tailJumpDistance" - the distance between the slider's tail and the next hitobject. + // In this case the most natural jump path is better approximated by a new distance called "tailJumpDistance" - the distance between the slider's tail and the next hitobject. // // Thus, the player is assumed to jump the minimum of these two distances in all cases. // From af1e97b7c73fb476c9208051d8f47fa9aa579837 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:49:36 +0100 Subject: [PATCH 058/117] Move playing text added samples to private helper and fix it never playing the last sample `RNG.Next` is exclusive of the upper bound, meaning that the last sample would never be played. --- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 96319b9fdd..145e51b05a 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -86,6 +86,8 @@ namespace osu.Game.Graphics.UserInterface protected override Color4 SelectionColour => selectionColour; + private void playTextAddedSample() => textAddedSamples[RNG.Next(0, textAddedSamples.Length)]?.Play(); + protected override void OnUserTextAdded(string added) { base.OnUserTextAdded(added); @@ -93,7 +95,7 @@ namespace osu.Game.Graphics.UserInterface if (added.Any(char.IsUpper) && AllowUniqueCharacterSamples) capsTextAddedSample?.Play(); else - textAddedSamples[RNG.Next(0, 3)]?.Play(); + playTextAddedSample(); } protected override void OnUserTextRemoved(string removed) From de89e321c8e57d3b914bbbbdb05c774e5cc43ed1 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Wed, 8 Dec 2021 09:57:53 +0100 Subject: [PATCH 059/117] Add sounds for IME composition --- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 145e51b05a..709f64d16d 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -119,6 +119,70 @@ namespace osu.Game.Graphics.UserInterface caretMovedSample?.Play(); } + protected override void OnImeComposition(string newComposition, int removedTextLength, int addedTextLength, bool caretMoved) + { + base.OnImeComposition(newComposition, removedTextLength, addedTextLength, caretMoved); + + if (string.IsNullOrEmpty(newComposition)) + { + switch (removedTextLength) + { + case 0: + // empty composition event, composition wasn't changed, don't play anything. + return; + + case 1: + // composition probably ended by pressing backspace, or was cancelled. + textRemovedSample?.Play(); + return; + + default: + // longer text removed, composition ended because it was cancelled. + // could be a different sample if desired. + textRemovedSample?.Play(); + return; + } + } + + if (addedTextLength > 0) + { + // some text was added, probably due to typing new text or by changing the candidate. + playTextAddedSample(); + return; + } + + if (removedTextLength > 0) + { + // text was probably removed by backspacing. + // it's also possible that a candidate that only removed text was changed to. + textRemovedSample?.Play(); + return; + } + + if (caretMoved) + { + // only the caret/selection was moved. + caretMovedSample?.Play(); + } + } + + protected override void OnImeResult(string result, bool successful) + { + base.OnImeResult(result, successful); + + if (successful) + { + // composition was successfully completed, usually by pressing the enter key. + textCommittedSample?.Play(); + } + else + { + // composition was prematurely ended, eg. by clicking inside the textbox. + // could be a different sample if desired. + textCommittedSample?.Play(); + } + } + protected override void OnFocus(FocusEvent e) { BorderThickness = 3; From 8fa73fcbf695c21911c8c9afda25fe9ee49e1928 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Dec 2021 18:30:08 +0900 Subject: [PATCH 060/117] Move helper method to end of class --- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 709f64d16d..6db3068d84 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -86,8 +86,6 @@ namespace osu.Game.Graphics.UserInterface protected override Color4 SelectionColour => selectionColour; - private void playTextAddedSample() => textAddedSamples[RNG.Next(0, textAddedSamples.Length)]?.Play(); - protected override void OnUserTextAdded(string added) { base.OnUserTextAdded(added); @@ -208,6 +206,8 @@ namespace osu.Game.Graphics.UserInterface SelectionColour = SelectionColour, }; + private void playTextAddedSample() => textAddedSamples[RNG.Next(0, textAddedSamples.Length)]?.Play(); + private class OsuCaret : Caret { private const float caret_move_time = 60; From beb5d61a42cc69841cfdbb1c8de0cc36d5973535 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 8 Dec 2021 20:38:18 +0900 Subject: [PATCH 061/117] Separate playlist item deletion to Playlists-specific class --- .../TestSceneDrawableRoomPlaylist.cs | 97 +-------- .../TestScenePlaylistsRoomPlaylist.cs | 188 ++++++++++++++++++ .../Components/MatchBeatmapDetailArea.cs | 3 +- .../OnlinePlay/DrawableRoomPlaylist.cs | 40 +--- .../DrawableRoomPlaylistWithResults.cs | 2 +- .../Playlists/PlaylistsRoomPlaylist.cs | 24 +++ .../Playlists/PlaylistsRoomSettingsOverlay.cs | 5 +- 7 files changed, 227 insertions(+), 132 deletions(-) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs create mode 100644 osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 55aa665ff1..c60b55d7e8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -128,95 +128,6 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("item 1 is selected", () => playlist.SelectedItem.Value == playlist.Items[1]); } - [Test] - public void TestItemRemovedOnDeletion() - { - PlaylistItem selectedItem = null; - - createPlaylist(true, true); - - moveToItem(0); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - AddStep("retrieve selection", () => selectedItem = playlist.SelectedItem.Value); - - moveToDeleteButton(0); - AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); - - AddAssert("item removed", () => !playlist.Items.Contains(selectedItem)); - } - - [Test] - public void TestNextItemSelectedAfterDeletion() - { - createPlaylist(true, true); - - moveToItem(0); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - - moveToDeleteButton(0); - AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); - - AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]); - } - - [Test] - public void TestLastItemSelectedAfterLastItemDeleted() - { - createPlaylist(true, true); - - AddWaitStep("wait for flow", 5); // Items may take 1 update frame to flow. A wait count of 5 is guaranteed to result in the flow being updated as desired. - AddStep("scroll to bottom", () => playlist.ChildrenOfType>().First().ScrollToEnd(false)); - - moveToItem(19); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - - moveToDeleteButton(19); - AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); - - AddAssert("item 18 is selected", () => playlist.SelectedItem.Value == playlist.Items[18]); - } - - [Test] - public void TestSelectionResetWhenAllItemsDeleted() - { - createPlaylist(true, true); - - AddStep("remove all but one item", () => - { - playlist.Items.RemoveRange(1, playlist.Items.Count - 1); - }); - - moveToItem(0); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - moveToDeleteButton(0); - AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); - - AddAssert("no item selected", () => playlist.SelectedItem.Value == null); - } - - // Todo: currently not possible due to bindable list shortcomings (https://github.com/ppy/osu-framework/issues/3081) - // [Test] - public void TestNextItemSelectedAfterExternalDeletion() - { - createPlaylist(true, true); - - moveToItem(0); - AddStep("click", () => InputManager.Click(MouseButton.Left)); - AddStep("remove item 0", () => playlist.Items.RemoveAt(0)); - - AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]); - } - - [Test] - public void TestChangeBeatmapAndRemove() - { - createPlaylist(true, true); - - AddStep("change beatmap of first item", () => playlist.Items[0].BeatmapID = 30); - moveToDeleteButton(0); - AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); - } - [Test] public void TestDownloadButtonHiddenWhenBeatmapExists() { @@ -326,12 +237,6 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.MoveMouseTo(item.ChildrenOfType.PlaylistItemHandle>().Single(), offset); }); - private void moveToDeleteButton(int index, Vector2? offset = null) => AddStep($"move mouse to delete button {index}", () => - { - var item = playlist.ChildrenOfType>().ElementAt(index); - InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset); - }); - private void assertHandleVisibility(int index, bool visible) => AddAssert($"handle {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType.PlaylistItemHandle>().ElementAt(index).Alpha > 0) == visible); @@ -425,7 +330,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public new IReadOnlyDictionary> ItemMap => base.ItemMap; public TestPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) - : base(allowEdit, allowSelection, showItemOwner: showItemOwner) + : base(allowEdit, allowSelection, showItemOwner) { } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs new file mode 100644 index 0000000000..5b0c7c7d55 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs @@ -0,0 +1,188 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables; +using osu.Game.Database; +using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Rooms; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Playlists; +using osu.Game.Tests.Beatmaps; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestScenePlaylistsRoomPlaylist : OsuManualInputManagerTestScene + { + private TestPlaylist playlist; + + [Cached(typeof(UserLookupCache))] + private readonly TestUserLookupCache userLookupCache = new TestUserLookupCache(); + + [Test] + public void TestItemRemovedOnDeletion() + { + PlaylistItem selectedItem = null; + + createPlaylist(true, true); + + moveToItem(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("retrieve selection", () => selectedItem = playlist.SelectedItem.Value); + + moveToDeleteButton(0); + AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); + + AddAssert("item removed", () => !playlist.Items.Contains(selectedItem)); + } + + [Test] + public void TestNextItemSelectedAfterDeletion() + { + createPlaylist(true, true); + + moveToItem(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + + moveToDeleteButton(0); + AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); + + AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]); + } + + [Test] + public void TestLastItemSelectedAfterLastItemDeleted() + { + createPlaylist(true, true); + + AddWaitStep("wait for flow", 5); // Items may take 1 update frame to flow. A wait count of 5 is guaranteed to result in the flow being updated as desired. + AddStep("scroll to bottom", () => playlist.ChildrenOfType>().First().ScrollToEnd(false)); + + moveToItem(19); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + + moveToDeleteButton(19); + AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); + + AddAssert("item 18 is selected", () => playlist.SelectedItem.Value == playlist.Items[18]); + } + + [Test] + public void TestSelectionResetWhenAllItemsDeleted() + { + createPlaylist(true, true); + + AddStep("remove all but one item", () => + { + playlist.Items.RemoveRange(1, playlist.Items.Count - 1); + }); + + moveToItem(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + moveToDeleteButton(0); + AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); + + AddAssert("no item selected", () => playlist.SelectedItem.Value == null); + } + + // Todo: currently not possible due to bindable list shortcomings (https://github.com/ppy/osu-framework/issues/3081) + // [Test] + public void TestNextItemSelectedAfterExternalDeletion() + { + createPlaylist(true, true); + + moveToItem(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + AddStep("remove item 0", () => playlist.Items.RemoveAt(0)); + + AddAssert("item 0 is selected", () => playlist.SelectedItem.Value == playlist.Items[0]); + } + + [Test] + public void TestChangeBeatmapAndRemove() + { + createPlaylist(true, true); + + AddStep("change beatmap of first item", () => playlist.Items[0].BeatmapID = 30); + moveToDeleteButton(0); + AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); + } + + private void moveToItem(int index, Vector2? offset = null) + => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(index), offset)); + + private void moveToDeleteButton(int index, Vector2? offset = null) => AddStep($"move mouse to delete button {index}", () => + { + var item = playlist.ChildrenOfType>().ElementAt(index); + InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset); + }); + + private void createPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) + { + AddStep("create playlist", () => + { + Child = playlist = new TestPlaylist(allowEdit, allowSelection, showItemOwner) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500, 300) + }; + + for (int i = 0; i < 20; i++) + { + playlist.Items.Add(new PlaylistItem + { + ID = i, + OwnerID = 2, + Beatmap = + { + Value = i % 2 == 1 + ? new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo + : new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Artist = "Artist", + Author = new APIUser { Username = "Creator name here" }, + Title = "Long title used to check background colour", + }, + BeatmapSet = new BeatmapSetInfo() + } + }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RequiredMods = + { + new OsuModHardRock(), + new OsuModDoubleTime(), + new OsuModAutoplay() + } + }); + } + }); + + AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); + } + + private class TestPlaylist : PlaylistsRoomPlaylist + { + public new IReadOnlyDictionary> ItemMap => base.ItemMap; + + public TestPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) + : base(allowEdit, allowSelection, showItemOwner) + { + } + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs index b013cbafd8..7afebb04af 100644 --- a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs +++ b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Rooms; +using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Select; using osuTK; @@ -43,7 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Bottom = 10 }, - Child = playlist = new DrawableRoomPlaylist(true, false) + Child = playlist = new PlaylistsRoomPlaylist(true, false) { RelativeSizeAxes = Axes.Both, } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index f2d31c8e67..35d1fb33ad 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.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. -using System.Collections.Specialized; +using System; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; @@ -16,6 +15,11 @@ namespace osu.Game.Screens.OnlinePlay { public readonly Bindable SelectedItem = new Bindable(); + /// + /// Invoked when an item is requested to be deleted. + /// + public Action DeletionRequested; + private readonly bool allowEdit; private readonly bool allowSelection; private readonly bool showItemOwner; @@ -27,23 +31,6 @@ namespace osu.Game.Screens.OnlinePlay this.showItemOwner = showItemOwner; } - protected override void LoadComplete() - { - base.LoadComplete(); - - // Scheduled since items are removed and re-added upon rearrangement - Items.CollectionChanged += (_, args) => Schedule(() => - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Remove: - if (allowSelection && args.OldItems.Contains(SelectedItem)) - SelectedItem.Value = null; - break; - } - }); - } - protected override ScrollContainer CreateScrollContainer() => base.CreateScrollContainer().With(d => { d.ScrollbarVisible = false; @@ -57,20 +44,7 @@ namespace osu.Game.Screens.OnlinePlay protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => new DrawableRoomPlaylistItem(item, allowEdit, allowSelection, showItemOwner) { SelectedItem = { BindTarget = SelectedItem }, - RequestDeletion = requestDeletion + RequestDeletion = i => DeletionRequested?.Invoke(i) }; - - private void requestDeletion(PlaylistItem item) - { - if (allowSelection && SelectedItem.Value == item) - { - if (Items.Count == 1) - SelectedItem.Value = null; - else - SelectedItem.Value = Items.GetNext(item) ?? Items[^2]; - } - - Items.Remove(item); - } } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs index 8b1bb7abc1..1acd239fc8 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.OnlinePlay private readonly bool showItemOwner; public DrawableRoomPlaylistWithResults(bool showItemOwner = false) - : base(false, true, showItemOwner: showItemOwner) + : base(false, true, showItemOwner) { this.showItemOwner = showItemOwner; } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs new file mode 100644 index 0000000000..de0960940d --- /dev/null +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.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 System.Linq; +using osu.Framework.Extensions.IEnumerableExtensions; + +namespace osu.Game.Screens.OnlinePlay.Playlists +{ + public class PlaylistsRoomPlaylist : DrawableRoomPlaylist + { + public PlaylistsRoomPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) + : base(allowEdit, allowSelection, showItemOwner) + { + DeletionRequested = item => + { + var nextItem = Items.GetNext(item); + + Items.Remove(item); + + SelectedItem.Value = nextItem ?? Items.LastOrDefault(); + }; + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 27c8dc1120..b903e9cb7b 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -205,7 +205,10 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { new Drawable[] { - playlist = new DrawableRoomPlaylist(true, false) { RelativeSizeAxes = Axes.Both } + playlist = new PlaylistsRoomPlaylist(true, false) + { + RelativeSizeAxes = Axes.Both + } }, new Drawable[] { From 3be4d8b68de9ac9c5286917dd85be79470849920 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 01:04:28 +0900 Subject: [PATCH 062/117] Remove ctor params from DrawableRoomPlaylist/DrawablePlaylistItem --- .../TestSceneDrawableRoomPlaylist.cs | 18 ++++- .../TestScenePlaylistsRoomPlaylist.cs | 20 +++--- .../Components/MatchBeatmapDetailArea.cs | 2 +- .../OnlinePlay/DrawableRoomPlaylist.cs | 14 +--- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 66 ++++++++++++------- .../Match/MultiplayerMatchSettingsOverlay.cs | 2 +- .../Match/Playlist/MultiplayerHistoryList.cs | 9 ++- .../Match/Playlist/MultiplayerQueueList.cs | 9 ++- .../Playlists/PlaylistsRoomPlaylist.cs | 23 ++++++- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 2 +- 10 files changed, 105 insertions(+), 60 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index c60b55d7e8..449fdaf0aa 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -329,10 +329,26 @@ namespace osu.Game.Tests.Visual.Multiplayer { public new IReadOnlyDictionary> ItemMap => base.ItemMap; + private readonly bool allowEdit; + private readonly bool allowSelection; + private readonly bool showItemOwner; + public TestPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) - : base(allowEdit, allowSelection, showItemOwner) { + this.allowEdit = allowEdit; + this.allowSelection = allowSelection; + this.showItemOwner = showItemOwner; } + + protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => base.CreateOsuDrawable(item).With(d => + { + var drawablePlaylistItem = (DrawableRoomPlaylistItem)d; + + drawablePlaylistItem.AllowReordering = allowEdit; + drawablePlaylistItem.AllowDeletion = allowEdit; + drawablePlaylistItem.AllowSelection = allowSelection; + drawablePlaylistItem.ShowItemOwner = showItemOwner; + }); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs index 5b0c7c7d55..8312964b50 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { PlaylistItem selectedItem = null; - createPlaylist(true, true); + createPlaylist(); moveToItem(0); AddStep("click", () => InputManager.Click(MouseButton.Left)); @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestNextItemSelectedAfterDeletion() { - createPlaylist(true, true); + createPlaylist(); moveToItem(0); AddStep("click", () => InputManager.Click(MouseButton.Left)); @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestLastItemSelectedAfterLastItemDeleted() { - createPlaylist(true, true); + createPlaylist(); AddWaitStep("wait for flow", 5); // Items may take 1 update frame to flow. A wait count of 5 is guaranteed to result in the flow being updated as desired. AddStep("scroll to bottom", () => playlist.ChildrenOfType>().First().ScrollToEnd(false)); @@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestSelectionResetWhenAllItemsDeleted() { - createPlaylist(true, true); + createPlaylist(); AddStep("remove all but one item", () => { @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Multiplayer // [Test] public void TestNextItemSelectedAfterExternalDeletion() { - createPlaylist(true, true); + createPlaylist(); moveToItem(0); AddStep("click", () => InputManager.Click(MouseButton.Left)); @@ -113,7 +113,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestChangeBeatmapAndRemove() { - createPlaylist(true, true); + createPlaylist(); AddStep("change beatmap of first item", () => playlist.Items[0].BeatmapID = 30); moveToDeleteButton(0); @@ -129,11 +129,11 @@ namespace osu.Game.Tests.Visual.Multiplayer InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset); }); - private void createPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) + private void createPlaylist() { AddStep("create playlist", () => { - Child = playlist = new TestPlaylist(allowEdit, allowSelection, showItemOwner) + Child = playlist = new TestPlaylist { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -179,8 +179,8 @@ namespace osu.Game.Tests.Visual.Multiplayer { public new IReadOnlyDictionary> ItemMap => base.ItemMap; - public TestPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) - : base(allowEdit, allowSelection, showItemOwner) + public TestPlaylist() + : base(true, true, true) { } } diff --git a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs index 7afebb04af..761f4818e0 100644 --- a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs +++ b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Bottom = 10 }, - Child = playlist = new PlaylistsRoomPlaylist(true, false) + Child = playlist = new PlaylistsRoomPlaylist(true, true, true) { RelativeSizeAxes = Axes.Both, } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 35d1fb33ad..2eb8cd8997 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -20,16 +20,6 @@ namespace osu.Game.Screens.OnlinePlay /// public Action DeletionRequested; - private readonly bool allowEdit; - private readonly bool allowSelection; - private readonly bool showItemOwner; - - public DrawableRoomPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) - { - this.allowEdit = allowEdit; - this.allowSelection = allowSelection; - this.showItemOwner = showItemOwner; - } protected override ScrollContainer CreateScrollContainer() => base.CreateScrollContainer().With(d => { @@ -41,10 +31,10 @@ namespace osu.Game.Screens.OnlinePlay Spacing = new Vector2(0, 2) }; - protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => new DrawableRoomPlaylistItem(item, allowEdit, allowSelection, showItemOwner) + protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => new DrawableRoomPlaylistItem(item) { SelectedItem = { BindTarget = SelectedItem }, - RequestDeletion = i => DeletionRequested?.Invoke(i) + RequestDeletion = i => DeletionRequested?.Invoke(i), }; } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index c291bddeeb..1814e172df 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading.Tasks; 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; @@ -53,6 +52,7 @@ namespace osu.Game.Screens.OnlinePlay private ModDisplay modDisplay; private FillFlowContainer buttonsFlow; private UpdateableAvatar ownerAvatar; + private Drawable removeButton; private readonly IBindable valid = new Bindable(); @@ -75,31 +75,20 @@ namespace osu.Game.Screens.OnlinePlay private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; - private readonly bool allowEdit; - private readonly bool allowSelection; - private readonly bool showItemOwner; - private FillFlowContainer mainFillFlow; - protected override bool ShouldBeConsideredForInput(Drawable child) => allowEdit || !allowSelection || SelectedItem.Value == Model; + protected override bool ShouldBeConsideredForInput(Drawable child) => AllowReordering || AllowDeletion || !AllowSelection || SelectedItem.Value == Model; - public DrawableRoomPlaylistItem(PlaylistItem item, bool allowEdit, bool allowSelection, bool showItemOwner) + public DrawableRoomPlaylistItem(PlaylistItem item) : base(item) { Item = item; - // TODO: edit support should be moved out into a derived class - this.allowEdit = allowEdit; - this.allowSelection = allowSelection; - this.showItemOwner = showItemOwner; - beatmap.BindTo(item.Beatmap); valid.BindTo(item.Valid); ruleset.BindTo(item.Ruleset); requiredMods.BindTo(item.RequiredMods); - ShowDragHandle.Value = allowEdit; - if (item.Expired) Colour = OsuColour.Gray(0.5f); } @@ -107,9 +96,6 @@ namespace osu.Game.Screens.OnlinePlay [BackgroundDependencyLoader] private void load() { - if (!allowEdit) - HandleColour = HandleColour.Opacity(0); - maskingContainer.BorderColour = colours.Yellow; } @@ -169,6 +155,42 @@ namespace osu.Game.Screens.OnlinePlay refresh(); } + public bool AllowSelection { get; set; } + + public bool AllowReordering + { + get => ShowDragHandle.Value; + set => ShowDragHandle.Value = value; + } + + private bool allowDeletion; + + public bool AllowDeletion + { + get => allowDeletion; + set + { + allowDeletion = value; + + if (removeButton != null) + removeButton.Alpha = value ? 1 : 0; + } + } + + private bool showItemOwner; + + public bool ShowItemOwner + { + get => showItemOwner; + set + { + showItemOwner = value; + + if (ownerAvatar != null) + ownerAvatar.Alpha = value ? 1 : 0; + } + } + private void refresh() { if (!valid.Value) @@ -336,7 +358,7 @@ namespace osu.Game.Screens.OnlinePlay Margin = new MarginPadding { Right = 8 }, Masking = true, CornerRadius = 4, - Alpha = showItemOwner ? 1 : 0 + Alpha = ShowItemOwner ? 1 : 0 }, } } @@ -349,11 +371,11 @@ namespace osu.Game.Screens.OnlinePlay new[] { Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), - new PlaylistRemoveButton + removeButton = new PlaylistRemoveButton { Size = new Vector2(30, 30), - Alpha = allowEdit ? 1 : 0, - Action = () => RequestDeletion?.Invoke(Model), + Alpha = AllowDeletion ? 1 : 0, + Action = () => RequestDeletion?.Invoke(Item), }, }; @@ -374,7 +396,7 @@ namespace osu.Game.Screens.OnlinePlay protected override bool OnClick(ClickEvent e) { - if (allowSelection && valid.Value) + if (AllowSelection && valid.Value) SelectedItem.Value = Model; return true; } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs index 39d60a0b05..7f1db733b3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerMatchSettingsOverlay.cs @@ -248,7 +248,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match Spacing = new Vector2(5), Children = new Drawable[] { - drawablePlaylist = new DrawableRoomPlaylist(false, false) + drawablePlaylist = new DrawableRoomPlaylist { RelativeSizeAxes = Axes.X, Height = DrawableRoomPlaylistItem.HEIGHT diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs index d708b39898..7102738271 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; using osu.Game.Online.Rooms; using osuTK; @@ -15,16 +16,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist /// public class MultiplayerHistoryList : DrawableRoomPlaylist { - public MultiplayerHistoryList() - : base(false, false, true) - { - } - protected override FillFlowContainer> CreateListFillFlowContainer() => new HistoryFillFlowContainer { Spacing = new Vector2(0, 2) }; + protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) + => base.CreateOsuDrawable(item).With(d => ((DrawableRoomPlaylistItem)d).ShowItemOwner = true); + private class HistoryFillFlowContainer : FillFlowContainer> { public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderByDescending(item => item.Model.PlayedAt); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 1b1b66273f..814ea48646 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; using osu.Game.Online.Rooms; using osuTK; @@ -17,16 +18,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist /// public class MultiplayerQueueList : DrawableRoomPlaylist { - public MultiplayerQueueList() - : base(false, false, true) - { - } - protected override FillFlowContainer> CreateListFillFlowContainer() => new QueueFillFlowContainer { Spacing = new Vector2(0, 2) }; + protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) + => base.CreateOsuDrawable(item).With(d => ((DrawableRoomPlaylistItem)d).ShowItemOwner = true); + private class QueueFillFlowContainer : FillFlowContainer> { [Resolved(typeof(Room), nameof(Room.Playlist))] diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs index de0960940d..f4df9c4406 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs @@ -3,14 +3,24 @@ using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Playlists { public class PlaylistsRoomPlaylist : DrawableRoomPlaylist { - public PlaylistsRoomPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) - : base(allowEdit, allowSelection, showItemOwner) + private readonly bool allowReordering; + private readonly bool allowDeletion; + private readonly bool allowSelection; + + public PlaylistsRoomPlaylist(bool allowReordering, bool allowDeletion, bool allowSelection) { + this.allowReordering = allowReordering; + this.allowDeletion = allowDeletion; + this.allowSelection = allowSelection; + DeletionRequested = item => { var nextItem = Items.GetNext(item); @@ -20,5 +30,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists SelectedItem.Value = nextItem ?? Items.LastOrDefault(); }; } + + protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => base.CreateOsuDrawable(item).With(d => + { + var drawablePlaylistItem = (DrawableRoomPlaylistItem)d; + + drawablePlaylistItem.AllowReordering = allowReordering; + drawablePlaylistItem.AllowDeletion = allowDeletion; + drawablePlaylistItem.AllowSelection = allowSelection; + }); } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index b903e9cb7b..40b0bc7571 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -205,7 +205,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { new Drawable[] { - playlist = new PlaylistsRoomPlaylist(true, false) + playlist = new PlaylistsRoomPlaylist(true, true, false) { RelativeSizeAxes = Axes.Both } From 26f6c5e5a5571469a810b54a68049da8a74461e2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 01:16:37 +0900 Subject: [PATCH 063/117] Remove ctor params from PlaylistsRoomPlaylist --- .../TestSceneDrawableRoomPlaylist.cs | 44 ++++---------- .../TestScenePlaylistsRoomPlaylist.cs | 2 +- .../Components/MatchBeatmapDetailArea.cs | 3 +- .../OnlinePlay/DrawableRoomPlaylist.cs | 60 +++++++++++++++++++ .../Playlists/PlaylistsRoomPlaylist.cs | 23 +------ .../Playlists/PlaylistsRoomSettingsOverlay.cs | 4 +- 6 files changed, 80 insertions(+), 56 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 449fdaf0aa..269cd15a0f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -48,7 +49,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestNonEditableNonSelectable() { - createPlaylist(false, false); + createPlaylist(); moveToItem(0); assertHandleVisibility(0, false); @@ -61,7 +62,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestEditable() { - createPlaylist(true, false); + createPlaylist(p => p.AllowReordering = p.AllowDeletion = true); moveToItem(0); assertHandleVisibility(0, true); @@ -74,7 +75,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestMarkInvalid() { - createPlaylist(true, true); + createPlaylist(p => p.AllowReordering = p.AllowDeletion = p.AllowSelection = true); AddStep("mark item 0 as invalid", () => playlist.Items[0].MarkInvalid()); @@ -87,7 +88,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestSelectable() { - createPlaylist(false, true); + createPlaylist(p => p.AllowSelection = true); moveToItem(0); assertHandleVisibility(0, false); @@ -101,7 +102,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestEditableSelectable() { - createPlaylist(true, true); + createPlaylist(p => p.AllowReordering = p.AllowDeletion = p.AllowSelection = true); moveToItem(0); assertHandleVisibility(0, true); @@ -115,7 +116,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestSelectionNotLostAfterRearrangement() { - createPlaylist(true, true); + createPlaylist(p => p.AllowReordering = p.AllowDeletion = p.AllowSelection = true); moveToItem(0); AddStep("click", () => InputManager.Click(MouseButton.Left)); @@ -180,7 +181,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create playlist", () => { - Child = playlist = new TestPlaylist(false, false) + Child = playlist = new TestPlaylist { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -223,7 +224,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [TestCase(true)] public void TestWithOwner(bool withOwner) { - createPlaylist(false, false, withOwner); + createPlaylist(p => p.ShowItemOwners = withOwner); AddAssert("owner visible", () => playlist.ChildrenOfType().All(a => a.IsPresent == withOwner)); } @@ -245,11 +246,11 @@ namespace osu.Game.Tests.Visual.Multiplayer => AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).Alpha > 0) == visible); - private void createPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) + private void createPlaylist(Action setupPlaylist) { AddStep("create playlist", () => { - Child = playlist = new TestPlaylist(allowEdit, allowSelection, showItemOwner) + Child = playlist = new TestPlaylist { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -295,7 +296,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create playlist", () => { - Child = playlist = new TestPlaylist(false, false) + Child = playlist = new TestPlaylist { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -328,27 +329,6 @@ namespace osu.Game.Tests.Visual.Multiplayer private class TestPlaylist : DrawableRoomPlaylist { public new IReadOnlyDictionary> ItemMap => base.ItemMap; - - private readonly bool allowEdit; - private readonly bool allowSelection; - private readonly bool showItemOwner; - - public TestPlaylist(bool allowEdit, bool allowSelection, bool showItemOwner = false) - { - this.allowEdit = allowEdit; - this.allowSelection = allowSelection; - this.showItemOwner = showItemOwner; - } - - protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => base.CreateOsuDrawable(item).With(d => - { - var drawablePlaylistItem = (DrawableRoomPlaylistItem)d; - - drawablePlaylistItem.AllowReordering = allowEdit; - drawablePlaylistItem.AllowDeletion = allowEdit; - drawablePlaylistItem.AllowSelection = allowSelection; - drawablePlaylistItem.ShowItemOwner = showItemOwner; - }); } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs index 8312964b50..264f6aa2c5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs @@ -180,8 +180,8 @@ namespace osu.Game.Tests.Visual.Multiplayer public new IReadOnlyDictionary> ItemMap => base.ItemMap; public TestPlaylist() - : base(true, true, true) { + AllowSelection = true; } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs index 761f4818e0..d56acff8c7 100644 --- a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs +++ b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs @@ -44,9 +44,10 @@ namespace osu.Game.Screens.OnlinePlay.Components { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Bottom = 10 }, - Child = playlist = new PlaylistsRoomPlaylist(true, true, true) + Child = playlist = new PlaylistsRoomPlaylist { RelativeSizeAxes = Axes.Both, + AllowSelection = true, } } }, diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 2eb8cd8997..4389f40afc 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -20,6 +21,61 @@ namespace osu.Game.Screens.OnlinePlay /// public Action DeletionRequested; + private bool allowReordering; + + public bool AllowReordering + { + get => allowReordering; + set + { + allowReordering = value; + + foreach (var item in ListContainer.OfType()) + item.AllowReordering = value; + } + } + + private bool allowDeletion; + + public bool AllowDeletion + { + get => allowDeletion; + set + { + allowDeletion = value; + + foreach (var item in ListContainer.OfType()) + item.AllowDeletion = value; + } + } + + private bool allowSelection; + + public bool AllowSelection + { + get => allowSelection; + set + { + allowSelection = value; + + foreach (var item in ListContainer.OfType()) + item.AllowSelection = value; + } + } + + private bool showItemOwners; + + public bool ShowItemOwners + { + get => showItemOwners; + set + { + showItemOwners = value; + + foreach (var item in ListContainer.OfType()) + item.ShowItemOwner = value; + } + } protected override ScrollContainer CreateScrollContainer() => base.CreateScrollContainer().With(d => { @@ -35,6 +91,10 @@ namespace osu.Game.Screens.OnlinePlay { SelectedItem = { BindTarget = SelectedItem }, RequestDeletion = i => DeletionRequested?.Invoke(i), + AllowReordering = AllowReordering, + AllowDeletion = AllowDeletion, + AllowSelection = AllowSelection, + ShowItemOwner = ShowItemOwners, }; } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs index f4df9c4406..d8c316b642 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs @@ -3,23 +3,15 @@ using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Playlists { public class PlaylistsRoomPlaylist : DrawableRoomPlaylist { - private readonly bool allowReordering; - private readonly bool allowDeletion; - private readonly bool allowSelection; - - public PlaylistsRoomPlaylist(bool allowReordering, bool allowDeletion, bool allowSelection) + public PlaylistsRoomPlaylist() { - this.allowReordering = allowReordering; - this.allowDeletion = allowDeletion; - this.allowSelection = allowSelection; + AllowReordering = true; + AllowDeletion = true; DeletionRequested = item => { @@ -30,14 +22,5 @@ namespace osu.Game.Screens.OnlinePlay.Playlists SelectedItem.Value = nextItem ?? Items.LastOrDefault(); }; } - - protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => base.CreateOsuDrawable(item).With(d => - { - var drawablePlaylistItem = (DrawableRoomPlaylistItem)d; - - drawablePlaylistItem.AllowReordering = allowReordering; - drawablePlaylistItem.AllowDeletion = allowDeletion; - drawablePlaylistItem.AllowSelection = allowSelection; - }); } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 40b0bc7571..915ec356d5 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -205,9 +205,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { new Drawable[] { - playlist = new PlaylistsRoomPlaylist(true, true, false) + playlist = new PlaylistsRoomPlaylist { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, } }, new Drawable[] From be2dbf42c3a0f69b1487522676a8a0ba29c3e987 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 01:17:25 +0900 Subject: [PATCH 064/117] Flatten DrawableRoomPlaylistWithResults into base class --- .../OnlinePlay/DrawableRoomPlaylist.cs | 21 ++++++ .../OnlinePlay/DrawableRoomPlaylistItem.cs | 44 +++++++++++- .../DrawableRoomPlaylistWithResults.cs | 69 ------------------- .../Playlists/PlaylistsRoomSubScreen.cs | 6 +- 4 files changed, 66 insertions(+), 74 deletions(-) delete mode 100644 osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 4389f40afc..e76d905849 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -21,6 +21,11 @@ namespace osu.Game.Screens.OnlinePlay /// public Action DeletionRequested; + /// + /// Invoked to request showing the results for an item. + /// + public Action ShowResultsRequested; + private bool allowReordering; public bool AllowReordering @@ -63,6 +68,20 @@ namespace osu.Game.Screens.OnlinePlay } } + private bool allowShowingResults; + + public bool AllowShowingResults + { + get => allowShowingResults; + set + { + allowShowingResults = value; + + foreach (var item in ListContainer.OfType()) + item.AllowShowingResults = value; + } + } + private bool showItemOwners; public bool ShowItemOwners @@ -94,7 +113,9 @@ namespace osu.Game.Screens.OnlinePlay AllowReordering = AllowReordering, AllowDeletion = AllowDeletion, AllowSelection = AllowSelection, + AllowShowingResults = AllowShowingResults, ShowItemOwner = ShowItemOwners, + ShowResultsRequested = i => ShowResultsRequested?.Invoke(i) }; } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 1814e172df..28b4997b63 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -41,6 +41,7 @@ namespace osu.Game.Screens.OnlinePlay public const float ICON_HEIGHT = 34; public Action RequestDeletion; + public Action ShowResultsRequested; public readonly Bindable SelectedItem = new Bindable(); @@ -53,6 +54,7 @@ namespace osu.Game.Screens.OnlinePlay private FillFlowContainer buttonsFlow; private UpdateableAvatar ownerAvatar; private Drawable removeButton; + private Drawable showResultsButton; private readonly IBindable valid = new Bindable(); @@ -177,6 +179,20 @@ namespace osu.Game.Screens.OnlinePlay } } + private bool allowShowingResults; + + public bool AllowShowingResults + { + get => allowShowingResults; + set + { + allowShowingResults = value; + + if (showResultsButton != null) + showResultsButton.Alpha = value ? 1 : 0; + } + } + private bool showItemOwner; public bool ShowItemOwner @@ -230,7 +246,7 @@ namespace osu.Game.Screens.OnlinePlay modDisplay.Current.Value = requiredMods.ToArray(); buttonsFlow.Clear(); - buttonsFlow.ChildrenEnumerable = CreateButtons(); + buttonsFlow.ChildrenEnumerable = createButtons(); difficultyIconContainer.FadeInFromZero(500, Easing.OutQuint); mainFillFlow.FadeInFromZero(500, Easing.OutQuint); @@ -344,7 +360,7 @@ namespace osu.Game.Screens.OnlinePlay Margin = new MarginPadding { Horizontal = 8 }, AutoSizeAxes = Axes.Both, Spacing = new Vector2(5), - ChildrenEnumerable = CreateButtons().Select(button => button.With(b => + ChildrenEnumerable = createButtons().Select(button => button.With(b => { b.Anchor = Anchor.Centre; b.Origin = Anchor.Centre; @@ -367,9 +383,14 @@ namespace osu.Game.Screens.OnlinePlay }; } - protected virtual IEnumerable CreateButtons() => + private IEnumerable createButtons() => new[] { + showResultsButton = new ShowResultsButton + { + Action = () => ShowResultsRequested?.Invoke(Item), + Alpha = AllowShowingResults ? 1 : 0, + }, Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), removeButton = new PlaylistRemoveButton { @@ -454,6 +475,23 @@ namespace osu.Game.Screens.OnlinePlay } } + private class ShowResultsButton : IconButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Icon = FontAwesome.Solid.ChartPie; + TooltipText = "View results"; + + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue, + Colour = colours.Gray4, + }); + } + } + // For now, this is the same implementation as in PanelBackground, but supports a beatmap info rather than a working beatmap private class PanelBackground : Container // todo: should be a buffered container (https://github.com/ppy/osu-framework/issues/3222) { diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs deleted file mode 100644 index 1acd239fc8..0000000000 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistWithResults.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online.Rooms; - -namespace osu.Game.Screens.OnlinePlay -{ - public class DrawableRoomPlaylistWithResults : DrawableRoomPlaylist - { - public Action RequestShowResults; - - private readonly bool showItemOwner; - - public DrawableRoomPlaylistWithResults(bool showItemOwner = false) - : base(false, true, showItemOwner) - { - this.showItemOwner = showItemOwner; - } - - protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => - new DrawableRoomPlaylistItemWithResults(item, false, true, showItemOwner) - { - RequestShowResults = () => RequestShowResults(item), - SelectedItem = { BindTarget = SelectedItem }, - }; - - private class DrawableRoomPlaylistItemWithResults : DrawableRoomPlaylistItem - { - public Action RequestShowResults; - - public DrawableRoomPlaylistItemWithResults(PlaylistItem item, bool allowEdit, bool allowSelection, bool showItemOwner) - : base(item, allowEdit, allowSelection, showItemOwner) - { - } - - protected override IEnumerable CreateButtons() => - base.CreateButtons().Prepend(new FilledIconButton - { - Icon = FontAwesome.Solid.ChartPie, - Action = () => RequestShowResults?.Invoke(), - TooltipText = "View results" - }); - - private class FilledIconButton : IconButton - { - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Add(new Box - { - RelativeSizeAxes = Axes.Both, - Depth = float.MaxValue, - Colour = colours.Gray4, - }); - } - } - } - } -} diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index 7e045802f7..dd2d22b742 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -88,12 +88,14 @@ namespace osu.Game.Screens.OnlinePlay.Playlists new Drawable[] { new OverlinedPlaylistHeader(), }, new Drawable[] { - new DrawableRoomPlaylistWithResults + new DrawableRoomPlaylist { RelativeSizeAxes = Axes.Both, Items = { BindTarget = Room.Playlist }, SelectedItem = { BindTarget = SelectedItem }, - RequestShowResults = item => + AllowSelection = true, + AllowShowingResults = true, + ShowResultsRequested = item => { Debug.Assert(RoomId.Value != null); ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false)); From 3b4833ca8eba289957601cab070d86703a74d1ae Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 01:29:45 +0900 Subject: [PATCH 065/117] A bit of cleanup + xmldocs on classes/members --- ...TestScenePlaylistsRoomSettingsPlaylist.cs} | 4 +- .../Components/MatchBeatmapDetailArea.cs | 2 +- .../OnlinePlay/DrawableRoomPlaylist.cs | 27 +++++++++- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 50 +++++++++++++------ .../Playlists/PlaylistsRoomSettingsOverlay.cs | 2 +- ...st.cs => PlaylistsRoomSettingsPlaylist.cs} | 7 ++- 6 files changed, 71 insertions(+), 21 deletions(-) rename osu.Game.Tests/Visual/Multiplayer/{TestScenePlaylistsRoomPlaylist.cs => TestScenePlaylistsRoomSettingsPlaylist.cs} (97%) rename osu.Game/Screens/OnlinePlay/Playlists/{PlaylistsRoomPlaylist.cs => PlaylistsRoomSettingsPlaylist.cs} (69%) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs similarity index 97% rename from osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs rename to osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs index 264f6aa2c5..93ccd5f1e1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs @@ -24,7 +24,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestScenePlaylistsRoomPlaylist : OsuManualInputManagerTestScene + public class TestScenePlaylistsRoomSettingsPlaylist : OsuManualInputManagerTestScene { private TestPlaylist playlist; @@ -175,7 +175,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); } - private class TestPlaylist : PlaylistsRoomPlaylist + private class TestPlaylist : PlaylistsRoomSettingsPlaylist { public new IReadOnlyDictionary> ItemMap => base.ItemMap; diff --git a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs index d56acff8c7..c29b8db26c 100644 --- a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs +++ b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.Components { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Bottom = 10 }, - Child = playlist = new PlaylistsRoomPlaylist + Child = playlist = new PlaylistsRoomSettingsPlaylist { RelativeSizeAxes = Axes.Both, AllowSelection = true, diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index e76d905849..5e58514705 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -12,8 +12,15 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay { + /// + /// A list scrollable list which displays the s in a . + /// public class DrawableRoomPlaylist : OsuRearrangeableListContainer { + /// + /// The currently-selected item, used to show a border around items. + /// May be updated by playlist items if is true. + /// public readonly Bindable SelectedItem = new Bindable(); /// @@ -22,12 +29,15 @@ namespace osu.Game.Screens.OnlinePlay public Action DeletionRequested; /// - /// Invoked to request showing the results for an item. + /// Invoked when an item requests its results to be shown. /// public Action ShowResultsRequested; private bool allowReordering; + /// + /// Whether to allow reordering items in the playlist. + /// public bool AllowReordering { get => allowReordering; @@ -42,6 +52,10 @@ namespace osu.Game.Screens.OnlinePlay private bool allowDeletion; + /// + /// Whether to allow deleting items from the playlist. + /// If true, requests to delete items may be satisfied via . + /// public bool AllowDeletion { get => allowDeletion; @@ -56,6 +70,10 @@ namespace osu.Game.Screens.OnlinePlay private bool allowSelection; + /// + /// Whether to allow selecting items from the playlist. + /// If true, clicking on items in the playlist will change the value of . + /// public bool AllowSelection { get => allowSelection; @@ -70,6 +88,10 @@ namespace osu.Game.Screens.OnlinePlay private bool allowShowingResults; + /// + /// Whether to allow items to request their results to be shown. + /// If true, requests to show the results may be satisfied via . + /// public bool AllowShowingResults { get => allowShowingResults; @@ -84,6 +106,9 @@ namespace osu.Game.Screens.OnlinePlay private bool showItemOwners; + /// + /// Whether to show the avatar of users which own each playlist item. + /// public bool ShowItemOwners { get => showItemOwners; diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 28b4997b63..fca944e9b6 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -40,11 +40,30 @@ namespace osu.Game.Screens.OnlinePlay public const float HEIGHT = 50; public const float ICON_HEIGHT = 34; + /// + /// Invoked when this item requests to be deleted. + /// public Action RequestDeletion; + + /// + /// Invoked when this item requests its results to be shown. + /// public Action ShowResultsRequested; + /// + /// The currently-selected item, used to show a border around this item. + /// May be updated by this item if is true. + /// public readonly Bindable SelectedItem = new Bindable(); + public readonly PlaylistItem Item; + + private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; + private readonly IBindable valid = new Bindable(); + private readonly Bindable beatmap = new Bindable(); + private readonly Bindable ruleset = new Bindable(); + private readonly BindableList requiredMods = new BindableList(); + private Container maskingContainer; private Container difficultyIconContainer; private LinkFlowContainer beatmapText; @@ -55,14 +74,8 @@ namespace osu.Game.Screens.OnlinePlay private UpdateableAvatar ownerAvatar; private Drawable removeButton; private Drawable showResultsButton; - - private readonly IBindable valid = new Bindable(); - - private readonly Bindable beatmap = new Bindable(); - private readonly Bindable ruleset = new Bindable(); - private readonly BindableList requiredMods = new BindableList(); - - public readonly PlaylistItem Item; + private PanelBackground panelBackground; + private FillFlowContainer mainFillFlow; [Resolved] private OsuColour colours { get; set; } @@ -73,12 +86,6 @@ namespace osu.Game.Screens.OnlinePlay [Resolved] private BeatmapLookupCache beatmapLookupCache { get; set; } - private PanelBackground panelBackground; - - private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; - - private FillFlowContainer mainFillFlow; - protected override bool ShouldBeConsideredForInput(Drawable child) => AllowReordering || AllowDeletion || !AllowSelection || SelectedItem.Value == Model; public DrawableRoomPlaylistItem(PlaylistItem item) @@ -157,8 +164,14 @@ namespace osu.Game.Screens.OnlinePlay refresh(); } + /// + /// Whether this item can be selected. + /// public bool AllowSelection { get; set; } + /// + /// Whether this item can be reordered in the playlist. + /// public bool AllowReordering { get => ShowDragHandle.Value; @@ -167,6 +180,9 @@ namespace osu.Game.Screens.OnlinePlay private bool allowDeletion; + /// + /// Whether this item can be deleted. + /// public bool AllowDeletion { get => allowDeletion; @@ -181,6 +197,9 @@ namespace osu.Game.Screens.OnlinePlay private bool allowShowingResults; + /// + /// Whether this item can have results shown. + /// public bool AllowShowingResults { get => allowShowingResults; @@ -195,6 +214,9 @@ namespace osu.Game.Screens.OnlinePlay private bool showItemOwner; + /// + /// Whether to display the avatar of the user which owns this playlist item. + /// public bool ShowItemOwner { get => showItemOwner; diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 915ec356d5..8f31422add 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -205,7 +205,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { new Drawable[] { - playlist = new PlaylistsRoomPlaylist + playlist = new PlaylistsRoomSettingsPlaylist { RelativeSizeAxes = Axes.Both, } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs similarity index 69% rename from osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs rename to osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs index d8c316b642..bbe67e76e3 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs @@ -6,9 +6,12 @@ using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Screens.OnlinePlay.Playlists { - public class PlaylistsRoomPlaylist : DrawableRoomPlaylist + /// + /// A which is displayed during the setup stage of a playlists room. + /// + public class PlaylistsRoomSettingsPlaylist : DrawableRoomPlaylist { - public PlaylistsRoomPlaylist() + public PlaylistsRoomSettingsPlaylist() { AllowReordering = true; AllowDeletion = true; From 273042aa16882993f553e9eb43fa44d2bdf25faa Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 01:47:46 +0900 Subject: [PATCH 066/117] Add virtual method for creating different DrawablePlaylistItem types --- .../OnlinePlay/DrawableRoomPlaylist.cs | 22 ++++++++++--------- .../Match/Playlist/MultiplayerHistoryList.cs | 9 ++++---- .../Match/Playlist/MultiplayerQueueList.cs | 9 ++++---- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 5e58514705..1abc7c47d6 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -131,16 +131,18 @@ namespace osu.Game.Screens.OnlinePlay Spacing = new Vector2(0, 2) }; - protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => new DrawableRoomPlaylistItem(item) + protected sealed override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => CreateDrawablePlaylistItem(item).With(d => { - SelectedItem = { BindTarget = SelectedItem }, - RequestDeletion = i => DeletionRequested?.Invoke(i), - AllowReordering = AllowReordering, - AllowDeletion = AllowDeletion, - AllowSelection = AllowSelection, - AllowShowingResults = AllowShowingResults, - ShowItemOwner = ShowItemOwners, - ShowResultsRequested = i => ShowResultsRequested?.Invoke(i) - }; + d.SelectedItem.BindTarget = SelectedItem; + d.RequestDeletion = i => DeletionRequested?.Invoke(i); + d.AllowReordering = AllowReordering; + d.AllowDeletion = AllowDeletion; + d.AllowSelection = AllowSelection; + d.AllowShowingResults = AllowShowingResults; + d.ShowItemOwner = ShowItemOwners; + d.ShowResultsRequested = i => ShowResultsRequested?.Invoke(i); + }); + + protected virtual DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new DrawableRoomPlaylistItem(item); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs index 7102738271..32d355d149 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerHistoryList.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Containers; using osu.Game.Online.Rooms; using osuTK; @@ -16,14 +15,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist /// public class MultiplayerHistoryList : DrawableRoomPlaylist { + public MultiplayerHistoryList() + { + ShowItemOwners = true; + } + protected override FillFlowContainer> CreateListFillFlowContainer() => new HistoryFillFlowContainer { Spacing = new Vector2(0, 2) }; - protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) - => base.CreateOsuDrawable(item).With(d => ((DrawableRoomPlaylistItem)d).ShowItemOwner = true); - private class HistoryFillFlowContainer : FillFlowContainer> { public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderByDescending(item => item.Model.PlayedAt); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 814ea48646..7dfee36895 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -7,7 +7,6 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Containers; using osu.Game.Online.Rooms; using osuTK; @@ -18,14 +17,16 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist /// public class MultiplayerQueueList : DrawableRoomPlaylist { + public MultiplayerQueueList() + { + ShowItemOwners = true; + } + protected override FillFlowContainer> CreateListFillFlowContainer() => new QueueFillFlowContainer { Spacing = new Vector2(0, 2) }; - protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) - => base.CreateOsuDrawable(item).With(d => ((DrawableRoomPlaylistItem)d).ShowItemOwner = true); - private class QueueFillFlowContainer : FillFlowContainer> { [Resolved(typeof(Room), nameof(Room.Playlist))] From 23332995d133672ec63e3c29e8859d58c5d8625a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 01:52:59 +0900 Subject: [PATCH 067/117] Invert naming of exposed actions --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs | 12 ++++++------ .../Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 4 ++-- .../Playlists/PlaylistsRoomSettingsPlaylist.cs | 2 +- .../OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 1abc7c47d6..8bd2daa2c3 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -26,12 +26,12 @@ namespace osu.Game.Screens.OnlinePlay /// /// Invoked when an item is requested to be deleted. /// - public Action DeletionRequested; + public Action RequestDeletion; /// /// Invoked when an item requests its results to be shown. /// - public Action ShowResultsRequested; + public Action RequestResults; private bool allowReordering; @@ -54,7 +54,7 @@ namespace osu.Game.Screens.OnlinePlay /// /// Whether to allow deleting items from the playlist. - /// If true, requests to delete items may be satisfied via . + /// If true, requests to delete items may be satisfied via . /// public bool AllowDeletion { @@ -90,7 +90,7 @@ namespace osu.Game.Screens.OnlinePlay /// /// Whether to allow items to request their results to be shown. - /// If true, requests to show the results may be satisfied via . + /// If true, requests to show the results may be satisfied via . /// public bool AllowShowingResults { @@ -134,13 +134,13 @@ namespace osu.Game.Screens.OnlinePlay protected sealed override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => CreateDrawablePlaylistItem(item).With(d => { d.SelectedItem.BindTarget = SelectedItem; - d.RequestDeletion = i => DeletionRequested?.Invoke(i); + d.RequestDeletion = i => RequestDeletion?.Invoke(i); d.AllowReordering = AllowReordering; d.AllowDeletion = AllowDeletion; d.AllowSelection = AllowSelection; d.AllowShowingResults = AllowShowingResults; d.ShowItemOwner = ShowItemOwners; - d.ShowResultsRequested = i => ShowResultsRequested?.Invoke(i); + d.RequestResults = i => RequestResults?.Invoke(i); }); protected virtual DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new DrawableRoomPlaylistItem(item); diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index fca944e9b6..7a1c069df3 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.OnlinePlay /// /// Invoked when this item requests its results to be shown. /// - public Action ShowResultsRequested; + public Action RequestResults; /// /// The currently-selected item, used to show a border around this item. @@ -410,7 +410,7 @@ namespace osu.Game.Screens.OnlinePlay { showResultsButton = new ShowResultsButton { - Action = () => ShowResultsRequested?.Invoke(Item), + Action = () => RequestResults?.Invoke(Item), Alpha = AllowShowingResults ? 1 : 0, }, Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs index bbe67e76e3..2fe215eef2 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsPlaylist.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists AllowReordering = true; AllowDeletion = true; - DeletionRequested = item => + RequestDeletion = item => { var nextItem = Items.GetNext(item); diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs index dd2d22b742..4114a5e9a0 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSubScreen.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists SelectedItem = { BindTarget = SelectedItem }, AllowSelection = true, AllowShowingResults = true, - ShowResultsRequested = item => + RequestResults = item => { Debug.Assert(RoomId.Value != null); ParentScreen?.Push(new PlaylistsResultsScreen(null, RoomId.Value.Value, item, false)); From ce081c4acc6e02f8d22fac4d100050c1d40f0cac Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 02:01:11 +0900 Subject: [PATCH 068/117] Fix missing propagation of OwnerId in tests --- osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs index cee6d8fe41..8ec073ff1e 100644 --- a/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs +++ b/osu.Game/Online/Rooms/MultiplayerPlaylistItem.cs @@ -62,6 +62,7 @@ namespace osu.Game.Online.Rooms public MultiplayerPlaylistItem(PlaylistItem item) { ID = item.ID; + OwnerID = item.OwnerID; BeatmapID = item.BeatmapID; BeatmapChecksum = item.Beatmap.Value?.MD5Hash ?? string.Empty; RulesetID = item.RulesetID; From c34c580ad4a7da49bfe3cc7a5f6bc44fc9e750fb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 02:12:33 +0900 Subject: [PATCH 069/117] Add client-side + interface implementation --- .../Online/TestSceneMultiplayerQueueList.cs | 177 ++++++++++++++++++ .../Multiplayer/IMultiplayerRoomServer.cs | 6 + .../Online/Multiplayer/MultiplayerClient.cs | 2 + .../Multiplayer/OnlineMultiplayerClient.cs | 8 + .../Match/Playlist/MultiplayerQueueList.cs | 43 +++++ .../Multiplayer/TestMultiplayerClient.cs | 30 +++ 6 files changed, 266 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs new file mode 100644 index 0000000000..d693ee60f0 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs @@ -0,0 +1,177 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Database; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; +using osu.Game.Rulesets; +using osu.Game.Screens.OnlinePlay; +using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual.Multiplayer; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneMultiplayerQueueList : MultiplayerTestScene + { + private MultiplayerQueueList playlist; + + [Cached(typeof(UserLookupCache))] + private readonly TestUserLookupCache userLookupCache = new TestUserLookupCache(); + + private BeatmapManager beatmaps; + private RulesetStore rulesets; + private BeatmapSetInfo importedSet; + private BeatmapInfo importedBeatmap; + + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, Resources, host, Beatmap.Default)); + } + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("create playlist", () => + { + Child = playlist = new MultiplayerQueueList + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500, 300), + Items = { BindTarget = Client.APIRoom!.Playlist } + }; + }); + + AddStep("import beatmap", () => + { + beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); + importedSet = beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.All).First(); + importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); + }); + + AddStep("change to all players mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + } + + [Test] + public void TestDeleteButtonHiddenWithSingleItem() + { + AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + + assertDeleteButtonVisibility(0, false); + + addPlaylistItem(() => API.LocalUser.Value.OnlineID); + assertDeleteButtonVisibility(0, true); + + deleteItem(1); + assertDeleteButtonVisibility(0, false); + } + + [Test] + public void TestDeleteButtonHiddenInHostOnlyMode() + { + AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + addPlaylistItem(() => 1234); + + AddStep("set host-only queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.HostOnly })); + AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.HostOnly); + + assertDeleteButtonVisibility(0, false); + } + + [Test] + public void TestOnlyItemOwnerHasDeleteButton() + { + AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + + addPlaylistItem(() => API.LocalUser.Value.OnlineID); + assertDeleteButtonVisibility(0, true); + assertDeleteButtonVisibility(1, true); + + addPlaylistItem(() => 1234); + assertDeleteButtonVisibility(2, false); + } + + [Test] + public void TestNonOwnerDoesNotHaveDeleteButton() + { + AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + + addPlaylistItem(() => 1234); + assertDeleteButtonVisibility(0, true); + assertDeleteButtonVisibility(1, false); + } + + [Test] + public void TestSelectedItemDoesNotHaveDeleteButton() + { + AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + + addPlaylistItem(() => API.LocalUser.Value.OnlineID); + assertDeleteButtonVisibility(0, true); + + AddStep("set first playlist item as selected", () => playlist.SelectedItem.Value = playlist.Items[0]); + assertDeleteButtonVisibility(0, false); + } + + private void addPlaylistItem(Func userId) + { + long itemId = -1; + + AddStep("add playlist item", () => + { + MultiplayerPlaylistItem item = new MultiplayerPlaylistItem(new PlaylistItem + { + Beatmap = { Value = importedBeatmap }, + BeatmapID = importedBeatmap.OnlineID ?? -1, + }); + + Client.AddUserPlaylistItem(userId(), item); + + itemId = item.ID; + }); + + AddUntilStep("item arrived in playlist", () => playlist.ChildrenOfType>().Any(i => i.Model.ID == itemId)); + } + + private void deleteItem(int index) + { + OsuRearrangeableListItem item = null; + + AddStep($"move mouse to delete button {index}", () => + { + item = playlist.ChildrenOfType>().ElementAt(index); + InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0)); + }); + + AddStep("click", () => InputManager.Click(MouseButton.Left)); + + AddUntilStep("item removed from playlist", () => !playlist.ChildrenOfType>().Contains(item)); + } + + private void assertDeleteButtonVisibility(int index, bool visible) + => AddUntilStep($"delete button {index} {(visible ? "is" : "is not")} visible", + () => (playlist.ChildrenOfType().ElementAt(index).Alpha > 0) == visible); + } +} diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index 3e84e4b904..65467e6ba9 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -82,5 +82,11 @@ namespace osu.Game.Online.Multiplayer /// /// The item to add. Task AddPlaylistItem(MultiplayerPlaylistItem item); + + /// + /// Removes an item from the playlist. + /// + /// The item to remove. + Task RemovePlaylistItem(long playlistItemId); } } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 7e874495c8..34dc7ea5ea 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -335,6 +335,8 @@ namespace osu.Game.Online.Multiplayer public abstract Task AddPlaylistItem(MultiplayerPlaylistItem item); + public abstract Task RemovePlaylistItem(long playlistItemId); + Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state) { if (Room == null) diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 41687b54b0..7314603603 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -162,6 +162,14 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.AddPlaylistItem), item); } + public override Task RemovePlaylistItem(long playlistItemId) + { + if (!IsConnected.Value) + return Task.CompletedTask; + + return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), playlistItemId); + } + protected override Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default) { return beatmapLookupCache.GetBeatmapAsync(beatmapId, cancellationToken); diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 7dfee36895..e74b2e8384 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -7,6 +7,8 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osuTK; @@ -27,6 +29,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist Spacing = new Vector2(0, 2) }; + protected override DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new QueuePlaylistItem(item); + private class QueueFillFlowContainer : FillFlowContainer> { [Resolved(typeof(Room), nameof(Room.Playlist))] @@ -40,5 +44,44 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist public override IEnumerable FlowingChildren => base.FlowingChildren.OfType>().OrderBy(item => item.Model.PlaylistOrder); } + + private class QueuePlaylistItem : DrawableRoomPlaylistItem + { + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private MultiplayerClient multiplayerClient { get; set; } + + [Resolved(typeof(Room), nameof(Room.QueueMode))] + private Bindable queueMode { get; set; } + + [Resolved(typeof(Room), nameof(Room.Playlist))] + private BindableList playlist { get; set; } + + public QueuePlaylistItem(PlaylistItem item) + : base(item) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + RequestDeletion = item => multiplayerClient.RemovePlaylistItem(item.ID); + + playlist.BindCollectionChanged((_, __) => updateDeleteButtonVisibility()); + queueMode.BindValueChanged(_ => updateDeleteButtonVisibility()); + SelectedItem.BindValueChanged(_ => updateDeleteButtonVisibility(), true); + } + + private void updateDeleteButtonVisibility() + { + AllowDeletion = queueMode.Value != QueueMode.HostOnly + && playlist.Count > 1 + && Item.OwnerID == api.LocalUser.Value.OnlineID + && SelectedItem.Value != Item; + } + } } } diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index b3ea5bdc4a..1f4e031b40 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -339,6 +339,36 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task AddPlaylistItem(MultiplayerPlaylistItem item) => AddUserPlaylistItem(api.LocalUser.Value.OnlineID, item); + public async Task RemoveUserPlaylistItem(int userId, long playlistItemId) + { + Debug.Assert(Room != null); + Debug.Assert(APIRoom != null); + + if (Room.Settings.QueueMode == QueueMode.HostOnly) + throw new InvalidOperationException("Items cannot be removed in host-only mode."); + + if (Room.Playlist.Count == 1) + throw new InvalidOperationException("The singular item in the room cannot be removed."); + + var item = serverSidePlaylist.Find(i => i.ID == playlistItemId); + + if (item == null) + throw new InvalidOperationException("Item does not exist in the room."); + + if (item == currentItem) + throw new InvalidOperationException("The room's current item cannot be removed."); + + if (item.OwnerID != userId) + throw new InvalidOperationException("Attempted to remove an item which is not owned by the user."); + + serverSidePlaylist.Remove(item); + await ((IMultiplayerClient)this).PlaylistItemRemoved(playlistItemId).ConfigureAwait(false); + + await updateCurrentItem(Room).ConfigureAwait(false); + } + + public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, playlistItemId); + protected override Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default) { IBeatmapSetInfo? set = roomManager.ServerSideRooms.SelectMany(r => r.Playlist) From 8398f86440ae0f941ea07e9dc51366d6e135db16 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 04:02:16 +0900 Subject: [PATCH 070/117] Don't consider expired items in visibility check --- .../Match/Playlist/MultiplayerQueueList.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index e74b2e8384..72bcf7d343 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -29,7 +29,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist Spacing = new Vector2(0, 2) }; - protected override DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new QueuePlaylistItem(item); + protected override DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new QueuePlaylistItem(item) + { + Items = { BindTarget = Items } + }; private class QueueFillFlowContainer : FillFlowContainer> { @@ -47,6 +50,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist private class QueuePlaylistItem : DrawableRoomPlaylistItem { + public readonly IBindableList Items = new BindableList(); + [Resolved] private IAPIProvider api { get; set; } @@ -56,9 +61,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist [Resolved(typeof(Room), nameof(Room.QueueMode))] private Bindable queueMode { get; set; } - [Resolved(typeof(Room), nameof(Room.Playlist))] - private BindableList playlist { get; set; } - public QueuePlaylistItem(PlaylistItem item) : base(item) { @@ -70,7 +72,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist RequestDeletion = item => multiplayerClient.RemovePlaylistItem(item.ID); - playlist.BindCollectionChanged((_, __) => updateDeleteButtonVisibility()); + Items.BindCollectionChanged((_, __) => updateDeleteButtonVisibility()); queueMode.BindValueChanged(_ => updateDeleteButtonVisibility()); SelectedItem.BindValueChanged(_ => updateDeleteButtonVisibility(), true); } @@ -78,7 +80,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist private void updateDeleteButtonVisibility() { AllowDeletion = queueMode.Value != QueueMode.HostOnly - && playlist.Count > 1 + && Items.Count > 1 && Item.OwnerID == api.LocalUser.Value.OnlineID && SelectedItem.Value != Item; } From 4df2047a581d70687e5854209285ebdfadf2ca3f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 04:12:24 +0900 Subject: [PATCH 071/117] Prevent removal of expired items in TestMultiplayerClient --- osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 1f4e031b40..c82b748a2b 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -361,6 +361,9 @@ namespace osu.Game.Tests.Visual.Multiplayer if (item.OwnerID != userId) throw new InvalidOperationException("Attempted to remove an item which is not owned by the user."); + if (item.Expired) + throw new InvalidOperationException("Attempted to remove an item which has already been played"); + serverSidePlaylist.Remove(item); await ((IMultiplayerClient)this).PlaylistItemRemoved(playlistItemId).ConfigureAwait(false); From 80b2768a5f6c2d18cdd6225becc9a55cf8172210 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 04:18:53 +0900 Subject: [PATCH 072/117] Mirror recent server-side changes --- .../Tests/Visual/Multiplayer/TestMultiplayerClient.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index c82b748a2b..f151add430 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -347,9 +347,6 @@ namespace osu.Game.Tests.Visual.Multiplayer if (Room.Settings.QueueMode == QueueMode.HostOnly) throw new InvalidOperationException("Items cannot be removed in host-only mode."); - if (Room.Playlist.Count == 1) - throw new InvalidOperationException("The singular item in the room cannot be removed."); - var item = serverSidePlaylist.Find(i => i.ID == playlistItemId); if (item == null) @@ -362,7 +359,7 @@ namespace osu.Game.Tests.Visual.Multiplayer throw new InvalidOperationException("Attempted to remove an item which is not owned by the user."); if (item.Expired) - throw new InvalidOperationException("Attempted to remove an item which has already been played"); + throw new InvalidOperationException("Attempted to remove an item which has already been played."); serverSidePlaylist.Remove(item); await ((IMultiplayerClient)this).PlaylistItemRemoved(playlistItemId).ConfigureAwait(false); @@ -471,11 +468,12 @@ namespace osu.Game.Tests.Visual.Multiplayer await updatePlaylistOrder(Room).ConfigureAwait(false); } + private IEnumerable upcomingItems => serverSidePlaylist.Where(i => !i.Expired).OrderBy(i => i.PlaylistOrder); + private async Task updateCurrentItem(MultiplayerRoom room, bool notify = true) { // Pick the next non-expired playlist item by playlist order, or default to the most-recently-expired item. - MultiplayerPlaylistItem nextItem = serverSidePlaylist.Where(i => !i.Expired).OrderBy(i => i.PlaylistOrder).FirstOrDefault() - ?? serverSidePlaylist.OrderByDescending(i => i.PlayedAt).First(); + MultiplayerPlaylistItem nextItem = upcomingItems.FirstOrDefault() ?? serverSidePlaylist.OrderByDescending(i => i.PlayedAt).First(); currentIndex = serverSidePlaylist.IndexOf(nextItem); From aec36adf6ce2fff49265a6ca3cc6bfb50714b518 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 04:20:23 +0900 Subject: [PATCH 073/117] Fix test failures --- .../Multiplayer/TestSceneDrawableRoomPlaylist.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 269cd15a0f..c9a5552f39 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("import beatmap", () => manager.Import(beatmap.BeatmapSet).Wait()); - createPlaylist(beatmap); + createPlaylistWithBeatmaps(beatmap); assertDownloadButtonVisible(false); @@ -159,7 +159,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var byChecksum = CreateAPIBeatmap(); byChecksum.Checksum = "1337"; // Some random checksum that does not exist locally. - createPlaylist(byOnlineId, byChecksum); + createPlaylistWithBeatmaps(byOnlineId, byChecksum); AddAssert("download buttons shown", () => playlist.ChildrenOfType().All(d => d.IsPresent)); } @@ -173,7 +173,7 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmap.BeatmapSet.HasExplicitContent = true; - createPlaylist(beatmap); + createPlaylistWithBeatmaps(beatmap); } [Test] @@ -246,7 +246,7 @@ namespace osu.Game.Tests.Visual.Multiplayer => AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).Alpha > 0) == visible); - private void createPlaylist(Action setupPlaylist) + private void createPlaylist(Action setupPlaylist = null) { AddStep("create playlist", () => { @@ -257,6 +257,8 @@ namespace osu.Game.Tests.Visual.Multiplayer Size = new Vector2(500, 300) }; + setupPlaylist?.Invoke(playlist); + for (int i = 0; i < 20; i++) { playlist.Items.Add(new PlaylistItem @@ -292,7 +294,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); } - private void createPlaylist(params IBeatmapInfo[] beatmaps) + private void createPlaylistWithBeatmaps(params IBeatmapInfo[] beatmaps) { AddStep("create playlist", () => { From 1a0945dabadbe14990004cd8dfbbb02a8d8271eb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 14:11:29 +0900 Subject: [PATCH 074/117] Siplify condition, allow host to always remove items --- .../Online/TestSceneMultiplayerQueueList.cs | 80 +++++++++---------- .../Match/Playlist/MultiplayerQueueList.cs | 17 ++-- 2 files changed, 43 insertions(+), 54 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs index d693ee60f0..19e5624a04 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs @@ -13,6 +13,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osu.Game.Rulesets; @@ -55,6 +56,7 @@ namespace osu.Game.Tests.Visual.Online Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(500, 300), + SelectedItem = { BindTarget = Client.CurrentMatchPlayingItem }, Items = { BindTarget = Client.APIRoom!.Playlist } }; }); @@ -70,69 +72,59 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestDeleteButtonHiddenWithSingleItem() - { - AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); - AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); - - assertDeleteButtonVisibility(0, false); - - addPlaylistItem(() => API.LocalUser.Value.OnlineID); - assertDeleteButtonVisibility(0, true); - - deleteItem(1); - assertDeleteButtonVisibility(0, false); - } - - [Test] - public void TestDeleteButtonHiddenInHostOnlyMode() - { - AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); - AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); - addPlaylistItem(() => 1234); - - AddStep("set host-only queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.HostOnly })); - AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.HostOnly); - - assertDeleteButtonVisibility(0, false); - } - - [Test] - public void TestOnlyItemOwnerHasDeleteButton() + public void TestDeleteButtonAlwaysVisibleForHost() { AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); addPlaylistItem(() => API.LocalUser.Value.OnlineID); - assertDeleteButtonVisibility(0, true); assertDeleteButtonVisibility(1, true); + addPlaylistItem(() => 1234); + assertDeleteButtonVisibility(2, true); + } + [Test] + public void TestDeleteButtonOnlyVisibleForItemOwnerIfNotHost() + { + AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); + AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + + AddStep("join other user", () => Client.AddUser(new APIUser { Id = 1234 })); + AddStep("set other user as host", () => Client.TransferHost(1234)); + + addPlaylistItem(() => API.LocalUser.Value.OnlineID); + assertDeleteButtonVisibility(1, true); addPlaylistItem(() => 1234); assertDeleteButtonVisibility(2, false); + + AddStep("set local user as host", () => Client.TransferHost(API.LocalUser.Value.OnlineID)); + assertDeleteButtonVisibility(1, true); + assertDeleteButtonVisibility(2, true); } [Test] - public void TestNonOwnerDoesNotHaveDeleteButton() + public void TestCurrentItemDoesNotHaveDeleteButton() { AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); - addPlaylistItem(() => 1234); - assertDeleteButtonVisibility(0, true); - assertDeleteButtonVisibility(1, false); - } - - [Test] - public void TestSelectedItemDoesNotHaveDeleteButton() - { - AddStep("set all players queue mode", () => Client.ChangeSettings(new MultiplayerRoomSettings { QueueMode = QueueMode.AllPlayers })); - AddUntilStep("wait for queue mode change", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); + assertDeleteButtonVisibility(0, false); addPlaylistItem(() => API.LocalUser.Value.OnlineID); - assertDeleteButtonVisibility(0, true); - - AddStep("set first playlist item as selected", () => playlist.SelectedItem.Value = playlist.Items[0]); assertDeleteButtonVisibility(0, false); + assertDeleteButtonVisibility(1, true); + + // Run through gameplay. + AddStep("set state to ready", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.Ready)); + AddUntilStep("local state is ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready); + AddStep("start match", () => Client.StartMatch()); + AddUntilStep("match started", () => Client.LocalUser?.State == MultiplayerUserState.WaitingForLoad); + AddStep("set state to loaded", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.Loaded)); + AddUntilStep("local state is playing", () => Client.LocalUser?.State == MultiplayerUserState.Playing); + AddStep("set state to finished play", () => Client.ChangeUserState(API.LocalUser.Value.Id, MultiplayerUserState.FinishedPlay)); + AddUntilStep("local state is results", () => Client.LocalUser?.State == MultiplayerUserState.Results); + + assertDeleteButtonVisibility(1, false); } private void addPlaylistItem(Func userId) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 72bcf7d343..8832c9bd60 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -8,6 +8,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; using osuTK; @@ -29,10 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist Spacing = new Vector2(0, 2) }; - protected override DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new QueuePlaylistItem(item) - { - Items = { BindTarget = Items } - }; + protected override DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new QueuePlaylistItem(item); private class QueueFillFlowContainer : FillFlowContainer> { @@ -50,14 +48,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist private class QueuePlaylistItem : DrawableRoomPlaylistItem { - public readonly IBindableList Items = new BindableList(); - [Resolved] private IAPIProvider api { get; set; } [Resolved] private MultiplayerClient multiplayerClient { get; set; } + [Resolved(typeof(Room), nameof(Room.Host))] + private Bindable host { get; set; } + [Resolved(typeof(Room), nameof(Room.QueueMode))] private Bindable queueMode { get; set; } @@ -72,16 +71,14 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist RequestDeletion = item => multiplayerClient.RemovePlaylistItem(item.ID); - Items.BindCollectionChanged((_, __) => updateDeleteButtonVisibility()); + host.BindValueChanged(_ => updateDeleteButtonVisibility()); queueMode.BindValueChanged(_ => updateDeleteButtonVisibility()); SelectedItem.BindValueChanged(_ => updateDeleteButtonVisibility(), true); } private void updateDeleteButtonVisibility() { - AllowDeletion = queueMode.Value != QueueMode.HostOnly - && Items.Count > 1 - && Item.OwnerID == api.LocalUser.Value.OnlineID + AllowDeletion = (Item.OwnerID == api.LocalUser.Value.OnlineID || multiplayerClient.IsHost) && SelectedItem.Value != Item; } } From 17d676200bd2ce4c35d2a5642632b59169dc69fb Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 17:33:36 +0900 Subject: [PATCH 075/117] Xmldoc fixes from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 8bd2daa2c3..5e180afcf9 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -13,13 +13,13 @@ using osuTK; namespace osu.Game.Screens.OnlinePlay { /// - /// A list scrollable list which displays the s in a . + /// A scrollable list which displays the s in a . /// public class DrawableRoomPlaylist : OsuRearrangeableListContainer { /// - /// The currently-selected item, used to show a border around items. - /// May be updated by playlist items if is true. + /// The currently-selected item. Selection is visually represented with a border. + /// May be updated by clicking playlist items if is true. /// public readonly Bindable SelectedItem = new Bindable(); From 0963b0045303887db0e11e0ef01728ac709adddc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 17:33:59 +0900 Subject: [PATCH 076/117] Disallow item selection in playlists song select --- .../Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs index c29b8db26c..89842e933b 100644 --- a/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs +++ b/osu.Game/Screens/OnlinePlay/Components/MatchBeatmapDetailArea.cs @@ -46,8 +46,7 @@ namespace osu.Game.Screens.OnlinePlay.Components Padding = new MarginPadding { Bottom = 10 }, Child = playlist = new PlaylistsRoomSettingsPlaylist { - RelativeSizeAxes = Axes.Both, - AllowSelection = true, + RelativeSizeAxes = Axes.Both } } }, From dfe19f350968d3cbf490690261ea940ccb80fa80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Dec 2021 17:48:09 +0900 Subject: [PATCH 077/117] Minor code reformatting --- .../TestSceneDrawableRoomPlaylist.cs | 27 +++++++++++--- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 36 +++++++++---------- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index c9a5552f39..f6c15f314e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -62,7 +62,11 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestEditable() { - createPlaylist(p => p.AllowReordering = p.AllowDeletion = true); + createPlaylist(p => + { + p.AllowReordering = true; + p.AllowDeletion = true; + }); moveToItem(0); assertHandleVisibility(0, true); @@ -75,7 +79,12 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestMarkInvalid() { - createPlaylist(p => p.AllowReordering = p.AllowDeletion = p.AllowSelection = true); + createPlaylist(p => + { + p.AllowReordering = true; + p.AllowDeletion = true; + p.AllowSelection = true; + }); AddStep("mark item 0 as invalid", () => playlist.Items[0].MarkInvalid()); @@ -102,7 +111,12 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestEditableSelectable() { - createPlaylist(p => p.AllowReordering = p.AllowDeletion = p.AllowSelection = true); + createPlaylist(p => + { + p.AllowReordering = true; + p.AllowDeletion = true; + p.AllowSelection = true; + }); moveToItem(0); assertHandleVisibility(0, true); @@ -116,7 +130,12 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestSelectionNotLostAfterRearrangement() { - createPlaylist(p => p.AllowReordering = p.AllowDeletion = p.AllowSelection = true); + createPlaylist(p => + { + p.AllowReordering = true; + p.AllowDeletion = true; + p.AllowSelection = true; + }); moveToItem(0); AddStep("click", () => InputManager.Click(MouseButton.Left)); diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 7a1c069df3..9ad43fced5 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -38,7 +38,8 @@ namespace osu.Game.Screens.OnlinePlay public class DrawableRoomPlaylistItem : OsuRearrangeableListItem { public const float HEIGHT = 50; - public const float ICON_HEIGHT = 34; + + private const float icon_height = 34; /// /// Invoked when this item requests to be deleted. @@ -238,7 +239,7 @@ namespace osu.Game.Screens.OnlinePlay } if (Item.Beatmap.Value != null) - difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(ICON_HEIGHT) }; + difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(icon_height) }; else difficultyIconContainer.Clear(); @@ -392,7 +393,7 @@ namespace osu.Game.Screens.OnlinePlay { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(ICON_HEIGHT), + Size = new Vector2(icon_height), Margin = new MarginPadding { Right = 8 }, Masking = true, CornerRadius = 4, @@ -405,22 +406,21 @@ namespace osu.Game.Screens.OnlinePlay }; } - private IEnumerable createButtons() => - new[] + private IEnumerable createButtons() => new[] + { + showResultsButton = new ShowResultsButton { - showResultsButton = new ShowResultsButton - { - Action = () => RequestResults?.Invoke(Item), - Alpha = AllowShowingResults ? 1 : 0, - }, - Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), - removeButton = new PlaylistRemoveButton - { - Size = new Vector2(30, 30), - Alpha = AllowDeletion ? 1 : 0, - Action = () => RequestDeletion?.Invoke(Item), - }, - }; + Action = () => RequestResults?.Invoke(Item), + Alpha = AllowShowingResults ? 1 : 0, + }, + Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), + removeButton = new PlaylistRemoveButton + { + Size = new Vector2(30, 30), + Alpha = AllowDeletion ? 1 : 0, + Action = () => RequestDeletion?.Invoke(Item), + }, + }; public class PlaylistRemoveButton : GrayButton { From e7e61cd9ab8b882ccb99325960e2b6677b972548 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Dec 2021 18:50:53 +0900 Subject: [PATCH 078/117] Fix potential crash due to children being mutated after disposal This is a bit of an unfortunate edge case where the unbind-on-disposal doesn't help, since the binding is happening in BDL, and the usage is in a nested `LoadComponentAsync` call. Combine those and you have a recipe for disaster. --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 25 ++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index e4cf9bd868..d292b7114f 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -330,6 +330,21 @@ namespace osu.Game.Screens.Select addInfoLabels(); } + protected override void LoadComplete() + { + base.LoadComplete(); + + mods.BindValueChanged(m => + { + settingChangeTracker?.Dispose(); + + refreshBPMLabel(); + + settingChangeTracker = new ModSettingChangeTracker(m.NewValue); + settingChangeTracker.SettingChanged += _ => refreshBPMLabel(); + }, true); + } + private void setMetadata(string source) { ArtistLabel.Text = artistBinding.Value; @@ -360,16 +375,6 @@ namespace osu.Game.Screens.Select Children = getRulesetInfoLabels() } }; - - mods.BindValueChanged(m => - { - settingChangeTracker?.Dispose(); - - refreshBPMLabel(); - - settingChangeTracker = new ModSettingChangeTracker(m.NewValue); - settingChangeTracker.SettingChanged += _ => refreshBPMLabel(); - }, true); } private InfoLabel[] getRulesetInfoLabels() From a3b53ac2f68a176f8406740b6fe486ac420a6ac4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Dec 2021 18:58:47 +0900 Subject: [PATCH 079/117] Change comparison to match in all locations --- osu.Game/Screens/Play/PlayerLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index dfc3c2b61d..60843acb4f 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -505,9 +505,9 @@ namespace osu.Game.Screens.Play volumeOverlay.IsMuted.Value = false; // Check values before resetting, as the user may have only had mute enabled, in which case we might not need to adjust volumes. - if (audioManager.Volume.Value < volume_requirement) + if (audioManager.Volume.Value <= volume_requirement) audioManager.Volume.SetDefault(); - if (audioManager.VolumeTrack.Value < volume_requirement) + if (audioManager.VolumeTrack.Value <= volume_requirement) audioManager.VolumeTrack.SetDefault(); return true; From f9af239ed95153275b66310f3caf67215a5ae73e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 9 Dec 2021 23:46:03 +0900 Subject: [PATCH 080/117] Cleanup duplicated classes in DrawableRoomPlaylistItem --- .../TestSceneDrawableRoomPlaylist.cs | 12 ++++- .../TestScenePlaylistsRoomSettingsPlaylist.cs | 2 +- .../Online/TestSceneMultiplayerQueueList.cs | 4 +- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 46 ++++--------------- 4 files changed, 23 insertions(+), 41 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index f6c15f314e..d0cf17db1f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -248,6 +248,16 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("owner visible", () => playlist.ChildrenOfType().All(a => a.IsPresent == withOwner)); } + [Test] + public void TestWithAllButtonsEnabled() + { + createPlaylist(p => + { + p.AllowDeletion = true; + p.AllowShowingResults = true; + }); + } + private void moveToItem(int index, Vector2? offset = null) => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType().ElementAt(index), offset)); @@ -263,7 +273,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertDeleteButtonVisibility(int index, bool visible) => AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", - () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).Alpha > 0) == visible); + () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).RemoveButton.Alpha > 0) == visible); private void createPlaylist(Action setupPlaylist = null) { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs index 93ccd5f1e1..c61b99c2c2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void moveToDeleteButton(int index, Vector2? offset = null) => AddStep($"move mouse to delete button {index}", () => { var item = playlist.ChildrenOfType>().ElementAt(index); - InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset); + InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0).RemoveButton, offset); }); private void createPlaylist() diff --git a/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs index 19e5624a04..95cef391f1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs @@ -154,7 +154,7 @@ namespace osu.Game.Tests.Visual.Online AddStep($"move mouse to delete button {index}", () => { item = playlist.ChildrenOfType>().ElementAt(index); - InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0)); + InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0).RemoveButton); }); AddStep("click", () => InputManager.Click(MouseButton.Left)); @@ -164,6 +164,6 @@ namespace osu.Game.Tests.Visual.Online private void assertDeleteButtonVisibility(int index, bool visible) => AddUntilStep($"delete button {index} {(visible ? "is" : "is not")} visible", - () => (playlist.ChildrenOfType().ElementAt(index).Alpha > 0) == visible); + () => (playlist.ChildrenOfType().ElementAt(index).RemoveButton.Alpha > 0) == visible); } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 9ad43fced5..ac5f77a72b 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -59,6 +59,8 @@ namespace osu.Game.Screens.OnlinePlay public readonly PlaylistItem Item; + public Drawable RemoveButton { get; private set; } + private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; private readonly IBindable valid = new Bindable(); private readonly Bindable beatmap = new Bindable(); @@ -73,7 +75,6 @@ namespace osu.Game.Screens.OnlinePlay private ModDisplay modDisplay; private FillFlowContainer buttonsFlow; private UpdateableAvatar ownerAvatar; - private Drawable removeButton; private Drawable showResultsButton; private PanelBackground panelBackground; private FillFlowContainer mainFillFlow; @@ -191,8 +192,8 @@ namespace osu.Game.Screens.OnlinePlay { allowDeletion = value; - if (removeButton != null) - removeButton.Alpha = value ? 1 : 0; + if (RemoveButton != null) + RemoveButton.Alpha = value ? 1 : 0; } } @@ -408,35 +409,23 @@ namespace osu.Game.Screens.OnlinePlay private IEnumerable createButtons() => new[] { - showResultsButton = new ShowResultsButton + showResultsButton = new GrayButton(FontAwesome.Solid.ChartPie) { + Size = new Vector2(30, 30), Action = () => RequestResults?.Invoke(Item), Alpha = AllowShowingResults ? 1 : 0, + TooltipText = "View results" }, Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), - removeButton = new PlaylistRemoveButton + RemoveButton = new GrayButton(FontAwesome.Solid.MinusSquare) { Size = new Vector2(30, 30), Alpha = AllowDeletion ? 1 : 0, Action = () => RequestDeletion?.Invoke(Item), + TooltipText = "Remove from playlist" }, }; - public class PlaylistRemoveButton : GrayButton - { - public PlaylistRemoveButton() - : base(FontAwesome.Solid.MinusSquare) - { - TooltipText = "Remove from playlist"; - } - - [BackgroundDependencyLoader] - private void load() - { - Icon.Scale = new Vector2(0.8f); - } - } - protected override bool OnClick(ClickEvent e) { if (AllowSelection && valid.Value) @@ -497,23 +486,6 @@ namespace osu.Game.Screens.OnlinePlay } } - private class ShowResultsButton : IconButton - { - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Icon = FontAwesome.Solid.ChartPie; - TooltipText = "View results"; - - Add(new Box - { - RelativeSizeAxes = Axes.Both, - Depth = float.MaxValue, - Colour = colours.Gray4, - }); - } - } - // For now, this is the same implementation as in PanelBackground, but supports a beatmap info rather than a working beatmap private class PanelBackground : Container // todo: should be a buffered container (https://github.com/ppy/osu-framework/issues/3222) { From 05aa9635a8cee0503e30a5200a37ccd3c80114c9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Dec 2021 00:38:18 +0900 Subject: [PATCH 081/117] Privatise button again --- .../TestSceneDrawableRoomPlaylist.cs | 2 +- .../TestScenePlaylistsRoomSettingsPlaylist.cs | 2 +- .../Online/TestSceneMultiplayerQueueList.cs | 4 ++-- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 17 ++++++++++++----- 4 files changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index d0cf17db1f..24d7d62aa3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -273,7 +273,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void assertDeleteButtonVisibility(int index, bool visible) => AddAssert($"delete button {index} {(visible ? "is" : "is not")} visible", - () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).RemoveButton.Alpha > 0) == visible); + () => (playlist.ChildrenOfType().ElementAt(2 + index * 2).Alpha > 0) == visible); private void createPlaylist(Action setupPlaylist = null) { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs index c61b99c2c2..93ccd5f1e1 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsRoomSettingsPlaylist.cs @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.Multiplayer private void moveToDeleteButton(int index, Vector2? offset = null) => AddStep($"move mouse to delete button {index}", () => { var item = playlist.ChildrenOfType>().ElementAt(index); - InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0).RemoveButton, offset); + InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0), offset); }); private void createPlaylist() diff --git a/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs index 95cef391f1..19e5624a04 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs @@ -154,7 +154,7 @@ namespace osu.Game.Tests.Visual.Online AddStep($"move mouse to delete button {index}", () => { item = playlist.ChildrenOfType>().ElementAt(index); - InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0).RemoveButton); + InputManager.MoveMouseTo(item.ChildrenOfType().ElementAt(0)); }); AddStep("click", () => InputManager.Click(MouseButton.Left)); @@ -164,6 +164,6 @@ namespace osu.Game.Tests.Visual.Online private void assertDeleteButtonVisibility(int index, bool visible) => AddUntilStep($"delete button {index} {(visible ? "is" : "is not")} visible", - () => (playlist.ChildrenOfType().ElementAt(index).RemoveButton.Alpha > 0) == visible); + () => (playlist.ChildrenOfType().ElementAt(index).Alpha > 0) == visible); } } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index ac5f77a72b..9640d9db46 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -59,8 +59,6 @@ namespace osu.Game.Screens.OnlinePlay public readonly PlaylistItem Item; - public Drawable RemoveButton { get; private set; } - private readonly DelayedLoadWrapper onScreenLoader = new DelayedLoadWrapper(Empty) { RelativeSizeAxes = Axes.Both }; private readonly IBindable valid = new Bindable(); private readonly Bindable beatmap = new Bindable(); @@ -76,6 +74,7 @@ namespace osu.Game.Screens.OnlinePlay private FillFlowContainer buttonsFlow; private UpdateableAvatar ownerAvatar; private Drawable showResultsButton; + private Drawable removeButton; private PanelBackground panelBackground; private FillFlowContainer mainFillFlow; @@ -192,8 +191,8 @@ namespace osu.Game.Screens.OnlinePlay { allowDeletion = value; - if (RemoveButton != null) - RemoveButton.Alpha = value ? 1 : 0; + if (removeButton != null) + removeButton.Alpha = value ? 1 : 0; } } @@ -417,7 +416,7 @@ namespace osu.Game.Screens.OnlinePlay TooltipText = "View results" }, Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), - RemoveButton = new GrayButton(FontAwesome.Solid.MinusSquare) + removeButton = new PlaylistRemoveButton { Size = new Vector2(30, 30), Alpha = AllowDeletion ? 1 : 0, @@ -433,6 +432,14 @@ namespace osu.Game.Screens.OnlinePlay return true; } + public class PlaylistRemoveButton : GrayButton + { + public PlaylistRemoveButton() + : base(FontAwesome.Solid.MinusSquare) + { + } + } + private sealed class PlaylistDownloadButton : BeatmapDownloadButton { private readonly PlaylistItem playlistItem; From 4d1c06c0612a76d527d6458d04e1bb87486482be Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Dec 2021 01:03:36 +0900 Subject: [PATCH 082/117] Add support for host enqueueing in TestMultiplayerClient --- .../Multiplayer/TestMultiplayerClient.cs | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index f151add430..1516d0e473 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -309,31 +309,36 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); + bool isNewAddition = item.ID == 0; + if (Room.Settings.QueueMode == QueueMode.HostOnly && Room.Host?.UserID != LocalUser?.UserID) throw new InvalidOperationException("Local user is not the room host."); item.OwnerID = userId; - switch (Room.Settings.QueueMode) + if (isNewAddition) { - case QueueMode.HostOnly: - // In host-only mode, the current item is re-used. - item.ID = currentItem.ID; - item.PlaylistOrder = currentItem.PlaylistOrder; + await addItem(item).ConfigureAwait(false); + await updateCurrentItem(Room).ConfigureAwait(false); + } + else + { + var existingItem = serverSidePlaylist.SingleOrDefault(i => i.ID == item.ID); - serverSidePlaylist[currentIndex] = item; - await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); + if (existingItem == null) + throw new InvalidOperationException("Attempted to change an item that doesn't exist."); - // Note: Unlike the server, this is the easiest way to update the current item at this point. - await updateCurrentItem(Room, false).ConfigureAwait(false); - break; + if (existingItem.OwnerID != userId && Room.Host?.UserID != LocalUser?.UserID) + throw new InvalidOperationException("Attempted to change an item which is not owned by the user."); - default: - await addItem(item).ConfigureAwait(false); + if (existingItem.Expired) + throw new InvalidOperationException("Attempted to change an item which has already been played."); - // The current item can change as a result of an item being added. For example, if all items earlier in the queue were expired. - await updateCurrentItem(Room).ConfigureAwait(false); - break; + // Ensure the playlist order doesn't change. + item.PlaylistOrder = existingItem.PlaylistOrder; + + serverSidePlaylist[serverSidePlaylist.IndexOf(existingItem)] = item; + await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); } } From 048a4951156593ae4c2a45d019fe15210228ca2c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Dec 2021 01:08:54 +0900 Subject: [PATCH 083/117] Add edit button to DrawableRoomPlaylistItem --- .../TestSceneDrawableRoomPlaylist.cs | 1 + .../OnlinePlay/DrawableRoomPlaylist.cs | 27 ++++++++++++- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 38 +++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 24d7d62aa3..f9784384fd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -255,6 +255,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { p.AllowDeletion = true; p.AllowShowingResults = true; + p.AllowEditing = true; }); } diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs index 5e180afcf9..57bb4253cb 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylist.cs @@ -33,6 +33,11 @@ namespace osu.Game.Screens.OnlinePlay /// public Action RequestResults; + /// + /// Invoked when an item requests to be edited. + /// + public Action RequestEdit; + private bool allowReordering; /// @@ -104,6 +109,24 @@ namespace osu.Game.Screens.OnlinePlay } } + private bool allowEditing; + + /// + /// Whether to allow items to be edited. + /// If true, requests to edit items may be satisfied via . + /// + public bool AllowEditing + { + get => allowEditing; + set + { + allowEditing = value; + + foreach (var item in ListContainer.OfType()) + item.AllowEditing = value; + } + } + private bool showItemOwners; /// @@ -135,12 +158,14 @@ namespace osu.Game.Screens.OnlinePlay { d.SelectedItem.BindTarget = SelectedItem; d.RequestDeletion = i => RequestDeletion?.Invoke(i); + d.RequestResults = i => RequestResults?.Invoke(i); + d.RequestEdit = i => RequestEdit?.Invoke(i); d.AllowReordering = AllowReordering; d.AllowDeletion = AllowDeletion; d.AllowSelection = AllowSelection; d.AllowShowingResults = AllowShowingResults; + d.AllowEditing = AllowEditing; d.ShowItemOwner = ShowItemOwners; - d.RequestResults = i => RequestResults?.Invoke(i); }); protected virtual DrawableRoomPlaylistItem CreateDrawablePlaylistItem(PlaylistItem item) => new DrawableRoomPlaylistItem(item); diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 9640d9db46..8042f7d772 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -51,6 +51,11 @@ namespace osu.Game.Screens.OnlinePlay /// public Action RequestResults; + /// + /// Invoked when this item requests to be edited. + /// + public Action RequestEdit; + /// /// The currently-selected item, used to show a border around this item. /// May be updated by this item if is true. @@ -74,6 +79,7 @@ namespace osu.Game.Screens.OnlinePlay private FillFlowContainer buttonsFlow; private UpdateableAvatar ownerAvatar; private Drawable showResultsButton; + private Drawable editButton; private Drawable removeButton; private PanelBackground panelBackground; private FillFlowContainer mainFillFlow; @@ -213,6 +219,23 @@ namespace osu.Game.Screens.OnlinePlay } } + private bool allowEditing; + + /// + /// Whether this item can be edited. + /// + public bool AllowEditing + { + get => allowEditing; + set + { + allowEditing = value; + + if (editButton != null) + editButton.Alpha = value ? 1 : 0; + } + } + private bool showItemOwner; /// @@ -416,6 +439,13 @@ namespace osu.Game.Screens.OnlinePlay TooltipText = "View results" }, Item.Beatmap.Value == null ? Empty() : new PlaylistDownloadButton(Item), + editButton = new PlaylistEditButton + { + Size = new Vector2(30, 30), + Alpha = AllowEditing ? 1 : 0, + Action = () => RequestEdit?.Invoke(Item), + TooltipText = "Edit" + }, removeButton = new PlaylistRemoveButton { Size = new Vector2(30, 30), @@ -432,6 +462,14 @@ namespace osu.Game.Screens.OnlinePlay return true; } + public class PlaylistEditButton : GrayButton + { + public PlaylistEditButton() + : base(FontAwesome.Solid.Edit) + { + } + } + public class PlaylistRemoveButton : GrayButton { public PlaylistRemoveButton() From 671582a9255706334c67f75a0cece57a454be14a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Dec 2021 01:15:15 +0900 Subject: [PATCH 084/117] Allow host to enqeue items and items to be edited --- .../TestSceneAllPlayersQueueMode.cs | 4 +- .../Multiplayer/TestSceneHostOnlyQueueMode.cs | 24 ++++++++++- .../Multiplayer/TestSceneMultiplayer.cs | 3 +- .../TestSceneMultiplayerMatchSongSelect.cs | 2 +- .../Match/Playlist/MultiplayerPlaylist.cs | 9 +++- .../Match/Playlist/MultiplayerQueueList.cs | 6 ++- .../Multiplayer/MultiplayerMatchSongSelect.cs | 8 +++- .../Multiplayer/MultiplayerMatchSubScreen.cs | 42 +++++++------------ .../OnlinePlay/OnlinePlaySongSelect.cs | 12 +++--- 9 files changed, 67 insertions(+), 43 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index ccfae1deef..8373979308 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -87,9 +87,9 @@ namespace osu.Game.Tests.Visual.Multiplayer private void addItem(Func beatmap) { - AddStep("click edit button", () => + AddStep("click add button", () => { - InputManager.MoveMouseTo(this.ChildrenOfType().Single().AddOrEditPlaylistButton); + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); InputManager.Click(MouseButton.Left); }); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index 1de7289446..ccac3de304 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; +using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Multiplayer; using osuTK.Input; @@ -74,11 +75,19 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("api room updated", () => Client.APIRoom?.QueueMode.Value == QueueMode.AllPlayers); } + [Test] + public void TestAddItemsAsHost() + { + addItem(() => OtherBeatmap); + + AddAssert("playlist contains two items", () => Client.APIRoom?.Playlist.Count == 2); + } + private void selectNewItem(Func beatmap) { AddStep("click edit button", () => { - InputManager.MoveMouseTo(this.ChildrenOfType().Single().AddOrEditPlaylistButton); + InputManager.MoveMouseTo(this.ChildrenOfType().First()); InputManager.Click(MouseButton.Left); }); @@ -90,5 +99,18 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); AddUntilStep("selected item is new beatmap", () => Client.CurrentMatchPlayingItem.Value?.Beatmap.Value?.OnlineID == otherBeatmap.OnlineID); } + + private void addItem(Func beatmap) + { + AddStep("click add button", () => + { + InputManager.MoveMouseTo(this.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded); + AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap())); + AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); + } } } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 2411f39ae3..5eb0abc830 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -416,8 +416,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerScreenStack.CurrentScreen).CurrentSubScreen; - - ((MultiplayerMatchSubScreen)currentSubScreen).SelectBeatmap(); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.CurrentMatchPlayingItem.Value); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index a5229702a8..d671673d3c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -179,7 +179,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public new BeatmapCarousel Carousel => base.Carousel; public TestMultiplayerMatchSongSelect(Room room, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null) - : base(room, beatmap, ruleset) + : base(room, null, beatmap, ruleset) { } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs index c3245b550f..4971489769 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerPlaylist.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -19,6 +20,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist { public readonly Bindable DisplayMode = new Bindable(); + /// + /// Invoked when an item requests to be edited. + /// + public Action RequestEdit; + private MultiplayerQueueList queueList; private MultiplayerHistoryList historyList; private bool firstPopulation = true; @@ -46,7 +52,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist queueList = new MultiplayerQueueList { RelativeSizeAxes = Axes.Both, - SelectedItem = { BindTarget = SelectedItem } + SelectedItem = { BindTarget = SelectedItem }, + RequestEdit = item => RequestEdit?.Invoke(item) }, historyList = new MultiplayerHistoryList { diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs index 8832c9bd60..3e0f663d42 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/Playlist/MultiplayerQueueList.cs @@ -78,8 +78,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist private void updateDeleteButtonVisibility() { - AllowDeletion = (Item.OwnerID == api.LocalUser.Value.OnlineID || multiplayerClient.IsHost) - && SelectedItem.Value != Item; + bool isItemOwner = Item.OwnerID == api.LocalUser.Value.OnlineID || multiplayerClient.IsHost; + + AllowDeletion = isItemOwner && SelectedItem.Value != Item; + AllowEditing = isItemOwner; } } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 44efef53f5..80a0289ba9 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -24,17 +24,22 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private MultiplayerClient client { get; set; } + private readonly PlaylistItem itemToEdit; + private LoadingLayer loadingLayer; /// /// Construct a new instance of multiplayer song select. /// /// The room. + /// The item to be edited. May be null, in which case a new item will be added to the playlist. /// An optional initial beatmap selection to perform. /// An optional initial ruleset selection to perform. - public MultiplayerMatchSongSelect(Room room, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null) + public MultiplayerMatchSongSelect(Room room, PlaylistItem itemToEdit = null, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null) : base(room) { + this.itemToEdit = itemToEdit; + if (beatmap != null || ruleset != null) { Schedule(() => @@ -61,6 +66,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer client.AddPlaylistItem(new MultiplayerPlaylistItem { + ID = itemToEdit?.ID ?? 0, BeatmapID = item.BeatmapID, BeatmapChecksum = item.Beatmap.Value.MD5Hash, RulesetID = item.RulesetID, diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 3a25bd7b06..96a9804067 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -14,7 +14,6 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -44,8 +43,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer public override string ShortTitle => "room"; - public OsuButton AddOrEditPlaylistButton { get; private set; } - [Resolved] private MultiplayerClient client { get; set; } @@ -57,6 +54,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [CanBeNull] private IDisposable readyClickOperation; + private AddItemButton addItemButton; + public MultiplayerMatchSubScreen(Room room) : base(room) { @@ -134,12 +133,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new Drawable[] { new OverlinedHeader("Beatmap") }, new Drawable[] { - AddOrEditPlaylistButton = new PurpleTriangleButton + addItemButton = new AddItemButton { RelativeSizeAxes = Axes.X, Height = 40, - Action = SelectBeatmap, - Alpha = 0 + Text = "Add item", + Action = () => OpenSongSelection(null) }, }, null, @@ -147,7 +146,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { new MultiplayerPlaylist { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + RequestEdit = OpenSongSelection } }, new[] @@ -220,12 +220,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } }; - internal void SelectBeatmap() + internal void OpenSongSelection(PlaylistItem itemToEdit) { if (!this.IsCurrentScreen()) return; - this.Push(new MultiplayerMatchSongSelect(Room)); + this.Push(new MultiplayerMatchSongSelect(Room, itemToEdit)); } protected override Drawable CreateFooter() => new MultiplayerMatchFooter @@ -385,23 +385,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; } - switch (client.Room.Settings.QueueMode) - { - case QueueMode.HostOnly: - AddOrEditPlaylistButton.Text = "Edit beatmap"; - AddOrEditPlaylistButton.Alpha = client.IsHost ? 1 : 0; - break; - - case QueueMode.AllPlayers: - case QueueMode.AllPlayersRoundRobin: - AddOrEditPlaylistButton.Text = "Add beatmap"; - AddOrEditPlaylistButton.Alpha = 1; - break; - - default: - AddOrEditPlaylistButton.Alpha = 0; - break; - } + addItemButton.Alpha = client.IsHost || Room.QueueMode.Value != QueueMode.HostOnly ? 1 : 0; Scheduler.AddOnce(UpdateMods); } @@ -466,7 +450,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; } - this.Push(new MultiplayerMatchSongSelect(Room, beatmap, ruleset)); + this.Push(new MultiplayerMatchSongSelect(Room, SelectedItem.Value, beatmap, ruleset)); } protected override void Dispose(bool isDisposing) @@ -481,5 +465,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer modSettingChangeTracker?.Dispose(); } + + public class AddItemButton : PurpleTriangleButton + { + } } } diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs index 4bc0b55433..63957caee3 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlaySongSelect.cs @@ -33,14 +33,14 @@ namespace osu.Game.Screens.OnlinePlay [Resolved(typeof(Room), nameof(Room.Playlist))] protected BindableList Playlist { get; private set; } + [CanBeNull] + [Resolved(CanBeNull = true)] + protected IBindable SelectedItem { get; private set; } + protected override UserActivity InitialActivity => new UserActivity.InLobby(room); protected readonly Bindable> FreeMods = new Bindable>(Array.Empty()); - [CanBeNull] - [Resolved(CanBeNull = true)] - private IBindable selectedItem { get; set; } - private readonly FreeModSelectOverlay freeModSelectOverlay; private readonly Room room; @@ -80,8 +80,8 @@ namespace osu.Game.Screens.OnlinePlay // At this point, Mods contains both the required and allowed mods. For selection purposes, it should only contain the required mods. // Similarly, freeMods is currently empty but should only contain the allowed mods. - Mods.Value = selectedItem?.Value?.RequiredMods.Select(m => m.DeepClone()).ToArray() ?? Array.Empty(); - FreeMods.Value = selectedItem?.Value?.AllowedMods.Select(m => m.DeepClone()).ToArray() ?? Array.Empty(); + Mods.Value = SelectedItem?.Value?.RequiredMods.Select(m => m.DeepClone()).ToArray() ?? Array.Empty(); + FreeMods.Value = SelectedItem?.Value?.AllowedMods.Select(m => m.DeepClone()).ToArray() ?? Array.Empty(); Mods.BindValueChanged(onModsChanged); Ruleset.BindValueChanged(onRulesetChanged); From a445dcd2c60a0f5456884f8346de75b37717ea2e Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Dec 2021 02:09:31 +0900 Subject: [PATCH 085/117] Fix incorrect test namespace --- .../{Online => Multiplayer}/TestSceneMultiplayerQueueList.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename osu.Game.Tests/Visual/{Online => Multiplayer}/TestSceneMultiplayerQueueList.cs (98%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs similarity index 98% rename from osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs rename to osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index 19e5624a04..a2b2da0aec 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -20,11 +20,10 @@ using osu.Game.Rulesets; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist; using osu.Game.Tests.Resources; -using osu.Game.Tests.Visual.Multiplayer; using osuTK; using osuTK.Input; -namespace osu.Game.Tests.Visual.Online +namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerQueueList : MultiplayerTestScene { From 612f47bb9ff7eaf565def5970fc24daf80f7007d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 13:45:29 +0900 Subject: [PATCH 086/117] Add the ability to create playlists of 2 weeks ~ 3 months in duration --- .../Playlists/PlaylistsRoomSettingsOverlay.cs | 52 ++++++++++++++----- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs index 8f31422add..6c8ab52d22 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsRoomSettingsOverlay.cs @@ -5,7 +5,9 @@ using System; using System.Collections.Specialized; using System.Linq; using Humanizer; +using Humanizer.Localisation; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -14,6 +16,8 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Screens.OnlinePlay.Match.Components; @@ -69,6 +73,9 @@ namespace osu.Game.Screens.OnlinePlay.Playlists [Resolved(CanBeNull = true)] private IRoomManager manager { get; set; } + [Resolved] + private IAPIProvider api { get; set; } + private readonly Room room; public MatchSettings(Room room) @@ -134,19 +141,6 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Child = DurationField = new DurationDropdown { RelativeSizeAxes = Axes.X, - Items = new[] - { - TimeSpan.FromMinutes(30), - TimeSpan.FromHours(1), - TimeSpan.FromHours(2), - TimeSpan.FromHours(4), - TimeSpan.FromHours(8), - TimeSpan.FromHours(12), - //TimeSpan.FromHours(16), - TimeSpan.FromHours(24), - TimeSpan.FromDays(3), - TimeSpan.FromDays(7) - } } }, new Section("Allowed attempts (across all playlist items)") @@ -303,10 +297,40 @@ namespace osu.Game.Screens.OnlinePlay.Playlists MaxAttempts.BindValueChanged(count => MaxAttemptsField.Text = count.NewValue?.ToString(), true); Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue ?? TimeSpan.FromMinutes(30), true); + api.LocalUser.BindValueChanged(populateDurations, true); + playlist.Items.BindTo(Playlist); Playlist.BindCollectionChanged(onPlaylistChanged, true); } + private void populateDurations(ValueChangedEvent user) + { + DurationField.Items = new[] + { + TimeSpan.FromMinutes(30), + TimeSpan.FromHours(1), + TimeSpan.FromHours(2), + TimeSpan.FromHours(4), + TimeSpan.FromHours(8), + TimeSpan.FromHours(12), + TimeSpan.FromHours(24), + TimeSpan.FromDays(3), + TimeSpan.FromDays(7), + TimeSpan.FromDays(14), + }; + + // TODO: show these in the interface at all times. + if (user.NewValue.IsSupporter) + { + // roughly correct (see https://github.com/Humanizr/Humanizer/blob/18167e56c082449cc4fe805b8429e3127a7b7f93/readme.md?plain=1#L427) + // if we want this to be more accurate we might consider sending an actual end time, not a time span. probably not required though. + const int days_in_month = 31; + + DurationField.AddDropdownItem(TimeSpan.FromDays(days_in_month)); + DurationField.AddDropdownItem(TimeSpan.FromDays(days_in_month * 3)); + } + } + protected override void Update() { base.Update(); @@ -405,7 +429,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Menu.MaxHeight = 100; } - protected override LocalisableString GenerateItemText(TimeSpan item) => item.Humanize(); + protected override LocalisableString GenerateItemText(TimeSpan item) => item.Humanize(maxUnit: TimeUnit.Month); } } } From 9ac8e6c81cfa391b757168f9621f5766a2ea4078 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 13:53:48 +0900 Subject: [PATCH 087/117] Add missing null check before attempting to populate bpm info --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 31 +++++++++++---------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index d292b7114f..6791565828 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -163,7 +163,7 @@ namespace osu.Game.Screens.Select private FillFlowContainer infoLabelContainer; private Container bpmLabelContainer; - private readonly WorkingBeatmap beatmap; + private readonly WorkingBeatmap working; private readonly RulesetInfo ruleset; [Resolved] @@ -171,10 +171,10 @@ namespace osu.Game.Screens.Select private ModSettingChangeTracker settingChangeTracker; - public WedgeInfoText(WorkingBeatmap beatmap, RulesetInfo userRuleset) + public WedgeInfoText(WorkingBeatmap working, RulesetInfo userRuleset) { - this.beatmap = beatmap; - ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset; + this.working = working; + ruleset = userRuleset ?? working.BeatmapInfo.Ruleset; } private CancellationTokenSource cancellationSource; @@ -183,8 +183,8 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader] private void load(OsuColour colours, LocalisationManager localisation, BeatmapDifficultyCache difficultyCache) { - var beatmapInfo = beatmap.BeatmapInfo; - var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); + var beatmapInfo = working.BeatmapInfo; + var metadata = beatmapInfo.Metadata ?? working.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); RelativeSizeAxes = Axes.Both; @@ -353,7 +353,7 @@ namespace osu.Game.Screens.Select private void addInfoLabels() { - if (beatmap.Beatmap?.HitObjects?.Any() != true) + if (working.Beatmap?.HitObjects?.Any() != true) return; infoLabelContainer.Children = new Drawable[] @@ -362,7 +362,7 @@ namespace osu.Game.Screens.Select { Name = "Length", CreateIcon = () => new BeatmapStatisticIcon(BeatmapStatisticsIconType.Length), - Content = beatmap.BeatmapInfo.Length.ToFormattedDuration().ToString(), + Content = working.BeatmapInfo.Length.ToFormattedDuration().ToString(), }), bpmLabelContainer = new Container { @@ -386,12 +386,12 @@ namespace osu.Game.Screens.Select try { // Try to get the beatmap with the user's ruleset - playableBeatmap = beatmap.GetPlayableBeatmap(ruleset, Array.Empty()); + playableBeatmap = working.GetPlayableBeatmap(ruleset, Array.Empty()); } catch (BeatmapInvalidForRulesetException) { // Can't be converted to the user's ruleset, so use the beatmap's own ruleset - playableBeatmap = beatmap.GetPlayableBeatmap(beatmap.BeatmapInfo.Ruleset, Array.Empty()); + playableBeatmap = working.GetPlayableBeatmap(working.BeatmapInfo.Ruleset, Array.Empty()); } return playableBeatmap.GetStatistics().Select(s => new InfoLabel(s)).ToArray(); @@ -406,8 +406,9 @@ namespace osu.Game.Screens.Select private void refreshBPMLabel() { - var b = beatmap.Beatmap; - if (b == null) + var beatmap = working.Beatmap; + + if (beatmap == null || bpmLabelContainer == null) return; // this doesn't consider mods which apply variable rates, yet. @@ -415,9 +416,9 @@ namespace osu.Game.Screens.Select foreach (var mod in mods.Value.OfType()) rate = mod.ApplyToRate(0, rate); - double bpmMax = b.ControlPointInfo.BPMMaximum * rate; - double bpmMin = b.ControlPointInfo.BPMMinimum * rate; - double mostCommonBPM = 60000 / b.GetMostCommonBeatLength() * rate; + double bpmMax = beatmap.ControlPointInfo.BPMMaximum * rate; + double bpmMin = beatmap.ControlPointInfo.BPMMinimum * rate; + double mostCommonBPM = 60000 / beatmap.GetMostCommonBeatLength() * rate; string labelText = Precision.AlmostEquals(bpmMin, bpmMax) ? $"{bpmMin:0}" From 88670c3b014ce53d805dc51fd5426ef8540824bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 14:14:22 +0900 Subject: [PATCH 088/117] Document `OpenSongSelection` and mark null param --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 96a9804067..946c749db3 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -138,7 +138,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer RelativeSizeAxes = Axes.X, Height = 40, Text = "Add item", - Action = () => OpenSongSelection(null) + Action = () => OpenSongSelection() }, }, null, @@ -220,7 +220,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer } }; - internal void OpenSongSelection(PlaylistItem itemToEdit) + /// + /// Opens the song selection screen to add or edit an item. + /// + /// An optional playlist item to edit. If null, a new item will be added instead. + internal void OpenSongSelection([CanBeNull] PlaylistItem itemToEdit = null) { if (!this.IsCurrentScreen()) return; From de0f37b08d60477048a9015d1dcd8bb96e019996 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Dec 2021 14:44:35 +0900 Subject: [PATCH 089/117] Separate editing and adding playlist items --- .../Multiplayer/IMultiplayerRoomServer.cs | 6 ++ .../Online/Multiplayer/MultiplayerClient.cs | 2 + .../Multiplayer/OnlineMultiplayerClient.cs | 8 +++ .../Multiplayer/MultiplayerMatchSongSelect.cs | 9 ++- .../Multiplayer/TestMultiplayerClient.cs | 60 ++++++++++--------- 5 files changed, 54 insertions(+), 31 deletions(-) diff --git a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs index 65467e6ba9..73fda78d00 100644 --- a/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs +++ b/osu.Game/Online/Multiplayer/IMultiplayerRoomServer.cs @@ -83,6 +83,12 @@ namespace osu.Game.Online.Multiplayer /// The item to add. Task AddPlaylistItem(MultiplayerPlaylistItem item); + /// + /// Edits an existing playlist item with new values. + /// + /// The item to edit, containing new properties. Must have an ID. + Task EditPlaylistItem(MultiplayerPlaylistItem item); + /// /// Removes an item from the playlist. /// diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 34dc7ea5ea..55b4def908 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -335,6 +335,8 @@ namespace osu.Game.Online.Multiplayer public abstract Task AddPlaylistItem(MultiplayerPlaylistItem item); + public abstract Task EditPlaylistItem(MultiplayerPlaylistItem item); + public abstract Task RemovePlaylistItem(long playlistItemId); Task IMultiplayerClient.RoomStateChanged(MultiplayerRoomState state) diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index 7314603603..d268d2bf69 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -162,6 +162,14 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.AddPlaylistItem), item); } + public override Task EditPlaylistItem(MultiplayerPlaylistItem item) + { + if (!IsConnected.Value) + return Task.CompletedTask; + + return connection.InvokeAsync(nameof(IMultiplayerServer.EditPlaylistItem), item); + } + public override Task RemovePlaylistItem(long playlistItemId) { if (!IsConnected.Value) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 80a0289ba9..8d3686dd6d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR; using osu.Framework.Allocation; using osu.Framework.Logging; @@ -64,7 +65,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { loadingLayer.Show(); - client.AddPlaylistItem(new MultiplayerPlaylistItem + var multiplayerItem = new MultiplayerPlaylistItem { ID = itemToEdit?.ID ?? 0, BeatmapID = item.BeatmapID, @@ -72,7 +73,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer RulesetID = item.RulesetID, RequiredMods = item.RequiredMods.Select(m => new APIMod(m)).ToArray(), AllowedMods = item.AllowedMods.Select(m => new APIMod(m)).ToArray() - }).ContinueWith(t => + }; + + Task task = itemToEdit != null ? client.EditPlaylistItem(multiplayerItem) : client.AddPlaylistItem(multiplayerItem); + + task.ContinueWith(t => { Schedule(() => { diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 1516d0e473..d22f0415e6 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -309,49 +309,51 @@ namespace osu.Game.Tests.Visual.Multiplayer Debug.Assert(APIRoom != null); Debug.Assert(currentItem != null); - bool isNewAddition = item.ID == 0; - if (Room.Settings.QueueMode == QueueMode.HostOnly && Room.Host?.UserID != LocalUser?.UserID) throw new InvalidOperationException("Local user is not the room host."); item.OwnerID = userId; - if (isNewAddition) - { - await addItem(item).ConfigureAwait(false); - await updateCurrentItem(Room).ConfigureAwait(false); - } - else - { - var existingItem = serverSidePlaylist.SingleOrDefault(i => i.ID == item.ID); - - if (existingItem == null) - throw new InvalidOperationException("Attempted to change an item that doesn't exist."); - - if (existingItem.OwnerID != userId && Room.Host?.UserID != LocalUser?.UserID) - throw new InvalidOperationException("Attempted to change an item which is not owned by the user."); - - if (existingItem.Expired) - throw new InvalidOperationException("Attempted to change an item which has already been played."); - - // Ensure the playlist order doesn't change. - item.PlaylistOrder = existingItem.PlaylistOrder; - - serverSidePlaylist[serverSidePlaylist.IndexOf(existingItem)] = item; - await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); - } + await addItem(item).ConfigureAwait(false); + await updateCurrentItem(Room).ConfigureAwait(false); } public override Task AddPlaylistItem(MultiplayerPlaylistItem item) => AddUserPlaylistItem(api.LocalUser.Value.OnlineID, item); + public async Task EditUserPlaylistItem(int userId, MultiplayerPlaylistItem item) + { + Debug.Assert(Room != null); + Debug.Assert(APIRoom != null); + Debug.Assert(currentItem != null); + + item.OwnerID = userId; + + var existingItem = serverSidePlaylist.SingleOrDefault(i => i.ID == item.ID); + + if (existingItem == null) + throw new InvalidOperationException("Attempted to change an item that doesn't exist."); + + if (existingItem.OwnerID != userId && Room.Host?.UserID != LocalUser?.UserID) + throw new InvalidOperationException("Attempted to change an item which is not owned by the user."); + + if (existingItem.Expired) + throw new InvalidOperationException("Attempted to change an item which has already been played."); + + // Ensure the playlist order doesn't change. + item.PlaylistOrder = existingItem.PlaylistOrder; + + serverSidePlaylist[serverSidePlaylist.IndexOf(existingItem)] = item; + + await ((IMultiplayerClient)this).PlaylistItemChanged(item).ConfigureAwait(false); + } + + public override Task EditPlaylistItem(MultiplayerPlaylistItem item) => EditUserPlaylistItem(api.LocalUser.Value.OnlineID, item); + public async Task RemoveUserPlaylistItem(int userId, long playlistItemId) { Debug.Assert(Room != null); Debug.Assert(APIRoom != null); - if (Room.Settings.QueueMode == QueueMode.HostOnly) - throw new InvalidOperationException("Items cannot be removed in host-only mode."); - var item = serverSidePlaylist.Find(i => i.ID == playlistItemId); if (item == null) From 261847bbec333933dd4f848976f78ba3b58fdd35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 15:29:31 +0900 Subject: [PATCH 090/117] Avoid touching `ScoreInfo.User` directly --- osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index b7b7407428..2f9652d354 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -159,8 +159,8 @@ namespace osu.Game.Tests.Visual.Ranking var firstScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); var secondScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); - firstScore.User.Username = "A"; - secondScore.User.Username = "B"; + firstScore.UserString = "A"; + secondScore.UserString = "B"; createListStep(() => new ScorePanelList()); From bf1418bafc6a3d61ad03ccaa6c00f4f9e125b4d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 15:28:41 +0900 Subject: [PATCH 091/117] Use `OnlineID` instead of legacy IDs for equality and lookups --- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 2 +- .../Visual/Playlists/TestScenePlaylistsResultsScreen.cs | 9 +++++---- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 4 ++-- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- osu.Game/OsuGame.cs | 4 ++-- osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs | 2 +- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 4 ++-- osu.Game/Scoring/ScoreManager.cs | 2 +- osu.Game/Scoring/ScoreModelManager.cs | 2 +- .../OnlinePlay/Playlists/PlaylistsResultsScreen.cs | 4 ++-- osu.Game/Screens/Play/SpectatorPlayer.cs | 2 +- osu.Game/Screens/Ranking/ScorePanelList.cs | 2 +- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 2 +- 14 files changed, 22 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 0dee0f89ea..4a01117031 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Scores.IO Assert.AreEqual(toImport.Combo, imported.Combo); Assert.AreEqual(toImport.User.Username, imported.User.Username); Assert.AreEqual(toImport.Date, imported.Date); - Assert.AreEqual(toImport.OnlineScoreID, imported.OnlineScoreID); + Assert.AreEqual(toImport.OnlineID, imported.OnlineID); } finally { diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 4284bc6358..38dd507e00 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -30,6 +30,7 @@ namespace osu.Game.Tests.Visual.Playlists private const int scores_per_result = 10; private TestResultsScreen resultsScreen; + private int currentScoreId; private bool requestComplete; private int totalCount; @@ -37,7 +38,7 @@ namespace osu.Game.Tests.Visual.Playlists [SetUp] public void Setup() => Schedule(() => { - currentScoreId = 0; + currentScoreId = 1; requestComplete = false; totalCount = 0; bindHandler(); @@ -56,7 +57,7 @@ namespace osu.Game.Tests.Visual.Playlists createResults(() => userScore); - AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineScoreID == userScore.OnlineScoreID).State == PanelState.Expanded); + AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineScoreID).State == PanelState.Expanded); } [Test] @@ -81,7 +82,7 @@ namespace osu.Game.Tests.Visual.Playlists createResults(() => userScore); AddAssert("more than 1 panel displayed", () => this.ChildrenOfType().Count() > 1); - AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineScoreID == userScore.OnlineScoreID).State == PanelState.Expanded); + AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineScoreID).State == PanelState.Expanded); } [Test] @@ -230,7 +231,7 @@ namespace osu.Game.Tests.Visual.Playlists { var multiplayerUserScore = new MultiplayerScore { - ID = (int)(userScore.OnlineScoreID ?? currentScoreId++), + ID = (int)(userScore.OnlineID > 0 ? userScore.OnlineID : currentScoreId++), Accuracy = userScore.Accuracy, EndedAt = userScore.Date, Passed = userScore.Passed, diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 9f0f4a6b8b..d22eff9e75 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -163,7 +163,7 @@ namespace osu.Game.Tests.Visual.UserInterface }); AddUntilStep("wait for fetch", () => leaderboard.Scores != null); - AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scoreBeingDeleted.OnlineScoreID)); + AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != scoreBeingDeleted.OnlineID)); } [Test] @@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("delete top score", () => scoreManager.Delete(importedScores[0])); AddUntilStep("wait for fetch", () => leaderboard.Scores != null); - AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != importedScores[0].OnlineScoreID)); + AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineID != importedScores[0].OnlineID)); } } } diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 644c2e2a99..14eec8b388 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -111,7 +111,7 @@ namespace osu.Game.Online.Leaderboards background = new Box { RelativeSizeAxes = Axes.Both, - Colour = user.Id == api.LocalUser.Value.Id && allowHighlight ? colour.Green : Color4.Black, + Colour = user.OnlineID == api.LocalUser.Value.Id && allowHighlight ? colour.Green : Color4.Black, Alpha = background_alpha, }, }, diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 047c3b4225..565ee7e71d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -487,8 +487,8 @@ namespace osu.Game // to ensure all the required data for presenting a replay are present. ScoreInfo databasedScoreInfo = null; - if (score.OnlineScoreID != null) - databasedScoreInfo = ScoreManager.Query(s => s.OnlineScoreID == score.OnlineScoreID); + if (score.OnlineID > 0) + databasedScoreInfo = ScoreManager.Query(s => s.OnlineID == score.OnlineID); databasedScoreInfo ??= ScoreManager.Query(s => s.Hash == score.Hash); diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index 2fcdc9402d..695661d5c9 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -94,7 +94,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores topScoresContainer.Add(new DrawableTopScore(topScore)); - if (userScoreInfo != null && userScoreInfo.OnlineScoreID != topScore.OnlineScoreID) + if (userScoreInfo != null && userScoreInfo.OnlineID != topScore.OnlineID) topScoresContainer.Add(new DrawableTopScore(userScoreInfo, userScore.Position)); }), TaskContinuationOptions.OnlyOnRanToCompletion); }); diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index f943422389..b46c1a27ed 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -84,7 +84,7 @@ namespace osu.Game.Scoring.Legacy else if (version >= 20121008) scoreInfo.OnlineScoreID = sr.ReadInt32(); - if (scoreInfo.OnlineScoreID <= 0) + if (scoreInfo.OnlineID <= 0) scoreInfo.OnlineScoreID = null; if (compressedReplay?.Length > 0) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 5dc88d7644..ba20ec030a 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -237,8 +237,8 @@ namespace osu.Game.Scoring if (ID != 0 && other.ID != 0) return ID == other.ID; - if (OnlineScoreID.HasValue && other.OnlineScoreID.HasValue) - return OnlineScoreID == other.OnlineScoreID; + if (OnlineID > 0) + return OnlineID == other.OnlineID; if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash)) return Hash == other.Hash; diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index e9cd44ae83..6de6b57066 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -71,7 +71,7 @@ namespace osu.Game.Scoring return scores.Select((score, index) => (score, totalScore: totalScores[index])) .OrderByDescending(g => g.totalScore) - .ThenBy(g => g.score.OnlineScoreID) + .ThenBy(g => g.score.OnlineID) .Select(g => g.score) .ToArray(); } diff --git a/osu.Game/Scoring/ScoreModelManager.cs b/osu.Game/Scoring/ScoreModelManager.cs index 2cbd3aded7..44f0fe4fdf 100644 --- a/osu.Game/Scoring/ScoreModelManager.cs +++ b/osu.Game/Scoring/ScoreModelManager.cs @@ -66,6 +66,6 @@ namespace osu.Game.Scoring protected override bool CheckLocalAvailability(ScoreInfo model, IQueryable items) => base.CheckLocalAvailability(model, items) - || (model.OnlineScoreID != null && items.Any(i => i.OnlineScoreID == model.OnlineScoreID)); + || (model.OnlineID > 0 && items.Any(i => i.OnlineID == model.OnlineID)); } } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs index aed3635cbc..67727ef784 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs @@ -186,12 +186,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists Schedule(() => { // Prefer selecting the local user's score, or otherwise default to the first visible score. - SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.Id == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); + SelectedScore.Value = scoreInfos.FirstOrDefault(s => s.User.OnlineID == api.LocalUser.Value.Id) ?? scoreInfos.FirstOrDefault(); }); } // Invoke callback to add the scores. Exclude the user's current score which was added previously. - callback.Invoke(scoreInfos.Where(s => s.OnlineScoreID != Score?.OnlineScoreID)); + callback.Invoke(scoreInfos.Where(s => s.OnlineID != Score?.OnlineID)); hideLoadingSpinners(pivot); })); diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index f6a89e7fa9..d42643c416 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -54,7 +54,7 @@ namespace osu.Game.Screens.Play private void userSentFrames(int userId, FrameDataBundle bundle) { - if (userId != score.ScoreInfo.User.Id) + if (userId != score.ScoreInfo.User.OnlineID) return; if (!LoadedBeatmapSuccessfully) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 22be91b974..f3de48dcf0 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -341,7 +341,7 @@ namespace osu.Game.Screens.Ranking private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() .OrderByDescending(GetLayoutPosition) - .ThenBy(s => s.Panel.Score.OnlineScoreID); + .ThenBy(s => s.Panel.Score.OnlineID); } private class Scroll : OsuScrollContainer diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 929bda6508..afebc728b4 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -31,7 +31,7 @@ namespace osu.Game.Screens.Ranking return null; getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset); - getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets, Beatmap.Value.BeatmapInfo))); + getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineID != Score.OnlineID).Select(s => s.CreateScoreInfo(rulesets, Beatmap.Value.BeatmapInfo))); return getScoreRequest; } From 7ac63485ef03d40ae987ae27eb7d25832ed3d97d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 15:31:35 +0900 Subject: [PATCH 092/117] Add setter for `ScoreInfo.OnlineID` --- osu.Game/Scoring/ScoreInfo.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index ba20ec030a..d6b7b2712b 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -248,7 +248,12 @@ namespace osu.Game.Scoring #region Implementation of IHasOnlineID - public long OnlineID => OnlineScoreID ?? -1; + [NotMapped] + public long OnlineID + { + get => OnlineScoreID ?? -1; + set => OnlineScoreID = value; + } #endregion From dbb08f7d463aca7cb0d6e8206766f4aaf53821c2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 15:37:12 +0900 Subject: [PATCH 093/117] Use `OnlineID` for set operations --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 2 +- osu.Game.Tests/Database/BeatmapImporterTests.cs | 2 +- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 6 +++--- .../Visual/Multiplayer/TestSceneMultiplayerResults.cs | 2 +- .../Multiplayer/TestSceneMultiplayerTeamResults.cs | 2 +- .../Visual/Navigation/TestScenePresentScore.cs | 2 +- .../Playlists/TestScenePlaylistsResultsScreen.cs | 10 +++++----- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 +- osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs | 2 +- osu.Game/Online/Rooms/MultiplayerScore.cs | 2 +- osu.Game/Online/ScoreDownloadTracker.cs | 2 +- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 7 ++----- osu.Game/Screens/Play/Player.cs | 6 +++--- osu.Game/Screens/Play/SubmittingPlayer.cs | 2 +- 14 files changed, 23 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 6e2b9d20a8..6d0d5702e9 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -1022,7 +1022,7 @@ namespace osu.Game.Tests.Beatmaps.IO { return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo { - OnlineScoreID = 2, + OnlineID = 2, BeatmapInfo = beatmapInfo, BeatmapInfoID = beatmapInfo.ID }, new ImportScoreTest.TestArchiveReader()); diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index a6edd6cb5f..e47e24021f 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -809,7 +809,7 @@ namespace osu.Game.Tests.Database // TODO: reimplement when we have score support in realm. // return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo // { - // OnlineScoreID = 2, + // OnlineID = 2, // Beatmap = beatmap, // BeatmapInfoID = beatmap.ID // }, new ImportScoreTest.TestArchiveReader()); diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 4a01117031..bbc92b7817 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tests.Scores.IO Combo = 250, User = new APIUser { Username = "Test user" }, Date = DateTimeOffset.Now, - OnlineScoreID = 12345, + OnlineID = 12345, }; var imported = await LoadScoreIntoOsu(osu, toImport); @@ -163,12 +163,12 @@ namespace osu.Game.Tests.Scores.IO { var osu = LoadOsuIntoHost(host, true); - await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2 }, new TestArchiveReader()); + await LoadScoreIntoOsu(osu, new ScoreInfo { OnlineID = 2 }, new TestArchiveReader()); var scoreManager = osu.Dependencies.Get(); // Note: A new score reference is used here since the import process mutates the original object to set an ID - Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineScoreID = 2 })); + Assert.That(scoreManager.IsAvailableLocally(new ScoreInfo { OnlineID = 2 })); } finally { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs index 80f807e7d3..744a2d187d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.Multiplayer BeatmapInfo = beatmapInfo, User = new APIUser { Username = "Test user" }, Date = DateTimeOffset.Now, - OnlineScoreID = 12345, + OnlineID = 12345, Ruleset = rulesetInfo, }; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs index da1fa226e1..99b6edc3b6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Multiplayer BeatmapInfo = beatmapInfo, User = new APIUser { Username = "Test user" }, Date = DateTimeOffset.Now, - OnlineScoreID = 12345, + OnlineID = 12345, Ruleset = rulesetInfo, }; diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index c9dec25ad3..1653247570 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -128,7 +128,7 @@ namespace osu.Game.Tests.Visual.Navigation imported = Game.ScoreManager.Import(new ScoreInfo { Hash = Guid.NewGuid().ToString(), - OnlineScoreID = i, + OnlineID = i, BeatmapInfo = beatmap.Beatmaps.First(), Ruleset = ruleset ?? new OsuRuleset().RulesetInfo }).Result.Value; diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 38dd507e00..4ac3b7c733 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -51,13 +51,13 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { - userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ }; + userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineID = currentScoreId++ }; bindHandler(userScore: userScore); }); createResults(() => userScore); - AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineScoreID).State == PanelState.Expanded); + AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineID).State == PanelState.Expanded); } [Test] @@ -75,14 +75,14 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { - userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ }; + userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineID = currentScoreId++ }; bindHandler(true, userScore); }); createResults(() => userScore); AddAssert("more than 1 panel displayed", () => this.ChildrenOfType().Count() > 1); - AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineScoreID).State == PanelState.Expanded); + AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineID).State == PanelState.Expanded); } [Test] @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { - userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineScoreID = currentScoreId++ }; + userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineID = currentScoreId++ }; bindHandler(userScore: userScore); }); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index d22eff9e75..2363bbbfcf 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.UserInterface { var score = new ScoreInfo { - OnlineScoreID = i, + OnlineID = i, BeatmapInfo = beatmapInfo, BeatmapInfoID = beatmapInfo.ID, Accuracy = RNG.NextDouble(), diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs index 467d5a9f23..057e98c421 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs @@ -101,7 +101,7 @@ namespace osu.Game.Online.API.Requests.Responses BeatmapInfo = beatmap, User = User, Accuracy = Accuracy, - OnlineScoreID = OnlineID, + OnlineID = OnlineID, Date = Date, PP = PP, RulesetID = RulesetID, diff --git a/osu.Game/Online/Rooms/MultiplayerScore.cs b/osu.Game/Online/Rooms/MultiplayerScore.cs index 7bc3377ad9..05c9a1b6cf 100644 --- a/osu.Game/Online/Rooms/MultiplayerScore.cs +++ b/osu.Game/Online/Rooms/MultiplayerScore.cs @@ -69,7 +69,7 @@ namespace osu.Game.Online.Rooms var scoreInfo = new ScoreInfo { - OnlineScoreID = ID, + OnlineID = ID, TotalScore = TotalScore, MaxCombo = MaxCombo, BeatmapInfo = beatmap, diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index e09cc7c9cd..6320b7ff97 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -35,7 +35,7 @@ namespace osu.Game.Online var scoreInfo = new ScoreInfo { ID = TrackedItem.ID, - OnlineScoreID = TrackedItem.OnlineScoreID + OnlineID = TrackedItem.OnlineID }; if (Manager.IsAvailableLocally(scoreInfo)) diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index b46c1a27ed..fefee370b9 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -80,12 +80,9 @@ namespace osu.Game.Scoring.Legacy byte[] compressedReplay = sr.ReadByteArray(); if (version >= 20140721) - scoreInfo.OnlineScoreID = sr.ReadInt64(); + scoreInfo.OnlineID = sr.ReadInt64(); else if (version >= 20121008) - scoreInfo.OnlineScoreID = sr.ReadInt32(); - - if (scoreInfo.OnlineID <= 0) - scoreInfo.OnlineScoreID = null; + scoreInfo.OnlineID = sr.ReadInt32(); if (compressedReplay?.Length > 0) { diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a0e9428cff..521cf7d1e9 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1031,13 +1031,13 @@ namespace osu.Game.Screens.Play // // Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint // conflicts across various systems (ie. solo and multiplayer). - long? onlineScoreId = score.ScoreInfo.OnlineScoreID; - score.ScoreInfo.OnlineScoreID = null; + long onlineScoreId = score.ScoreInfo.OnlineID; + score.ScoreInfo.OnlineID = -1; await scoreManager.Import(score.ScoreInfo, replayReader).ConfigureAwait(false); // ... And restore the online ID for other processes to handle correctly (e.g. de-duplication for the results screen). - score.ScoreInfo.OnlineScoreID = onlineScoreId; + score.ScoreInfo.OnlineID = onlineScoreId; } /// diff --git a/osu.Game/Screens/Play/SubmittingPlayer.cs b/osu.Game/Screens/Play/SubmittingPlayer.cs index c07cfa9c4d..c613167908 100644 --- a/osu.Game/Screens/Play/SubmittingPlayer.cs +++ b/osu.Game/Screens/Play/SubmittingPlayer.cs @@ -156,7 +156,7 @@ namespace osu.Game.Screens.Play request.Success += s => { - score.ScoreInfo.OnlineScoreID = s.ID; + score.ScoreInfo.OnlineID = s.ID; score.ScoreInfo.Position = s.Position; scoreSubmissionSource.SetResult(true); From bff02bedbfc28a94cab1f87a8e423c2fd653c29f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 16:01:41 +0900 Subject: [PATCH 094/117] Rename `APIScoreInfo` to `APIScore` --- .../Gameplay/TestSceneReplayDownloadButton.cs | 2 +- .../Visual/Online/TestSceneScoresContainer.cs | 20 +++++++++---------- .../Online/TestSceneUserProfileScores.cs | 8 ++++---- .../API/Requests/GetUserScoresRequest.cs | 2 +- .../{APIScoreInfo.cs => APIScore.cs} | 2 +- .../Responses/APIScoreWithPosition.cs | 2 +- .../Requests/Responses/APIScoresCollection.cs | 2 +- osu.Game/Online/Solo/SubmittableScore.cs | 2 +- .../Sections/Ranks/DrawableProfileScore.cs | 4 ++-- .../Ranks/DrawableProfileWeightedScore.cs | 2 +- .../Sections/Ranks/PaginatedScoreContainer.cs | 8 ++++---- 11 files changed, 27 insertions(+), 27 deletions(-) rename osu.Game/Online/API/Requests/Responses/{APIScoreInfo.cs => APIScore.cs} (99%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index f47fae33ca..42c4f89e9d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -164,7 +164,7 @@ namespace osu.Game.Tests.Visual.Gameplay private ScoreInfo getScoreInfo(bool replayAvailable) { - return new APIScoreInfo + return new APIScore { OnlineID = 2553163309, RulesetID = 0, diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 50969aad9b..be2db9a8a0 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -47,9 +47,9 @@ namespace osu.Game.Tests.Visual.Online var allScores = new APIScoresCollection { - Scores = new List + Scores = new List { - new APIScoreInfo + new APIScore { User = new APIUser { @@ -74,7 +74,7 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 1234567890, Accuracy = 1, }, - new APIScoreInfo + new APIScore { User = new APIUser { @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 1234789, Accuracy = 0.9997, }, - new APIScoreInfo + new APIScore { User = new APIUser { @@ -121,7 +121,7 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 12345678, Accuracy = 0.9854, }, - new APIScoreInfo + new APIScore { User = new APIUser { @@ -143,7 +143,7 @@ namespace osu.Game.Tests.Visual.Online TotalScore = 1234567, Accuracy = 0.8765, }, - new APIScoreInfo + new APIScore { User = new APIUser { @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Visual.Online var myBestScore = new APIScoreWithPosition { - Score = new APIScoreInfo + Score = new APIScore { User = new APIUser { @@ -189,7 +189,7 @@ namespace osu.Game.Tests.Visual.Online var myBestScoreWithNullPosition = new APIScoreWithPosition { - Score = new APIScoreInfo + Score = new APIScore { User = new APIUser { @@ -212,9 +212,9 @@ namespace osu.Game.Tests.Visual.Online var oneScore = new APIScoresCollection { - Scores = new List + Scores = new List { - new APIScoreInfo + new APIScore { User = new APIUser { diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs index 9c2cc13416..7dfdca8276 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Online { public TestSceneUserProfileScores() { - var firstScore = new APIScoreInfo + var firstScore = new APIScore { PP = 1047.21, Rank = ScoreRank.SH, @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual.Online Accuracy = 0.9813 }; - var secondScore = new APIScoreInfo + var secondScore = new APIScore { PP = 134.32, Rank = ScoreRank.A, @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online Accuracy = 0.998546 }; - var thirdScore = new APIScoreInfo + var thirdScore = new APIScore { PP = 96.83, Rank = ScoreRank.S, @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Online Accuracy = 0.9726 }; - var noPPScore = new APIScoreInfo + var noPPScore = new APIScore { Rank = ScoreRank.B, Beatmap = new APIBeatmap diff --git a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs index e13ac8e539..653abf7427 100644 --- a/osu.Game/Online/API/Requests/GetUserScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserScoresRequest.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests { - public class GetUserScoresRequest : PaginatedAPIRequest> + public class GetUserScoresRequest : PaginatedAPIRequest> { private readonly long userId; private readonly ScoreType type; diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScore.cs similarity index 99% rename from osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs rename to osu.Game/Online/API/Requests/Responses/APIScore.cs index 057e98c421..bd99a05a88 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScore.cs @@ -16,7 +16,7 @@ using osu.Game.Scoring.Legacy; namespace osu.Game.Online.API.Requests.Responses { - public class APIScoreInfo : IScoreInfo + public class APIScore : IScoreInfo { [JsonProperty(@"score")] public long TotalScore { get; set; } diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs index 48b7134901..d3c9ba0c7e 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreWithPosition.cs @@ -14,7 +14,7 @@ namespace osu.Game.Online.API.Requests.Responses public int? Position; [JsonProperty(@"score")] - public APIScoreInfo Score; + public APIScore Score; public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) { diff --git a/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs b/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs index 5304664bf8..283ebf2411 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoresCollection.cs @@ -9,7 +9,7 @@ namespace osu.Game.Online.API.Requests.Responses public class APIScoresCollection { [JsonProperty(@"scores")] - public List Scores; + public List Scores; [JsonProperty(@"userScore")] public APIScoreWithPosition UserScore; diff --git a/osu.Game/Online/Solo/SubmittableScore.cs b/osu.Game/Online/Solo/SubmittableScore.cs index 373c302844..5ca5ad9619 100644 --- a/osu.Game/Online/Solo/SubmittableScore.cs +++ b/osu.Game/Online/Solo/SubmittableScore.cs @@ -16,7 +16,7 @@ namespace osu.Game.Online.Solo { /// /// A class specifically for sending scores to the API during score submission. - /// This is used instead of due to marginally different serialisation naming requirements. + /// This is used instead of due to marginally different serialisation naming requirements. /// [Serializable] public class SubmittableScore diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index fb464e1b41..562be0403e 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private const float performance_background_shear = 0.45f; - protected readonly APIScoreInfo Score; + protected readonly APIScore Score; [Resolved] private OsuColour colours { get; set; } @@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks [Resolved] private OverlayColourProvider colourProvider { get; set; } - public DrawableProfileScore(APIScoreInfo score) + public DrawableProfileScore(APIScore score) { Score = score; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs index e653be5cfa..78ae0a5634 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { private readonly double weight; - public DrawableProfileWeightedScore(APIScoreInfo score, double weight) + public DrawableProfileWeightedScore(APIScore score, double weight) : base(score) { this.weight = weight; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs index c3f10587a9..5532e35cc5 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -15,7 +15,7 @@ using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; namespace osu.Game.Overlays.Profile.Sections.Ranks { - public class PaginatedScoreContainer : PaginatedProfileSubsection + public class PaginatedScoreContainer : PaginatedProfileSubsection { private readonly ScoreType type; @@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks } } - protected override void OnItemsReceived(List items) + protected override void OnItemsReceived(List items) { if (VisiblePages == 0) drawableItemIndex = 0; @@ -59,12 +59,12 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks base.OnItemsReceived(items); } - protected override APIRequest> CreateRequest() => + protected override APIRequest> CreateRequest() => new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); private int drawableItemIndex; - protected override Drawable CreateDrawableItem(APIScoreInfo model) + protected override Drawable CreateDrawableItem(APIScore model) { switch (type) { From c6d0d6451d463767f60400143c6e195ed1c8f363 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 11 Nov 2021 23:18:31 +0900 Subject: [PATCH 095/117] Change `IScoreInfo.User` to an interface type --- osu.Game/Online/API/Requests/Responses/APIScore.cs | 6 ++++++ osu.Game/Scoring/IScoreInfo.cs | 4 ++-- osu.Game/Scoring/ScoreInfo.cs | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIScore.cs b/osu.Game/Online/API/Requests/Responses/APIScore.cs index bd99a05a88..4f795bee6c 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScore.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScore.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osu.Game.Scoring.Legacy; +using osu.Game.Users; namespace osu.Game.Online.API.Requests.Responses { @@ -150,6 +151,11 @@ namespace osu.Game.Online.API.Requests.Responses public IRulesetInfo Ruleset => new RulesetInfo { OnlineID = RulesetID }; IEnumerable IHasNamedFiles.Files => throw new NotImplementedException(); + #region Implementation of IScoreInfo + IBeatmapInfo IScoreInfo.Beatmap => Beatmap; + IUser IScoreInfo.User => User; + + #endregion } } diff --git a/osu.Game/Scoring/IScoreInfo.cs b/osu.Game/Scoring/IScoreInfo.cs index 8b5b228632..b4ad183cd3 100644 --- a/osu.Game/Scoring/IScoreInfo.cs +++ b/osu.Game/Scoring/IScoreInfo.cs @@ -4,14 +4,14 @@ using System; using osu.Game.Beatmaps; using osu.Game.Database; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; +using osu.Game.Users; namespace osu.Game.Scoring { public interface IScoreInfo : IHasOnlineID, IHasNamedFiles { - APIUser User { get; } + IUser User { get; } long TotalScore { get; } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index d6b7b2712b..7729e01389 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -14,6 +14,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; +using osu.Game.Users; using osu.Game.Utils; namespace osu.Game.Scoring @@ -261,6 +262,7 @@ namespace osu.Game.Scoring IBeatmapInfo IScoreInfo.Beatmap => BeatmapInfo; IRulesetInfo IScoreInfo.Ruleset => Ruleset; + IUser IScoreInfo.User => User; bool IScoreInfo.HasReplay => Files.Any(); #endregion From 970a9ae0748d8d6f99fd6035c5f96673702665a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 17:22:24 +0900 Subject: [PATCH 096/117] Add update thread asserts to `RoomManager` methods --- osu.Game/Screens/OnlinePlay/Components/RoomManager.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index 02565c6ebe..6979b5bc30 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -7,11 +7,13 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Development; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; +using osu.Game.Utils; namespace osu.Game.Screens.OnlinePlay.Components { @@ -107,6 +109,7 @@ namespace osu.Game.Screens.OnlinePlay.Components public void AddOrUpdateRoom(Room room) { + Debug.Assert(ThreadSafety.IsUpdateThread); Debug.Assert(room.RoomID.Value != null); if (ignoredRooms.Contains(room.RoomID.Value.Value)) @@ -136,12 +139,16 @@ namespace osu.Game.Screens.OnlinePlay.Components public void RemoveRoom(Room room) { + Debug.Assert(ThreadSafety.IsUpdateThread); + rooms.Remove(room); notifyRoomsUpdated(); } public void ClearRooms() { + Debug.Assert(ThreadSafety.IsUpdateThread); + rooms.Clear(); notifyRoomsUpdated(); } From 73227c084e380d893bad1bb9a1d6e0fefcecccb2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 17:42:40 +0900 Subject: [PATCH 097/117] 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 0c922c09ac..5cf59decec 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 adb25f46fe..6f01cc65fe 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index db5d9af865..fdb63a19d3 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -60,7 +60,7 @@ - + @@ -83,7 +83,7 @@ - + From e98060ac37571471fa1477bd0038b64d219525db Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Dec 2021 17:55:18 +0900 Subject: [PATCH 098/117] Remove unused using --- osu.Game/Screens/OnlinePlay/Components/RoomManager.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index 6979b5bc30..238aa4059d 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -13,7 +13,6 @@ using osu.Framework.Logging; using osu.Game.Online.API; using osu.Game.Online.Rooms; using osu.Game.Rulesets; -using osu.Game.Utils; namespace osu.Game.Screens.OnlinePlay.Components { From 5f6e887be71bc8ebd5894f07669958d3e4445dd0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 18:11:52 +0900 Subject: [PATCH 099/117] Remove `OnlineID` comparison from `ScoreInfo.Equals` This matches the implementation we have for `BeatmapInfo` and `BeatmapSetInfo`. All comparisons of `OnlineID` should be done directly using them (ie. how it's done in `ScoreModelDownloader`). --- osu.Game.Tests/Scores/IO/TestScoreEquality.cs | 18 ------------------ osu.Game/Scoring/ScoreInfo.cs | 12 +++--------- 2 files changed, 3 insertions(+), 27 deletions(-) diff --git a/osu.Game.Tests/Scores/IO/TestScoreEquality.cs b/osu.Game.Tests/Scores/IO/TestScoreEquality.cs index d1374eb6e5..42fcb3acab 100644 --- a/osu.Game.Tests/Scores/IO/TestScoreEquality.cs +++ b/osu.Game.Tests/Scores/IO/TestScoreEquality.cs @@ -44,24 +44,6 @@ namespace osu.Game.Tests.Scores.IO Assert.That(score1, Is.EqualTo(score2)); } - [Test] - public void TestNonMatchingByHash() - { - ScoreInfo score1 = new ScoreInfo { Hash = "a" }; - ScoreInfo score2 = new ScoreInfo { Hash = "b" }; - - Assert.That(score1, Is.Not.EqualTo(score2)); - } - - [Test] - public void TestMatchingByHash() - { - ScoreInfo score1 = new ScoreInfo { Hash = "a" }; - ScoreInfo score2 = new ScoreInfo { Hash = "a" }; - - Assert.That(score1, Is.EqualTo(score2)); - } - [Test] public void TestNonMatchingByNull() { diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 7729e01389..7c2d882f91 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -232,19 +232,13 @@ namespace osu.Game.Scoring public bool Equals(ScoreInfo other) { - if (other == null) - return false; + if (ReferenceEquals(this, other)) return true; + if (other == null) return false; if (ID != 0 && other.ID != 0) return ID == other.ID; - if (OnlineID > 0) - return OnlineID == other.OnlineID; - - if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash)) - return Hash == other.Hash; - - return ReferenceEquals(this, other); + return false; } #region Implementation of IHasOnlineID From c9f6c5c673aaff4ae53d4cce9ffb2832e49c31b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 18:32:06 +0900 Subject: [PATCH 100/117] Add `MatchesOnlineID` implementation for `IScoreInfo` --- osu.Game/Extensions/ModelExtensions.cs | 8 ++++++++ osu.Game/Online/ScoreDownloadTracker.cs | 3 ++- osu.Game/Scoring/ScoreModelDownloader.cs | 3 ++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Extensions/ModelExtensions.cs b/osu.Game/Extensions/ModelExtensions.cs index 2274da0fd4..f178a5c97b 100644 --- a/osu.Game/Extensions/ModelExtensions.cs +++ b/osu.Game/Extensions/ModelExtensions.cs @@ -104,6 +104,14 @@ namespace osu.Game.Extensions /// Whether online IDs match. If either instance is missing an online ID, this will return false. public static bool MatchesOnlineID(this APIUser? instance, APIUser? other) => matchesOnlineID(instance, other); + /// + /// Check whether the online ID of two s match. + /// + /// The instance to compare. + /// The other instance to compare against. + /// Whether online IDs match. If either instance is missing an online ID, this will return false. + public static bool MatchesOnlineID(this IScoreInfo? instance, IScoreInfo? other) => matchesOnlineID(instance, other); + private static bool matchesOnlineID(this IHasOnlineID? instance, IHasOnlineID? other) { if (instance == null || other == null) diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index 6320b7ff97..68932cc388 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Scoring; @@ -113,7 +114,7 @@ namespace osu.Game.Online UpdateState(DownloadState.NotDownloaded); }); - private bool checkEquality(IScoreInfo x, IScoreInfo y) => x.OnlineID == y.OnlineID; + private bool checkEquality(IScoreInfo x, IScoreInfo y) => x.MatchesOnlineID(y); #region Disposal diff --git a/osu.Game/Scoring/ScoreModelDownloader.cs b/osu.Game/Scoring/ScoreModelDownloader.cs index 038a4bc351..514b7a57de 100644 --- a/osu.Game/Scoring/ScoreModelDownloader.cs +++ b/osu.Game/Scoring/ScoreModelDownloader.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Database; +using osu.Game.Extensions; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -17,6 +18,6 @@ namespace osu.Game.Scoring protected override ArchiveDownloadRequest CreateDownloadRequest(IScoreInfo score, bool minimiseDownload) => new DownloadReplayRequest(score); public override ArchiveDownloadRequest GetExistingDownload(IScoreInfo model) - => CurrentDownloads.Find(r => r.Model.OnlineID == model.OnlineID); + => CurrentDownloads.Find(r => r.Model.MatchesOnlineID(model)); } } From f7c5a3f506815eee95fc36e62fb88e411319f8ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 18:32:32 +0900 Subject: [PATCH 101/117] Use similar method of consuming `OnlineID` as done in beatmap classes --- osu.Game/Database/OsuDbContext.cs | 2 +- osu.Game/Scoring/ScoreInfo.cs | 16 +++++++++------- osu.Game/Screens/Play/Player.cs | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 13d362e0be..7cd9ae2885 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -147,7 +147,7 @@ namespace osu.Game.Database modelBuilder.Entity().HasOne(b => b.BaseDifficulty); - modelBuilder.Entity().HasIndex(b => b.OnlineScoreID).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.OnlineID).IsUnique(); } private class OsuDbLoggerFactory : ILoggerFactory diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 7c2d882f91..7acc7bd055 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -137,7 +137,14 @@ namespace osu.Game.Scoring [Column("Beatmap")] public BeatmapInfo BeatmapInfo { get; set; } - public long? OnlineScoreID { get; set; } + private long? onlineID; + + [Column("OnlineScoreID")] + public long? OnlineID + { + get => onlineID; + set => onlineID = value > 0 ? value : null; + } public DateTimeOffset Date { get; set; } @@ -243,12 +250,7 @@ namespace osu.Game.Scoring #region Implementation of IHasOnlineID - [NotMapped] - public long OnlineID - { - get => OnlineScoreID ?? -1; - set => OnlineScoreID = value; - } + long IHasOnlineID.OnlineID => OnlineID ?? -1; #endregion diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 521cf7d1e9..e2896c174f 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -1031,7 +1031,7 @@ namespace osu.Game.Screens.Play // // Until we better define the server-side logic behind this, let's not store the online ID to avoid potential unique constraint // conflicts across various systems (ie. solo and multiplayer). - long onlineScoreId = score.ScoreInfo.OnlineID; + long? onlineScoreId = score.ScoreInfo.OnlineID; score.ScoreInfo.OnlineID = -1; await scoreManager.Import(score.ScoreInfo, replayReader).ConfigureAwait(false); From 3b899af061f2e3f450bbec9913c32c6167681b53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 18:37:57 +0900 Subject: [PATCH 102/117] Update libraries --- .config/dotnet-tools.json | 4 ++-- osu.Game/osu.Game.csproj | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 6444127594..985fc09df3 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -27,10 +27,10 @@ ] }, "ppy.localisationanalyser.tools": { - "version": "2021.725.0", + "version": "2021.1210.0", "commands": [ "localisation" ] } } -} \ No newline at end of file +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6f01cc65fe..46064e320b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -20,7 +20,7 @@ - + @@ -31,15 +31,15 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + From 5e9510be3608a3718c9b0d109cc8ae80f6b44add Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 18:57:33 +0900 Subject: [PATCH 103/117] Add test coverage of editor resetting mods on entering --- osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs index 160af47a6d..50794f15ed 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -9,6 +9,7 @@ using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; @@ -44,6 +45,7 @@ namespace osu.Game.Tests.Visual.Editing protected override void LoadEditor() { Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0)); + SelectedMods.Value = new[] { new ModCinema() }; base.LoadEditor(); } @@ -67,6 +69,7 @@ namespace osu.Game.Tests.Visual.Editing var background = this.ChildrenOfType().Single(); return background.Colour == Color4.DarkGray && background.BlurAmount.Value == 0; }); + AddAssert("no mods selected", () => SelectedMods.Value.Count == 0); } [Test] From c1b3ee6bb23765a540708dd5af9f13064f310786 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Dec 2021 18:57:43 +0900 Subject: [PATCH 104/117] Fix editor not resetting mods when entering Would leave the user potentially in a test mode that is in a weird state (ie. if cinema mod was enabled). Eventually we'll add the ability to choose mods for test play, but that will be done in a saner way. Closes #15870. --- osu.Game/Screens/Edit/EditorLoader.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 2a01a5b6b2..27ae8ae497 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -9,6 +10,7 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Mods; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; @@ -53,6 +55,14 @@ namespace osu.Game.Screens.Edit }); } + protected override void LoadComplete() + { + base.LoadComplete(); + + // will be restored via lease, see `DisallowExternalBeatmapRulesetChanges`. + Mods.Value = ArraySegment.Empty; + } + protected virtual Editor CreateEditor() => new Editor(this); protected override void LogoArriving(OsuLogo logo, bool resuming) From 63a017bc8e43bbdc61db396047e100eaf9814d80 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 11 Dec 2021 19:33:37 +0900 Subject: [PATCH 105/117] Use Array.Empty instead --- osu.Game/Screens/Edit/EditorLoader.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/EditorLoader.cs b/osu.Game/Screens/Edit/EditorLoader.cs index 27ae8ae497..15d70e28b6 100644 --- a/osu.Game/Screens/Edit/EditorLoader.cs +++ b/osu.Game/Screens/Edit/EditorLoader.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Edit base.LoadComplete(); // will be restored via lease, see `DisallowExternalBeatmapRulesetChanges`. - Mods.Value = ArraySegment.Empty; + Mods.Value = Array.Empty(); } protected virtual Editor CreateEditor() => new Editor(this); From 2cd2b10ce1b1fd705a5d63fe007aba821745a5d4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Dec 2021 06:54:57 +0900 Subject: [PATCH 106/117] Fix results sometimes showing incorrect score position --- .../TestScenePlaylistsResultsScreen.cs | 5 ++++- .../Playlists/PlaylistsResultsScreen.cs | 7 +++++++ .../Contracted/ContractedPanelTopContent.cs | 20 +++++++++++------- osu.Game/Screens/Ranking/ScorePanel.cs | 21 +++++++++++++++---- 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 4ac3b7c733..64fc4797a0 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -28,6 +28,7 @@ namespace osu.Game.Tests.Visual.Playlists public class TestScenePlaylistsResultsScreen : ScreenTestScene { private const int scores_per_result = 10; + private const int real_user_position = 200; private TestResultsScreen resultsScreen; @@ -58,6 +59,8 @@ namespace osu.Game.Tests.Visual.Playlists createResults(() => userScore); AddAssert("user score selected", () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineID).State == PanelState.Expanded); + AddAssert($"score panel position is {real_user_position}", + () => this.ChildrenOfType().Single(p => p.Score.OnlineID == userScore.OnlineID).ScorePosition.Value == real_user_position); } [Test] @@ -236,7 +239,7 @@ namespace osu.Game.Tests.Visual.Playlists EndedAt = userScore.Date, Passed = userScore.Passed, Rank = userScore.Rank, - Position = 200, + Position = real_user_position, MaxCombo = userScore.MaxCombo, TotalScore = userScore.TotalScore, User = userScore.User, diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs index 67727ef784..1e6722d51e 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs @@ -87,6 +87,13 @@ namespace osu.Game.Screens.OnlinePlay.Playlists { var allScores = new List { userScore }; + // Other scores could have arrived between score submission and entering the results screen. Ensure the local player score position is up to date. + if (Score != null) + { + Score.Position = userScore.Position; + ScorePanelList.GetPanelForScore(Score).ScorePosition.Value = userScore.Position; + } + if (userScore.ScoresAround?.Higher != null) { allScores.AddRange(userScore.ScoresAround.Higher.Scores); diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelTopContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelTopContent.cs index 0935ee7fb2..beff509dc6 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelTopContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelTopContent.cs @@ -2,36 +2,42 @@ // 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; using osu.Game.Graphics.Sprites; -using osu.Game.Scoring; namespace osu.Game.Screens.Ranking.Contracted { public class ContractedPanelTopContent : CompositeDrawable { - private readonly ScoreInfo score; + public readonly Bindable ScorePosition = new Bindable(); - public ContractedPanelTopContent(ScoreInfo score) + private OsuSpriteText text; + + public ContractedPanelTopContent() { - this.score = score; - RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] private void load() { - InternalChild = new OsuSpriteText + InternalChild = text = new OsuSpriteText { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Y = 6, - Text = score.Position != null ? $"#{score.Position}" : string.Empty, Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold) }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ScorePosition.BindValueChanged(pos => text.Text = pos.NewValue != null ? $"#{pos.NewValue}" : string.Empty, true); + } } } diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 6ddecf8297..bc6eb9e366 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -4,6 +4,7 @@ using System; using osu.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -78,6 +79,11 @@ namespace osu.Game.Screens.Ranking public event Action StateChanged; + /// + /// The position of the score in the rankings. + /// + public readonly Bindable ScorePosition = new Bindable(); + /// /// An action to be invoked if this is clicked while in an expanded state. /// @@ -103,6 +109,8 @@ namespace osu.Game.Screens.Ranking { Score = score; displayWithFlair = isNewLocalScore; + + ScorePosition.Value = score.Position; } [BackgroundDependencyLoader] @@ -211,8 +219,8 @@ namespace osu.Game.Screens.Ranking topLayerBackground.FadeColour(expanded_top_layer_colour, RESIZE_DURATION, Easing.OutQuint); middleLayerBackground.FadeColour(expanded_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint); - topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User).With(d => d.Alpha = 0)); - middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair).With(d => d.Alpha = 0)); + topLayerContentContainer.Add(topLayerContent = new ExpandedPanelTopContent(Score.User) { Alpha = 0 }); + middleLayerContentContainer.Add(middleLayerContent = new ExpandedPanelMiddleContent(Score, displayWithFlair) { Alpha = 0 }); // only the first expanded display should happen with flair. displayWithFlair = false; @@ -224,8 +232,13 @@ namespace osu.Game.Screens.Ranking topLayerBackground.FadeColour(contracted_top_layer_colour, RESIZE_DURATION, Easing.OutQuint); middleLayerBackground.FadeColour(contracted_middle_layer_colour, RESIZE_DURATION, Easing.OutQuint); - topLayerContentContainer.Add(topLayerContent = new ContractedPanelTopContent(Score).With(d => d.Alpha = 0)); - middleLayerContentContainer.Add(middleLayerContent = new ContractedPanelMiddleContent(Score).With(d => d.Alpha = 0)); + topLayerContentContainer.Add(topLayerContent = new ContractedPanelTopContent + { + ScorePosition = { BindTarget = ScorePosition }, + Alpha = 0 + }); + + middleLayerContentContainer.Add(middleLayerContent = new ContractedPanelMiddleContent(Score) { Alpha = 0 }); break; } From fd979a52fef9597b7e732bc8a1750723168fc520 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Dec 2021 07:15:21 +0900 Subject: [PATCH 107/117] Increase score submission request timeout to 60s --- osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs | 1 + osu.Game/Online/Solo/SubmitSoloScoreRequest.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs b/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs index d5da6c401c..4ee4be6164 100644 --- a/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs +++ b/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs @@ -31,6 +31,7 @@ namespace osu.Game.Online.Rooms req.ContentType = "application/json"; req.Method = HttpMethod.Put; + req.Timeout = 60000; req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings { diff --git a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs index 25c2e5a61f..763fcf3f20 100644 --- a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs +++ b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs @@ -31,6 +31,7 @@ namespace osu.Game.Online.Solo req.ContentType = "application/json"; req.Method = HttpMethod.Put; + req.Timeout = 60000; req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings { From d0fbbf110b74761e210937b94fd2f8bffd6bb5b9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 13 Dec 2021 06:48:15 +0300 Subject: [PATCH 108/117] Expose `ScreenContainer` for access in `OsuGameDesktop` --- osu.Game/OsuGame.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 565ee7e71d..a35191613c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -103,7 +103,7 @@ namespace osu.Game private Container topMostOverlayContent; - private ScalingContainer screenContainer; + protected ScalingContainer ScreenContainer { get; private set; } protected Container ScreenOffsetContainer { get; private set; } @@ -179,7 +179,7 @@ namespace osu.Game } private void updateBlockingOverlayFade() => - screenContainer.FadeColour(visibleBlockingOverlays.Any() ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint); + ScreenContainer.FadeColour(visibleBlockingOverlays.Any() ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint); public void AddBlockingOverlay(OverlayContainer overlay) { @@ -698,7 +698,7 @@ namespace osu.Game RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays) + ScreenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays) { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -801,7 +801,7 @@ namespace osu.Game loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true); loadComponentSingleFile(beatmapSetOverlay = new BeatmapSetOverlay(), overlayContent.Add, true); loadComponentSingleFile(wikiOverlay = new WikiOverlay(), overlayContent.Add, true); - loadComponentSingleFile(skinEditor = new SkinEditorOverlay(screenContainer), overlayContent.Add, true); + loadComponentSingleFile(skinEditor = new SkinEditorOverlay(ScreenContainer), overlayContent.Add, true); loadComponentSingleFile(new LoginOverlay { From d92f5039cd78edf204e9b40ede9f72869191019f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 13 Dec 2021 06:48:40 +0300 Subject: [PATCH 109/117] Reorder version overlay to display behind game-wide overlays --- osu.Desktop/OsuGameDesktop.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 645ea66654..b234207848 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -70,7 +70,9 @@ namespace osu.Desktop if (!string.IsNullOrEmpty(stableInstallPath) && checkExists(stableInstallPath)) return stableInstallPath; } - catch { } + catch + { + } } stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); @@ -113,7 +115,7 @@ namespace osu.Desktop base.LoadComplete(); if (!noVersionOverlay) - LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, Add); + LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, ScreenContainer.Add); LoadComponentAsync(new DiscordRichPresence(), Add); From 4cac87e93323a46752fb2353ed41488ba0f332b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 15:26:32 +0900 Subject: [PATCH 110/117] Add test coverage showing that `KeyBindingStore` won't remove excess key bindings --- .../Database/TestRealmKeyBindingStore.cs | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs index 860828ae81..f05d9ab3dc 100644 --- a/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs +++ b/osu.Game.Tests/Database/TestRealmKeyBindingStore.cs @@ -52,6 +52,45 @@ namespace osu.Game.Tests.Database Assert.That(queryCount(GlobalAction.Select), Is.EqualTo(2)); } + [Test] + public void TestDefaultsPopulationRemovesExcess() + { + Assert.That(queryCount(), Is.EqualTo(0)); + + KeyBindingContainer testContainer = new TestKeyBindingContainer(); + + // Add some excess bindings for an action which only supports 1. + using (var realm = realmContextFactory.CreateContext()) + using (var transaction = realm.BeginWrite()) + { + realm.Add(new RealmKeyBinding + { + Action = GlobalAction.Back, + KeyCombination = new KeyCombination(InputKey.A) + }); + + realm.Add(new RealmKeyBinding + { + Action = GlobalAction.Back, + KeyCombination = new KeyCombination(InputKey.S) + }); + + realm.Add(new RealmKeyBinding + { + Action = GlobalAction.Back, + KeyCombination = new KeyCombination(InputKey.D) + }); + + transaction.Commit(); + } + + Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(3)); + + keyBindingStore.Register(testContainer, Enumerable.Empty()); + + Assert.That(queryCount(GlobalAction.Back), Is.EqualTo(1)); + } + private int queryCount(GlobalAction? match = null) { using (var realm = realmContextFactory.CreateContext()) From 7318ff3f98cee2e689d1f20ea736a8ec6b4fdd3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 15:26:49 +0900 Subject: [PATCH 111/117] Refactor `KeyBindingStore` to clean up any excess bindings for individual actions While this isn't strictly required (outside of rulesets, potentially), I think it's best that we keep these counts in a sane state. Right now, it will remove any excess. Arguably, in the case an entry is found with too many, we should wipe all entries and re-populate the defaults. Interested in opinions on that before merging. See https://github.com/ppy/osu/discussions/15925 for an example where wiping may be the more appropriate behaviour. --- osu.Game/Input/RealmKeyBindingStore.cs | 39 ++++++++++++++++++-------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/osu.Game/Input/RealmKeyBindingStore.cs b/osu.Game/Input/RealmKeyBindingStore.cs index 3bdb0a180d..cb51797685 100644 --- a/osu.Game/Input/RealmKeyBindingStore.cs +++ b/osu.Game/Input/RealmKeyBindingStore.cs @@ -81,20 +81,37 @@ namespace osu.Game.Input // compare counts in database vs defaults for each action type. foreach (var defaultsForAction in defaults.GroupBy(k => k.Action)) { - // avoid performing redundant queries when the database is empty and needs to be re-filled. - int existingCount = existingBindings.Count(k => k.RulesetName == rulesetName && k.Variant == variant && k.ActionInt == (int)defaultsForAction.Key); + IEnumerable existing = existingBindings.Where(k => + k.RulesetName == rulesetName + && k.Variant == variant + && k.ActionInt == (int)defaultsForAction.Key); - if (defaultsForAction.Count() <= existingCount) - continue; + int defaultsCount = defaultsForAction.Count(); + int existingCount = existing.Count(); - // insert any defaults which are missing. - realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding + if (defaultsCount > existingCount) { - KeyCombinationString = k.KeyCombination.ToString(), - ActionInt = (int)k.Action, - RulesetName = rulesetName, - Variant = variant - })); + // insert any defaults which are missing. + realm.Add(defaultsForAction.Skip(existingCount).Select(k => new RealmKeyBinding + { + KeyCombinationString = k.KeyCombination.ToString(), + ActionInt = (int)k.Action, + RulesetName = rulesetName, + Variant = variant + })); + } + else if (defaultsCount < existingCount) + { + // generally this shouldn't happen, but if the user has more key bindings for an action than we expect, + // remove the last entries until the count matches for sanity. + foreach (var k in existing.TakeLast(existingCount - defaultsCount).ToArray()) + { + realm.Remove(k); + + // Remove from the local flattened/cached list so future lookups don't query now deleted rows. + existingBindings.Remove(k); + } + } } } From b0d14526eac084234a7b4c2e1d30f63a36262f35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 16:34:48 +0900 Subject: [PATCH 112/117] Move test `ScoreInfo` creation to `TestResources` The main goal here is to remove the inheritance, since realm doesn't like that. Unfortunate that we can't use object initialisers in a few of these places, but no real way around that. --- .../Formats/LegacyScoreDecoderTest.cs | 2 +- osu.Game.Tests/Resources/TestResources.cs | 67 +++++++++++++++++++ .../TestScenePlaylistsResultsScreen.cs | 13 +++- .../TestSceneContractedPanelMiddleContent.cs | 5 +- .../TestSceneExpandedPanelMiddleContent.cs | 28 +++----- .../TestSceneExpandedPanelTopContent.cs | 4 +- .../Visual/Ranking/TestSceneResultsScreen.cs | 20 +++--- .../Visual/Ranking/TestSceneScorePanel.cs | 42 +++++++++--- .../Visual/Ranking/TestSceneScorePanelList.cs | 47 ++++++++----- .../Ranking/TestSceneStatisticsPanel.cs | 15 ++--- osu.Game/Tests/TestScoreInfo.cs | 66 ------------------ 11 files changed, 170 insertions(+), 139 deletions(-) delete mode 100644 osu.Game/Tests/TestScoreInfo.cs diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs index a73ae9dcdb..81d89359e0 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyScoreDecoderTest.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestCultureInvariance() { var ruleset = new OsuRuleset().RulesetInfo; - var scoreInfo = new TestScoreInfo(ruleset); + var scoreInfo = TestResources.CreateTestScoreInfo(ruleset); var beatmap = new TestBeatmap(ruleset); var score = new Score { diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 440d5e701f..7535f6769f 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; using System.Threading; using NUnit.Framework; @@ -12,8 +13,12 @@ using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Tests.Resources { @@ -137,5 +142,67 @@ namespace osu.Game.Tests.Resources } } } + + /// + /// Create a test score model. + /// + /// The ruleset for which the score was set against. + /// Whether to include an excessive number of mods. If false, only two will be added. + /// + public static ScoreInfo CreateTestScoreInfo(RulesetInfo ruleset = null, bool excessMods = false) => + CreateTestScoreInfo(CreateTestBeatmapSetInfo(1, new[] { ruleset ?? new OsuRuleset().RulesetInfo }).Beatmaps.First(), excessMods); + + /// + /// Create a test score model. + /// + /// The beatmap for which the score was set against. + /// Whether to include an excessive number of mods. If false, only two will be added. + /// + public static ScoreInfo CreateTestScoreInfo(BeatmapInfo beatmap, bool excessMods = false) => new ScoreInfo + { + User = new APIUser + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, + BeatmapInfo = beatmap, + Ruleset = beatmap.Ruleset, + RulesetID = beatmap.Ruleset.ID ?? 0, + Mods = excessMods + ? beatmap.Ruleset.CreateInstance().CreateAllMods().ToArray() + : new Mod[] { new TestModHardRock(), new TestModDoubleTime() }, + TotalScore = 2845370, + Accuracy = 0.95, + MaxCombo = 999, + Position = 1, + Rank = ScoreRank.S, + Date = DateTimeOffset.Now, + Statistics = new Dictionary + { + [HitResult.Miss] = 1, + [HitResult.Meh] = 50, + [HitResult.Ok] = 100, + [HitResult.Good] = 200, + [HitResult.Great] = 300, + [HitResult.Perfect] = 320, + [HitResult.SmallTickHit] = 50, + [HitResult.SmallTickMiss] = 25, + [HitResult.LargeTickHit] = 100, + [HitResult.LargeTickMiss] = 50, + [HitResult.SmallBonus] = 10, + [HitResult.SmallBonus] = 50 + }, + }; + + private class TestModHardRock : ModHardRock + { + public override double ScoreMultiplier => 1; + } + + private class TestModDoubleTime : ModDoubleTime + { + public override double ScoreMultiplier => 1; + } } } diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index 64fc4797a0..faa0ce2bdc 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -22,6 +22,7 @@ using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Ranking; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Playlists { @@ -52,7 +53,9 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { - userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineID = currentScoreId++ }; + userScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + userScore.OnlineID = currentScoreId++; + bindHandler(userScore: userScore); }); @@ -78,7 +81,9 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { - userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineID = currentScoreId++ }; + userScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + userScore.OnlineID = currentScoreId++; + bindHandler(true, userScore); }); @@ -127,7 +132,9 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { - userScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { OnlineID = currentScoreId++ }; + userScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + userScore.OnlineID = currentScoreId++; + bindHandler(userScore: userScore); }); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs index f246560c82..04ddc5d7f0 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Contracted; +using osu.Game.Tests.Resources; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -22,13 +23,13 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestShowPanel() { - AddStep("show example score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new TestScoreInfo(new OsuRuleset().RulesetInfo))); + AddStep("show example score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo))); } [Test] public void TestExcessMods() { - AddStep("show excess mods score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new TestScoreInfo(new OsuRuleset().RulesetInfo, true))); + AddStep("show excess mods score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo, true))); } private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index 9983993d9c..1f34606fcb 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -20,6 +20,7 @@ using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Expanded; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Resources; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -34,10 +35,7 @@ namespace osu.Game.Tests.Visual.Ranking { var author = new APIUser { Username = "mapper_name" }; - AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo) - { - BeatmapInfo = createTestBeatmap(author) - })); + AddStep("show example score", () => showPanel(TestResources.CreateTestScoreInfo(createTestBeatmap(author)))); } [Test] @@ -45,10 +43,7 @@ namespace osu.Game.Tests.Visual.Ranking { var author = new APIUser { Username = "mapper_name" }; - AddStep("show excess mods score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo, true) - { - BeatmapInfo = createTestBeatmap(author) - })); + AddStep("show excess mods score", () => showPanel(TestResources.CreateTestScoreInfo(createTestBeatmap(author), true))); AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Current.Value == "mapper_name")); } @@ -56,10 +51,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestMapWithUnknownMapper() { - AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo) - { - BeatmapInfo = createTestBeatmap(new APIUser()) - })); + AddStep("show example score", () => showPanel(TestResources.CreateTestScoreInfo(createTestBeatmap(new APIUser())))); AddAssert("mapped by text not present", () => this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text.ToString(), "mapped", "by"))); @@ -77,12 +69,12 @@ namespace osu.Game.Tests.Visual.Ranking var mods = new Mod[] { ruleset.GetAutoplayMod() }; var beatmap = createTestBeatmap(new APIUser()); - showPanel(new TestScoreInfo(ruleset.RulesetInfo) - { - Mods = mods, - BeatmapInfo = beatmap, - Date = default, - }); + var score = TestResources.CreateTestScoreInfo(beatmap); + + score.Mods = mods; + score.Date = default; + + showPanel(score); }); AddAssert("play time not displayed", () => !this.ChildrenOfType().Any()); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs index a32bcbe7f0..a2fa142896 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs @@ -5,8 +5,8 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Rulesets.Osu; using osu.Game.Screens.Ranking.Expanded; +using osu.Game.Tests.Resources; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#444"), }, - new ExpandedPanelTopContent(new TestScoreInfo(new OsuRuleset().RulesetInfo).User), + new ExpandedPanelTopContent(TestResources.CreateTestScoreInfo().User), } }; } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 94700bac6a..809f513a83 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -21,6 +21,7 @@ using osu.Game.Screens; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Statistics; +using osu.Game.Tests.Resources; using osuTK; using osuTK.Input; @@ -72,11 +73,10 @@ namespace osu.Game.Tests.Visual.Ranking { TestResultsScreen screen = null; - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) - { - Accuracy = accuracy, - Rank = rank - }; + var score = TestResources.CreateTestScoreInfo(); + + score.Accuracy = accuracy; + score.Rank = rank; AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen(score))); AddUntilStep("wait for loaded", () => screen.IsLoaded); @@ -204,7 +204,7 @@ namespace osu.Game.Tests.Visual.Ranking { DelayedFetchResultsScreen screen = null; - AddStep("load results", () => Child = new TestResultsContainer(screen = new DelayedFetchResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo), 3000))); + AddStep("load results", () => Child = new TestResultsContainer(screen = new DelayedFetchResultsScreen(TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo), 3000))); AddUntilStep("wait for loaded", () => screen.IsLoaded); AddStep("click expanded panel", () => { @@ -237,9 +237,9 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("download button is enabled", () => screen.ChildrenOfType().Last().Enabled.Value); } - private TestResultsScreen createResultsScreen(ScoreInfo score = null) => new TestResultsScreen(score ?? new TestScoreInfo(new OsuRuleset().RulesetInfo)); + private TestResultsScreen createResultsScreen(ScoreInfo score = null) => new TestResultsScreen(score ?? TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo)); - private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo)); private class TestResultsContainer : Container { @@ -282,7 +282,7 @@ namespace osu.Game.Tests.Visual.Ranking for (int i = 0; i < 20; i++) { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var score = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); score.TotalScore += 10 - i; score.Hash = $"test{i}"; scores.Add(score); @@ -316,7 +316,7 @@ namespace osu.Game.Tests.Visual.Ranking for (int i = 0; i < 20; i++) { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var score = TestResources.CreateTestScoreInfo(); score.TotalScore += 10 - i; scores.Add(score); } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index 5af55e99f8..5dbeefd390 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -3,10 +3,10 @@ using NUnit.Framework; using osu.Framework.Graphics; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Ranking { @@ -17,7 +17,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestDRank() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.5, Rank = ScoreRank.D }; + var score = TestResources.CreateTestScoreInfo(); + score.Accuracy = 0.5; + score.Rank = ScoreRank.D; addPanelStep(score); } @@ -25,7 +27,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestCRank() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.75, Rank = ScoreRank.C }; + var score = TestResources.CreateTestScoreInfo(); + score.Accuracy = 0.75; + score.Rank = ScoreRank.C; addPanelStep(score); } @@ -33,7 +37,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestBRank() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.85, Rank = ScoreRank.B }; + var score = TestResources.CreateTestScoreInfo(); + score.Accuracy = 0.85; + score.Rank = ScoreRank.B; addPanelStep(score); } @@ -41,7 +47,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestARank() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; + var score = TestResources.CreateTestScoreInfo(); + score.Accuracy = 0.925; + score.Rank = ScoreRank.A; addPanelStep(score); } @@ -49,7 +57,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestSRank() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.975, Rank = ScoreRank.S }; + var score = TestResources.CreateTestScoreInfo(); + score.Accuracy = 0.975; + score.Rank = ScoreRank.S; addPanelStep(score); } @@ -57,7 +67,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAlmostSSRank() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.9999, Rank = ScoreRank.S }; + var score = TestResources.CreateTestScoreInfo(); + score.Accuracy = 0.9999; + score.Rank = ScoreRank.S; addPanelStep(score); } @@ -65,7 +77,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestSSRank() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 1, Rank = ScoreRank.X }; + var score = TestResources.CreateTestScoreInfo(); + score.Accuracy = 1; + score.Rank = ScoreRank.X; addPanelStep(score); } @@ -73,7 +87,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAllHitResults() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Statistics = { [HitResult.Perfect] = 350, [HitResult.Ok] = 200 } }; + var score = TestResources.CreateTestScoreInfo(); + score.Statistics[HitResult.Perfect] = 350; + score.Statistics[HitResult.Ok] = 200; addPanelStep(score); } @@ -81,7 +97,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestContractedPanel() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; + var score = TestResources.CreateTestScoreInfo(); + score.Accuracy = 0.925; + score.Rank = ScoreRank.A; addPanelStep(score, PanelState.Contracted); } @@ -89,7 +107,9 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestExpandAndContract() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; + var score = TestResources.CreateTestScoreInfo(); + score.Accuracy = 0.925; + score.Rank = ScoreRank.A; addPanelStep(score, PanelState.Contracted); AddWaitStep("wait for transition", 10); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index 2f9652d354..f963be5d81 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -10,6 +10,7 @@ using osu.Framework.Utils; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; +using osu.Game.Tests.Resources; using osuTK.Input; namespace osu.Game.Tests.Visual.Ranking @@ -29,14 +30,14 @@ namespace osu.Game.Tests.Visual.Ranking { createListStep(() => new ScorePanelList { - SelectedScore = { Value = new TestScoreInfo(new OsuRuleset().RulesetInfo) } + SelectedScore = { Value = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo) } }); } [Test] public void TestAddPanelAfterSelectingScore() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var score = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); createListStep(() => new ScorePanelList { @@ -52,7 +53,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddPanelBeforeSelectingScore() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var score = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); createListStep(() => new ScorePanelList()); @@ -75,7 +76,7 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("add many scores", () => { for (int i = 0; i < 20; i++) - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo)); }); assertFirstPanelCentred(); @@ -84,7 +85,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddManyScoresAfterExpandedPanel() { - var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var initialScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); createListStep(() => new ScorePanelList()); @@ -97,7 +98,7 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("add many scores", () => { for (int i = 0; i < 20; i++) - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 }); + list.AddScore(createScoreForTotalScore(initialScore.TotalScore - i - 1)); }); assertScoreState(initialScore, true); @@ -107,7 +108,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddManyScoresBeforeExpandedPanel() { - var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var initialScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); createListStep(() => new ScorePanelList()); @@ -120,7 +121,7 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("add scores", () => { for (int i = 0; i < 20; i++) - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 }); + list.AddScore(createScoreForTotalScore(initialScore.TotalScore + i + 1)); }); assertScoreState(initialScore, true); @@ -130,7 +131,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddManyPanelsOnBothSidesOfExpandedPanel() { - var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var initialScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); createListStep(() => new ScorePanelList()); @@ -143,10 +144,10 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("add scores after", () => { for (int i = 0; i < 20; i++) - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 }); + list.AddScore(createScoreForTotalScore(initialScore.TotalScore - i - 1)); for (int i = 0; i < 20; i++) - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 }); + list.AddScore(createScoreForTotalScore(initialScore.TotalScore + i + 1)); }); assertScoreState(initialScore, true); @@ -156,8 +157,8 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestSelectMultipleScores() { - var firstScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); - var secondScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var firstScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + var secondScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); firstScore.UserString = "A"; secondScore.UserString = "B"; @@ -190,7 +191,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddScoreImmediately() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var score = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); createListStep(() => { @@ -206,9 +207,14 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestKeyboardNavigation() { - var lowestScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 100 }; - var middleScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 200 }; - var highestScore = new TestScoreInfo(new OsuRuleset().RulesetInfo) { MaxCombo = 300 }; + var lowestScore = TestResources.CreateTestScoreInfo(); + lowestScore.MaxCombo = 100; + + var middleScore = TestResources.CreateTestScoreInfo(); + middleScore.MaxCombo = 200; + + var highestScore = TestResources.CreateTestScoreInfo(); + highestScore.MaxCombo = 300; createListStep(() => new ScorePanelList()); @@ -270,6 +276,13 @@ namespace osu.Game.Tests.Visual.Ranking assertExpandedPanelCentred(); } + private ScoreInfo createScoreForTotalScore(long totalScore) + { + var score = TestResources.CreateTestScoreInfo(); + score.TotalScore = totalScore; + return score; + } + private void createListStep(Func creationFunc) { AddStep("create list", () => Child = list = creationFunc().With(d => diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index d91aec753c..ebea523b9e 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -11,6 +11,7 @@ using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Tests.Resources; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -20,10 +21,8 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestScoreWithTimeStatistics() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) - { - HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents() - }; + var score = TestResources.CreateTestScoreInfo(); + score.HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents(); loadPanel(score); } @@ -31,10 +30,8 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestScoreWithPositionStatistics() { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) - { - HitEvents = createPositionDistributedHitEvents() - }; + var score = TestResources.CreateTestScoreInfo(); + score.HitEvents = createPositionDistributedHitEvents(); loadPanel(score); } @@ -42,7 +39,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestScoreWithoutStatistics() { - loadPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + loadPanel(TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo)); } [Test] diff --git a/osu.Game/Tests/TestScoreInfo.cs b/osu.Game/Tests/TestScoreInfo.cs deleted file mode 100644 index a53cb0ae78..0000000000 --- a/osu.Game/Tests/TestScoreInfo.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Linq; -using osu.Game.Online.API.Requests.Responses; -using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; -using osu.Game.Tests.Beatmaps; - -namespace osu.Game.Tests -{ - public class TestScoreInfo : ScoreInfo - { - public TestScoreInfo(RulesetInfo ruleset, bool excessMods = false) - { - User = new APIUser - { - Id = 2, - Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", - }; - - BeatmapInfo = new TestBeatmap(ruleset).BeatmapInfo; - Ruleset = ruleset; - RulesetID = ruleset.ID ?? 0; - - Mods = excessMods - ? ruleset.CreateInstance().CreateAllMods().ToArray() - : new Mod[] { new TestModHardRock(), new TestModDoubleTime() }; - - TotalScore = 2845370; - Accuracy = 0.95; - MaxCombo = 999; - Rank = ScoreRank.S; - Date = DateTimeOffset.Now; - - Statistics[HitResult.Miss] = 1; - Statistics[HitResult.Meh] = 50; - Statistics[HitResult.Ok] = 100; - Statistics[HitResult.Good] = 200; - Statistics[HitResult.Great] = 300; - Statistics[HitResult.Perfect] = 320; - Statistics[HitResult.SmallTickHit] = 50; - Statistics[HitResult.SmallTickMiss] = 25; - Statistics[HitResult.LargeTickHit] = 100; - Statistics[HitResult.LargeTickMiss] = 50; - Statistics[HitResult.SmallBonus] = 10; - Statistics[HitResult.SmallBonus] = 50; - - Position = 1; - } - - private class TestModHardRock : ModHardRock - { - public override double ScoreMultiplier => 1; - } - - private class TestModDoubleTime : ModDoubleTime - { - public override double ScoreMultiplier => 1; - } - } -} From 99ac71c1fe9b109c064b2deac88aadd10b290544 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 16:37:20 +0900 Subject: [PATCH 113/117] Simplify usages where the ruleset being used is osu! ruleset --- .../TestScenePlaylistsResultsScreen.cs | 6 +++--- .../TestSceneContractedPanelMiddleContent.cs | 4 ++-- .../Visual/Ranking/TestSceneResultsScreen.cs | 9 ++++---- .../Visual/Ranking/TestSceneScorePanelList.cs | 21 +++++++++---------- .../Ranking/TestSceneStatisticsPanel.cs | 3 +-- 5 files changed, 20 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs index faa0ce2bdc..25ca1299ef 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsResultsScreen.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { - userScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + userScore = TestResources.CreateTestScoreInfo(); userScore.OnlineID = currentScoreId++; bindHandler(userScore: userScore); @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { - userScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + userScore = TestResources.CreateTestScoreInfo(); userScore.OnlineID = currentScoreId++; bindHandler(true, userScore); @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Playlists AddStep("bind user score info handler", () => { - userScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + userScore = TestResources.CreateTestScoreInfo(); userScore.OnlineID = currentScoreId++; bindHandler(userScore: userScore); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs index 04ddc5d7f0..a84a7b3993 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -23,13 +23,13 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestShowPanel() { - AddStep("show example score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo))); + AddStep("show example score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), TestResources.CreateTestScoreInfo())); } [Test] public void TestExcessMods() { - AddStep("show excess mods score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo, true))); + AddStep("show excess mods score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), TestResources.CreateTestScoreInfo(excessMods: true))); } private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 809f513a83..d0bd5a6e66 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -15,7 +15,6 @@ using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; -using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; @@ -204,7 +203,7 @@ namespace osu.Game.Tests.Visual.Ranking { DelayedFetchResultsScreen screen = null; - AddStep("load results", () => Child = new TestResultsContainer(screen = new DelayedFetchResultsScreen(TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo), 3000))); + AddStep("load results", () => Child = new TestResultsContainer(screen = new DelayedFetchResultsScreen(TestResources.CreateTestScoreInfo(), 3000))); AddUntilStep("wait for loaded", () => screen.IsLoaded); AddStep("click expanded panel", () => { @@ -237,9 +236,9 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("download button is enabled", () => screen.ChildrenOfType().Last().Enabled.Value); } - private TestResultsScreen createResultsScreen(ScoreInfo score = null) => new TestResultsScreen(score ?? TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo)); + private TestResultsScreen createResultsScreen(ScoreInfo score = null) => new TestResultsScreen(score ?? TestResources.CreateTestScoreInfo()); - private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo)); + private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(TestResources.CreateTestScoreInfo()); private class TestResultsContainer : Container { @@ -282,7 +281,7 @@ namespace osu.Game.Tests.Visual.Ranking for (int i = 0; i < 20; i++) { - var score = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + var score = TestResources.CreateTestScoreInfo(); score.TotalScore += 10 - i; score.Hash = $"test{i}"; scores.Add(score); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index f963be5d81..f5ad352b9c 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -7,7 +7,6 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Framework.Utils; -using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Tests.Resources; @@ -30,14 +29,14 @@ namespace osu.Game.Tests.Visual.Ranking { createListStep(() => new ScorePanelList { - SelectedScore = { Value = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo) } + SelectedScore = { Value = TestResources.CreateTestScoreInfo() } }); } [Test] public void TestAddPanelAfterSelectingScore() { - var score = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + var score = TestResources.CreateTestScoreInfo(); createListStep(() => new ScorePanelList { @@ -53,7 +52,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddPanelBeforeSelectingScore() { - var score = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + var score = TestResources.CreateTestScoreInfo(); createListStep(() => new ScorePanelList()); @@ -76,7 +75,7 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("add many scores", () => { for (int i = 0; i < 20; i++) - list.AddScore(TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(TestResources.CreateTestScoreInfo()); }); assertFirstPanelCentred(); @@ -85,7 +84,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddManyScoresAfterExpandedPanel() { - var initialScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + var initialScore = TestResources.CreateTestScoreInfo(); createListStep(() => new ScorePanelList()); @@ -108,7 +107,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddManyScoresBeforeExpandedPanel() { - var initialScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + var initialScore = TestResources.CreateTestScoreInfo(); createListStep(() => new ScorePanelList()); @@ -131,7 +130,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddManyPanelsOnBothSidesOfExpandedPanel() { - var initialScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + var initialScore = TestResources.CreateTestScoreInfo(); createListStep(() => new ScorePanelList()); @@ -157,8 +156,8 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestSelectMultipleScores() { - var firstScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); - var secondScore = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + var firstScore = TestResources.CreateTestScoreInfo(); + var secondScore = TestResources.CreateTestScoreInfo(); firstScore.UserString = "A"; secondScore.UserString = "B"; @@ -191,7 +190,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAddScoreImmediately() { - var score = TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo); + var score = TestResources.CreateTestScoreInfo(); createListStep(() => { diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index ebea523b9e..f64b7b2b65 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; using osu.Game.Rulesets.Osu.Objects; @@ -39,7 +38,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestScoreWithoutStatistics() { - loadPanel(TestResources.CreateTestScoreInfo(new OsuRuleset().RulesetInfo)); + loadPanel(TestResources.CreateTestScoreInfo()); } [Test] From 654b47c7ecd67272038672a9f468059f667b0247 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 16:41:29 +0900 Subject: [PATCH 114/117] Move "excess mods" test behaviour to local usages There were only two of these, so it doesn't make sense to add extra complexity in the test resources class. --- osu.Game.Tests/Resources/TestResources.cs | 12 ++++-------- .../Ranking/TestSceneContractedPanelMiddleContent.cs | 8 +++++++- .../Ranking/TestSceneExpandedPanelMiddleContent.cs | 10 ++++++++-- 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 7535f6769f..445394fc77 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -147,18 +147,16 @@ namespace osu.Game.Tests.Resources /// Create a test score model. /// /// The ruleset for which the score was set against. - /// Whether to include an excessive number of mods. If false, only two will be added. /// - public static ScoreInfo CreateTestScoreInfo(RulesetInfo ruleset = null, bool excessMods = false) => - CreateTestScoreInfo(CreateTestBeatmapSetInfo(1, new[] { ruleset ?? new OsuRuleset().RulesetInfo }).Beatmaps.First(), excessMods); + public static ScoreInfo CreateTestScoreInfo(RulesetInfo ruleset = null) => + CreateTestScoreInfo(CreateTestBeatmapSetInfo(1, new[] { ruleset ?? new OsuRuleset().RulesetInfo }).Beatmaps.First()); /// /// Create a test score model. /// /// The beatmap for which the score was set against. - /// Whether to include an excessive number of mods. If false, only two will be added. /// - public static ScoreInfo CreateTestScoreInfo(BeatmapInfo beatmap, bool excessMods = false) => new ScoreInfo + public static ScoreInfo CreateTestScoreInfo(BeatmapInfo beatmap) => new ScoreInfo { User = new APIUser { @@ -169,9 +167,7 @@ namespace osu.Game.Tests.Resources BeatmapInfo = beatmap, Ruleset = beatmap.Ruleset, RulesetID = beatmap.Ruleset.ID ?? 0, - Mods = excessMods - ? beatmap.Ruleset.CreateInstance().CreateAllMods().ToArray() - : new Mod[] { new TestModHardRock(), new TestModDoubleTime() }, + Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() }, TotalScore = 2845370, Accuracy = 0.95, MaxCombo = 999, diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs index a84a7b3993..85306b9354 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -29,7 +30,12 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestExcessMods() { - AddStep("show excess mods score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), TestResources.CreateTestScoreInfo(excessMods: true))); + AddStep("show excess mods score", () => + { + var score = TestResources.CreateTestScoreInfo(); + score.Mods = score.BeatmapInfo.Ruleset.CreateInstance().CreateAllMods().ToArray(); + showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), score); + }); } private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index 1f34606fcb..2cb4fb6b6b 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -41,9 +41,15 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestExcessMods() { - var author = new APIUser { Username = "mapper_name" }; + AddStep("show excess mods score", () => + { + var author = new APIUser { Username = "mapper_name" }; - AddStep("show excess mods score", () => showPanel(TestResources.CreateTestScoreInfo(createTestBeatmap(author), true))); + var score = TestResources.CreateTestScoreInfo(createTestBeatmap(author)); + score.Mods = score.BeatmapInfo.Ruleset.CreateInstance().CreateAllMods().ToArray(); + + showPanel(score); + }); AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Current.Value == "mapper_name")); } From 309290a3c9b92e40d88328720b23e544de7c892b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 16:50:29 +0900 Subject: [PATCH 115/117] Use new method in more places that can benefit from it --- .../Online/TestAPIModJsonSerialization.cs | 3 +-- .../Background/TestSceneUserDimBackgrounds.cs | 9 +-------- .../Visual/Gameplay/TestSceneFailJudgement.cs | 1 + .../TestSceneMultiplayerResults.cs | 19 ++----------------- .../TestSceneMultiplayerTeamResults.cs | 19 ++----------------- .../SongSelect/TestScenePlaySongSelect.cs | 7 +------ 6 files changed, 8 insertions(+), 50 deletions(-) diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index 8378b33b3d..8b8954a1d0 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Online [Test] public void TestDeserialiseSubmittableScoreWithEmptyMods() { - var score = new SubmittableScore(new ScoreInfo { Ruleset = new OsuRuleset().RulesetInfo }); + var score = new SubmittableScore(new ScoreInfo()); var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(score)); @@ -106,7 +106,6 @@ namespace osu.Game.Tests.Online { var score = new SubmittableScore(new ScoreInfo { - Ruleset = new OsuRuleset().RulesetInfo, Mods = new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 2 } } } }); diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 194341d1ab..33b1d9a67d 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -18,7 +18,6 @@ using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; @@ -28,7 +27,6 @@ using osu.Game.Screens.Play; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; -using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; using osuTK; using osuTK.Graphics; @@ -229,12 +227,7 @@ namespace osu.Game.Tests.Visual.Background FadeAccessibleResults results = null; - AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(new ScoreInfo - { - User = new APIUser { Username = "osu!" }, - BeatmapInfo = new TestBeatmap(Ruleset.Value).BeatmapInfo, - Ruleset = Ruleset.Value, - }))); + AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(TestResources.CreateTestScoreInfo()))); AddUntilStep("Wait for results is current", () => results.IsCurrentScreen()); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs index 745932315c..fa27e1abdd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailJudgement.cs @@ -26,6 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("total number of results == 1", () => { var score = new ScoreInfo(); + ((FailPlayer)Player).ScoreProcessor.PopulateScore(score); return score.Statistics.Values.Sum() == 1; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs index 744a2d187d..4674601f28 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs @@ -1,13 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using NUnit.Framework; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu; -using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Multiplayer { @@ -22,20 +20,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { var rulesetInfo = new OsuRuleset().RulesetInfo; var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo; - - var score = new ScoreInfo - { - Rank = ScoreRank.B, - TotalScore = 987654, - Accuracy = 0.8, - MaxCombo = 500, - Combo = 250, - BeatmapInfo = beatmapInfo, - User = new APIUser { Username = "Test user" }, - Date = DateTimeOffset.Now, - OnlineID = 12345, - Ruleset = rulesetInfo, - }; + var score = TestResources.CreateTestScoreInfo(beatmapInfo); PlaylistItem playlistItem = new PlaylistItem { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs index 99b6edc3b6..f5df8d7507 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs @@ -1,15 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Bindables; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu; -using osu.Game.Scoring; using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Visual.Multiplayer { @@ -26,20 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { var rulesetInfo = new OsuRuleset().RulesetInfo; var beatmapInfo = CreateBeatmap(rulesetInfo).BeatmapInfo; - - var score = new ScoreInfo - { - Rank = ScoreRank.B, - TotalScore = 987654, - Accuracy = 0.8, - MaxCombo = 500, - Combo = 250, - BeatmapInfo = beatmapInfo, - User = new APIUser { Username = "Test user" }, - Date = DateTimeOffset.Now, - OnlineID = 12345, - Ruleset = rulesetInfo, - }; + var score = TestResources.CreateTestScoreInfo(beatmapInfo); PlaylistItem playlistItem = new PlaylistItem { diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 0494d1de3c..be390742ea 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -835,12 +835,7 @@ namespace osu.Game.Tests.Visual.SongSelect // this beatmap change should be overridden by the present. Beatmap.Value = manager.GetWorkingBeatmap(getSwitchBeatmap()); - songSelect.PresentScore(new ScoreInfo - { - User = new APIUser { Username = "woo" }, - BeatmapInfo = getPresentBeatmap(), - Ruleset = getPresentBeatmap().Ruleset - }); + songSelect.PresentScore(TestResources.CreateTestScoreInfo(getPresentBeatmap())); }); AddUntilStep("wait for results screen presented", () => !songSelect.IsCurrentScreen()); From 9e9341597d3ba0ecc05b36f88fbf8dcad59f1d1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 17:59:04 +0900 Subject: [PATCH 116/117] Remove unused using statement --- osu.Game.Tests/Online/TestAPIModJsonSerialization.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index 8b8954a1d0..4b160e1d67 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs @@ -13,7 +13,6 @@ using osu.Game.Online.Solo; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.UI; using osu.Game.Scoring; From 7564658b5e01c780feadd62dccabbf1f8a051d31 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 14 Dec 2021 06:40:45 +0900 Subject: [PATCH 117/117] Reduce to 30s --- osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs | 2 +- osu.Game/Online/Solo/SubmitSoloScoreRequest.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs b/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs index 4ee4be6164..e24d113822 100644 --- a/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs +++ b/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs @@ -31,7 +31,7 @@ namespace osu.Game.Online.Rooms req.ContentType = "application/json"; req.Method = HttpMethod.Put; - req.Timeout = 60000; + req.Timeout = 30000; req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings { diff --git a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs index 763fcf3f20..99cf5ceff5 100644 --- a/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs +++ b/osu.Game/Online/Solo/SubmitSoloScoreRequest.cs @@ -31,7 +31,7 @@ namespace osu.Game.Online.Solo req.ContentType = "application/json"; req.Method = HttpMethod.Put; - req.Timeout = 60000; + req.Timeout = 30000; req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings {