From eecf6ad558983786890f5905370142fcff4d5a75 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 19 Nov 2021 21:53:40 +0900 Subject: [PATCH 001/118] 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/118] 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 e67d9b1c2153c068a762820b866372818a2c5405 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 24 Nov 2021 12:14:52 +0900 Subject: [PATCH 003/118] 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 004/118] 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 005/118] 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 006/118] 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 007/118] 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 008/118] 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 009/118] 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 010/118] 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 0a6c221de4dda5d3f41c504974f204b79be4f0ab Mon Sep 17 00:00:00 2001 From: tbrose Date: Mon, 6 Dec 2021 22:07:47 +0100 Subject: [PATCH 011/118] Adds tests for checkContainsUsername function of MessageNotifier component --- .../Online/Chat/MessageNotifierTest.cs | 66 +++++++++++++++++++ osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Tests/Online/Chat/MessageNotifierTest.cs diff --git a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs new file mode 100644 index 0000000000..9fed72d249 --- /dev/null +++ b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Online.Chat; + +namespace osu.Game.Tests.Online.Chat +{ + [TestFixture] + public class TestCheckUsername + { + [Test] + public void TestContainsUsernameMidlinePositive() + { + Assert.IsTrue(MessageNotifier.checkContainsUsername("This is a test message", "Test")); + } + + [Test] + public void TestContainsUsernameStartOfLinePositive() + { + Assert.IsTrue(MessageNotifier.checkContainsUsername("Test message", "Test")); + } + + [Test] + public void TestContainsUsernameEndOfLinePositive() + { + Assert.IsTrue(MessageNotifier.checkContainsUsername("This is a test", "Test")); + } + + [Test] + public void TestContainsUsernameMidlineNegative() + { + Assert.IsFalse(MessageNotifier.checkContainsUsername("This is a testmessage for notifications", "Test")); + } + + [Test] + public void TestContainsUsernameStartOfLineNegative() + { + Assert.IsFalse(MessageNotifier.checkContainsUsername("Testmessage", "Test")); + } + + [Test] + public void TestContainsUsernameEndOfLineNegative() + { + Assert.IsFalse(MessageNotifier.checkContainsUsername("This is a notificationtest", "Test")); + } + + [Test] + public void TestContainsUsernameBetweenInterpunction() + { + Assert.IsTrue(MessageNotifier.checkContainsUsername("Hello 'test'-message", "Test")); + } + + [Test] + public void TestContainsUsernameUnicode() + { + Assert.IsTrue(MessageNotifier.checkContainsUsername("Test \u0460\u0460 message", "\u0460\u0460")); + } + + [Test] + public void TestContainsUsernameUnicodeNegative() + { + Assert.IsFalse(MessageNotifier.checkContainsUsername("Test ha\u0460\u0460o message", "\u0460\u0460")); + } + } +} diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index ca6317566f..69b3b18b3e 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -129,7 +129,7 @@ namespace osu.Game.Online.Chat /// Checks if contains . /// This will match against the case where underscores are used instead of spaces (which is how osu-stable handles usernames with spaces). /// - private static bool checkContainsUsername(string message, string username) => message.Contains(username, StringComparison.OrdinalIgnoreCase) || message.Contains(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase); + public static bool checkContainsUsername(string message, string username) => message.Contains(username, StringComparison.OrdinalIgnoreCase) || message.Contains(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase); public class PrivateMessageNotification : OpenChannelNotification { From 39594b7362747b329bb9486ab7b53019d15671fe Mon Sep 17 00:00:00 2001 From: tbrose Date: Mon, 6 Dec 2021 23:32:21 +0100 Subject: [PATCH 012/118] Fixes detection of mentioning of user falsely detects messages where the username is coincidentally contained in words of a message. --- osu.Game/Online/Chat/MessageNotifier.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 69b3b18b3e..aca9df0600 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; +using System.Text.RegularExpressions; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -126,10 +127,14 @@ namespace osu.Game.Online.Chat } /// - /// Checks if contains . + /// Checks if mentions . /// This will match against the case where underscores are used instead of spaces (which is how osu-stable handles usernames with spaces). /// - public static bool checkContainsUsername(string message, string username) => message.Contains(username, StringComparison.OrdinalIgnoreCase) || message.Contains(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase); + public static bool checkContainsUsername(string message, string username) { + string fullName = Regex.Escape(username); + string underscoreName = Regex.Escape(username.Replace(' ', '_')); + return new Regex($"\\b({fullName}|{underscoreName})\\b", RegexOptions.IgnoreCase).Matches(message).Count > 0; + } public class PrivateMessageNotification : OpenChannelNotification { From f02e44d552495823285f23f5e9af8138b70ec2eb Mon Sep 17 00:00:00 2001 From: tbrose Date: Tue, 7 Dec 2021 01:38:37 +0100 Subject: [PATCH 013/118] Fixes not matching coding style --- osu.Game.Tests/Online/Chat/MessageNotifierTest.cs | 2 +- osu.Game/Online/Chat/MessageNotifier.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs index 9fed72d249..81baaef2d2 100644 --- a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs +++ b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs @@ -7,7 +7,7 @@ using osu.Game.Online.Chat; namespace osu.Game.Tests.Online.Chat { [TestFixture] - public class TestCheckUsername + public class MessageNotifierTest { [Test] public void TestContainsUsernameMidlinePositive() diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index aca9df0600..fbbf8fff38 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -130,7 +130,8 @@ namespace osu.Game.Online.Chat /// Checks if mentions . /// This will match against the case where underscores are used instead of spaces (which is how osu-stable handles usernames with spaces). /// - public static bool checkContainsUsername(string message, string username) { + public static bool checkContainsUsername(string message, string username) + { string fullName = Regex.Escape(username); string underscoreName = Regex.Escape(username.Replace(' ', '_')); return new Regex($"\\b({fullName}|{underscoreName})\\b", RegexOptions.IgnoreCase).Matches(message).Count > 0; From 7a0d4fca17fd22f72a721bb18f20ab387dbb260d Mon Sep 17 00:00:00 2001 From: tbrose Date: Tue, 7 Dec 2021 01:41:21 +0100 Subject: [PATCH 014/118] Fixes using Matches+Count instead of IsMatch negatively affecting performance --- osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index fbbf8fff38..4ae4e64855 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -134,7 +134,7 @@ namespace osu.Game.Online.Chat { string fullName = Regex.Escape(username); string underscoreName = Regex.Escape(username.Replace(' ', '_')); - return new Regex($"\\b({fullName}|{underscoreName})\\b", RegexOptions.IgnoreCase).Matches(message).Count > 0; + return new Regex($"\\b({fullName}|{underscoreName})\\b", RegexOptions.IgnoreCase).IsMatch(message); } public class PrivateMessageNotification : OpenChannelNotification From 0a8c4f4cecfa4e483f97decf6a6383102aadf228 Mon Sep 17 00:00:00 2001 From: tbrose Date: Tue, 7 Dec 2021 01:55:45 +0100 Subject: [PATCH 015/118] Adds test cases for usernames with special characters --- osu.Game.Tests/Online/Chat/MessageNotifierTest.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs index 81baaef2d2..25dbdb9ea2 100644 --- a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs +++ b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs @@ -62,5 +62,17 @@ namespace osu.Game.Tests.Online.Chat { Assert.IsFalse(MessageNotifier.checkContainsUsername("Test ha\u0460\u0460o message", "\u0460\u0460")); } + + [Test] + public void TestContainsUsernameSpecialCharactersPositive() + { + Assert.IsTrue(MessageNotifier.checkContainsUsername("Test [#^-^#] message", "[#^-^#]")); + } + + [Test] + public void TestContainsUsernameSpecialCharactersNegative() + { + Assert.IsFalse(MessageNotifier.checkContainsUsername("Test pad[#^-^#]oru message", "[#^-^#]")); + } } } From b6d47a41f411959a4fe2105f7fa25eff76f46dc2 Mon Sep 17 00:00:00 2001 From: tbrose Date: Tue, 7 Dec 2021 02:14:40 +0100 Subject: [PATCH 016/118] Adjusted RegEx pattern to also take special characters into account --- osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 4ae4e64855..883f6a7284 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -134,7 +134,7 @@ namespace osu.Game.Online.Chat { string fullName = Regex.Escape(username); string underscoreName = Regex.Escape(username.Replace(' ', '_')); - return new Regex($"\\b({fullName}|{underscoreName})\\b", RegexOptions.IgnoreCase).IsMatch(message); + return new Regex($"(^|^\\W|\\W|\\w\\W)({fullName}|{underscoreName})($|$\\W|\\W|\\w\\W)", RegexOptions.IgnoreCase).IsMatch(message); } public class PrivateMessageNotification : OpenChannelNotification From be86ca582cc114788c28e44fcb487e99e1c32dbf Mon Sep 17 00:00:00 2001 From: tbrose Date: Tue, 7 Dec 2021 02:34:35 +0100 Subject: [PATCH 017/118] Adds test cases for at-sign and colon adjacent to the username --- osu.Game.Tests/Online/Chat/MessageNotifierTest.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs index 25dbdb9ea2..b885299d1f 100644 --- a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs +++ b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs @@ -74,5 +74,17 @@ namespace osu.Game.Tests.Online.Chat { Assert.IsFalse(MessageNotifier.checkContainsUsername("Test pad[#^-^#]oru message", "[#^-^#]")); } + + [Test] + public void TestContainsUsernameAtSign() + { + Assert.IsTrue(MessageNotifier.checkContainsUsername("@username hi", "username")); + } + + [Test] + public void TestContainsUsernameColon() + { + Assert.IsTrue(MessageNotifier.checkContainsUsername("username: hi", "username")); + } } } From 882223b27f684cbc9c02b4365f4f3728862a808a Mon Sep 17 00:00:00 2001 From: tbrose Date: Tue, 7 Dec 2021 02:38:10 +0100 Subject: [PATCH 018/118] Using static call and verbatim symbol and optimizes regex pattern for username check --- osu.Game/Online/Chat/MessageNotifier.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 883f6a7284..db7c5e47f5 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -134,7 +134,7 @@ namespace osu.Game.Online.Chat { string fullName = Regex.Escape(username); string underscoreName = Regex.Escape(username.Replace(' ', '_')); - return new Regex($"(^|^\\W|\\W|\\w\\W)({fullName}|{underscoreName})($|$\\W|\\W|\\w\\W)", RegexOptions.IgnoreCase).IsMatch(message); + return Regex.IsMatch(message, $@"(^|\W)({fullName}|{underscoreName})($|\W)", RegexOptions.IgnoreCase); } public class PrivateMessageNotification : OpenChannelNotification From ded86282c1b44574b8f1fe1711766640b77b17b4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 7 Dec 2021 23:14:35 +0900 Subject: [PATCH 019/118] 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 25a0505c97df49a825d874551afc69daf0972c47 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Dec 2021 14:26:11 +0900 Subject: [PATCH 020/118] 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 021/118] 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 022/118] 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 023/118] 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 024/118] 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 025/118] 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 026/118] 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 027/118] 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 028/118] 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 029/118] 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 030/118] 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 031/118] 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 032/118] 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 033/118] 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 034/118] 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 035/118] 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 036/118] 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 037/118] 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 038/118] 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 039/118] 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 040/118] 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 041/118] 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 042/118] 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 043/118] 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 044/118] 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 045/118] 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 046/118] 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 047/118] 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 048/118] 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 049/118] 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 050/118] 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 051/118] 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 052/118] 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 053/118] 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 054/118] 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 055/118] 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 056/118] 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 057/118] 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 058/118] 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 059/118] 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 060/118] 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 061/118] 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 062/118] 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 063/118] 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 064/118] 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 065/118] 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 066/118] 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 067/118] 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 068/118] 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 069/118] 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 070/118] 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 071/118] 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 072/118] 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 073/118] 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 074/118] 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 075/118] 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 076/118] 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 077/118] 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 078/118] 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 079/118] 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 6057037e355b06eb763609200c06adefa99e31c2 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Fri, 10 Dec 2021 20:08:59 +0900 Subject: [PATCH 080/118] Move playlist item beatmap population to MatchSubScreen --- .../StatefulMultiplayerClientTest.cs | 4 -- .../TestSceneAllPlayersQueueMode.cs | 16 ++--- .../Multiplayer/TestSceneHostOnlyQueueMode.cs | 10 +-- .../Multiplayer/TestSceneMultiplayer.cs | 2 +- .../Multiplayer/TestSceneMultiplayerPlayer.cs | 7 +- .../TestSceneMultiplayerQueueList.cs | 24 +++---- .../Online/Multiplayer/MultiplayerClient.cs | 65 +++++-------------- .../Multiplayer/OnlineMultiplayerClient.cs | 2 +- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 2 +- .../Match/MultiplayerReadyButton.cs | 10 ++- .../Multiplayer/MultiplayerMatchSongSelect.cs | 6 +- .../Multiplayer/MultiplayerMatchSubScreen.cs | 49 ++++++++++++-- .../Multiplayer/TestMultiplayerClient.cs | 2 +- 13 files changed, 103 insertions(+), 96 deletions(-) diff --git a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs index 42305ccd81..bc0041e2c2 100644 --- a/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs +++ b/osu.Game.Tests/NonVisual/Multiplayer/StatefulMultiplayerClientTest.cs @@ -45,8 +45,6 @@ namespace osu.Game.Tests.NonVisual.Multiplayer AddRepeatStep("add some users", () => Client.AddUser(new APIUser { Id = id++ }), 5); checkPlayingUserCount(0); - AddAssert("playlist item is available", () => Client.CurrentMatchPlayingItem.Value != null); - changeState(3, MultiplayerUserState.WaitingForLoad); checkPlayingUserCount(3); @@ -64,8 +62,6 @@ namespace osu.Game.Tests.NonVisual.Multiplayer AddStep("leave room", () => Client.LeaveRoom()); checkPlayingUserCount(0); - - AddAssert("playlist item is null", () => Client.CurrentMatchPlayingItem.Value == null); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index 8373979308..4bf38c9ff8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFirstItemSelectedByDefault() { - AddAssert("first item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); + AddAssert("first item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID); } [Test] @@ -27,13 +27,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { addItem(() => OtherBeatmap); AddAssert("playlist has 2 items", () => Client.APIRoom?.Playlist.Count == 2); - AddAssert("last playlist item is different", () => Client.APIRoom?.Playlist[1].Beatmap.Value.OnlineID == OtherBeatmap.OnlineID); addItem(() => InitialBeatmap); AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3); - AddAssert("last playlist item is different", () => Client.APIRoom?.Playlist[2].Beatmap.Value.OnlineID == InitialBeatmap.OnlineID); - AddAssert("first item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); + AddAssert("first item still selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID); } [Test] @@ -43,7 +41,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("playlist has only one item", () => Client.APIRoom?.Playlist.Count == 1); AddAssert("playlist item is expired", () => Client.APIRoom?.Playlist[0].Expired == true); - AddAssert("last item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); + AddAssert("last item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID); } [Test] @@ -55,12 +53,12 @@ namespace osu.Game.Tests.Visual.Multiplayer RunGameplay(); AddAssert("first item expired", () => Client.APIRoom?.Playlist[0].Expired == true); - AddAssert("next item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[1].ID); + AddAssert("next item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[1].ID); RunGameplay(); AddAssert("second item expired", () => Client.APIRoom?.Playlist[1].Expired == true); - AddAssert("next item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[2].ID); + AddAssert("next item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[2].ID); } [Test] @@ -74,8 +72,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("change queue mode", () => Client.ChangeSettings(queueMode: QueueMode.HostOnly)); AddAssert("playlist has 3 items", () => Client.APIRoom?.Playlist.Count == 3); - AddAssert("playlist item is the other beatmap", () => Client.CurrentMatchPlayingItem.Value?.BeatmapID == OtherBeatmap.OnlineID); - AddAssert("playlist item is not expired", () => Client.APIRoom?.Playlist[1].Expired == false); + AddAssert("item 2 is not expired", () => Client.APIRoom?.Playlist[1].Expired == false); + AddAssert("current item is the other beatmap", () => Client.Room?.Settings.PlaylistItemId == 2); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index ccac3de304..c06bd8fdbe 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestFirstItemSelectedByDefault() { - AddAssert("first item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); + AddAssert("first item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID); } [Test] @@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { selectNewItem(() => InitialBeatmap); - AddAssert("playlist item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); + AddAssert("playlist item still selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID); } [Test] @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { selectNewItem(() => OtherBeatmap); - AddAssert("playlist item still selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[0].ID); + AddAssert("playlist item still selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[0].ID); } [Test] @@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("playlist contains two items", () => Client.APIRoom?.Playlist.Count == 2); AddAssert("first playlist item expired", () => Client.APIRoom?.Playlist[0].Expired == true); AddAssert("second playlist item not expired", () => Client.APIRoom?.Playlist[1].Expired == false); - AddAssert("second playlist item selected", () => Client.CurrentMatchPlayingItem.Value?.ID == Client.APIRoom?.Playlist[1].ID); + AddAssert("second playlist item selected", () => Client.Room?.Settings.PlaylistItemId == Client.APIRoom?.Playlist[1].ID); } [Test] @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); - AddUntilStep("selected item is new beatmap", () => Client.CurrentMatchPlayingItem.Value?.Beatmap.Value?.OnlineID == otherBeatmap.OnlineID); + AddUntilStep("selected item is new beatmap", () => (CurrentSubScreen as MultiplayerMatchSubScreen)?.SelectedItem.Value.BeatmapID == otherBeatmap.OnlineID); } private void addItem(Func beatmap) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 5eb0abc830..f20ab914e2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -416,7 +416,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerScreenStack.CurrentScreen).CurrentSubScreen; - ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.CurrentMatchPlayingItem.Value); + ((MultiplayerMatchSubScreen)currentSubScreen).OpenSongSelection(client.Room?.Settings.PlaylistItemId); }); AddUntilStep("wait for song select", () => this.ChildrenOfType().FirstOrDefault()?.BeatmapSetsLoaded == true); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs index 5708b2f789..73f2ed5b39 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerPlayer.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Online.Multiplayer; +using osu.Game.Online.Rooms; using osu.Game.Rulesets.Osu; using osu.Game.Screens.OnlinePlay.Multiplayer; @@ -27,7 +28,11 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("initialise gameplay", () => { - Stack.Push(player = new MultiplayerPlayer(Client.APIRoom, Client.CurrentMatchPlayingItem.Value, Client.Room?.Users.ToArray())); + Stack.Push(player = new MultiplayerPlayer(Client.APIRoom, new PlaylistItem + { + Beatmap = { Value = Beatmap.Value.BeatmapInfo }, + Ruleset = { Value = Beatmap.Value.BeatmapInfo.Ruleset } + }, Client.Room?.Users.ToArray())); }); AddUntilStep("wait for player to be current", () => player.IsCurrentScreen() && player.IsLoaded); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs index a2b2da0aec..61a92c32a4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerQueueList.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Platform; @@ -27,11 +28,12 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneMultiplayerQueueList : MultiplayerTestScene { - private MultiplayerQueueList playlist; + private readonly Bindable selectedItem = new Bindable(); [Cached(typeof(UserLookupCache))] private readonly TestUserLookupCache userLookupCache = new TestUserLookupCache(); + private MultiplayerQueueList playlist; private BeatmapManager beatmaps; private RulesetStore rulesets; private BeatmapSetInfo importedSet; @@ -50,12 +52,14 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("create playlist", () => { + selectedItem.Value = null; + Child = playlist = new MultiplayerQueueList { Anchor = Anchor.Centre, Origin = Anchor.Centre, Size = new Vector2(500, 300), - SelectedItem = { BindTarget = Client.CurrentMatchPlayingItem }, + SelectedItem = { BindTarget = selectedItem }, Items = { BindTarget = Client.APIRoom!.Playlist } }; }); @@ -107,22 +111,14 @@ namespace osu.Game.Tests.Visual.Multiplayer 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); + + AddStep("select item 0", () => selectedItem.Value = playlist.ChildrenOfType>().ElementAt(0).Model); 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); - + AddStep("select item 1", () => selectedItem.Value = playlist.ChildrenOfType>().ElementAt(1).Model); + assertDeleteButtonVisibility(0, true); assertDeleteButtonVisibility(1, false); } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index 55b4def908..f366de557f 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -95,8 +95,6 @@ namespace osu.Game.Online.Multiplayer protected readonly BindableList PlayingUserIds = new BindableList(); - public readonly Bindable CurrentMatchPlayingItem = new Bindable(); - /// /// The corresponding to the local player, if available. /// @@ -162,9 +160,6 @@ namespace osu.Game.Online.Multiplayer var joinedRoom = await JoinRoom(room.RoomID.Value.Value, password ?? room.Password.Value).ConfigureAwait(false); Debug.Assert(joinedRoom != null); - // Populate playlist items. - 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); await Task.WhenAll(joinedRoom.Users.Select(PopulateUser)).ConfigureAwait(false); @@ -176,7 +171,7 @@ namespace osu.Game.Online.Multiplayer APIRoom = room; APIRoom.Playlist.Clear(); - APIRoom.Playlist.AddRange(playlistItems); + APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem)); Debug.Assert(LocalUser != null); addUserToAPIRoom(LocalUser); @@ -219,7 +214,6 @@ namespace osu.Game.Online.Multiplayer { APIRoom = null; Room = null; - CurrentMatchPlayingItem.Value = null; PlayingUserIds.Clear(); RoomUpdated?.Invoke(); @@ -477,28 +471,7 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); Debug.Assert(Room != null); - Scheduler.Add(() => - { - // ensure the new selected item is populated immediately. - var playlistItem = APIRoom.Playlist.Single(p => p.ID == newSettings.PlaylistItemId); - - if (playlistItem != null) - { - GetAPIBeatmap(playlistItem.BeatmapID).ContinueWith(b => - { - // 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(() => - { - if (success) - playlistItem.Beatmap.Value = b.Result; - - updateLocalRoomSettings(newSettings); - }); - }); - } - }); + Scheduler.Add(() => updateLocalRoomSettings(newSettings)); return Task.CompletedTask; } @@ -653,12 +626,10 @@ namespace osu.Game.Online.Multiplayer return Task.CompletedTask; } - public async Task PlaylistItemAdded(MultiplayerPlaylistItem item) + public Task PlaylistItemAdded(MultiplayerPlaylistItem item) { if (Room == null) - return; - - var playlistItem = await createPlaylistItem(item, true).ConfigureAwait(false); + return Task.CompletedTask; Scheduler.Add(() => { @@ -668,11 +639,13 @@ namespace osu.Game.Online.Multiplayer Debug.Assert(APIRoom != null); Room.Playlist.Add(item); - APIRoom.Playlist.Add(playlistItem); + APIRoom.Playlist.Add(createPlaylistItem(item)); ItemAdded?.Invoke(item); RoomUpdated?.Invoke(); }); + + return Task.CompletedTask; } public Task PlaylistItemRemoved(long playlistItemId) @@ -697,12 +670,10 @@ namespace osu.Game.Online.Multiplayer return Task.CompletedTask; } - public async Task PlaylistItemChanged(MultiplayerPlaylistItem item) + public Task PlaylistItemChanged(MultiplayerPlaylistItem item) { if (Room == null) - return; - - var playlistItem = await createPlaylistItem(item, true).ConfigureAwait(false); + return Task.CompletedTask; Scheduler.Add(() => { @@ -715,15 +686,13 @@ namespace osu.Game.Online.Multiplayer int existingIndex = APIRoom.Playlist.IndexOf(APIRoom.Playlist.Single(existing => existing.ID == item.ID)); APIRoom.Playlist.RemoveAt(existingIndex); - APIRoom.Playlist.Insert(existingIndex, playlistItem); - - // If the currently-selected item was the one that got replaced, update the selected item to the new one. - if (CurrentMatchPlayingItem.Value?.ID == playlistItem.ID) - CurrentMatchPlayingItem.Value = playlistItem; + APIRoom.Playlist.Insert(existingIndex, createPlaylistItem(item)); ItemChanged?.Invoke(item); RoomUpdated?.Invoke(); }); + + return Task.CompletedTask; } /// @@ -752,12 +721,11 @@ namespace osu.Game.Online.Multiplayer APIRoom.Password.Value = Room.Settings.Password; APIRoom.Type.Value = Room.Settings.MatchType; APIRoom.QueueMode.Value = Room.Settings.QueueMode; - RoomUpdated?.Invoke(); - CurrentMatchPlayingItem.Value = APIRoom.Playlist.SingleOrDefault(p => p.ID == settings.PlaylistItemId); + RoomUpdated?.Invoke(); } - private async Task createPlaylistItem(MultiplayerPlaylistItem item, bool populateBeatmapImmediately) + private PlaylistItem createPlaylistItem(MultiplayerPlaylistItem item) { var ruleset = Rulesets.GetRuleset(item.RulesetID); @@ -779,9 +747,6 @@ 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 (populateBeatmapImmediately) - playlistItem.Beatmap.Value = await GetAPIBeatmap(item.BeatmapID).ConfigureAwait(false); - return playlistItem; } @@ -791,7 +756,7 @@ namespace osu.Game.Online.Multiplayer /// The beatmap ID. /// A token to cancel the request. /// The retrieval task. - protected abstract Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default); + public abstract Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default); /// /// For the provided user ID, update whether the user is included in . diff --git a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs index d268d2bf69..f911ef3121 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -178,7 +178,7 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), playlistItemId); } - protected override Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default) + public override Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default) { return beatmapLookupCache.GetBeatmapAsync(beatmapId, cancellationToken); } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 184ac2c563..55f5622afd 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.OnlinePlay.Match public abstract class RoomSubScreen : OnlinePlaySubScreen, IPreviewTrackOwner { [Cached(typeof(IBindable))] - protected readonly Bindable SelectedItem = new Bindable(); + public readonly Bindable SelectedItem = new Bindable(); public override bool? AllowTrackAdjustments => true; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs index 874113d859..06959d942f 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Match/MultiplayerReadyButton.cs @@ -63,6 +63,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match sampleUnready = audio.Samples.Get(@"Multiplayer/player-unready"); } + protected override void LoadComplete() + { + base.LoadComplete(); + + SelectedItem.BindValueChanged(_ => updateState()); + } + protected override void OnRoomUpdated() { base.OnRoomUpdated(); @@ -104,7 +111,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match bool enableButton = Room?.State == MultiplayerRoomState.Open - && Client.CurrentMatchPlayingItem.Value?.Expired == false + && SelectedItem.Value?.ID == Room.Settings.PlaylistItemId + && !Room.Playlist.Single(i => i.ID == Room.Settings.PlaylistItemId).Expired && !operationInProgress.Value; // When the local user is the host and spectating the match, the "start match" state should be enabled if any users are ready. diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs index 8d3686dd6d..073497e1ce 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSongSelect.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer [Resolved] private MultiplayerClient client { get; set; } - private readonly PlaylistItem itemToEdit; + private readonly long? itemToEdit; private LoadingLayer loadingLayer; @@ -36,7 +36,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer /// 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, PlaylistItem itemToEdit = null, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null) + public MultiplayerMatchSongSelect(Room room, long? itemToEdit = null, WorkingBeatmap beatmap = null, RulesetInfo ruleset = null) : base(room) { this.itemToEdit = itemToEdit; @@ -67,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer var multiplayerItem = new MultiplayerPlaylistItem { - ID = itemToEdit?.ID ?? 0, + ID = itemToEdit ?? 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 946c749db3..3543836d8d 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -67,8 +68,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer { base.LoadComplete(); - SelectedItem.BindTo(client.CurrentMatchPlayingItem); - BeatmapAvailability.BindValueChanged(updateBeatmapAvailability, true); UserMods.BindValueChanged(onUserModsChanged); @@ -147,7 +146,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer new MultiplayerPlaylist { RelativeSizeAxes = Axes.Both, - RequestEdit = OpenSongSelection + RequestEdit = item => OpenSongSelection(item.ID) } }, new[] @@ -224,7 +223,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer /// Opens the song selection screen to add or edit an item. /// /// An optional playlist item to edit. If null, a new item will be added instead. - internal void OpenSongSelection([CanBeNull] PlaylistItem itemToEdit = null) + internal void OpenSongSelection(long? itemToEdit = null) { if (!this.IsCurrentScreen()) return; @@ -389,11 +388,48 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; } + updateCurrentItem(); + addItemButton.Alpha = client.IsHost || Room.QueueMode.Value != QueueMode.HostOnly ? 1 : 0; Scheduler.AddOnce(UpdateMods); } + private void updateCurrentItem() + { + Debug.Assert(client.Room != null); + + var expectedSelectedItem = Room.Playlist.SingleOrDefault(i => i.ID == client.Room.Settings.PlaylistItemId); + + if (expectedSelectedItem == null) + return; + + if (SelectedItem.Value?.Equals(expectedSelectedItem) == true && expectedSelectedItem.Beatmap.Value != null) + return; + + SelectedItem.Value = null; + + if (expectedSelectedItem.Beatmap.Value == null) + { + Task.Run(async () => + { + var beatmap = await client.GetAPIBeatmap(expectedSelectedItem.BeatmapID).ConfigureAwait(false); + + Schedule(() => + { + expectedSelectedItem.Beatmap.Value = beatmap; + + if (Room.Playlist.SingleOrDefault(i => i.ID == client.Room?.Settings.PlaylistItemId)?.Equals(expectedSelectedItem) == true) + applyCurrentItem(); + }); + }); + } + else + applyCurrentItem(); + + void applyCurrentItem() => SelectedItem.Value = expectedSelectedItem; + } + private void handleRoomLost() => Schedule(() => { if (this.IsCurrentScreen()) @@ -446,6 +482,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (!this.IsCurrentScreen()) return; + if (client.Room == null) + return; + if (!client.IsHost) { // todo: should handle this when the request queue is implemented. @@ -454,7 +493,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer return; } - this.Push(new MultiplayerMatchSongSelect(Room, SelectedItem.Value, beatmap, ruleset)); + this.Push(new MultiplayerMatchSongSelect(Room, client.Room.Settings.PlaylistItemId, beatmap, ruleset)); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index d22f0415e6..d20d6b1d37 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -376,7 +376,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public override Task RemovePlaylistItem(long playlistItemId) => RemoveUserPlaylistItem(api.LocalUser.Value.OnlineID, playlistItemId); - protected override Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default) + public override Task GetAPIBeatmap(int beatmapId, CancellationToken cancellationToken = default) { IBeatmapSetInfo? set = roomManager.ServerSideRooms.SelectMany(r => r.Playlist) .FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet From 63a017bc8e43bbdc61db396047e100eaf9814d80 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 11 Dec 2021 19:33:37 +0900 Subject: [PATCH 081/118] 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 25b274c323737c73de6d45d55a57096eec2faca8 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 11 Dec 2021 22:47:08 +0900 Subject: [PATCH 082/118] Fix starting gameplay too early after import --- .../Multiplayer/TestSceneMultiplayer.cs | 40 +++++++++++-------- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 8 ++-- .../Multiplayer/MultiplayerMatchSubScreen.cs | 21 ++++++++-- 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index f20ab914e2..bc2902480d 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -391,9 +391,9 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddStep("set user ready", () => client.ChangeState(MultiplayerUserState.Ready)); - AddStep("delete beatmap", () => beatmaps.Delete(importedSet)); + pressReadyButton(); + AddStep("delete beatmap", () => beatmaps.Delete(importedSet)); AddUntilStep("user state is idle", () => client.LocalUser?.State == MultiplayerUserState.Idle); } @@ -413,6 +413,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); + pressReadyButton(); + AddStep("Enter song select", () => { var currentSubScreen = ((Screens.OnlinePlay.Multiplayer.Multiplayer)multiplayerScreenStack.CurrentScreen).CurrentSubScreen; @@ -592,19 +594,8 @@ namespace osu.Game.Tests.Visual.Multiplayer } }); - AddUntilStep("wait for ready button to be enabled", () => readyButton.ChildrenOfType().Single().Enabled.Value); - - AddStep("click ready button", () => - { - InputManager.MoveMouseTo(readyButton); - InputManager.Click(MouseButton.Left); - }); - - AddUntilStep("wait for player to be ready", () => client.Room?.Users[0].State == MultiplayerUserState.Ready); - AddUntilStep("wait for ready button to be enabled", () => readyButton.ChildrenOfType().Single().Enabled.Value); - - AddStep("click start button", () => InputManager.Click(MouseButton.Left)); - + pressReadyButton(); + pressReadyButton(); AddUntilStep("wait for player", () => multiplayerScreenStack.CurrentScreen is Player); // Gameplay runs in real-time, so we need to incrementally check if gameplay has finished in order to not time out. @@ -665,7 +656,24 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } - private MultiplayerReadyButton readyButton => this.ChildrenOfType().Single(); + private ReadyButton readyButton => this.ChildrenOfType().Single(); + + private void pressReadyButton() + { + AddUntilStep("wait for ready button to be enabled", () => readyButton.Enabled.Value); + + MultiplayerUserState lastState = MultiplayerUserState.Idle; + + AddStep("click ready button", () => + { + lastState = client.LocalUser?.State ?? MultiplayerUserState.Idle; + + InputManager.MoveMouseTo(readyButton); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for state change", () => client.LocalUser?.State != lastState); + } private void createRoom(Func room) { diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 55f5622afd..e5a6915ca3 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -302,7 +302,7 @@ namespace osu.Game.Screens.OnlinePlay.Match public override void OnResuming(IScreen last) { base.OnResuming(last); - updateWorkingBeatmap(); + UpdateWorkingBeatmap(); beginHandlingTrack(); Scheduler.AddOnce(UpdateMods); } @@ -345,7 +345,7 @@ namespace osu.Game.Screens.OnlinePlay.Match private void selectedItemChanged() { - updateWorkingBeatmap(); + UpdateWorkingBeatmap(); var selected = SelectedItem.Value; @@ -374,9 +374,9 @@ namespace osu.Game.Screens.OnlinePlay.Match } } - private void beatmapUpdated(BeatmapSetInfo set) => Schedule(updateWorkingBeatmap); + private void beatmapUpdated(BeatmapSetInfo set) => Schedule(UpdateWorkingBeatmap); - private void updateWorkingBeatmap() + protected virtual void UpdateWorkingBeatmap() { var beatmap = SelectedItem.Value?.Beatmap.Value; diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 3543836d8d..b517142069 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -15,6 +15,7 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Extensions; using osu.Game.Online; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -326,10 +327,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client.LocalUser?.State == MultiplayerUserState.Ready) client.ChangeState(MultiplayerUserState.Idle); } - else + } + + protected override void UpdateWorkingBeatmap() + { + var lastBeatmap = Beatmap.Value; + + base.UpdateWorkingBeatmap(); + + if (Beatmap.Value.BeatmapInfo.MatchesOnlineID(lastBeatmap.BeatmapInfo)) + return; + + if (!Beatmap.Value.BeatmapInfo.MatchesOnlineID(SelectedItem.Value?.Beatmap.Value)) + return; + + if (client.LocalUser?.State == MultiplayerUserState.Spectating + && (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing)) { - if (client.LocalUser?.State == MultiplayerUserState.Spectating && (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing)) - onLoadRequested(); + onLoadRequested(); } } From ece2cddb7fd11171450b336bcf049efff3b28d38 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 11 Dec 2021 22:51:20 +0900 Subject: [PATCH 083/118] Fix DrawableRoomPlaylistItem lookup interfering with tests --- .../Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 8042f7d772..a877c6aca9 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -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; @@ -90,6 +92,10 @@ namespace osu.Game.Screens.OnlinePlay [Resolved] private UserLookupCache userLookupCache { get; set; } + [CanBeNull] + [Resolved(CanBeNull = true)] + private MultiplayerClient multiplayerClient { get; set; } + [Resolved] private BeatmapLookupCache beatmapLookupCache { get; set; } @@ -157,7 +163,13 @@ namespace osu.Game.Screens.OnlinePlay if (Item.Beatmap.Value == null) { - var foundBeatmap = await beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ConfigureAwait(false); + IBeatmapInfo foundBeatmap; + + if (multiplayerClient != null) + foundBeatmap = await multiplayerClient.GetAPIBeatmap(Item.BeatmapID).ConfigureAwait(false); + else + foundBeatmap = await beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ConfigureAwait(false); + Schedule(() => Item.Beatmap.Value = foundBeatmap); } } From 99cd36d2f611eea89ae74591b6ba37fb16cac9f9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 11 Dec 2021 22:52:06 +0900 Subject: [PATCH 084/118] Resolve some test failures due to async population --- .../Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs | 2 +- osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs index 4bf38c9ff8..a5744f9986 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneAllPlayersQueueMode.cs @@ -80,7 +80,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public void TestCorrectItemSelectedAfterNewItemAdded() { addItem(() => OtherBeatmap); - AddAssert("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); + AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID); } private void addItem(Func beatmap) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index c06bd8fdbe..ac1efdda4a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap())); AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen); - AddUntilStep("selected item is new beatmap", () => (CurrentSubScreen as MultiplayerMatchSubScreen)?.SelectedItem.Value.BeatmapID == otherBeatmap.OnlineID); + AddUntilStep("selected item is new beatmap", () => (CurrentSubScreen as MultiplayerMatchSubScreen)?.SelectedItem.Value?.BeatmapID == otherBeatmap.OnlineID); } private void addItem(Func beatmap) From d6c08fae48dccfd80d2bd003f3b8119130a171ca Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 11 Dec 2021 23:08:17 +0900 Subject: [PATCH 085/118] Fix incorrect global beatmap with availability changes --- osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs | 7 +++++++ .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 2 ++ 2 files changed, 9 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index e5a6915ca3..6b395058fa 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Online; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.Mods; @@ -378,6 +379,12 @@ namespace osu.Game.Screens.OnlinePlay.Match protected virtual void UpdateWorkingBeatmap() { + if (BeatmapAvailability.Value.State != DownloadState.LocallyAvailable) + { + Beatmap.Value = beatmapManager.GetWorkingBeatmap(null); + return; + } + var beatmap = SelectedItem.Value?.Beatmap.Value; // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index b517142069..65190101b8 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -327,6 +327,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client.LocalUser?.State == MultiplayerUserState.Ready) client.ChangeState(MultiplayerUserState.Idle); } + + UpdateWorkingBeatmap(); } protected override void UpdateWorkingBeatmap() From 9f792fec49366e8ac3cc9d31a4f8c0bc214d50c6 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 12 Dec 2021 16:11:48 +0900 Subject: [PATCH 086/118] Fix test failures from async item loading --- .../Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs index ac1efdda4a..c7eeff81fe 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneHostOnlyQueueMode.cs @@ -9,6 +9,7 @@ using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; using osu.Game.Screens.OnlinePlay; using osu.Game.Screens.OnlinePlay.Multiplayer; +using osu.Game.Screens.OnlinePlay.Multiplayer.Match.Playlist; using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer @@ -85,6 +86,12 @@ namespace osu.Game.Tests.Visual.Multiplayer private void selectNewItem(Func beatmap) { + AddUntilStep("wait for playlist panels to load", () => + { + var queueList = this.ChildrenOfType().Single(); + return queueList.ChildrenOfType().Count() == queueList.Items.Count; + }); + AddStep("click edit button", () => { InputManager.MoveMouseTo(this.ChildrenOfType().First()); From a6e77f172d30e4f4f0126f20f0ad95197549906a Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sun, 12 Dec 2021 16:30:37 +0900 Subject: [PATCH 087/118] Add some comments --- .../OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 65190101b8..4fa21cb847 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -337,9 +337,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer base.UpdateWorkingBeatmap(); + // Nothing to do if the beatmap hasn't changed. if (Beatmap.Value.BeatmapInfo.MatchesOnlineID(lastBeatmap.BeatmapInfo)) return; + // The selected item is nulled during the beatmap query. During this, the working beatmap will be the dummy beatmap. + // We don't want to enter spectate mode with the dummy beatmap. if (!Beatmap.Value.BeatmapInfo.MatchesOnlineID(SelectedItem.Value?.Beatmap.Value)) return; @@ -421,9 +424,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (expectedSelectedItem == null) return; + // There's no reason to renew the selected item if its content hasn't changed. if (SelectedItem.Value?.Equals(expectedSelectedItem) == true && expectedSelectedItem.Beatmap.Value != null) return; + // Clear the selected item while the lookup is performed, so components like the ready button can enter their disabled states. SelectedItem.Value = null; if (expectedSelectedItem.Beatmap.Value == null) From 2cd2b10ce1b1fd705a5d63fe007aba821745a5d4 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Dec 2021 06:54:57 +0900 Subject: [PATCH 088/118] 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 089/118] 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 090/118] 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 091/118] 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 c097dc80488ff45357b33eb907c12bb8a34629db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 13:39:32 +0900 Subject: [PATCH 092/118] Add note about reasoning behind `MultiplayerClient.GetAPIBeatmap` call --- osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index a877c6aca9..e1f7ea5e92 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -166,6 +166,8 @@ namespace osu.Game.Screens.OnlinePlay IBeatmapInfo foundBeatmap; if (multiplayerClient != null) + // This call can eventually go away (and use the else case below). + // Currently required only due to the method being overridden to provide special behaviour in tests. foundBeatmap = await multiplayerClient.GetAPIBeatmap(Item.BeatmapID).ConfigureAwait(false); else foundBeatmap = await beatmapLookupCache.GetBeatmapAsync(Item.BeatmapID).ConfigureAwait(false); From cac684c0448d69f5a3d427c7b0a375e5266dd93a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 14:46:49 +0900 Subject: [PATCH 093/118] Improve appearance of player-wide background after failing with low background dim --- osu.Game/Screens/Play/FailAnimation.cs | 10 ++++++++++ osu.Game/Screens/Play/Player.cs | 2 ++ 2 files changed, 12 insertions(+) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index 193e1e4129..aa777f18a8 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Game.Rulesets.UI; using System; using System.Collections.Generic; +using JetBrains.Annotations; using ManagedBass.Fx; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; @@ -18,6 +19,7 @@ using osu.Framework.Utils; using osu.Game.Audio.Effects; using osu.Game.Beatmaps; using osu.Game.Configuration; +using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osuTK; using osuTK.Graphics; @@ -58,6 +60,12 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, }; + /// + /// The player screen background, used to adjust appearance on failing. + /// + [CanBeNull] + public BackgroundScreen Background { private get; set; } + public FailAnimation(DrawableRuleset drawableRuleset) { this.drawableRuleset = drawableRuleset; @@ -136,6 +144,8 @@ namespace osu.Game.Screens.Play Content.ScaleTo(0.85f, duration, Easing.OutQuart); Content.RotateTo(1, duration, Easing.OutQuart); Content.FadeColour(Color4.Gray, duration); + + Background?.FadeColour(OsuColour.Gray(0.3f), 60); } public void RemoveFilters(bool resetTrackFrequency = true) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index e2896c174f..745e1f9e7c 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -921,6 +921,8 @@ namespace osu.Game.Screens.Play b.IsBreakTime.BindTo(breakTracker.IsBreakTime); b.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground); + + failAnimationLayer.Background = b; }); HUDOverlay.IsBreakTime.BindTo(breakTracker.IsBreakTime); From 4cac87e93323a46752fb2353ed41488ba0f332b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 15:26:32 +0900 Subject: [PATCH 094/118] 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 095/118] 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 70045494babd97eec564f624e3a5a38a1019ebdc Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Dec 2021 16:09:53 +0900 Subject: [PATCH 096/118] Re-simplify code by removing BeatmapManager event instead --- .../Screens/OnlinePlay/Match/RoomSubScreen.cs | 36 ++++--------------- .../Multiplayer/MultiplayerMatchSubScreen.cs | 24 ++----------- 2 files changed, 9 insertions(+), 51 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 6b395058fa..a560d85b7d 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; -using osu.Game.Online; using osu.Game.Online.Rooms; using osu.Game.Overlays; using osu.Game.Overlays.Mods; @@ -68,7 +67,7 @@ namespace osu.Game.Screens.OnlinePlay.Match protected OnlinePlayScreen ParentScreen { get; private set; } [Cached] - private OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker { get; set; } + private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker(); protected IBindable BeatmapAvailability => beatmapAvailabilityTracker.Availability; @@ -91,11 +90,6 @@ namespace osu.Game.Screens.OnlinePlay.Match Padding = new MarginPadding { Top = Header.HEIGHT }; - beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker - { - SelectedItem = { BindTarget = SelectedItem } - }; - RoomId.BindTo(room.RoomID); } @@ -248,10 +242,10 @@ namespace osu.Game.Screens.OnlinePlay.Match }, true); SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged)); - - beatmapManager.ItemUpdated += beatmapUpdated; - UserMods.BindValueChanged(_ => Scheduler.AddOnce(UpdateMods)); + + beatmapAvailabilityTracker.SelectedItem.BindTo(SelectedItem); + beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateWorkingBeatmap()); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -303,7 +297,7 @@ namespace osu.Game.Screens.OnlinePlay.Match public override void OnResuming(IScreen last) { base.OnResuming(last); - UpdateWorkingBeatmap(); + updateWorkingBeatmap(); beginHandlingTrack(); Scheduler.AddOnce(UpdateMods); } @@ -346,7 +340,7 @@ namespace osu.Game.Screens.OnlinePlay.Match private void selectedItemChanged() { - UpdateWorkingBeatmap(); + updateWorkingBeatmap(); var selected = SelectedItem.Value; @@ -375,16 +369,8 @@ namespace osu.Game.Screens.OnlinePlay.Match } } - private void beatmapUpdated(BeatmapSetInfo set) => Schedule(UpdateWorkingBeatmap); - - protected virtual void UpdateWorkingBeatmap() + private void updateWorkingBeatmap() { - if (BeatmapAvailability.Value.State != DownloadState.LocallyAvailable) - { - Beatmap.Value = beatmapManager.GetWorkingBeatmap(null); - return; - } - var beatmap = SelectedItem.Value?.Beatmap.Value; // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info @@ -450,14 +436,6 @@ namespace osu.Game.Screens.OnlinePlay.Match /// The room to change the settings of. protected abstract RoomSettingsOverlay CreateRoomSettingsOverlay(Room room); - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - if (beatmapManager != null) - beatmapManager.ItemUpdated -= beatmapUpdated; - } - public class UserModSelectButton : PurpleTriangleButton { } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs index 4fa21cb847..6895608c8e 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerMatchSubScreen.cs @@ -15,7 +15,6 @@ using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Configuration; -using osu.Game.Extensions; using osu.Game.Online; using osu.Game.Online.Multiplayer; using osu.Game.Online.Rooms; @@ -327,27 +326,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer if (client.LocalUser?.State == MultiplayerUserState.Ready) client.ChangeState(MultiplayerUserState.Idle); } - - UpdateWorkingBeatmap(); - } - - protected override void UpdateWorkingBeatmap() - { - var lastBeatmap = Beatmap.Value; - - base.UpdateWorkingBeatmap(); - - // Nothing to do if the beatmap hasn't changed. - if (Beatmap.Value.BeatmapInfo.MatchesOnlineID(lastBeatmap.BeatmapInfo)) - return; - - // The selected item is nulled during the beatmap query. During this, the working beatmap will be the dummy beatmap. - // We don't want to enter spectate mode with the dummy beatmap. - if (!Beatmap.Value.BeatmapInfo.MatchesOnlineID(SelectedItem.Value?.Beatmap.Value)) - return; - - if (client.LocalUser?.State == MultiplayerUserState.Spectating - && (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing)) + else if (client.LocalUser?.State == MultiplayerUserState.Spectating + && (client.Room?.State == MultiplayerRoomState.WaitingForLoad || client.Room?.State == MultiplayerRoomState.Playing)) { onLoadRequested(); } From b0d14526eac084234a7b4c2e1d30f63a36262f35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Dec 2021 16:34:48 +0900 Subject: [PATCH 097/118] 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 098/118] 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 099/118] 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 100/118] 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 101/118] 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 c87ff82c1cde3af45c173fcb264de999340b743c Mon Sep 17 00:00:00 2001 From: rumoi Date: Tue, 14 Dec 2021 09:25:29 +1300 Subject: [PATCH 102/118] calculateRhythmBonus performance fix. --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index d00d89a577..30d989cfba 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -55,7 +55,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills bool firstDeltaSwitch = false; - for (int i = Previous.Count - 2; i > 0; i--) + int rhythmStart = Math.Min(Previous.Count - 2, 0); + + while (rhythmStart < Previous.Count - 2 && current.StartTime - Previous[rhythmStart].StartTime < history_time_max) + rhythmStart++; + + for (int i = rhythmStart; i > 0; i--) { OsuDifficultyHitObject currObj = (OsuDifficultyHitObject)Previous[i - 1]; OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)Previous[i]; From 6e3558b2226d834bd57146dec9bcd197ab441c30 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 14 Dec 2021 06:38:57 +0900 Subject: [PATCH 103/118] Remove weird test --- .../TestScenePlaylistsRoomCreation.cs | 36 ------------------- 1 file changed, 36 deletions(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index c5287d4257..450e821ba6 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -109,42 +109,6 @@ namespace osu.Game.Tests.Visual.Playlists AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]); } - [Test] - public void TestBeatmapUpdatedOnReImport() - { - BeatmapSetInfo importedSet = null; - - AddStep("import altered beatmap", () => - { - IBeatmap beatmap = CreateBeatmap(new OsuRuleset().RulesetInfo); - - beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1; - - // intentionally increment online IDs to clash with import below. - beatmap.BeatmapInfo.OnlineID++; - beatmap.BeatmapInfo.BeatmapSet.OnlineID++; - - importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result.Value; - }); - - setupAndCreateRoom(room => - { - room.Name.Value = "my awesome room"; - room.Host.Value = API.LocalUser.Value; - room.Playlist.Add(new PlaylistItem - { - Beatmap = { Value = importedSet.Beatmaps[0] }, - Ruleset = { Value = new OsuRuleset().RulesetInfo } - }); - }); - - AddAssert("match has altered beatmap", () => match.Beatmap.Value.Beatmap.Difficulty.CircleSize == 1); - - importBeatmap(); - - AddAssert("match has original beatmap", () => match.Beatmap.Value.Beatmap.Difficulty.CircleSize != 1); - } - private void setupAndCreateRoom(Action room) { AddStep("setup room", () => room(SelectedRoom.Value)); From 7564658b5e01c780feadd62dccabbf1f8a051d31 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 14 Dec 2021 06:40:45 +0900 Subject: [PATCH 104/118] 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 { From 357a6613790bf82f6bb1fccd0742317bcbd96e70 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 14 Dec 2021 13:13:12 +0900 Subject: [PATCH 105/118] Fix storyboard sprites sometimes starting too early --- osu.Game/Storyboards/StoryboardSprite.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 6fb2f5994b..f941cec20c 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -33,10 +33,8 @@ namespace osu.Game.Storyboards foreach (var l in loops) { - if (!(l.EarliestDisplayedTime is double lEarliest)) - continue; - - earliestStartTime = Math.Min(earliestStartTime, lEarliest); + if (l.EarliestDisplayedTime != null) + earliestStartTime = Math.Min(earliestStartTime, l.StartTime); } if (earliestStartTime < double.MaxValue) From 7e576ae9d3adc3133c03437e705a9a8879997205 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 13:25:19 +0900 Subject: [PATCH 106/118] Add note about how the background colour is restored --- osu.Game/Screens/Play/FailAnimation.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index aa777f18a8..cfbfdc9966 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -145,6 +145,7 @@ namespace osu.Game.Screens.Play Content.RotateTo(1, duration, Easing.OutQuart); Content.FadeColour(Color4.Gray, duration); + // Will be restored by `ApplyToBackground` logic in `SongSelect`. Background?.FadeColour(OsuColour.Gray(0.3f), 60); } From eb3050b2ac9fd4245d9006eb300e844ed90c3178 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 14 Dec 2021 15:08:00 +0900 Subject: [PATCH 107/118] Fix incorrect test --- osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs index f5f17a0bc1..e03c8d7561 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneLeadIn.cs @@ -85,11 +85,12 @@ namespace osu.Game.Tests.Visual.Gameplay loopGroup.Scale.Add(Easing.None, -20000, -18000, 0, 1); var target = addEventToLoop ? loopGroup : sprite.TimelineGroup; - target.Alpha.Add(Easing.None, firstStoryboardEvent, firstStoryboardEvent + 500, 0, 1); + double targetTime = addEventToLoop ? 20000 : 0; + target.Alpha.Add(Easing.None, targetTime + firstStoryboardEvent, targetTime + firstStoryboardEvent + 500, 0, 1); // these should be ignored due to being in the future. sprite.TimelineGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1); - loopGroup.Alpha.Add(Easing.None, 18000, 20000, 0, 1); + loopGroup.Alpha.Add(Easing.None, 38000, 40000, 0, 1); storyboard.GetLayer("Background").Add(sprite); From b6a272e31a0fe8300761532c5688b3b0320d6e4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 16:10:53 +0900 Subject: [PATCH 108/118] Add failing test coverage of `BackgroundScreeNDefault`'s beatmap background tracking when active/non-active --- .../TestSceneBackgroundScreenDefault.cs | 134 +++++++++++++++++- .../Backgrounds/BackgroundScreenDefault.cs | 2 +- 2 files changed, 129 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs index bdd1b92c8d..476eadf9bb 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs @@ -5,8 +5,11 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Graphics.Textures; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics.Backgrounds; @@ -15,6 +18,7 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Screens; using osu.Game.Screens.Backgrounds; using osu.Game.Skinning; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual.Background { @@ -22,8 +26,7 @@ namespace osu.Game.Tests.Visual.Background public class TestSceneBackgroundScreenDefault : OsuTestScene { private BackgroundScreenStack stack; - private BackgroundScreenDefault screen; - + private TestBackgroundScreenDefault screen; private Graphics.Backgrounds.Background getCurrentBackground() => screen.ChildrenOfType().FirstOrDefault(); [Resolved] @@ -36,10 +39,95 @@ namespace osu.Game.Tests.Visual.Background public void SetUpSteps() { AddStep("create background stack", () => Child = stack = new BackgroundScreenStack()); - AddStep("push default screen", () => stack.Push(screen = new BackgroundScreenDefault(false))); + AddStep("push default screen", () => stack.Push(screen = new TestBackgroundScreenDefault())); AddUntilStep("wait for screen to load", () => screen.IsCurrentScreen()); } + [Test] + public void TestBeatmapBackgroundTracksBeatmap() + { + setSupporter(true); + setSourceMode(BackgroundSource.Beatmap); + + AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground()); + AddAssert("background changed", () => screen.CheckLastLoadChange() == true); + + Graphics.Backgrounds.Background last = null; + + AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackground)); + AddStep("store background", () => last = getCurrentBackground()); + + AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground()); + + AddUntilStep("wait for beatmap background to change", () => screen.CheckLastLoadChange() == true); + + AddUntilStep("background is new beatmap background", () => last != getCurrentBackground()); + AddStep("store background", () => last = getCurrentBackground()); + + AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground()); + + AddUntilStep("wait for beatmap background to change", () => screen.CheckLastLoadChange() == true); + AddUntilStep("background is new beatmap background", () => last != getCurrentBackground()); + } + + [Test] + public void TestBeatmapBackgroundTracksBeatmapWhenSuspended() + { + setSupporter(true); + setSourceMode(BackgroundSource.Beatmap); + + AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground()); + AddAssert("background changed", () => screen.CheckLastLoadChange() == true); + AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackground)); + + BackgroundScreenBeatmap nestedScreen = null; + + // of note, this needs to be a type that doesn't match BackgroundScreenDefault else it is silently not pushed by the background stack. + AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value))); + AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen()); + + AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null); + + AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground()); + + AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null); + + AddStep("pop screen back to top level", () => screen.MakeCurrent()); + + AddAssert("top level background changed", () => screen.CheckLastLoadChange() == true); + } + + [Test] + public void TestBeatmapBackgroundIgnoresNoChangeWhenSuspended() + { + BackgroundScreenBeatmap nestedScreen = null; + WorkingBeatmap originalWorking = null; + + setSupporter(true); + setSourceMode(BackgroundSource.Beatmap); + + AddStep("change beatmap", () => originalWorking = Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground()); + AddAssert("background changed", () => screen.CheckLastLoadChange() == true); + AddUntilStep("wait for beatmap background to be loaded", () => getCurrentBackground()?.GetType() == typeof(BeatmapBackground)); + + // of note, this needs to be a type that doesn't match BackgroundScreenDefault else it is silently not pushed by the background stack. + AddStep("push new background to stack", () => stack.Push(nestedScreen = new BackgroundScreenBeatmap(Beatmap.Value))); + AddUntilStep("wait for screen to load", () => nestedScreen.IsLoaded && nestedScreen.IsCurrentScreen()); + + // we're testing a case where scheduling may be used to avoid issues, so ensure the scheduler is no longer running. + AddUntilStep("wait for top level not alive", () => !screen.IsAlive); + + AddStep("change beatmap", () => Beatmap.Value = createTestWorkingBeatmapWithUniqueBackground()); + AddStep("change beatmap back", () => Beatmap.Value = originalWorking); + + AddAssert("top level background hasn't changed yet", () => screen.CheckLastLoadChange() == null); + + AddStep("pop screen back to top level", () => screen.MakeCurrent()); + + AddStep("top level screen is current", () => screen.IsCurrentScreen()); + AddAssert("top level background reused existing", () => screen.CheckLastLoadChange() == false); + } + [Test] public void TestBackgroundTypeSwitch() { @@ -96,13 +184,11 @@ namespace osu.Game.Tests.Visual.Background [Test] public void TestBackgroundCyclingOnDefaultSkin([Values] bool supporter) { - Graphics.Backgrounds.Background last = null; - setSourceMode(BackgroundSource.Skin); setSupporter(supporter); setDefaultSkin(); - AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground())?.GetType() == typeof(Graphics.Backgrounds.Background)); + AddUntilStep("wait for beatmap background to be loaded", () => (getCurrentBackground())?.GetType() == typeof(Graphics.Backgrounds.Background)); AddAssert("next cycles background", () => screen.Next()); // doesn't really need to be checked but might as well. @@ -120,6 +206,42 @@ namespace osu.Game.Tests.Visual.Background Id = API.LocalUser.Value.Id + 1, }); + private WorkingBeatmap createTestWorkingBeatmapWithUniqueBackground() => new UniqueBackgroundTestWorkingBeatmap(Audio); + + private class TestBackgroundScreenDefault : BackgroundScreenDefault + { + private bool? lastLoadTriggerCausedChange; + + public TestBackgroundScreenDefault() + : base(false) + { + } + + public override bool Next() + { + bool didChange = base.Next(); + lastLoadTriggerCausedChange = didChange; + return didChange; + } + + public bool? CheckLastLoadChange() + { + bool? lastChange = lastLoadTriggerCausedChange; + lastLoadTriggerCausedChange = null; + return lastChange; + } + } + + private class UniqueBackgroundTestWorkingBeatmap : TestWorkingBeatmap + { + public UniqueBackgroundTestWorkingBeatmap(AudioManager audioManager) + : base(new Beatmap(), null, audioManager) + { + } + + protected override Texture GetBackground() => new Texture(1, 1); + } + private void setCustomSkin() { // feign a skin switch. this doesn't do anything except force CurrentSkin to become a LegacySkin. diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 4a922c45b9..a1b1b52c14 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Backgrounds /// Request loading the next background. /// /// Whether a new background was queued for load. May return false if the current background is still valid. - public bool Next() + public virtual bool Next() { var nextBackground = createBackground(); From 25a056dfad45269be89e509a6554ad3c61ca02f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 16:35:18 +0900 Subject: [PATCH 109/118] Remove pointless/broken test steps These aren't accurate and are tested via a more accurate means directly above. --- .../Background/TestSceneBackgroundScreenDefault.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs index 476eadf9bb..1d3dd35e1d 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneBackgroundScreenDefault.cs @@ -166,19 +166,13 @@ namespace osu.Game.Tests.Visual.Background [TestCase(BackgroundSource.Skin, typeof(SkinBackground))] public void TestBackgroundDoesntReloadOnNoChange(BackgroundSource source, Type backgroundType) { - Graphics.Backgrounds.Background last = null; - setSourceMode(source); setSupporter(true); if (source == BackgroundSource.Skin) setCustomSkin(); - AddUntilStep("wait for beatmap background to be loaded", () => (last = getCurrentBackground())?.GetType() == backgroundType); + AddUntilStep("wait for beatmap background to be loaded", () => (getCurrentBackground())?.GetType() == backgroundType); AddAssert("next doesn't load new background", () => screen.Next() == false); - - // doesn't really need to be checked but might as well. - AddWaitStep("wait a bit", 5); - AddUntilStep("ensure same background instance", () => last == getCurrentBackground()); } [Test] @@ -190,10 +184,6 @@ namespace osu.Game.Tests.Visual.Background AddUntilStep("wait for beatmap background to be loaded", () => (getCurrentBackground())?.GetType() == typeof(Graphics.Backgrounds.Background)); AddAssert("next cycles background", () => screen.Next()); - - // doesn't really need to be checked but might as well. - AddWaitStep("wait a bit", 5); - AddUntilStep("ensure different background instance", () => last != getCurrentBackground()); } private void setSourceMode(BackgroundSource source) => From 8c6f50ddb1b3f7993485a06129803e442b6061fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 15:17:01 +0900 Subject: [PATCH 110/118] Fix `BackgroundScreenDefault` incorrectly updating current background after being inactive If the beatmap was changed but then reverted to the previously displayed map, the background may have displayed incorrectly on resuming. Closes #15804. --- .../Backgrounds/BackgroundScreenDefault.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index a1b1b52c14..452f033dcc 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -48,16 +48,19 @@ namespace osu.Game.Screens.Backgrounds AddInternal(seasonalBackgroundLoader); - user.ValueChanged += _ => Next(); - skin.ValueChanged += _ => Next(); - mode.ValueChanged += _ => Next(); - beatmap.ValueChanged += _ => Next(); - introSequence.ValueChanged += _ => Next(); - seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Next(); + user.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); + skin.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); + mode.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); + beatmap.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); + introSequence.ValueChanged += _ => Scheduler.AddOnce(loadNextIfRequired); + seasonalBackgroundLoader.SeasonalBackgroundChanged += () => Scheduler.AddOnce(loadNextIfRequired); currentDisplay = RNG.Next(0, background_count); Next(); + + // helper function required for AddOnce usage. + void loadNextIfRequired() => Next(); } private ScheduledDelegate nextTask; From 79dd9674fc5a580a10d3005d272a1b94b4aaff4c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 17:41:02 +0900 Subject: [PATCH 111/118] Use longer form to read better Using `l.StartTime` reads like a coding issue, even though if you go down the call chain looks to be correct. --- osu.Game/Storyboards/StoryboardSprite.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index f941cec20c..ebd1a941a8 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -33,8 +33,8 @@ namespace osu.Game.Storyboards foreach (var l in loops) { - if (l.EarliestDisplayedTime != null) - earliestStartTime = Math.Min(earliestStartTime, l.StartTime); + if (l.EarliestDisplayedTime is double loopEarliestDisplayTime) + earliestStartTime = Math.Min(earliestStartTime, l.LoopStartTime + loopEarliestDisplayTime); } if (earliestStartTime < double.MaxValue) From 3bc2de4889cb0874c9a3d4adb0392ab06523f10a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 19:11:23 +0900 Subject: [PATCH 112/118] Add failing test coverage of modified beatmap import breaking online availability state --- .../TestSceneOnlinePlayBeatmapAvailabilityTracker.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index 24824b1e23..239c787349 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -114,18 +114,23 @@ namespace osu.Game.Tests.Online public void TestTrackerRespectsChecksum() { AddStep("allow importing", () => beatmaps.AllowImport.SetResult(true)); + AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait()); + addAvailabilityCheckStep("initially locally available", BeatmapAvailability.LocallyAvailable); AddStep("import altered beatmap", () => { beatmaps.Import(TestResources.GetTestBeatmapForImport(true)).Wait(); }); - addAvailabilityCheckStep("state still not downloaded", BeatmapAvailability.NotDownloaded); + addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); AddStep("recreate tracker", () => Child = availabilityTracker = new OnlinePlayBeatmapAvailabilityTracker { SelectedItem = { BindTarget = selectedItem } }); addAvailabilityCheckStep("state not downloaded as well", BeatmapAvailability.NotDownloaded); + + AddStep("reimport original beatmap", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).Wait()); + addAvailabilityCheckStep("locally available after re-import", BeatmapAvailability.LocallyAvailable); } private void addAvailabilityCheckStep(string description, Func expected) From 453ecd21b32b552947ee1141f793e26d88774113 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 19:11:56 +0900 Subject: [PATCH 113/118] Fix `OnlinePlayBeatmapAvailabilityTracker` potentially in incorrect state Adter an import of a modified version of a beatmap (that was already present in the local database), it's feasible that one of these trackers would not see the state change due to the nuances of the import process. --- .../OnlinePlayBeatmapAvailabilityTracker.cs | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index aa0e37363b..a32f069470 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -40,6 +40,11 @@ namespace osu.Game.Online.Rooms private BeatmapDownloadTracker downloadTracker; + /// + /// The beatmap matching the required hash (and providing a final state). + /// + private BeatmapInfo matchingHash; + protected override void LoadComplete() { base.LoadComplete(); @@ -71,13 +76,34 @@ namespace osu.Game.Online.Rooms progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500); }, true); }, true); + + // These events are needed for a fringe case where a modified/altered beatmap is imported with matching OnlineIDs. + // During the import process this will cause the existing beatmap set to be silently deleted and replaced with the new one. + // This is not exposed to us via `BeatmapDownloadTracker` so we have to take it into our own hands (as we care about the hash matching). + beatmapManager.ItemUpdated += itemUpdated; + beatmapManager.ItemRemoved += itemRemoved; } + private void itemUpdated(BeatmapSetInfo item) => Schedule(() => + { + if (matchingHash?.BeatmapSet.ID == item.ID || SelectedItem.Value?.Beatmap.Value.BeatmapSet?.OnlineID == item.OnlineID) + updateAvailability(); + }); + + private void itemRemoved(BeatmapSetInfo item) => Schedule(() => + { + if (matchingHash?.BeatmapSet.ID == item.ID) + updateAvailability(); + }); + private void updateAvailability() { if (downloadTracker == null) return; + // will be repopulated below if still valid. + matchingHash = null; + switch (downloadTracker.State.Value) { case DownloadState.NotDownloaded: @@ -93,7 +119,9 @@ namespace osu.Game.Online.Rooms break; case DownloadState.LocallyAvailable: - bool hashMatches = checkHashValidity(); + matchingHash = findMatchingHash(); + + bool hashMatches = matchingHash != null; availability.Value = hashMatches ? BeatmapAvailability.LocallyAvailable() : BeatmapAvailability.NotDownloaded(); @@ -108,12 +136,23 @@ namespace osu.Game.Online.Rooms } } - private bool checkHashValidity() + private BeatmapInfo findMatchingHash() { int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID; string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; - return beatmapManager.QueryBeatmap(b => b.OnlineID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending) != null; + return beatmapManager.QueryBeatmap(b => b.OnlineID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (beatmapManager != null) + { + beatmapManager.ItemUpdated -= itemUpdated; + beatmapManager.ItemRemoved -= itemRemoved; + } } } } From 0950d8d327eb9d8a85564c917826461dd83b4c6f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Dec 2021 19:16:51 +0900 Subject: [PATCH 114/118] Add back `PlaylistRoomCreation` test Was spiritually removed in https://github.com/ppy/osu/pull/16045. This implementation is mostly taken from that PR's comment thread verbatim, and now works due to the associated changes to `OnlinePlayBeatmapAvailabilityTracker`. --- .../TestScenePlaylistsRoomCreation.cs | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 450e821ba6..9dd8b41dcd 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -15,9 +15,11 @@ using osu.Game.Database; using osu.Game.Online.Rooms; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.OnlinePlay.Components; using osu.Game.Screens.OnlinePlay.Playlists; using osu.Game.Screens.Play; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Visual.OnlinePlay; using osuTK.Input; @@ -109,7 +111,87 @@ namespace osu.Game.Tests.Visual.Playlists AddAssert("first playlist item selected", () => match.SelectedItem.Value == SelectedRoom.Value.Playlist[0]); } + [Test] + public void TestBeatmapUpdatedOnReImport() + { + string realHash = null; + int realOnlineId = 0; + int realOnlineSetId = 0; + + AddStep("store real beatmap values", () => + { + realHash = importedBeatmap.Value.Beatmaps[0].MD5Hash; + realOnlineId = importedBeatmap.Value.Beatmaps[0].OnlineID ?? -1; + realOnlineSetId = importedBeatmap.Value.OnlineID ?? -1; + }); + + AddStep("import modified beatmap", () => + { + var modifiedBeatmap = new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = + { + OnlineID = realOnlineId, + BeatmapSet = + { + OnlineID = realOnlineSetId + } + }, + }; + + modifiedBeatmap.HitObjects.Clear(); + modifiedBeatmap.HitObjects.Add(new HitCircle { StartTime = 5000 }); + + manager.Import(modifiedBeatmap.BeatmapInfo.BeatmapSet).Wait(); + }); + + // Create the room using the real beatmap values. + setupAndCreateRoom(room => + { + room.Name.Value = "my awesome room"; + room.Host.Value = API.LocalUser.Value; + room.Playlist.Add(new PlaylistItem + { + Beatmap = + { + Value = new BeatmapInfo + { + MD5Hash = realHash, + OnlineID = realOnlineId, + BeatmapSet = new BeatmapSetInfo + { + OnlineID = realOnlineSetId, + } + } + }, + Ruleset = { Value = new OsuRuleset().RulesetInfo } + }); + }); + + AddAssert("match has default beatmap", () => match.Beatmap.IsDefault); + + AddStep("reimport original beatmap", () => + { + var originalBeatmap = new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = + { + OnlineID = realOnlineId, + BeatmapSet = + { + OnlineID = realOnlineSetId + } + }, + }; + + manager.Import(originalBeatmap.BeatmapInfo.BeatmapSet).Wait(); + }); + + AddUntilStep("match has correct beatmap", () => realHash == match.Beatmap.Value.BeatmapInfo.MD5Hash); + } + private void setupAndCreateRoom(Action room) + { AddStep("setup room", () => room(SelectedRoom.Value)); From 8e79fac389d5f5ad5b90a15c2da7d19dc7219f70 Mon Sep 17 00:00:00 2001 From: tbrose Date: Tue, 14 Dec 2021 16:23:51 +0100 Subject: [PATCH 115/118] Fixes code quality check failed --- .../Online/Chat/MessageNotifierTest.cs | 26 +++++++++---------- osu.Game/Online/Chat/MessageNotifier.cs | 5 ++-- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs index b885299d1f..2ec5b778d1 100644 --- a/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs +++ b/osu.Game.Tests/Online/Chat/MessageNotifierTest.cs @@ -12,79 +12,79 @@ namespace osu.Game.Tests.Online.Chat [Test] public void TestContainsUsernameMidlinePositive() { - Assert.IsTrue(MessageNotifier.checkContainsUsername("This is a test message", "Test")); + Assert.IsTrue(MessageNotifier.CheckContainsUsername("This is a test message", "Test")); } [Test] public void TestContainsUsernameStartOfLinePositive() { - Assert.IsTrue(MessageNotifier.checkContainsUsername("Test message", "Test")); + Assert.IsTrue(MessageNotifier.CheckContainsUsername("Test message", "Test")); } [Test] public void TestContainsUsernameEndOfLinePositive() { - Assert.IsTrue(MessageNotifier.checkContainsUsername("This is a test", "Test")); + Assert.IsTrue(MessageNotifier.CheckContainsUsername("This is a test", "Test")); } [Test] public void TestContainsUsernameMidlineNegative() { - Assert.IsFalse(MessageNotifier.checkContainsUsername("This is a testmessage for notifications", "Test")); + Assert.IsFalse(MessageNotifier.CheckContainsUsername("This is a testmessage for notifications", "Test")); } [Test] public void TestContainsUsernameStartOfLineNegative() { - Assert.IsFalse(MessageNotifier.checkContainsUsername("Testmessage", "Test")); + Assert.IsFalse(MessageNotifier.CheckContainsUsername("Testmessage", "Test")); } [Test] public void TestContainsUsernameEndOfLineNegative() { - Assert.IsFalse(MessageNotifier.checkContainsUsername("This is a notificationtest", "Test")); + Assert.IsFalse(MessageNotifier.CheckContainsUsername("This is a notificationtest", "Test")); } [Test] public void TestContainsUsernameBetweenInterpunction() { - Assert.IsTrue(MessageNotifier.checkContainsUsername("Hello 'test'-message", "Test")); + Assert.IsTrue(MessageNotifier.CheckContainsUsername("Hello 'test'-message", "Test")); } [Test] public void TestContainsUsernameUnicode() { - Assert.IsTrue(MessageNotifier.checkContainsUsername("Test \u0460\u0460 message", "\u0460\u0460")); + Assert.IsTrue(MessageNotifier.CheckContainsUsername("Test \u0460\u0460 message", "\u0460\u0460")); } [Test] public void TestContainsUsernameUnicodeNegative() { - Assert.IsFalse(MessageNotifier.checkContainsUsername("Test ha\u0460\u0460o message", "\u0460\u0460")); + Assert.IsFalse(MessageNotifier.CheckContainsUsername("Test ha\u0460\u0460o message", "\u0460\u0460")); } [Test] public void TestContainsUsernameSpecialCharactersPositive() { - Assert.IsTrue(MessageNotifier.checkContainsUsername("Test [#^-^#] message", "[#^-^#]")); + Assert.IsTrue(MessageNotifier.CheckContainsUsername("Test [#^-^#] message", "[#^-^#]")); } [Test] public void TestContainsUsernameSpecialCharactersNegative() { - Assert.IsFalse(MessageNotifier.checkContainsUsername("Test pad[#^-^#]oru message", "[#^-^#]")); + Assert.IsFalse(MessageNotifier.CheckContainsUsername("Test pad[#^-^#]oru message", "[#^-^#]")); } [Test] public void TestContainsUsernameAtSign() { - Assert.IsTrue(MessageNotifier.checkContainsUsername("@username hi", "username")); + Assert.IsTrue(MessageNotifier.CheckContainsUsername("@username hi", "username")); } [Test] public void TestContainsUsernameColon() { - Assert.IsTrue(MessageNotifier.checkContainsUsername("username: hi", "username")); + Assert.IsTrue(MessageNotifier.CheckContainsUsername("username: hi", "username")); } } } diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index db7c5e47f5..a11af7b305 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; @@ -121,7 +120,7 @@ namespace osu.Game.Online.Chat private void checkForMentions(Channel channel, Message message) { - if (!notifyOnUsername.Value || !checkContainsUsername(message.Content, localUser.Value.Username)) return; + if (!notifyOnUsername.Value || !CheckContainsUsername(message.Content, localUser.Value.Username)) return; notifications.Post(new MentionNotification(message.Sender.Username, channel)); } @@ -130,7 +129,7 @@ namespace osu.Game.Online.Chat /// Checks if mentions . /// This will match against the case where underscores are used instead of spaces (which is how osu-stable handles usernames with spaces). /// - public static bool checkContainsUsername(string message, string username) + public static bool CheckContainsUsername(string message, string username) { string fullName = Regex.Escape(username); string underscoreName = Regex.Escape(username.Replace(' ', '_')); From 4664bb1d29ad5d8d442f97f94e12d5bdc7e57bc0 Mon Sep 17 00:00:00 2001 From: rumoi Date: Wed, 15 Dec 2021 05:16:10 +1300 Subject: [PATCH 116/118] Remove uneeded complexity --- 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 30d989cfba..d26808bf04 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills bool firstDeltaSwitch = false; - int rhythmStart = Math.Min(Previous.Count - 2, 0); + int rhythmStart = 0; while (rhythmStart < Previous.Count - 2 && current.StartTime - Previous[rhythmStart].StartTime < history_time_max) rhythmStart++; From da00c020be8cd32aa1ee870d242337fb3cc60a9c Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Wed, 15 Dec 2021 07:33:49 +0900 Subject: [PATCH 117/118] Remove whitespace --- .../Visual/Playlists/TestScenePlaylistsRoomCreation.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index 9dd8b41dcd..a426f075e1 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -191,7 +191,6 @@ namespace osu.Game.Tests.Visual.Playlists } private void setupAndCreateRoom(Action room) - { AddStep("setup room", () => room(SelectedRoom.Value)); From e662a9f0c44bd5b9dab876f60a90919f5c53e44b Mon Sep 17 00:00:00 2001 From: rumoi Date: Wed, 15 Dec 2021 12:36:45 +1300 Subject: [PATCH 118/118] Remove redundant code. --- .../Difficulty/Skills/Speed.cs | 101 +++++++++--------- 1 file changed, 49 insertions(+), 52 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index d26808bf04..06d1ef7346 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -66,67 +66,64 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills OsuDifficultyHitObject prevObj = (OsuDifficultyHitObject)Previous[i]; OsuDifficultyHitObject lastObj = (OsuDifficultyHitObject)Previous[i + 1]; - double currHistoricalDecay = Math.Max(0, (history_time_max - (current.StartTime - currObj.StartTime))) / history_time_max; // scales note 0 to 1 from history to now + double currHistoricalDecay = (history_time_max - (current.StartTime - currObj.StartTime)) / history_time_max; // scales note 0 to 1 from history to now - if (currHistoricalDecay != 0) + currHistoricalDecay = Math.Min((double)(Previous.Count - i) / Previous.Count, currHistoricalDecay); // either we're limited by time or limited by object count. + + double currDelta = currObj.StrainTime; + double prevDelta = prevObj.StrainTime; + double lastDelta = lastObj.StrainTime; + double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. + + double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - greatWindow * 0.6) / (greatWindow * 0.6)); + + windowPenalty = Math.Min(1, windowPenalty); + + double effectiveRatio = windowPenalty * currRatio; + + if (firstDeltaSwitch) { - currHistoricalDecay = Math.Min((double)(Previous.Count - i) / Previous.Count, currHistoricalDecay); // either we're limited by time or limited by object count. - - double currDelta = currObj.StrainTime; - double prevDelta = prevObj.StrainTime; - double lastDelta = lastObj.StrainTime; - double currRatio = 1.0 + 6.0 * Math.Min(0.5, Math.Pow(Math.Sin(Math.PI / (Math.Min(prevDelta, currDelta) / Math.Max(prevDelta, currDelta))), 2)); // fancy function to calculate rhythmbonuses. - - double windowPenalty = Math.Min(1, Math.Max(0, Math.Abs(prevDelta - currDelta) - greatWindow * 0.6) / (greatWindow * 0.6)); - - windowPenalty = Math.Min(1, windowPenalty); - - double effectiveRatio = windowPenalty * currRatio; - - if (firstDeltaSwitch) + if (!(prevDelta > 1.25 * currDelta || prevDelta * 1.25 < currDelta)) { - if (!(prevDelta > 1.25 * currDelta || prevDelta * 1.25 < currDelta)) - { - if (islandSize < 7) - islandSize++; // island is still progressing, count size. - } - else - { - if (Previous[i - 1].BaseObject is Slider) // bpm change is into slider, this is easy acc window - effectiveRatio *= 0.125; - - if (Previous[i].BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle - effectiveRatio *= 0.25; - - if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet) - effectiveRatio *= 0.25; - - if (previousIslandSize % 2 == islandSize % 2) // repeated island polartiy (2 -> 4, 3 -> 5) - effectiveRatio *= 0.50; - - if (lastDelta > prevDelta + 10 && prevDelta > currDelta + 10) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this. - effectiveRatio *= 0.125; - - rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay * Math.Sqrt(4 + islandSize) / 2 * Math.Sqrt(4 + previousIslandSize) / 2; - - startRatio = effectiveRatio; - - previousIslandSize = islandSize; // log the last island size. - - if (prevDelta * 1.25 < currDelta) // we're slowing down, stop counting - firstDeltaSwitch = false; // if we're speeding up, this stays true and we keep counting island size. - - islandSize = 1; - } + if (islandSize < 7) + islandSize++; // island is still progressing, count size. } - else if (prevDelta > 1.25 * currDelta) // we want to be speeding up. + else { - // Begin counting island until we change speed again. - firstDeltaSwitch = true; + if (Previous[i - 1].BaseObject is Slider) // bpm change is into slider, this is easy acc window + effectiveRatio *= 0.125; + + if (Previous[i].BaseObject is Slider) // bpm change was from a slider, this is easier typically than circle -> circle + effectiveRatio *= 0.25; + + if (previousIslandSize == islandSize) // repeated island size (ex: triplet -> triplet) + effectiveRatio *= 0.25; + + if (previousIslandSize % 2 == islandSize % 2) // repeated island polartiy (2 -> 4, 3 -> 5) + effectiveRatio *= 0.50; + + if (lastDelta > prevDelta + 10 && prevDelta > currDelta + 10) // previous increase happened a note ago, 1/1->1/2-1/4, dont want to buff this. + effectiveRatio *= 0.125; + + rhythmComplexitySum += Math.Sqrt(effectiveRatio * startRatio) * currHistoricalDecay * Math.Sqrt(4 + islandSize) / 2 * Math.Sqrt(4 + previousIslandSize) / 2; + startRatio = effectiveRatio; + + previousIslandSize = islandSize; // log the last island size. + + if (prevDelta * 1.25 < currDelta) // we're slowing down, stop counting + firstDeltaSwitch = false; // if we're speeding up, this stays true and we keep counting island size. + islandSize = 1; } } + else if (prevDelta > 1.25 * currDelta) // we want to be speeding up. + { + // Begin counting island until we change speed again. + firstDeltaSwitch = true; + startRatio = effectiveRatio; + islandSize = 1; + } } return Math.Sqrt(4 + rhythmComplexitySum * rhythm_multiplier) / 2; //produces multiplier that can be applied to strain. range [1, infinity) (not really though)