diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 1b06aa4d17..6444127594 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -14,8 +14,8 @@ "jb" ] }, - "smoogipoo.nvika": { - "version": "1.0.3", + "nvika": { + "version": "2.2.0", "commands": [ "nvika" ] diff --git a/Directory.Build.props b/Directory.Build.props index 53ad973e47..894ea25c8b 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -16,7 +16,7 @@ - + diff --git a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj index e28053d0ca..e9b92be0c3 100644 --- a/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj +++ b/Templates/Rulesets/ruleset-empty/osu.Game.Rulesets.EmptyFreeform.Tests/osu.Game.Rulesets.EmptyFreeform.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 027bd0b7e2..e145dd7b69 100644 --- a/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj index e2c715d385..a301432a6c 100644 --- a/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-empty/osu.Game.Rulesets.EmptyScrolling.Tests/osu.Game.Rulesets.EmptyScrolling.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj index 027bd0b7e2..e145dd7b69 100644 --- a/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj +++ b/Templates/Rulesets/ruleset-scrolling-example/osu.Game.Rulesets.Pippidon.Tests/osu.Game.Rulesets.Pippidon.Tests.csproj @@ -10,7 +10,7 @@ - + diff --git a/osu.Android.props b/osu.Android.props index dec994bcb2..6d2e8428a7 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index 0bcbfc4baf..fec96c9165 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -20,6 +20,7 @@ namespace osu.Android [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullUser, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false, LaunchMode = LaunchMode.SingleInstance, Exported = true)] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osz", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osk", DataHost = "*", DataMimeType = "*/*")] + [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataPathPattern = ".*\\\\.osr", DataHost = "*", DataMimeType = "*/*")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-beatmap-archive")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-skin-archive")] [IntentFilter(new[] { Intent.ActionView }, Categories = new[] { Intent.CategoryDefault }, DataScheme = "content", DataMimeType = "application/x-osu-replay")] diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 6457ec92da..4c8b9b2b08 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs index f1b51e51d0..6f3e6763bd 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModAutoplay.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { - ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } }, + ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad" } }, Replay = new CatchAutoGenerator(beatmap).Generate(), }; } diff --git a/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs index d53d019e90..1b7d254321 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchModCinema.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Catch.Mods { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { - ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad!" } }, + ScoreInfo = new ScoreInfo { User = new User { Username = "osu!salad" } }, Replay = new CatchAutoGenerator(beatmap).Generate(), }; } diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index 674a22df98..fad39ef9d6 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs index 6ae854e7f3..86f667466f 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModAutoplay.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { - ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } }, + ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus" } }, Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), }; } diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs index 064c55ed8d..1c06bb389b 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModCinema.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Mania.Mods { public override Score CreateReplayScore(IBeatmap beatmap, IReadOnlyList mods) => new Score { - ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus!" } }, + ScoreInfo = new ScoreInfo { User = new User { Username = "osu!topus" } }, Replay = new ManiaAutoGenerator((ManiaBeatmap)beatmap).Generate(), }; } diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index 15675e74d1..7cd06c5225 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs @@ -15,13 +15,13 @@ namespace osu.Game.Rulesets.Osu.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Osu"; - [TestCase(6.5867229481955389d, "diffcalc-test")] - [TestCase(1.0416315570967911d, "zero-length-sliders")] + [TestCase(6.5295339534769958d, "diffcalc-test")] + [TestCase(1.1514260533755143d, "zero-length-sliders")] public void Test(double expected, string name) => base.Test(expected, name); - [TestCase(8.2730989071947896d, "diffcalc-test")] - [TestCase(1.2726413186221039d, "zero-length-sliders")] + [TestCase(9.047752485219954d, "diffcalc-test")] + [TestCase(1.3985711787077566d, "zero-length-sliders")] public void TestClockRateAdjusted(double expected, string name) => Test(expected, name, new OsuModDoubleTime()); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs new file mode 100644 index 0000000000..ef05bcd320 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneNoSpinnerStacking.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestSceneNoSpinnerStacking : TestSceneOsuPlayer + { + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) + { + var beatmap = new Beatmap + { + BeatmapInfo = new BeatmapInfo + { + BaseDifficulty = new BeatmapDifficulty { OverallDifficulty = 10 }, + Ruleset = ruleset + } + }; + + for (int i = 0; i < 512; i++) + { + if (i % 32 < 20) + beatmap.HitObjects.Add(new Spinner { Position = new Vector2(256, 192), StartTime = i * 200, EndTime = (i * 200) + 100 }); + } + + return beatmap; + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index f5f1159542..66f4ad3d3f 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index 49ac6a7af3..4b90285fd4 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -12,20 +12,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing { public class OsuDifficultyHitObject : DifficultyHitObject { - private const int normalized_radius = 52; + private const int normalized_radius = 50; // Change radius to 50 to make 100 the diameter. Easier for mental maths. + private const int min_delta_time = 25; protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject; - /// - /// Milliseconds elapsed since the start time of the previous , with a minimum of 25ms to account for simultaneous s. - /// - public double StrainTime { get; private set; } - /// /// Normalized distance from the end position of the previous to the start position of this . /// public double JumpDistance { get; private set; } + /// + /// Minimum distance from the end position of the previous to the start position of this . + /// + public double MovementDistance { get; private set; } + /// /// Normalized distance between the start and end position of the previous . /// @@ -37,6 +38,21 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing /// 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; } + + /// + /// 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. + /// + public readonly double StrainTime; + private readonly OsuHitObject lastLastObject; private readonly OsuHitObject lastObject; @@ -46,13 +62,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing this.lastLastObject = (OsuHitObject)lastLastObject; this.lastObject = (OsuHitObject)lastObject; - setDistances(); + // Capped to 25ms to prevent difficulty calculation breaking from simultaneous objects. + StrainTime = Math.Max(DeltaTime, min_delta_time); - // Capped to 25ms to prevent difficulty calculation breaking from simulatenous objects. - StrainTime = Math.Max(DeltaTime, 25); + setDistances(clockRate); } - private void setDistances() + private void setDistances(double clockRate) { // 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) @@ -67,15 +83,29 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing scalingFactor *= 1 + smallCircleBonus; } + Vector2 lastCursorPosition = getEndCursorPosition(lastObject); + JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; + if (lastObject is Slider lastSlider) { computeSliderCursorPosition(lastSlider); TravelDistance = lastSlider.LazyTravelDistance * scalingFactor; + TravelTime = Math.Max(lastSlider.LazyTravelTime / clockRate, min_delta_time); + MovementTime = Math.Max(StrainTime - TravelTime, 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; + + // 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. + MovementDistance = Math.Min(JumpDistance, tailJumpDistance); + } + else + { + MovementTime = StrainTime; + MovementDistance = JumpDistance; } - - Vector2 lastCursorPosition = getEndCursorPosition(lastObject); - - JumpDistance = (BaseObject.StackedPosition * scalingFactor - lastCursorPosition * scalingFactor).Length; if (lastLastObject != null && !(lastLastObject is Spinner)) { @@ -98,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing slider.LazyEndPosition = slider.StackedPosition; - float approxFollowCircleRadius = (float)(slider.Radius * 3); + float followCircleRadius = (float)(slider.Radius * 2.4); var computeVertex = new Action(t => { double progress = (t - slider.StartTime) / slider.SpanDuration; @@ -111,11 +141,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing var diff = slider.StackedPosition + slider.Path.PositionAt(progress) - slider.LazyEndPosition.Value; float dist = diff.Length; - if (dist > approxFollowCircleRadius) + slider.LazyTravelTime = t - slider.StartTime; + + if (dist > followCircleRadius) { // The cursor would be outside the follow circle, we need to move it diff.Normalize(); // Obtain direction of diff - dist -= approxFollowCircleRadius; + dist -= followCircleRadius; slider.LazyEndPosition += diff * dist; slider.LazyTravelDistance += dist; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 64ca567a15..a054b46366 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -14,53 +14,96 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Aim : OsuStrainSkill { - private const double angle_bonus_begin = Math.PI / 3; - private const double timing_threshold = 107; - public Aim(Mod[] mods) : base(mods) { } + protected override int HistoryLength => 2; + + private const double wide_angle_multiplier = 1.5; + private const double acute_angle_multiplier = 2.0; + private double currentStrain = 1; - private double skillMultiplier => 26.25; + private double skillMultiplier => 23.25; private double strainDecayBase => 0.15; private double strainValueOf(DifficultyHitObject current) { - if (current.BaseObject is Spinner) + if (current.BaseObject is Spinner || Previous.Count <= 1 || Previous[0].BaseObject is Spinner) return 0; - var osuCurrent = (OsuDifficultyHitObject)current; + var osuCurrObj = (OsuDifficultyHitObject)current; + var osuLastObj = (OsuDifficultyHitObject)Previous[0]; + var osuLastLastObj = (OsuDifficultyHitObject)Previous[1]; - double aimStrain = 0; + // 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; - if (Previous.Count > 0) + // 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) { - var osuPrevious = (OsuDifficultyHitObject)Previous[0]; + 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. - if (osuCurrent.Angle != null && osuCurrent.Angle.Value > angle_bonus_begin) + 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; + + if (osuLastLastObj.BaseObject is Slider) + { + double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime; + double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; + + prevVelocity = Math.Max(prevVelocity, movementVelocity + travelVelocity); + } + + double angleBonus = 0; + double aimStrain = currVelocity; // Start strain with regular velocity. + + if (Math.Max(osuCurrObj.StrainTime, osuLastObj.StrainTime) < 1.25 * Math.Min(osuCurrObj.StrainTime, osuLastObj.StrainTime)) // If rhythms are the same. + { + if (osuCurrObj.Angle != null && osuLastObj.Angle != null && osuLastLastObj.Angle != null) { - const double scale = 90; + double currAngle = osuCurrObj.Angle.Value; + double lastAngle = osuLastObj.Angle.Value; + double lastLastAngle = osuLastLastObj.Angle.Value; - double angleBonus = Math.Sqrt( - Math.Max(osuPrevious.JumpDistance - scale, 0) - * Math.Pow(Math.Sin(osuCurrent.Angle.Value - angle_bonus_begin), 2) - * Math.Max(osuCurrent.JumpDistance - scale, 0)); - aimStrain = 1.4 * applyDiminishingExp(Math.Max(0, angleBonus)) / Math.Max(timing_threshold, osuPrevious.StrainTime); + // Rewarding angles, take the smaller velocity as base. + angleBonus = Math.Min(currVelocity, prevVelocity); + + double wideAngleBonus = calcWideAngleBonus(currAngle); + double acuteAngleBonus = calcAcuteAngleBonus(currAngle); + + if (osuCurrObj.StrainTime > 100) // Only buff deltaTime exceeding 300 bpm 1/2. + acuteAngleBonus = 0; + else + { + 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). + } + + wideAngleBonus *= angleBonus * (1 - Math.Min(wideAngleBonus, Math.Pow(calcWideAngleBonus(lastAngle), 3))); // Penalize wide angles if they're repeated, reducing the penalty as the lastAngle gets more acute. + acuteAngleBonus *= 0.5 + 0.5 * (1 - Math.Min(acuteAngleBonus, Math.Pow(calcAcuteAngleBonus(lastLastAngle), 3))); // Penalize acute angles if they're repeated, reducing the penalty as the lastLastAngle gets more obtuse. + + angleBonus = acuteAngleBonus * acute_angle_multiplier + wideAngleBonus * wide_angle_multiplier; // add the angle buffs together. } } - double jumpDistanceExp = applyDiminishingExp(osuCurrent.JumpDistance); - double travelDistanceExp = applyDiminishingExp(osuCurrent.TravelDistance); + aimStrain += angleBonus; // Add in angle bonus. - return Math.Max( - aimStrain + (jumpDistanceExp + travelDistanceExp + Math.Sqrt(travelDistanceExp * jumpDistanceExp)) / Math.Max(osuCurrent.StrainTime, timing_threshold), - (Math.Sqrt(travelDistanceExp * jumpDistanceExp) + jumpDistanceExp + travelDistanceExp) / osuCurrent.StrainTime - ); + return aimStrain; } + private double calcWideAngleBonus(double angle) => Math.Pow(Math.Sin(3.0 / 4 * (Math.Min(5.0 / 6 * Math.PI, Math.Max(Math.PI / 6, angle)) - Math.PI / 6)), 2); + + private double calcAcuteAngleBonus(double angle) => 1 - calcWideAngleBonus(angle); + private double applyDiminishingExp(double val) => Math.Pow(val, 0.99); private double strainDecay(double ms) => Math.Pow(strainDecayBase, ms / 1000); diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 7c45b2bc07..8b7de9e109 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Objects set => StackHeightBindable.Value = value; } - public Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f); + public virtual Vector2 StackOffset => new Vector2(StackHeight * Scale * -6.4f); public double Radius => OBJECT_RADIUS * Scale; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 9b2babb9ff..5c1c3fd253 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -79,6 +79,12 @@ namespace osu.Game.Rulesets.Osu.Objects /// internal float LazyTravelDistance; + /// + /// The time taken by the cursor upon completion of this if it was hit + /// with as few movements as possible. This is set and used by difficulty calculation. + /// + internal double LazyTravelTime; + public IList> NodeSamples { get; set; } = new List>(); [JsonIgnore] diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index f85dc0d391..0ad8e4ea68 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -8,6 +8,7 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; +using osuTK; namespace osu.Game.Rulesets.Osu.Objects { @@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Osu.Objects /// public int MaximumBonusSpins { get; protected set; } = 1; + public override Vector2 StackOffset => Vector2.Zero; + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index b9b295767e..568e35c221 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index d37e09aa29..7a95856c36 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -673,6 +673,8 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.That(first.ControlPoints[1].Position, Is.EqualTo(new Vector2(161, -244))); Assert.That(first.ControlPoints[1].Type, Is.EqualTo(null)); + // ReSharper disable once HeuristicUnreachableCode + // weird one, see https://youtrack.jetbrains.com/issue/RIDER-70159. Assert.That(first.ControlPoints[2].Position, Is.EqualTo(new Vector2(376, -3))); Assert.That(first.ControlPoints[2].Type, Is.EqualTo(PathType.Bezier)); Assert.That(first.ControlPoints[3].Position, Is.EqualTo(new Vector2(68, 15))); diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 935194db58..d68d43c998 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -846,6 +846,42 @@ namespace osu.Game.Tests.Beatmaps.IO } } + // TODO: needs to be pulled across to realm implementation when this file is nuked. + [Test] + public void TestSaveRemovesInvalidCharactersFromPath() + { + // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + { + try + { + var osu = LoadOsuIntoHost(host); + + var manager = osu.Dependencies.Get(); + + var working = manager.CreateNew(new OsuRuleset().RulesetInfo, User.SYSTEM_USER); + + var beatmap = working.Beatmap; + + beatmap.BeatmapInfo.Version = "difficulty"; + beatmap.BeatmapInfo.Metadata = new BeatmapMetadata + { + Artist = "Artist/With\\Slashes", + Title = "Title", + AuthorString = "mapper", + }; + + manager.Save(beatmap.BeatmapInfo, working.Beatmap); + + Assert.AreEqual("Artist_With_Slashes - Title (mapper) [difficulty].osu", beatmap.BeatmapInfo.Path); + } + finally + { + host.Exit(); + } + } + } + [Test] public void TestCreateNewEmptyBeatmap() { diff --git a/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs index 39fbf11d51..e22dfa5f8b 100644 --- a/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckAudioQualityTest.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Editing.Checks public void TestMissing() { // While this is a problem, it is out of scope for this check and is caught by a different one. - beatmap.Metadata.AudioFile = null; + beatmap.Metadata.AudioFile = string.Empty; var mock = new Mock(); mock.SetupGet(w => w.Beatmap).Returns(beatmap); diff --git a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs index 3424cfe732..2918dde2db 100644 --- a/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckBackgroundQualityTest.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Editing.Checks public void TestMissing() { // While this is a problem, it is out of scope for this check and is caught by a different one. - beatmap.Metadata.BackgroundFile = null; + beatmap.Metadata.BackgroundFile = string.Empty; var context = getContext(null, System.Array.Empty()); Assert.That(check.Run(context), Is.Empty); diff --git a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs index 39a1d76d83..70e4c76b19 100644 --- a/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs +++ b/osu.Game.Tests/Editing/Checks/CheckFilePresenceTest.cs @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Editing.Checks [Test] public void TestBackgroundNotSet() { - beatmap.Metadata.BackgroundFile = null; + beatmap.Metadata.BackgroundFile = string.Empty; var context = new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); var issues = check.Run(context).ToList(); diff --git a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs index 42a3b4cf43..9ce7e0a0e0 100644 --- a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs +++ b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs @@ -39,8 +39,8 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestCheckNullID() { - var ourInfo = new BeatmapSetInfo { Status = BeatmapSetOnlineStatus.Loved }; - var otherInfo = new BeatmapSetInfo { Status = BeatmapSetOnlineStatus.Approved }; + var ourInfo = new BeatmapSetInfo { Hash = "1" }; + var otherInfo = new BeatmapSetInfo { Hash = "2" }; Assert.AreNotEqual(ourInfo, otherInfo); } diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index ed86daf8b6..41939cec3f 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -189,7 +189,7 @@ namespace osu.Game.Tests.NonVisual.Filtering public void TestCriteriaMatchingArtistWithNullUnicodeName(string artistName, bool filtered) { var exampleBeatmapInfo = getExampleBeatmap(); - exampleBeatmapInfo.Metadata.ArtistUnicode = null; + exampleBeatmapInfo.Metadata.ArtistUnicode = string.Empty; var criteria = new FilterCriteria { diff --git a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs b/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs index c8848ab7d8..656e333073 100644 --- a/osu.Game.Tests/Online/TestAPIModJsonSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModJsonSerialization.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 Newtonsoft.Json; using NUnit.Framework; @@ -67,9 +68,11 @@ namespace osu.Game.Tests.Online var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(apiMod)); var converted = (TestModTimeRamp)deserialised?.ToMod(new TestRuleset()); - Assert.That(converted?.AdjustPitch.Value, Is.EqualTo(false)); - Assert.That(converted?.InitialRate.Value, Is.EqualTo(1.25)); - Assert.That(converted?.FinalRate.Value, Is.EqualTo(0.25)); + Assert.That(converted, Is.Not.Null); + + Assert.That(converted.AdjustPitch.Value, Is.EqualTo(false)); + Assert.That(converted.InitialRate.Value, Is.EqualTo(1.25)); + Assert.That(converted.FinalRate.Value, Is.EqualTo(0.25)); } [Test] @@ -121,11 +124,11 @@ namespace osu.Game.Tests.Online new TestModDifficultyAdjust() }; - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new System.NotImplementedException(); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); - public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new System.NotImplementedException(); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); - public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new System.NotImplementedException(); + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException(); public override string Description { get; } = string.Empty; public override string ShortName { get; } = string.Empty; diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index c75714032e..ecc9c92025 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -38,6 +38,15 @@ namespace osu.Game.Tests.Skins.IO assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu); }); + [Test] + public Task TestSingleImportMissingSectionHeader() => runSkinTest(async osu => + { + var import1 = await loadSkinIntoOsu(osu, new ZipArchiveReader(createOskWithIni("test skin", "skinner", includeSectionHeader: false), "skin.osk")); + + // When the import filename doesn't match, it should be appended (and update the skin.ini). + assertCorrectMetadata(import1, "test skin [skin]", "skinner", osu); + }); + [Test] public Task TestSingleImportMatchingFilename() => runSkinTest(async osu => { @@ -199,21 +208,23 @@ namespace osu.Game.Tests.Skins.IO return zipStream; } - private MemoryStream createOskWithIni(string name, string author, bool makeUnique = false, string iniFilename = @"skin.ini") + private MemoryStream createOskWithIni(string name, string author, bool makeUnique = false, string iniFilename = @"skin.ini", bool includeSectionHeader = true) { var zipStream = new MemoryStream(); using var zip = ZipArchive.Create(); - zip.AddEntry(iniFilename, generateSkinIni(name, author, makeUnique)); + zip.AddEntry(iniFilename, generateSkinIni(name, author, makeUnique, includeSectionHeader)); zip.SaveTo(zipStream); return zipStream; } - private MemoryStream generateSkinIni(string name, string author, bool makeUnique = true) + private MemoryStream generateSkinIni(string name, string author, bool makeUnique = true, bool includeSectionHeader = true) { var stream = new MemoryStream(); var writer = new StreamWriter(stream); - writer.WriteLine("[General]"); + if (includeSectionHeader) + writer.WriteLine("[General]"); + writer.WriteLine($"Name: {name}"); writer.WriteLine($"Author: {author}"); diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs new file mode 100644 index 0000000000..2aeb4ab4e2 --- /dev/null +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -0,0 +1,181 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Drawables.Cards; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Tests.Visual.Beatmaps +{ + public class TestSceneBeatmapCard : OsuTestScene + { + private APIBeatmapSet[] testCases; + + #region Test case generation + + [BackgroundDependencyLoader] + private void load() + { + var normal = CreateAPIBeatmapSet(Ruleset.Value); + normal.HasVideo = true; + normal.HasStoryboard = true; + + var undownloadable = getUndownloadableBeatmapSet(); + + var someDifficulties = getManyDifficultiesBeatmapSet(11); + someDifficulties.Title = someDifficulties.TitleUnicode = "some difficulties"; + someDifficulties.Status = BeatmapSetOnlineStatus.Qualified; + + var manyDifficulties = getManyDifficultiesBeatmapSet(100); + manyDifficulties.Status = BeatmapSetOnlineStatus.Pending; + + var explicitMap = CreateAPIBeatmapSet(Ruleset.Value); + explicitMap.HasExplicitContent = true; + + var featuredMap = CreateAPIBeatmapSet(Ruleset.Value); + featuredMap.TrackId = 1; + + var explicitFeaturedMap = CreateAPIBeatmapSet(Ruleset.Value); + explicitFeaturedMap.HasExplicitContent = true; + explicitFeaturedMap.TrackId = 2; + + var longName = CreateAPIBeatmapSet(Ruleset.Value); + longName.Title = longName.TitleUnicode = "this track has an incredibly and implausibly long title"; + longName.Artist = longName.ArtistUnicode = "and this artist! who would have thunk it. it's really such a long name."; + longName.HasExplicitContent = true; + longName.TrackId = 444; + + testCases = new[] + { + normal, + undownloadable, + someDifficulties, + manyDifficulties, + explicitMap, + featuredMap, + explicitFeaturedMap, + longName + }; + } + + private APIBeatmapSet getUndownloadableBeatmapSet() => new APIBeatmapSet + { + OnlineID = 123, + Title = "undownloadable beatmap", + Artist = "test", + Source = "more tests", + Author = new User + { + Username = "BanchoBot", + Id = 3, + }, + Availability = new BeatmapSetOnlineAvailability + { + DownloadDisabled = true, + }, + Preview = @"https://b.ppy.sh/preview/12345.mp3", + PlayCount = 123, + FavouriteCount = 456, + BPM = 111, + HasVideo = true, + HasStoryboard = true, + Covers = new BeatmapSetOnlineCovers(), + Beatmaps = new[] + { + new APIBeatmap + { + RulesetID = Ruleset.Value.OnlineID, + DifficultyName = "Test", + StarRating = 6.42, + } + } + }; + + private static APIBeatmapSet getManyDifficultiesBeatmapSet(int count) + { + var beatmaps = new List(); + + for (int i = 0; i < count; i++) + { + beatmaps.Add(new APIBeatmap + { + RulesetID = i % 4, + StarRating = 2 + i % 4 * 2, + }); + } + + return new APIBeatmapSet + { + OnlineID = 1, + Title = "many difficulties beatmap", + Artist = "test", + Author = new User + { + Username = "BanchoBot", + Id = 3, + }, + HasVideo = true, + HasStoryboard = true, + Covers = new BeatmapSetOnlineCovers(), + Beatmaps = beatmaps.ToArray(), + }; + } + + #endregion + + private Drawable createContent(OverlayColourScheme colourScheme, Func creationFunc) + { + var colourProvider = new OverlayColourProvider(colourScheme); + + return new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] + { + (typeof(OverlayColourProvider), colourProvider) + }, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background5 + }, + new BasicScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + Padding = new MarginPadding(10), + Spacing = new Vector2(10), + ChildrenEnumerable = testCases.Select(creationFunc) + } + } + } + }; + } + + private void createTestCase(Func creationFunc) + { + foreach (var scheme in Enum.GetValues(typeof(OverlayColourScheme)).Cast()) + AddStep($"set {scheme} scheme", () => Child = createContent(scheme, creationFunc)); + } + + [Test] + public void TestNormal() => createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo)); + } +} diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultySpectrumDisplay.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultySpectrumDisplay.cs index 1f38b05879..4063fa1252 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultySpectrumDisplay.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneDifficultySpectrumDisplay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Beatmaps { RulesetID = difficulty.rulesetId, StarRating = difficulty.stars - }).ToList() + }).ToArray() }; [Test] diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs index a2a7b72283..dddd9f07ab 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposerSelection.cs @@ -5,7 +5,10 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Testing; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; @@ -29,6 +32,9 @@ namespace osu.Game.Tests.Visual.Editing private ComposeBlueprintContainer blueprintContainer => Editor.ChildrenOfType().First(); + private ContextMenuContainer contextMenuContainer + => Editor.ChildrenOfType().First(); + private void moveMouseToObject(Func targetFunc) { AddStep("move mouse to object", () => @@ -42,6 +48,19 @@ namespace osu.Game.Tests.Visual.Editing }); } + [Test] + public void TestSelectAndShowContextMenu() + { + var addedObject = new HitCircle { StartTime = 100, Position = new Vector2(100, 100) }; + AddStep("add hitobject", () => EditorBeatmap.Add(addedObject)); + + moveMouseToObject(() => addedObject); + AddStep("right click", () => InputManager.Click(MouseButton.Right)); + + AddUntilStep("hitobject selected", () => EditorBeatmap.SelectedHitObjects.Single() == addedObject); + AddUntilStep("context menu is visible", () => contextMenuContainer.ChildrenOfType().Single().State == MenuState.Open); + } + [Test] public void TestNudgeSelection() { diff --git a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs index 19081f3281..4621436cc6 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneMetadataSection.cs @@ -23,10 +23,10 @@ namespace osu.Game.Tests.Visual.Editing AddStep("set metadata", () => { editorBeatmap.Metadata.Artist = "Example Artist"; - editorBeatmap.Metadata.ArtistUnicode = null; + editorBeatmap.Metadata.ArtistUnicode = string.Empty; editorBeatmap.Metadata.Title = "Example Title"; - editorBeatmap.Metadata.TitleUnicode = null; + editorBeatmap.Metadata.TitleUnicode = string.Empty; }); createSection(); @@ -44,10 +44,10 @@ namespace osu.Game.Tests.Visual.Editing AddStep("set metadata", () => { editorBeatmap.Metadata.ArtistUnicode = "*なみりん"; - editorBeatmap.Metadata.Artist = null; + editorBeatmap.Metadata.Artist = string.Empty; editorBeatmap.Metadata.TitleUnicode = "コイシテイク・プラネット"; - editorBeatmap.Metadata.Title = null; + editorBeatmap.Metadata.Title = string.Empty; }); createSection(); @@ -86,10 +86,10 @@ namespace osu.Game.Tests.Visual.Editing AddStep("set metadata", () => { editorBeatmap.Metadata.ArtistUnicode = "*なみりん"; - editorBeatmap.Metadata.Artist = null; + editorBeatmap.Metadata.Artist = string.Empty; editorBeatmap.Metadata.TitleUnicode = "コイシテイク・プラネット"; - editorBeatmap.Metadata.Title = null; + editorBeatmap.Metadata.Title = string.Empty; }); createSection(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 311c3ddc03..64d9addc77 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Gameplay return new APIScoreInfo { OnlineID = 2553163309, - OnlineRulesetID = 0, + RulesetID = 0, Beatmap = CreateAPIBeatmapSet(new OsuRuleset().RulesetInfo).Beatmaps.First(), HasReplay = replayAvailable, User = new User diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs index 3545fc96e8..08578168d6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Gameplay ScoreInfo = { BeatmapInfo = gameplayState.Beatmap.BeatmapInfo } }) { - ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos) + ScreenSpaceToGamefield = pos => recordingManager?.ToLocalSpace(pos) ?? Vector2.Zero, }, Child = new Container { @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Gameplay { ReplayInputHandler = new TestFramedReplayInputHandler(replay) { - GamefieldToScreenSpace = pos => playbackManager.ToScreenSpace(pos), + GamefieldToScreenSpace = pos => playbackManager?.ToScreenSpace(pos) ?? Vector2.Zero, }, Child = new Container { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs index 22ff2b98ce..84a6e95883 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoom.cs @@ -67,6 +67,36 @@ namespace osu.Game.Tests.Visual.Multiplayer } }), createLoungeRoom(new Room + { + Name = { Value = "Multiplayer room" }, + Status = { Value = new RoomStatusOpen() }, + EndDate = { Value = DateTimeOffset.Now.AddDays(1) }, + Type = { Value = MatchType.HeadToHead }, + Playlist = + { + new PlaylistItem + { + Beatmap = + { + Value = new TestBeatmap(new OsuRuleset().RulesetInfo) + { + BeatmapInfo = + { + StarDifficulty = 2.5, + Metadata = + { + Artist = "very very very very very very very very very long artist", + ArtistUnicode = "very very very very very very very very very long artist", + Title = "very very very very very very very very very very very long title", + TitleUnicode = "very very very very very very very very very very very long title", + } + } + }.BeatmapInfo, + } + } + } + }), + createLoungeRoom(new Room { Name = { Value = "Playlist room with multiple beatmaps" }, Status = { Value = new RoomStatusPlaying() }, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 69d68f163e..b587fd03bd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -223,11 +224,11 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestDownloadButtonVisibleInitiallyWhenBeatmapDoesNotExist() { - var byOnlineId = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; - byOnlineId.BeatmapSet.OnlineBeatmapSetID = 1337; // Some random ID that does not exist locally. + var byOnlineId = CreateAPIBeatmap(); + byOnlineId.OnlineID = 1337; // Some random ID that does not exist locally. - var byChecksum = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; - byChecksum.MD5Hash = "1337"; // Some random checksum that does not exist locally. + var byChecksum = CreateAPIBeatmap(); + byChecksum.Checksum = "1337"; // Some random checksum that does not exist locally. createPlaylist(byOnlineId, byChecksum); @@ -237,8 +238,11 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestExplicitBeatmapItem() { - var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; - beatmap.BeatmapSet.OnlineInfo.HasExplicitContent = true; + var beatmap = CreateAPIBeatmap(); + + Debug.Assert(beatmap.BeatmapSet != null); + + beatmap.BeatmapSet.HasExplicitContent = true; createPlaylist(beatmap); } @@ -355,7 +359,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); } - private void createPlaylist(params BeatmapInfo[] beatmaps) + private void createPlaylist(params IBeatmapInfo[] beatmaps) { AddStep("create playlist", () => { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs index 11caf9f498..38cf9d662f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayer.cs @@ -231,6 +231,9 @@ namespace osu.Game.Tests.Visual.Multiplayer } } }); + + AddAssert("Check participant count correct", () => client.APIRoom?.ParticipantCount.Value == 1); + AddAssert("Check participant list contains user", () => client.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); } [Test] @@ -290,6 +293,9 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); AddUntilStep("wait for join", () => client.Room != null); + + AddAssert("Check participant count correct", () => client.APIRoom?.ParticipantCount.Value == 1); + AddAssert("Check participant list contains user", () => client.APIRoom?.RecentParticipants.Count(u => u.Id == API.LocalUser.Value.Id) == 1); } [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs index e50b150f94..2549681519 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerParticipantsList.cs @@ -45,11 +45,11 @@ namespace osu.Game.Tests.Visual.Multiplayer } [Test] - public void TestAddNullUser() + public void TestAddUnresolvedUser() { AddAssert("one unique panel", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 1); - AddStep("add non-resolvable user", () => Client.AddNullUser()); + AddStep("add non-resolvable user", () => Client.TestAddUnresolvedUser()); AddAssert("null user added", () => Client.Room.AsNonNull().Users.Count(u => u.User == null) == 1); AddUntilStep("two unique panels", () => this.ChildrenOfType().Select(p => p.User).Distinct().Count() == 2); diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapAvailability.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapAvailability.cs index 6f9744ca73..176e0592ef 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapAvailability.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapAvailability.cs @@ -21,15 +21,12 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestUndownloadableWithLink() { - AddStep("set undownloadable beatmapset with link", () => container.BeatmapSet = new BeatmapSetInfo + AddStep("set undownloadable beatmapset with link", () => container.BeatmapSet = new APIBeatmapSet { - OnlineInfo = new APIBeatmapSet + Availability = new BeatmapSetOnlineAvailability { - Availability = new BeatmapSetOnlineAvailability - { - DownloadDisabled = true, - ExternalLink = @"https://osu.ppy.sh", - }, + DownloadDisabled = true, + ExternalLink = @"https://osu.ppy.sh", }, }); @@ -39,14 +36,11 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestUndownloadableNoLink() { - AddStep("set undownloadable beatmapset without link", () => container.BeatmapSet = new BeatmapSetInfo + AddStep("set undownloadable beatmapset without link", () => container.BeatmapSet = new APIBeatmapSet { - OnlineInfo = new APIBeatmapSet + Availability = new BeatmapSetOnlineAvailability { - Availability = new BeatmapSetOnlineAvailability - { - DownloadDisabled = true, - }, + DownloadDisabled = true, }, }); @@ -56,15 +50,12 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestPartsRemovedWithLink() { - AddStep("set parts-removed beatmapset with link", () => container.BeatmapSet = new BeatmapSetInfo + AddStep("set parts-removed beatmapset with link", () => container.BeatmapSet = new APIBeatmapSet { - OnlineInfo = new APIBeatmapSet + Availability = new BeatmapSetOnlineAvailability { - Availability = new BeatmapSetOnlineAvailability - { - DownloadDisabled = false, - ExternalLink = @"https://osu.ppy.sh", - }, + DownloadDisabled = false, + ExternalLink = @"https://osu.ppy.sh", }, }); @@ -74,14 +65,11 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestNormal() { - AddStep("set normal beatmapset", () => container.BeatmapSet = new BeatmapSetInfo + AddStep("set normal beatmapset", () => container.BeatmapSet = new APIBeatmapSet { - OnlineInfo = new APIBeatmapSet + Availability = new BeatmapSetOnlineAvailability { - Availability = new BeatmapSetOnlineAvailability - { - DownloadDisabled = false, - }, + DownloadDisabled = false, }, }); diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs index eb34187cd6..90f3eb64e4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs @@ -1,15 +1,14 @@ // 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.Graphics.UserInterface; -using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; using osu.Game.Rulesets; -using System.Collections.Generic; -using System.Linq; namespace osu.Game.Tests.Visual.Online { @@ -35,9 +34,9 @@ namespace osu.Game.Tests.Visual.Online AddStep("load multiple rulesets beatmapset", () => { - selector.BeatmapSet = new BeatmapSetInfo + selector.BeatmapSet = new APIBeatmapSet { - Beatmaps = enabledRulesets.Select(r => new BeatmapInfo { Ruleset = r }).ToList() + Beatmaps = enabledRulesets.Select(r => new APIBeatmap { RulesetID = r.OnlineID }).ToArray() }; }); @@ -53,13 +52,13 @@ namespace osu.Game.Tests.Visual.Online AddStep("load single ruleset beatmapset", () => { - selector.BeatmapSet = new BeatmapSetInfo + selector.BeatmapSet = new APIBeatmapSet { - Beatmaps = new List + Beatmaps = new[] { - new BeatmapInfo + new APIBeatmap { - Ruleset = enabledRuleset + RulesetID = enabledRuleset.OnlineID } } }; @@ -71,10 +70,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestEmptyBeatmapSet() { - AddStep("load empty beatmapset", () => selector.BeatmapSet = new BeatmapSetInfo - { - Beatmaps = new List() - }); + AddStep("load empty beatmapset", () => selector.BeatmapSet = new APIBeatmapSet()); AddAssert("no ruleset selected", () => selector.SelectedTab == null); AddAssert("all rulesets disabled", () => selector.TabContainer.TabItems.All(t => !t.Enabled.Value)); diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 7f9b56e873..63ce057667 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -49,60 +49,48 @@ namespace osu.Game.Tests.Visual.Online { AddStep(@"show first", () => { - overlay.ShowBeatmapSet(new BeatmapSetInfo + overlay.ShowBeatmapSet(new APIBeatmapSet { - OnlineBeatmapSetID = 1235, - Metadata = new BeatmapMetadata + OnlineID = 1235, + Title = @"an awesome beatmap", + Artist = @"naru narusegawa", + Source = @"hinata sou", + Tags = @"test tag tag more tag", + Author = new User { - Title = @"an awesome beatmap", - Artist = @"naru narusegawa", - Source = @"hinata sou", - Tags = @"test tag tag more tag", - Author = new User - { - Username = @"BanchoBot", - Id = 3, - }, + Username = @"BanchoBot", + Id = 3, }, - OnlineInfo = new APIBeatmapSet + Preview = @"https://b.ppy.sh/preview/12345.mp3", + PlayCount = 123, + FavouriteCount = 456, + Submitted = DateTime.Now, + Ranked = DateTime.Now, + BPM = 111, + HasVideo = true, + Ratings = Enumerable.Range(0, 11).ToArray(), + HasStoryboard = true, + Covers = new BeatmapSetOnlineCovers(), + Beatmaps = new[] { - Preview = @"https://b.ppy.sh/preview/12345.mp3", - PlayCount = 123, - FavouriteCount = 456, - Submitted = DateTime.Now, - Ranked = DateTime.Now, - BPM = 111, - HasVideo = true, - Ratings = Enumerable.Range(0, 11).ToArray(), - HasStoryboard = true, - Covers = new BeatmapSetOnlineCovers(), - }, - Beatmaps = new List - { - new BeatmapInfo + new APIBeatmap { - StarDifficulty = 9.99, - Version = @"TEST", + StarRating = 9.99, + DifficultyName = @"TEST", Length = 456000, - Ruleset = rulesets.GetRuleset(3), - BaseDifficulty = new BeatmapDifficulty + RulesetID = 3, + CircleSize = 1, + DrainRate = 2.3f, + OverallDifficulty = 4.5f, + ApproachRate = 6, + CircleCount = 111, + SliderCount = 12, + PlayCount = 222, + PassCount = 21, + FailTimes = new APIFailTimes { - CircleSize = 1, - DrainRate = 2.3f, - OverallDifficulty = 4.5f, - ApproachRate = 6, - }, - OnlineInfo = new APIBeatmap - { - CircleCount = 111, - SliderCount = 12, - PlayCount = 222, - PassCount = 21, - FailTimes = new APIFailTimes - { - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), - }, + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), }, }, }, @@ -120,71 +108,15 @@ namespace osu.Game.Tests.Visual.Online { AddStep(@"show undownloadable", () => { - overlay.ShowBeatmapSet(new BeatmapSetInfo + var set = getBeatmapSet(); + + set.Availability = new BeatmapSetOnlineAvailability { - OnlineBeatmapSetID = 1234, - Metadata = new BeatmapMetadata - { - Title = @"undownloadable beatmap", - Artist = @"no one", - Source = @"some source", - Tags = @"another test tag tag more test tags", - Author = new User - { - Username = @"BanchoBot", - Id = 3, - }, - }, - OnlineInfo = new APIBeatmapSet - { - Availability = new BeatmapSetOnlineAvailability - { - DownloadDisabled = true, - ExternalLink = "https://osu.ppy.sh", - }, - Preview = @"https://b.ppy.sh/preview/1234.mp3", - PlayCount = 123, - FavouriteCount = 456, - Submitted = DateTime.Now, - Ranked = DateTime.Now, - BPM = 111, - HasVideo = true, - HasStoryboard = true, - Covers = new BeatmapSetOnlineCovers(), - Language = new BeatmapSetOnlineLanguage { Id = 3, Name = "English" }, - Genre = new BeatmapSetOnlineGenre { Id = 4, Name = "Rock" }, - Ratings = Enumerable.Range(0, 11).ToArray(), - }, - Beatmaps = new List - { - new BeatmapInfo - { - StarDifficulty = 5.67, - Version = @"ANOTHER TEST", - Length = 123000, - Ruleset = rulesets.GetRuleset(1), - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 9, - DrainRate = 8, - OverallDifficulty = 7, - ApproachRate = 6, - }, - OnlineInfo = new APIBeatmap - { - CircleCount = 123, - SliderCount = 45, - PlayCount = 567, - PassCount = 89, - FailTimes = new APIFailTimes - { - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), - }, - }, - }, - }, - }); + DownloadDisabled = true, + ExternalLink = "https://osu.ppy.sh", + }; + + overlay.ShowBeatmapSet(set); }); downloadAssert(false); @@ -195,48 +127,30 @@ namespace osu.Game.Tests.Visual.Online { AddStep("show multiple rulesets beatmap", () => { - var beatmaps = new List(); + var beatmaps = new List(); foreach (var ruleset in rulesets.AvailableRulesets.Skip(1)) { - beatmaps.Add(new BeatmapInfo + beatmaps.Add(new APIBeatmap { - Version = ruleset.Name, - Ruleset = ruleset, - BaseDifficulty = new BeatmapDifficulty(), - OnlineInfo = new APIBeatmap + DifficultyName = ruleset.Name, + RulesetID = ruleset.OnlineID, + FailTimes = new APIFailTimes { - FailTimes = new APIFailTimes - { - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), - }, - } + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), + }, }); } - overlay.ShowBeatmapSet(new BeatmapSetInfo - { - Metadata = new BeatmapMetadata - { - Title = @"multiple rulesets beatmap", - Artist = @"none", - Author = new User - { - Username = "BanchoBot", - Id = 3, - } - }, - OnlineInfo = new APIBeatmapSet - { - Covers = new BeatmapSetOnlineCovers(), - Ratings = Enumerable.Range(0, 11).ToArray(), - }, - Beatmaps = beatmaps - }); + var set = getBeatmapSet(); + + set.Beatmaps = beatmaps.ToArray(); + + overlay.ShowBeatmapSet(set); }); - AddAssert("shown beatmaps of current ruleset", () => overlay.Header.HeaderContent.Picker.Difficulties.All(b => b.BeatmapInfo.Ruleset.Equals(overlay.Header.RulesetSelector.Current.Value))); + AddAssert("shown beatmaps of current ruleset", () => overlay.Header.HeaderContent.Picker.Difficulties.All(b => b.Beatmap.Ruleset.OnlineID == overlay.Header.RulesetSelector.Current.Value.OnlineID)); AddAssert("left-most beatmap selected", () => overlay.Header.HeaderContent.Picker.Difficulties.First().State == BeatmapPicker.DifficultySelectorState.Selected); } @@ -246,7 +160,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("show explicit map", () => { var beatmapSet = getBeatmapSet(); - beatmapSet.OnlineInfo.HasExplicitContent = true; + beatmapSet.HasExplicitContent = true; overlay.ShowBeatmapSet(beatmapSet); }); } @@ -257,7 +171,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("show featured map", () => { var beatmapSet = getBeatmapSet(); - beatmapSet.OnlineInfo.TrackId = 1; + beatmapSet.TrackId = 1; overlay.ShowBeatmapSet(beatmapSet); }); } @@ -274,63 +188,41 @@ namespace osu.Game.Tests.Visual.Online AddStep(@"show without reload", overlay.Show); } - private BeatmapSetInfo createManyDifficultiesBeatmapSet() + private APIBeatmapSet createManyDifficultiesBeatmapSet() { - var beatmaps = new List(); + var set = getBeatmapSet(); + + var beatmaps = new List(); for (int i = 1; i < 41; i++) { - beatmaps.Add(new BeatmapInfo + beatmaps.Add(new APIBeatmap { - OnlineBeatmapID = i * 10, - Version = $"Test #{i}", - Ruleset = Ruleset.Value, - StarDifficulty = 2 + i * 0.1, - BaseDifficulty = new BeatmapDifficulty + OnlineID = i * 10, + DifficultyName = $"Test #{i}", + RulesetID = Ruleset.Value.ID ?? -1, + StarRating = 2 + i * 0.1, + OverallDifficulty = 3.5f, + FailTimes = new APIFailTimes { - OverallDifficulty = 3.5f, + Fails = Enumerable.Range(1, 100).Select(j => j % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(j => j % 12 - 6).ToArray(), }, - OnlineInfo = new APIBeatmap - { - FailTimes = new APIFailTimes - { - Fails = Enumerable.Range(1, 100).Select(j => j % 12 - 6).ToArray(), - Retries = Enumerable.Range(-2, 100).Select(j => j % 12 - 6).ToArray(), - }, - } }); } - return new BeatmapSetInfo - { - OnlineBeatmapSetID = 123, - Metadata = new BeatmapMetadata - { - Title = @"many difficulties beatmap", - Artist = @"none", - Author = new User - { - Username = @"BanchoBot", - Id = 3, - }, - }, - OnlineInfo = new APIBeatmapSet - { - Preview = @"https://b.ppy.sh/preview/123.mp3", - HasVideo = true, - HasStoryboard = true, - Covers = new BeatmapSetOnlineCovers(), - Ratings = Enumerable.Range(0, 11).ToArray(), - }, - Beatmaps = beatmaps, - }; + set.Beatmaps = beatmaps.ToArray(); + + return set; } - private BeatmapSetInfo getBeatmapSet() + private APIBeatmapSet getBeatmapSet() { - var beatmapSet = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; + var beatmapSet = CreateAPIBeatmapSet(Ruleset.Value); + // Make sure the overlay is reloaded (see `BeatmapSetInfo.Equals`). - beatmapSet.OnlineBeatmapSetID = nextBeatmapSetId++; + beatmapSet.OnlineID = nextBeatmapSetId++; + return beatmapSet; } diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs index d14f9f47d1..9c0c67b1d8 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlayDetails.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.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -44,27 +43,21 @@ namespace osu.Game.Tests.Visual.Online AddStep("set second set", () => details.BeatmapSet = secondSet); AddAssert("ratings set", () => details.Ratings.Ratings == secondSet.Ratings); - static BeatmapSetInfo createSet() => new BeatmapSetInfo + static APIBeatmapSet createSet() => new APIBeatmapSet { - Beatmaps = new List + Beatmaps = new[] { - new BeatmapInfo + new APIBeatmap { - OnlineInfo = new APIBeatmap + FailTimes = new APIFailTimes { - FailTimes = new APIFailTimes - { - Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(), - Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(), - }, - } + Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(), + }, } }, - OnlineInfo = new APIBeatmapSet - { - Ratings = Enumerable.Range(0, 11).Select(_ => RNG.Next(10)).ToArray(), - Status = BeatmapSetOnlineStatus.Ranked - } + Ratings = Enumerable.Range(0, 11).Select(_ => RNG.Next(10)).ToArray(), + Status = BeatmapSetOnlineStatus.Ranked }; } diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs index b3b67fcbca..be3fc7aff9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs @@ -59,21 +59,18 @@ namespace osu.Game.Tests.Visual.Online var firstBeatmap = createBeatmap(); var secondBeatmap = createBeatmap(); - AddStep("set first set", () => successRate.BeatmapInfo = firstBeatmap); + AddStep("set first set", () => successRate.Beatmap = firstBeatmap); AddAssert("ratings set", () => successRate.Graph.FailTimes == firstBeatmap.FailTimes); - AddStep("set second set", () => successRate.BeatmapInfo = secondBeatmap); + AddStep("set second set", () => successRate.Beatmap = secondBeatmap); AddAssert("ratings set", () => successRate.Graph.FailTimes == secondBeatmap.FailTimes); - static BeatmapInfo createBeatmap() => new BeatmapInfo + static APIBeatmap createBeatmap() => new APIBeatmap { - OnlineInfo = new APIBeatmap + FailTimes = new APIFailTimes { - FailTimes = new APIFailTimes - { - Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(), - Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(), - } + Fails = Enumerable.Range(1, 100).Select(_ => RNG.Next(10)).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(_ => RNG.Next(10)).ToArray(), } }; } @@ -81,14 +78,11 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestOnlyFailMetrics() { - AddStep("set beatmap", () => successRate.BeatmapInfo = new BeatmapInfo + AddStep("set beatmap", () => successRate.Beatmap = new APIBeatmap { - OnlineInfo = new APIBeatmap + FailTimes = new APIFailTimes { - FailTimes = new APIFailTimes - { - Fails = Enumerable.Range(1, 100).ToArray(), - } + Fails = Enumerable.Range(1, 100).ToArray(), } }); @@ -98,12 +92,9 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestEmptyMetrics() { - AddStep("set beatmap", () => successRate.BeatmapInfo = new BeatmapInfo + AddStep("set beatmap", () => successRate.Beatmap = new APIBeatmap { - OnlineInfo = new APIBeatmap - { - FailTimes = new APIFailTimes(), - } + FailTimes = new APIFailTimes() }); AddAssert("graph max values correct", () => successRate.ChildrenOfType().All(graph => graph.MaxValue == 0)); diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs index bb7fcc2fce..3d828077c8 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs @@ -9,7 +9,6 @@ using osu.Game.Beatmaps; using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapListing.Panels; -using osu.Game.Rulesets.Osu; using osu.Game.Tests.Resources; using osuTK; @@ -69,24 +68,7 @@ namespace osu.Game.Tests.Visual.Online AddAssert($"button {(enabled ? "enabled" : "disabled")}", () => downloadButton.DownloadEnabled == enabled); } - private BeatmapSetInfo createSoleily() - { - return new BeatmapSetInfo - { - ID = 1, - OnlineBeatmapSetID = 241526, - OnlineInfo = new APIBeatmapSet - { - Availability = new BeatmapSetOnlineAvailability - { - DownloadDisabled = false, - ExternalLink = string.Empty, - }, - }, - }; - } - - private void createButtonWithBeatmap(BeatmapSetInfo beatmap) + private void createButtonWithBeatmap(IBeatmapSetInfo beatmap) { AddStep("create button", () => { @@ -112,32 +94,47 @@ namespace osu.Game.Tests.Visual.Online }); } - private BeatmapSetInfo getDownloadableBeatmapSet() + private IBeatmapSetInfo createSoleily() { - var normal = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).BeatmapSetInfo; - normal.OnlineInfo.HasVideo = true; - normal.OnlineInfo.HasStoryboard = true; - - return normal; + return new APIBeatmapSet + { + OnlineID = 241526, + Availability = new BeatmapSetOnlineAvailability + { + DownloadDisabled = false, + ExternalLink = string.Empty, + }, + }; } - private BeatmapSetInfo getUndownloadableBeatmapSet() + private IBeatmapSetInfo getDownloadableBeatmapSet() { - var beatmap = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo).BeatmapSetInfo; - beatmap.Metadata.Artist = "test"; - beatmap.Metadata.Title = "undownloadable"; - beatmap.Metadata.AuthorString = "test"; + var apiBeatmapSet = CreateAPIBeatmapSet(); - beatmap.OnlineInfo.HasVideo = true; - beatmap.OnlineInfo.HasStoryboard = true; + apiBeatmapSet.HasVideo = true; + apiBeatmapSet.HasStoryboard = true; - beatmap.OnlineInfo.Availability = new BeatmapSetOnlineAvailability + return apiBeatmapSet; + } + + private IBeatmapSetInfo getUndownloadableBeatmapSet() + { + var apiBeatmapSet = CreateAPIBeatmapSet(); + + apiBeatmapSet.Artist = "test"; + apiBeatmapSet.Title = "undownloadable"; + apiBeatmapSet.AuthorString = "test"; + + apiBeatmapSet.HasVideo = true; + apiBeatmapSet.HasStoryboard = true; + + apiBeatmapSet.Availability = new BeatmapSetOnlineAvailability { DownloadDisabled = true, ExternalLink = "http://osu.ppy.sh", }; - return beatmap; + return apiBeatmapSet; } private class TestDownloadButton : BeatmapPanelDownloadButton @@ -146,7 +143,7 @@ namespace osu.Game.Tests.Visual.Online public DownloadState DownloadState => State.Value; - public TestDownloadButton(BeatmapSetInfo beatmapSet) + public TestDownloadButton(IBeatmapSetInfo beatmapSet) : base(beatmapSet) { } diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs index 6caca2a67c..a3c8935fa8 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectPanel.cs @@ -18,104 +18,25 @@ namespace osu.Game.Tests.Visual.Online [Cached(typeof(IPreviewTrackOwner))] public class TestSceneDirectPanel : OsuTestScene, IPreviewTrackOwner { - private BeatmapSetInfo getUndownloadableBeatmapSet() => new BeatmapSetInfo - { - OnlineBeatmapSetID = 123, - Metadata = new BeatmapMetadata - { - Title = "undownloadable beatmap", - Artist = "test", - Source = "more tests", - Author = new User - { - Username = "BanchoBot", - Id = 3, - }, - }, - OnlineInfo = new APIBeatmapSet - { - Availability = new BeatmapSetOnlineAvailability - { - DownloadDisabled = true, - }, - Preview = @"https://b.ppy.sh/preview/12345.mp3", - PlayCount = 123, - FavouriteCount = 456, - BPM = 111, - HasVideo = true, - HasStoryboard = true, - Covers = new BeatmapSetOnlineCovers(), - }, - Beatmaps = new List - { - new BeatmapInfo - { - Ruleset = Ruleset.Value, - Version = "Test", - StarDifficulty = 6.42, - } - } - }; - - private BeatmapSetInfo getManyDifficultiesBeatmapSet(RulesetStore rulesets) - { - var beatmaps = new List(); - - for (int i = 0; i < 100; i++) - { - beatmaps.Add(new BeatmapInfo - { - Ruleset = rulesets.GetRuleset(i % 4), - StarDifficulty = 2 + i % 4 * 2, - BaseDifficulty = new BeatmapDifficulty - { - OverallDifficulty = 3.5f, - } - }); - } - - return new BeatmapSetInfo - { - OnlineBeatmapSetID = 1, - Metadata = new BeatmapMetadata - { - Title = "many difficulties beatmap", - Artist = "test", - Author = new User - { - Username = "BanchoBot", - Id = 3, - } - }, - OnlineInfo = new APIBeatmapSet - { - HasVideo = true, - HasStoryboard = true, - Covers = new BeatmapSetOnlineCovers(), - }, - Beatmaps = beatmaps, - }; - } - [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { var normal = getBeatmapSet(); - normal.OnlineInfo.HasVideo = true; - normal.OnlineInfo.HasStoryboard = true; + normal.HasVideo = true; + normal.HasStoryboard = true; var undownloadable = getUndownloadableBeatmapSet(); - var manyDifficulties = getManyDifficultiesBeatmapSet(rulesets); + var manyDifficulties = getManyDifficultiesBeatmapSet(); var explicitMap = getBeatmapSet(); - explicitMap.OnlineInfo.HasExplicitContent = true; + explicitMap.HasExplicitContent = true; var featuredMap = getBeatmapSet(); - featuredMap.OnlineInfo.TrackId = 1; + featuredMap.TrackId = 1; var explicitFeaturedMap = getBeatmapSet(); - explicitFeaturedMap.OnlineInfo.HasExplicitContent = true; - explicitFeaturedMap.OnlineInfo.TrackId = 2; + explicitFeaturedMap.HasExplicitContent = true; + explicitFeaturedMap.TrackId = 2; Child = new BasicScrollContainer { @@ -145,7 +66,72 @@ namespace osu.Game.Tests.Visual.Online }, }; - BeatmapSetInfo getBeatmapSet() => CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; + APIBeatmapSet getBeatmapSet() => CreateAPIBeatmapSet(Ruleset.Value); + + APIBeatmapSet getUndownloadableBeatmapSet() => new APIBeatmapSet + { + OnlineID = 123, + Title = "undownloadable beatmap", + Artist = "test", + Source = "more tests", + Author = new User + { + Username = "BanchoBot", + Id = 3, + }, + Availability = new BeatmapSetOnlineAvailability + { + DownloadDisabled = true, + }, + Preview = @"https://b.ppy.sh/preview/12345.mp3", + PlayCount = 123, + FavouriteCount = 456, + BPM = 111, + HasVideo = true, + HasStoryboard = true, + Covers = new BeatmapSetOnlineCovers(), + Beatmaps = new[] + { + new APIBeatmap + { + RulesetID = Ruleset.Value.ID ?? 0, + DifficultyName = "Test", + StarRating = 6.42, + } + } + }; + + APIBeatmapSet getManyDifficultiesBeatmapSet() + { + var beatmaps = new List(); + + for (int i = 0; i < 100; i++) + { + beatmaps.Add(new APIBeatmap + { + RulesetID = i % 4, + StarRating = 2 + i % 4 * 2, + OverallDifficulty = 3.5f, + }); + } + + return new APIBeatmapSet + { + OnlineID = 1, + Title = "undownloadable beatmap", + Artist = "test", + Source = "more tests", + Author = new User + { + Username = "BanchoBot", + Id = 3, + }, + HasVideo = true, + HasStoryboard = true, + Covers = new BeatmapSetOnlineCovers(), + Beatmaps = beatmaps.ToArray(), + }; + } } } } diff --git a/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs b/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs index 8e2ee4e28d..87458da578 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneFavouriteButton.cs @@ -4,7 +4,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; -using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Buttons; using osuTK; @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestLoggedOutIn() { - AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo { OnlineBeatmapSetID = 88 }); + AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new APIBeatmapSet { OnlineID = 88 }); AddStep("log out", () => API.Logout()); checkEnabled(false); AddStep("log in", () => API.Login("test", "test")); @@ -40,9 +40,9 @@ namespace osu.Game.Tests.Visual.Online public void TestBeatmapChange() { AddStep("log in", () => API.Login("test", "test")); - AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo { OnlineBeatmapSetID = 88 }); + AddStep("set valid beatmap", () => favourite.BeatmapSet.Value = new APIBeatmapSet { OnlineID = 88 }); checkEnabled(true); - AddStep("set invalid beatmap", () => favourite.BeatmapSet.Value = new BeatmapSetInfo()); + AddStep("set invalid beatmap", () => favourite.BeatmapSet.Value = new APIBeatmapSet()); checkEnabled(false); } diff --git a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs index fc438ce6dd..aa442ded02 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneLeaderboardModSelector.cs @@ -26,7 +26,8 @@ namespace osu.Game.Tests.Visual.Online { LeaderboardModSelector modSelector; FillFlowContainer selectedMods; - var ruleset = new Bindable(); + + var ruleset = new Bindable(); Add(selectedMods = new FillFlowContainer { diff --git a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs index 991be33917..23899154c4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneScoresContainer.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; +using osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet.Scores; @@ -61,10 +62,10 @@ namespace osu.Game.Tests.Visual.Online }, Mods = new[] { - new OsuModDoubleTime().Acronym, - new OsuModHidden().Acronym, - new OsuModFlashlight().Acronym, - new OsuModHardRock().Acronym, + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, + new APIMod { Acronym = new OsuModHidden().Acronym }, + new APIMod { Acronym = new OsuModFlashlight().Acronym }, + new APIMod { Acronym = new OsuModHardRock().Acronym }, }, Rank = ScoreRank.XH, PP = 200, @@ -86,9 +87,9 @@ namespace osu.Game.Tests.Visual.Online }, Mods = new[] { - new OsuModDoubleTime().Acronym, - new OsuModHidden().Acronym, - new OsuModFlashlight().Acronym, + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, + new APIMod { Acronym = new OsuModHidden().Acronym }, + new APIMod { Acronym = new OsuModFlashlight().Acronym }, }, Rank = ScoreRank.S, PP = 190, @@ -110,8 +111,8 @@ namespace osu.Game.Tests.Visual.Online }, Mods = new[] { - new OsuModDoubleTime().Acronym, - new OsuModHidden().Acronym, + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, + new APIMod { Acronym = new OsuModHidden().Acronym }, }, Rank = ScoreRank.B, PP = 180, @@ -133,7 +134,7 @@ namespace osu.Game.Tests.Visual.Online }, Mods = new[] { - new OsuModDoubleTime().Acronym, + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, }, Rank = ScoreRank.C, PP = 170, @@ -226,10 +227,10 @@ namespace osu.Game.Tests.Visual.Online }, Mods = new[] { - new OsuModDoubleTime().Acronym, - new OsuModHidden().Acronym, - new OsuModFlashlight().Acronym, - new OsuModHardRock().Acronym, + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, + new APIMod { Acronym = new OsuModHidden().Acronym }, + new APIMod { Acronym = new OsuModFlashlight().Acronym }, + new APIMod { Acronym = new OsuModHardRock().Acronym }, }, Rank = ScoreRank.XH, PP = 200, diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs index 513631a221..9c2cc13416 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs @@ -2,16 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Game.Overlays.Profile.Sections.Ranks; -using osu.Framework.Graphics; -using osu.Game.Scoring; -using osu.Framework.Graphics.Containers; -using osuTK; -using osu.Game.Beatmaps; -using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Overlays; using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.Profile.Sections.Ranks; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Scoring; +using osuTK; namespace osu.Game.Tests.Visual.Online { @@ -19,79 +19,79 @@ namespace osu.Game.Tests.Visual.Online { public TestSceneUserProfileScores() { - var firstScore = new ScoreInfo + var firstScore = new APIScoreInfo { PP = 1047.21, Rank = ScoreRank.SH, - BeatmapInfo = new BeatmapInfo + Beatmap = new APIBeatmap { - Metadata = new BeatmapMetadata + BeatmapSet = new APIBeatmapSet { Title = "JUSTadICE (TV Size)", - Artist = "Oomori Seiko" + Artist = "Oomori Seiko", }, - Version = "Extreme" + DifficultyName = "Extreme" }, Date = DateTimeOffset.Now, - Mods = new Mod[] + Mods = new[] { - new OsuModHidden(), - new OsuModHardRock(), - new OsuModDoubleTime() + new APIMod { Acronym = new OsuModHidden().Acronym }, + new APIMod { Acronym = new OsuModHardRock().Acronym }, + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, }, Accuracy = 0.9813 }; - var secondScore = new ScoreInfo + var secondScore = new APIScoreInfo { PP = 134.32, Rank = ScoreRank.A, - BeatmapInfo = new BeatmapInfo + Beatmap = new APIBeatmap { - Metadata = new BeatmapMetadata + BeatmapSet = new APIBeatmapSet { Title = "Triumph & Regret", - Artist = "typeMARS" + Artist = "typeMARS", }, - Version = "[4K] Regret" + DifficultyName = "[4K] Regret" }, Date = DateTimeOffset.Now, - Mods = new Mod[] + Mods = new[] { - new OsuModHardRock(), - new OsuModDoubleTime(), + new APIMod { Acronym = new OsuModHardRock().Acronym }, + new APIMod { Acronym = new OsuModDoubleTime().Acronym }, }, Accuracy = 0.998546 }; - var thirdScore = new ScoreInfo + var thirdScore = new APIScoreInfo { PP = 96.83, Rank = ScoreRank.S, - BeatmapInfo = new BeatmapInfo + Beatmap = new APIBeatmap { - Metadata = new BeatmapMetadata + BeatmapSet = new APIBeatmapSet { Title = "Idolize", - Artist = "Creo" + Artist = "Creo", }, - Version = "Insane" + DifficultyName = "Insane" }, Date = DateTimeOffset.Now, Accuracy = 0.9726 }; - var noPPScore = new ScoreInfo + var noPPScore = new APIScoreInfo { Rank = ScoreRank.B, - BeatmapInfo = new BeatmapInfo + Beatmap = new APIBeatmap { - Metadata = new BeatmapMetadata + BeatmapSet = new APIBeatmapSet { Title = "C18H27NO3(extend)", - Artist = "Team Grimoire" + Artist = "Team Grimoire", }, - Version = "[4K] Cataclysmic Hypernova" + DifficultyName = "[4K] Cataclysmic Hypernova" }, Date = DateTimeOffset.Now, Accuracy = 0.55879 diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index 4538e36c5e..07e68ef509 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics.Sprites; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -88,7 +89,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select EZ mod", () => { - var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance(); + var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull(); SelectedMods.Value = new[] { ruleset.CreateMod() }; }); @@ -105,7 +106,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select HR mod", () => { - var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance(); + var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull(); SelectedMods.Value = new[] { ruleset.CreateMod() }; }); @@ -122,9 +123,9 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select unchanged Difficulty Adjust mod", () => { - var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance(); + var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull(); var difficultyAdjustMod = ruleset.CreateMod(); - difficultyAdjustMod.ReadFromDifficulty(advancedStats.BeatmapInfo.BaseDifficulty); + difficultyAdjustMod.ReadFromDifficulty(advancedStats.BeatmapInfo.Difficulty); SelectedMods.Value = new[] { difficultyAdjustMod }; }); @@ -141,9 +142,9 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select changed Difficulty Adjust mod", () => { - var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance(); + var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance().AsNonNull(); var difficultyAdjustMod = ruleset.CreateMod(); - var originalDifficulty = advancedStats.BeatmapInfo.BaseDifficulty; + var originalDifficulty = advancedStats.BeatmapInfo.Difficulty; difficultyAdjustMod.ReadFromDifficulty(originalDifficulty); difficultyAdjustMod.DrainRate.Value = originalDifficulty.DrainRate - 0.5f; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs index 1125e16d91..bd15c40271 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs @@ -31,154 +31,112 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestAllMetrics() { - AddStep("all metrics", () => details.BeatmapInfo = new BeatmapInfo + AddStep("all metrics", () => details.BeatmapInfo = new APIBeatmap { - BeatmapSet = new BeatmapSetInfo - { - OnlineInfo = new APIBeatmapSet - { - Ratings = Enumerable.Range(0, 11).ToArray(), - } - }, - Version = "All Metrics", - Metadata = new BeatmapMetadata + BeatmapSet = new APIBeatmapSet { Source = "osu!", Tags = "this beatmap has all the metrics", + Ratings = Enumerable.Range(0, 11).ToArray(), }, - BaseDifficulty = new BeatmapDifficulty + DifficultyName = "All Metrics", + CircleSize = 7, + DrainRate = 1, + OverallDifficulty = 5.7f, + ApproachRate = 3.5f, + StarRating = 5.3f, + FailTimes = new APIFailTimes { - CircleSize = 7, - DrainRate = 1, - OverallDifficulty = 5.7f, - ApproachRate = 3.5f, + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), }, - StarDifficulty = 5.3f, - OnlineInfo = new APIBeatmap - { - FailTimes = new APIFailTimes - { - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), - }, - } }); } [Test] public void TestAllMetricsExceptSource() { - AddStep("all except source", () => details.BeatmapInfo = new BeatmapInfo + AddStep("all except source", () => details.BeatmapInfo = new APIBeatmap { - BeatmapSet = new BeatmapSetInfo - { - OnlineInfo = new APIBeatmapSet - { - Ratings = Enumerable.Range(0, 11).ToArray(), - } - }, - Version = "All Metrics", - Metadata = new BeatmapMetadata + BeatmapSet = new APIBeatmapSet { Tags = "this beatmap has all the metrics", + Ratings = Enumerable.Range(0, 11).ToArray(), }, - BaseDifficulty = new BeatmapDifficulty + DifficultyName = "All Metrics", + CircleSize = 7, + DrainRate = 1, + OverallDifficulty = 5.7f, + ApproachRate = 3.5f, + StarRating = 5.3f, + FailTimes = new APIFailTimes { - CircleSize = 7, - DrainRate = 1, - OverallDifficulty = 5.7f, - ApproachRate = 3.5f, + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), }, - StarDifficulty = 5.3f, - OnlineInfo = new APIBeatmap - { - FailTimes = new APIFailTimes - { - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), - }, - } }); } [Test] public void TestOnlyRatings() { - AddStep("ratings", () => details.BeatmapInfo = new BeatmapInfo + AddStep("ratings", () => details.BeatmapInfo = new APIBeatmap { - BeatmapSet = new BeatmapSetInfo - { - OnlineInfo = new APIBeatmapSet - { - Ratings = Enumerable.Range(0, 11).ToArray(), - } - }, - Version = "Only Ratings", - Metadata = new BeatmapMetadata + BeatmapSet = new APIBeatmapSet { + Ratings = Enumerable.Range(0, 11).ToArray(), Source = "osu!", Tags = "this beatmap has ratings metrics but not retries or fails", }, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 6, - DrainRate = 9, - OverallDifficulty = 6, - ApproachRate = 6, - }, - StarDifficulty = 4.8f, + DifficultyName = "Only Ratings", + CircleSize = 6, + DrainRate = 9, + OverallDifficulty = 6, + ApproachRate = 6, + StarRating = 4.8f, }); } [Test] public void TestOnlyFailsAndRetries() { - AddStep("fails retries", () => details.BeatmapInfo = new BeatmapInfo + AddStep("fails retries", () => details.BeatmapInfo = new APIBeatmap { - Version = "Only Retries and Fails", - Metadata = new BeatmapMetadata + DifficultyName = "Only Retries and Fails", + BeatmapSet = new APIBeatmapSet { Source = "osu!", Tags = "this beatmap has retries and fails but no ratings", }, - BaseDifficulty = new BeatmapDifficulty + CircleSize = 3.7f, + DrainRate = 6, + OverallDifficulty = 6, + ApproachRate = 7, + StarRating = 2.91f, + FailTimes = new APIFailTimes { - CircleSize = 3.7f, - DrainRate = 6, - OverallDifficulty = 6, - ApproachRate = 7, + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), }, - StarDifficulty = 2.91f, - OnlineInfo = new APIBeatmap - { - FailTimes = new APIFailTimes - { - Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), - Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), - }, - } }); } [Test] public void TestNoMetrics() { - AddStep("no metrics", () => details.BeatmapInfo = new BeatmapInfo + AddStep("no metrics", () => details.BeatmapInfo = new APIBeatmap { - Version = "No Metrics", - Metadata = new BeatmapMetadata + DifficultyName = "No Metrics", + BeatmapSet = new APIBeatmapSet { Source = "osu!", Tags = "this beatmap has no metrics", }, - BaseDifficulty = new BeatmapDifficulty - { - CircleSize = 5, - DrainRate = 5, - OverallDifficulty = 5.5f, - ApproachRate = 6.5f, - }, - StarDifficulty = 1.97f, + CircleSize = 5, + DrainRate = 5, + OverallDifficulty = 5.5f, + ApproachRate = 6.5f, + StarRating = 1.97f, }); } @@ -191,9 +149,9 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestOnlineMetrics() { - AddStep("online ratings/retries/fails", () => details.BeatmapInfo = new BeatmapInfo + AddStep("online ratings/retries/fails", () => details.BeatmapInfo = new APIBeatmap { - OnlineBeatmapID = 162, + OnlineID = 162, }); AddStep("set online", () => api.SetState(APIState.Online)); AddStep("set offline", () => api.SetState(APIState.Offline)); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs index a9fe7ed7d8..f17de75f5c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatmapListingSearchControl.cs @@ -110,25 +110,19 @@ namespace osu.Game.Tests.Visual.UserInterface base.Dispose(isDisposing); } - private static readonly BeatmapSetInfo beatmap_set = new BeatmapSetInfo + private static readonly APIBeatmapSet beatmap_set = new APIBeatmapSet { - OnlineInfo = new APIBeatmapSet + Covers = new BeatmapSetOnlineCovers { - Covers = new BeatmapSetOnlineCovers - { - Cover = "https://assets.ppy.sh/beatmaps/1094296/covers/cover@2x.jpg?1581416305" - } + Cover = "https://assets.ppy.sh/beatmaps/1094296/covers/cover@2x.jpg?1581416305" } }; - private static readonly BeatmapSetInfo no_cover_beatmap_set = new BeatmapSetInfo + private static readonly APIBeatmapSet no_cover_beatmap_set = new APIBeatmapSet { - OnlineInfo = new APIBeatmapSet + Covers = new BeatmapSetOnlineCovers { - Covers = new BeatmapSetOnlineCovers - { - Cover = string.Empty - } + Cover = string.Empty } }; } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs index fe312ccc8f..d5dda6f6cd 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledDrawable.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.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; @@ -65,10 +66,10 @@ namespace osu.Game.Tests.Visual.UserInterface private void createPaddedComponent(bool hasDescription = false, bool padded = true) { + LabelledDrawable component = null; + AddStep("create component", () => { - LabelledDrawable component; - Child = new Container { Anchor = Anchor.Centre, @@ -81,6 +82,8 @@ namespace osu.Game.Tests.Visual.UserInterface component.Label = "a sample component"; component.Description = hasDescription ? "this text describes the component" : string.Empty; }); + + AddAssert($"description {(hasDescription ? "visible" : "hidden")}", () => component.ChildrenOfType().ElementAt(1).IsPresent == hasDescription); } private class PaddedLabelledDrawable : LabelledDrawable diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs index 74cd675a05..d30f1e8889 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs @@ -12,6 +12,7 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics.Containers; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Tests.Beatmaps.IO; using osuTK; @@ -24,7 +25,6 @@ namespace osu.Game.Tests.Visual.UserInterface private BeatmapSetInfo testBeatmap; private IAPIProvider api; - private RulesetStore rulesets; [Resolved] private BeatmapManager beatmaps { get; set; } @@ -33,7 +33,6 @@ namespace osu.Game.Tests.Visual.UserInterface private void load(OsuGameBase osu, IAPIProvider api, RulesetStore rulesets) { this.api = api; - this.rulesets = rulesets; testBeatmap = ImportBeatmapTest.LoadOszIntoOsu(osu).Result; } @@ -81,7 +80,7 @@ namespace osu.Game.Tests.Visual.UserInterface Child = background = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both, - Beatmap = { Value = new BeatmapInfo { BeatmapSet = req.Response?.ToBeatmapSet(rulesets) } } + Beatmap = { Value = new APIBeatmap { BeatmapSet = req.Response } } }; }); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs index f67f6258cc..7b6774a6b2 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapSetCover.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("setup cover", () => Child = new UpdateableOnlineBeatmapSetCover(coverType) { - OnlineInfo = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet.OnlineInfo, + OnlineInfo = CreateAPIBeatmapSet(), RelativeSizeAxes = Axes.Both, Masking = true, }); @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("setup covers", () => { - BeatmapSetInfo setInfo = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet; + var beatmapSet = CreateAPIBeatmapSet(); FillFlowContainer fillFlow; @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.UserInterface var cover = new UpdateableOnlineBeatmapSetCover(coverType) { - OnlineInfo = setInfo.OnlineInfo, + OnlineInfo = beatmapSet, Height = 100, Masking = true, }; @@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("setup cover", () => Child = updateableCover = new TestUpdateableOnlineBeatmapSetCover { - OnlineInfo = CreateBeatmap(Ruleset.Value).BeatmapInfo.BeatmapSet.OnlineInfo, + OnlineInfo = CreateAPIBeatmapSet(), RelativeSizeAxes = Axes.Both, Masking = true, }); @@ -117,7 +117,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("setup cover", () => Child = updateableCover = new TestUpdateableOnlineBeatmapSetCover(0) { - OnlineInfo = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg").OnlineInfo, + OnlineInfo = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1189904/covers/cover.jpg"), RelativeSizeAxes = Axes.Both, Masking = true, Alpha = 0.4f @@ -128,16 +128,13 @@ namespace osu.Game.Tests.Visual.UserInterface AddUntilStep("wait for fade complete", () => initialCover.Alpha == 1); AddStep("switch beatmap", - () => updateableCover.OnlineInfo = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg").OnlineInfo); + () => updateableCover.OnlineInfo = createBeatmapWithCover("https://assets.ppy.sh/beatmaps/1079428/covers/cover.jpg")); AddUntilStep("new cover loaded", () => updateableCover.ChildrenOfType().Except(new[] { initialCover }).Any()); } - private static BeatmapSetInfo createBeatmapWithCover(string coverUrl) => new BeatmapSetInfo + private static APIBeatmapSet createBeatmapWithCover(string coverUrl) => new APIBeatmapSet { - OnlineInfo = new APIBeatmapSet - { - Covers = new BeatmapSetOnlineCovers { Cover = coverUrl } - } + Covers = new BeatmapSetOnlineCovers { Cover = coverUrl } }; private class TestUpdateableOnlineBeatmapSetCover : UpdateableOnlineBeatmapSetCover diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index cd56cb51ae..57815d9273 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 2673c9ec9f..c0f94d49c7 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 9a0cdb387d..035f438b89 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 || localRulesetInfo == null) + if (localBeatmapInfo == null || localBeatmapInfo.ID == 0 || 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/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 9069ea4404..01a819dead 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -17,7 +17,7 @@ namespace osu.Game.Beatmaps { [ExcludeFromDynamicCompile] [Serializable] - public class BeatmapInfo : IEquatable, IHasPrimaryKey, IBeatmapInfo, IBeatmapOnlineInfo + public class BeatmapInfo : IEquatable, IHasPrimaryKey, IBeatmapInfo { public int ID { get; set; } @@ -186,7 +186,7 @@ namespace osu.Game.Beatmaps string IBeatmapInfo.DifficultyName => Version; [JsonIgnore] - IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata; + IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata ?? BeatmapSet?.Metadata ?? new BeatmapMetadata(); [JsonIgnore] IBeatmapDifficultyInfo IBeatmapInfo.Difficulty => BaseDifficulty; @@ -201,24 +201,5 @@ namespace osu.Game.Beatmaps double IBeatmapInfo.StarRating => StarDifficulty; #endregion - - #region Implementation of IBeatmapOnlineInfo - - [JsonIgnore] - public int CircleCount => OnlineInfo.CircleCount; - - [JsonIgnore] - public int SliderCount => OnlineInfo.SliderCount; - - [JsonIgnore] - public int PlayCount => OnlineInfo.PlayCount; - - [JsonIgnore] - public int PassCount => OnlineInfo.PassCount; - - [JsonIgnore] - public APIFailTimes FailTimes => OnlineInfo.FailTimes; - - #endregion } } diff --git a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs index 2d69015933..707b588063 100644 --- a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs @@ -11,14 +11,14 @@ namespace osu.Game.Beatmaps /// /// A user-presentable display title representing this beatmap. /// - public static string GetDisplayTitle(this IBeatmapInfo beatmapInfo) => $"{getClosestMetadata(beatmapInfo)} {getVersionString(beatmapInfo)}".Trim(); + public static string GetDisplayTitle(this IBeatmapInfo beatmapInfo) => $"{beatmapInfo.Metadata} {getVersionString(beatmapInfo)}".Trim(); /// /// A user-presentable display title representing this beatmap, with localisation handling for potentially romanisable fields. /// public static RomanisableString GetDisplayTitleRomanisable(this IBeatmapInfo beatmapInfo, bool includeDifficultyName = true, bool includeCreator = true) { - var metadata = getClosestMetadata(beatmapInfo).GetDisplayTitleRomanisable(includeCreator); + var metadata = beatmapInfo.Metadata.GetDisplayTitleRomanisable(includeCreator); if (includeDifficultyName) { @@ -32,12 +32,8 @@ namespace osu.Game.Beatmaps public static string[] GetSearchableTerms(this IBeatmapInfo beatmapInfo) => new[] { beatmapInfo.DifficultyName - }.Concat(getClosestMetadata(beatmapInfo).GetSearchableTerms()).Where(s => !string.IsNullOrEmpty(s)).ToArray(); + }.Concat(beatmapInfo.Metadata.GetSearchableTerms()).Where(s => !string.IsNullOrEmpty(s)).ToArray(); private static string getVersionString(IBeatmapInfo beatmapInfo) => string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? string.Empty : $"[{beatmapInfo.DifficultyName}]"; - - // temporary helper methods until we figure which metadata should be where. - private static IBeatmapMetadataInfo getClosestMetadata(IBeatmapInfo beatmapInfo) => - beatmapInfo.Metadata ?? beatmapInfo.BeatmapSet?.Metadata ?? new BeatmapMetadata(); } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 0509a9db47..7a9f3e0a45 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -114,7 +114,8 @@ namespace osu.Game.Beatmaps /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. - public virtual void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) => beatmapModelManager.Save(info, beatmapContent, beatmapSkin); + public virtual void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) => + beatmapModelManager.Save(info, beatmapContent, beatmapSkin); /// /// Returns a list of all usable s. @@ -249,6 +250,23 @@ namespace osu.Game.Beatmaps public IBindable>> DownloadFailed => beatmapModelDownloader.DownloadFailed; + // Temporary method until this class supports IBeatmapSetInfo or otherwise. + public bool Download(IBeatmapSetInfo model, bool minimiseDownloadSize = false) + { + return beatmapModelDownloader.Download(new BeatmapSetInfo + { + OnlineBeatmapSetID = model.OnlineID, + Metadata = new BeatmapMetadata + { + Title = model.Metadata?.Title ?? string.Empty, + Artist = model.Metadata?.Artist ?? string.Empty, + TitleUnicode = model.Metadata?.TitleUnicode ?? string.Empty, + ArtistUnicode = model.Metadata?.ArtistUnicode ?? string.Empty, + Author = new User { Username = model.Metadata?.Author }, + } + }, minimiseDownloadSize); + } + public bool Download(BeatmapSetInfo model, bool minimiseDownloadSize = false) { return beatmapModelDownloader.Download(model, minimiseDownloadSize); diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 711533e118..5d3b4007ae 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -9,6 +9,8 @@ using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Users; +#nullable enable + namespace osu.Game.Beatmaps { [ExcludeFromDynamicCompile] @@ -17,21 +19,21 @@ namespace osu.Game.Beatmaps { public int ID { get; set; } - public string Title { get; set; } + public string Title { get; set; } = string.Empty; [JsonProperty("title_unicode")] - public string TitleUnicode { get; set; } + public string TitleUnicode { get; set; } = string.Empty; - public string Artist { get; set; } + public string Artist { get; set; } = string.Empty; [JsonProperty("artist_unicode")] - public string ArtistUnicode { get; set; } + public string ArtistUnicode { get; set; } = string.Empty; [JsonIgnore] - public List Beatmaps { get; set; } + public List Beatmaps { get; set; } = new List(); [JsonIgnore] - public List BeatmapSets { get; set; } + public List BeatmapSets { get; set; } = new List(); /// /// Helper property to deserialize a username to . @@ -55,7 +57,7 @@ namespace osu.Game.Beatmaps [Column("Author")] public string AuthorString { - get => Author?.Username; + get => Author?.Username ?? string.Empty; set { Author ??= new User(); @@ -67,22 +69,22 @@ namespace osu.Game.Beatmaps /// The author of the beatmaps in this set. /// [JsonIgnore] - public User Author; + public User? Author; - public string Source { get; set; } + public string Source { get; set; } = string.Empty; [JsonProperty(@"tags")] - public string Tags { get; set; } + public string Tags { get; set; } = string.Empty; /// /// The time in milliseconds to begin playing the track for preview purposes. /// If -1, the track should begin playing at 40% of its length. /// - public int PreviewTime { get; set; } + public int PreviewTime { get; set; } = -1; - public string AudioFile { get; set; } + public string AudioFile { get; set; } = string.Empty; - public string BackgroundFile { get; set; } + public string BackgroundFile { get; set; } = string.Empty; public bool Equals(BeatmapMetadata other) => ((IBeatmapMetadataInfo)this).Equals(other); diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 16cf6193f9..f148d05aca 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -216,7 +216,8 @@ namespace osu.Game.Beatmaps var fileInfo = setInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, beatmapInfo.Path, StringComparison.OrdinalIgnoreCase)) ?? new BeatmapSetFileInfo(); // metadata may have changed; update the path with the standard format. - beatmapInfo.Path = $"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}].osu"; + beatmapInfo.Path = GetValidFilename($"{metadata.Artist} - {metadata.Title} ({metadata.Author}) [{beatmapInfo.Version}].osu"); + beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); // update existing or populate new file's filename. diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 79cc8b70fb..638366c580 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -6,10 +6,8 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using JetBrains.Annotations; -using Newtonsoft.Json; using osu.Framework.Testing; using osu.Game.Database; -using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Beatmaps { @@ -32,13 +30,11 @@ namespace osu.Game.Beatmaps public List Beatmaps { get; set; } + public BeatmapSetOnlineStatus Status { get; set; } = BeatmapSetOnlineStatus.None; + [NotNull] public List Files { get; set; } = new List(); - // This field is temporary and only used by `APIBeatmapSet.ToBeatmapSet` (soon to be removed) and tests (to be updated to provide APIBeatmapSet instead). - [NotMapped] - public APIBeatmapSet OnlineInfo { get; set; } - /// /// The maximum star difficulty of all beatmaps in this set. /// @@ -100,80 +96,5 @@ namespace osu.Game.Beatmaps IEnumerable IBeatmapSetInfo.Files => Files; #endregion - - #region Delegation for IBeatmapSetOnlineInfo - - [NotMapped] - [JsonIgnore] - public DateTimeOffset Submitted => OnlineInfo.Submitted; - - [NotMapped] - [JsonIgnore] - public DateTimeOffset? Ranked => OnlineInfo.Ranked; - - [NotMapped] - [JsonIgnore] - public DateTimeOffset? LastUpdated => OnlineInfo.LastUpdated; - - [JsonIgnore] - public BeatmapSetOnlineStatus Status { get; set; } = BeatmapSetOnlineStatus.None; - - [NotMapped] - [JsonIgnore] - public bool HasExplicitContent => OnlineInfo.HasExplicitContent; - - [NotMapped] - [JsonIgnore] - public bool HasVideo => OnlineInfo.HasVideo; - - [NotMapped] - [JsonIgnore] - public bool HasStoryboard => OnlineInfo.HasStoryboard; - - [NotMapped] - [JsonIgnore] - public BeatmapSetOnlineCovers Covers => OnlineInfo.Covers; - - [NotMapped] - [JsonIgnore] - public string Preview => OnlineInfo.Preview; - - [NotMapped] - [JsonIgnore] - public double BPM => OnlineInfo.BPM; - - [NotMapped] - [JsonIgnore] - public int PlayCount => OnlineInfo.PlayCount; - - [NotMapped] - [JsonIgnore] - public int FavouriteCount => OnlineInfo.FavouriteCount; - - [NotMapped] - [JsonIgnore] - public bool HasFavourited => OnlineInfo.HasFavourited; - - [NotMapped] - [JsonIgnore] - public BeatmapSetOnlineAvailability Availability => OnlineInfo.Availability; - - [NotMapped] - [JsonIgnore] - public BeatmapSetOnlineGenre Genre => OnlineInfo.Genre; - - [NotMapped] - [JsonIgnore] - public BeatmapSetOnlineLanguage Language => OnlineInfo.Language; - - [NotMapped] - [JsonIgnore] - public int? TrackId => OnlineInfo?.TrackId; - - [NotMapped] - [JsonIgnore] - public int[] Ratings => OnlineInfo?.Ratings; - - #endregion } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs new file mode 100644 index 0000000000..8136ebbd70 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -0,0 +1,280 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.BeatmapSet; +using osuTK; +using osu.Game.Overlays.BeatmapListing.Panels; +using osu.Game.Resources.Localisation.Web; +using osuTK.Graphics; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public class BeatmapCard : OsuClickableContainer + { + public const float TRANSITION_DURATION = 400; + + private const float width = 408; + private const float height = 100; + private const float corner_radius = 10; + + private readonly APIBeatmapSet beatmapSet; + + private UpdateableOnlineBeatmapSetCover leftCover; + private FillFlowContainer iconArea; + + private Container mainContent; + private BeatmapCardContentBackground mainContentBackground; + + private GridContainer titleContainer; + private GridContainer artistContainer; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + public BeatmapCard(APIBeatmapSet beatmapSet) + : base(HoverSampleSet.Submit) + { + this.beatmapSet = beatmapSet; + } + + [BackgroundDependencyLoader] + private void load() + { + Width = width; + Height = height; + CornerRadius = corner_radius; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background3 + }, + new Container + { + Name = @"Left (icon) area", + Size = new Vector2(height), + Children = new Drawable[] + { + leftCover = new UpdateableOnlineBeatmapSetCover(BeatmapSetCoverType.List) + { + RelativeSizeAxes = Axes.Both, + OnlineInfo = beatmapSet + }, + iconArea = new FillFlowContainer + { + Margin = new MarginPadding(5), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(1) + } + } + }, + mainContent = new Container + { + Name = @"Main content", + X = height - corner_radius, + Height = height, + CornerRadius = corner_radius, + Masking = true, + Children = new Drawable[] + { + mainContentBackground = new BeatmapCardContentBackground(beatmapSet) + { + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Horizontal = 10, + Vertical = 4 + }, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + titleContainer = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new[] + { + new OsuSpriteText + { + Text = new RomanisableString(beatmapSet.TitleUnicode, beatmapSet.Title), + Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold), + RelativeSizeAxes = Axes.X, + Truncate = true + }, + Empty() + } + } + }, + artistContainer = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new[] + { + new OsuSpriteText + { + Text = createArtistText(), + Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold), + RelativeSizeAxes = Axes.X, + Truncate = true + }, + Empty() + }, + } + }, + new LinkFlowContainer(s => + { + s.Shadow = false; + s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold); + }).With(d => + { + d.AutoSizeAxes = Axes.Both; + d.Margin = new MarginPadding { Top = 2 }; + d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); + d.AddUserLink(beatmapSet.Author); + }), + } + }, + new FillFlowContainer + { + Name = @"Bottom content", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding + { + Horizontal = 10, + Vertical = 4 + }, + Spacing = new Vector2(4, 0), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Children = new Drawable[] + { + new BeatmapSetOnlineStatusPill + { + AutoSizeAxes = Axes.Both, + Status = beatmapSet.Status, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft + }, + new DifficultySpectrumDisplay(beatmapSet) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + DotSize = new Vector2(6, 12) + } + } + } + } + } + }; + + if (beatmapSet.HasVideo) + iconArea.Add(new IconPill(FontAwesome.Solid.Film)); + + if (beatmapSet.HasStoryboard) + iconArea.Add(new IconPill(FontAwesome.Solid.Image)); + + if (beatmapSet.HasExplicitContent) + { + titleContainer.Content[0][1] = new ExplicitContentBeatmapPill + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding { Left = 5 } + }; + } + + if (beatmapSet.TrackId != null) + { + artistContainer.Content[0][1] = new FeaturedArtistBeatmapPill + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding { Left = 5 } + }; + } + } + + protected override void LoadComplete() + { + base.LoadComplete(); + updateState(); + FinishTransforms(true); + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateState(); + base.OnHoverLost(e); + } + + private LocalisableString createArtistText() + { + var romanisableArtist = new RomanisableString(beatmapSet.ArtistUnicode, beatmapSet.Artist); + return BeatmapsetsStrings.ShowDetailsByArtist(romanisableArtist); + } + + private void updateState() + { + float targetWidth = width - height; + if (IsHovered) + targetWidth -= 20; + + mainContent.ResizeWidthTo(targetWidth, TRANSITION_DURATION, Easing.OutQuint); + mainContentBackground.Dimmed.Value = IsHovered; + + leftCover.FadeColour(IsHovered ? OsuColour.Gray(0.2f) : Color4.White, TRANSITION_DURATION, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContentBackground.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContentBackground.cs new file mode 100644 index 0000000000..392f5d1bfa --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContentBackground.cs @@ -0,0 +1,71 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Overlays; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public class BeatmapCardContentBackground : CompositeDrawable + { + public BindableBool Dimmed { get; } = new BindableBool(); + + private readonly Box background; + private readonly DelayedLoadUnloadWrapper cover; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + public BeatmapCardContentBackground(IBeatmapSetOnlineInfo onlineInfo) + { + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + }, + cover = new DelayedLoadUnloadWrapper(() => createCover(onlineInfo), 500, 500) + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.Transparent + } + }; + } + + private static Drawable createCover(IBeatmapSetOnlineInfo onlineInfo) => new OnlineBeatmapSetCover(onlineInfo) + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill + }; + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + background.Colour = colourProvider.Background2; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + Dimmed.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + private void updateState() => Schedule(() => + { + background.FadeColour(Dimmed.Value ? colourProvider.Background4 : colourProvider.Background2, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + + var gradient = ColourInfo.GradientHorizontal(Colour4.White.Opacity(0), Colour4.White.Opacity(0.2f)); + cover.FadeColour(gradient, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + }); + } +} diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 64412675bb..6e573cc2a0 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -60,8 +60,9 @@ namespace osu.Game.Beatmaps.Drawables /// The ruleset to show the difficulty with. /// The mods to show the difficulty with. /// Whether to display a tooltip when hovered. - public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true) - : this(beatmapInfo, shouldShowTooltip) + /// Whether to perform difficulty lookup (including calculation if necessary). + public DifficultyIcon([NotNull] IBeatmapInfo beatmapInfo, [CanBeNull] IRulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true) + : this(beatmapInfo, shouldShowTooltip, performBackgroundDifficultyLookup) { this.ruleset = ruleset ?? beatmapInfo.Ruleset; this.mods = mods ?? Array.Empty(); diff --git a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs index 4100fe9586..636c568bd0 100644 --- a/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game/Beatmaps/Drawables/UpdateableBeatmapBackgroundSprite.cs @@ -57,12 +57,7 @@ namespace osu.Game.Beatmaps.Drawables return new OnlineBeatmapSetCover(online, beatmapSetCoverType); if (model is BeatmapInfo localModel) - { - if (localModel.BeatmapSet?.OnlineInfo != null) - return new OnlineBeatmapSetCover(localModel.BeatmapSet.OnlineInfo, beatmapSetCoverType); - return new BeatmapBackgroundSprite(beatmaps.GetWorkingBeatmap(localModel)); - } return new BeatmapBackgroundSprite(beatmaps.DefaultBeatmap); } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 89541a0845..3ed5055b7f 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -82,7 +82,7 @@ namespace osu.Game.Beatmaps.Formats { writer.WriteLine("[General]"); - if (beatmap.Metadata.AudioFile != null) writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}")); + if (!string.IsNullOrEmpty(beatmap.Metadata.AudioFile)) writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}")); writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}")); writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}")); writer.WriteLine(FormattableString.Invariant($"Countdown: {(int)beatmap.BeatmapInfo.Countdown}")); @@ -126,13 +126,13 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine("[Metadata]"); writer.WriteLine(FormattableString.Invariant($"Title: {beatmap.Metadata.Title}")); - if (beatmap.Metadata.TitleUnicode != null) writer.WriteLine(FormattableString.Invariant($"TitleUnicode: {beatmap.Metadata.TitleUnicode}")); + if (!string.IsNullOrEmpty(beatmap.Metadata.TitleUnicode)) writer.WriteLine(FormattableString.Invariant($"TitleUnicode: {beatmap.Metadata.TitleUnicode}")); writer.WriteLine(FormattableString.Invariant($"Artist: {beatmap.Metadata.Artist}")); - if (beatmap.Metadata.ArtistUnicode != null) writer.WriteLine(FormattableString.Invariant($"ArtistUnicode: {beatmap.Metadata.ArtistUnicode}")); + if (!string.IsNullOrEmpty(beatmap.Metadata.ArtistUnicode)) writer.WriteLine(FormattableString.Invariant($"ArtistUnicode: {beatmap.Metadata.ArtistUnicode}")); writer.WriteLine(FormattableString.Invariant($"Creator: {beatmap.Metadata.AuthorString}")); writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.Version}")); - if (beatmap.Metadata.Source != null) writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}")); - if (beatmap.Metadata.Tags != null) writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}")); + if (!string.IsNullOrEmpty(beatmap.Metadata.Source)) writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}")); + if (!string.IsNullOrEmpty(beatmap.Metadata.Tags)) writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}")); if (beatmap.BeatmapInfo.OnlineBeatmapID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineBeatmapID}")); if (beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID}")); } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 56525ddb14..0276abc3ff 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -27,7 +27,7 @@ namespace osu.Game.Beatmaps.Formats protected override void ParseStreamInto(LineBufferedReader stream, T output) { - Section section = Section.None; + Section section = Section.General; string line; @@ -47,10 +47,7 @@ namespace osu.Game.Beatmaps.Formats if (line.StartsWith('[') && line.EndsWith(']')) { if (!Enum.TryParse(line[1..^1], out section)) - { Logger.Log($"Unknown section \"{line}\" in \"{output}\""); - section = Section.None; - } OnBeginNewSection(section); continue; @@ -148,7 +145,6 @@ namespace osu.Game.Beatmaps.Formats protected enum Section { - None, General, Editor, Metadata, diff --git a/osu.Game/Beatmaps/IBeatmapInfo.cs b/osu.Game/Beatmaps/IBeatmapInfo.cs index d206cfaaed..84ea6d3019 100644 --- a/osu.Game/Beatmaps/IBeatmapInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapInfo.cs @@ -21,7 +21,7 @@ namespace osu.Game.Beatmaps /// /// The metadata representing this beatmap. May be shared between multiple beatmaps. /// - IBeatmapMetadataInfo? Metadata { get; } + IBeatmapMetadataInfo Metadata { get; } /// /// The difficulty settings for this beatmap. diff --git a/osu.Game/Beatmaps/WorkingBeatmapCache.cs b/osu.Game/Beatmaps/WorkingBeatmapCache.cs index 65f84984c2..11257e3abc 100644 --- a/osu.Game/Beatmaps/WorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/WorkingBeatmapCache.cs @@ -149,7 +149,7 @@ namespace osu.Game.Beatmaps protected override Texture GetBackground() { - if (Metadata?.BackgroundFile == null) + if (string.IsNullOrEmpty(Metadata?.BackgroundFile)) return null; try @@ -165,7 +165,7 @@ namespace osu.Game.Beatmaps protected override Track GetBeatmapTrack() { - if (Metadata?.AudioFile == null) + if (string.IsNullOrEmpty(Metadata?.AudioFile)) return null; try @@ -181,7 +181,7 @@ namespace osu.Game.Beatmaps protected override Waveform GetWaveform() { - if (Metadata?.AudioFile == null) + if (string.IsNullOrEmpty(Metadata?.AudioFile)) return null; try diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 49c9ac5c65..4e40e52051 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -466,7 +466,7 @@ namespace osu.Game.Database if (retrievedItem == null) throw new ArgumentException(@"Specified model could not be found", nameof(item)); - string filename = $"{getValidFilename(item.ToString())}{HandledExtensions.First()}"; + string filename = $"{GetValidFilename(item.ToString())}{HandledExtensions.First()}"; using (var stream = exportStorage.GetStream(filename, FileAccess.Write, FileMode.Create)) ExportModelTo(retrievedItem, stream); @@ -913,9 +913,15 @@ namespace osu.Game.Database return Guid.NewGuid().ToString(); } - private string getValidFilename(string filename) + private readonly char[] invalidFilenameCharacters = Path.GetInvalidFileNameChars() + // Backslash is added to avoid issues when exporting to zip. + // See SharpCompress filename normalisation https://github.com/adamhathcock/sharpcompress/blob/a1e7c0068db814c9aa78d86a94ccd1c761af74bd/src/SharpCompress/Writers/Zip/ZipWriter.cs#L143. + .Append('\\') + .ToArray(); + + protected string GetValidFilename(string filename) { - foreach (char c in Path.GetInvalidFileNameChars()) + foreach (char c in invalidFilenameCharacters) filename = filename.Replace(c, '_'); return filename; } diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 0b43c16ebe..7d1210d0e3 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -7,12 +7,10 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using System.Collections.Generic; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; using osu.Framework.Platform; -using osu.Game.Graphics.Sprites; using osu.Game.Users; namespace osu.Game.Graphics.Containers @@ -58,23 +56,14 @@ namespace osu.Game.Graphics.Containers AddText(text.Substring(previousLinkEnd)); } - public void AddLink(string text, string url, Action creationParameters = null) => + public void AddLink(LocalisableString text, string url, Action creationParameters = null) => createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.External, url), url); - public void AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null) + public void AddLink(LocalisableString text, Action action, string tooltipText = null, Action creationParameters = null) => createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(LinkAction.Custom, string.Empty), tooltipText, action); - public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) - => createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(action, argument), tooltipText); - public void AddLink(LocalisableString text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) - { - var spriteText = new OsuSpriteText { Text = text }; - - AddText(spriteText, creationParameters); - RemoveInternal(spriteText); // TODO: temporary, will go away when TextParts support localisation properly. - createLink(new TextPartManual(spriteText.Yield()), new LinkDetails(action, argument), tooltipText); - } + => createLink(CreateChunkFor(text, true, CreateSpriteText, creationParameters), new LinkDetails(action, argument), tooltipText); public void AddLink(IEnumerable text, LinkAction action, string linkArgument, string tooltipText = null) { diff --git a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs index e8f80dec57..da511d8212 100644 --- a/osu.Game/Graphics/UserInterface/OsuCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuCheckbox.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics.Containers; namespace osu.Game.Graphics.UserInterface @@ -19,7 +20,7 @@ namespace osu.Game.Graphics.UserInterface /// protected virtual bool PlaySoundsOnUserChange => true; - public string LabelText + public LocalisableString LabelText { set { diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs index d5f76733cf..1e6032c1d0 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs @@ -8,6 +8,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Localisation; using osu.Game.Graphics.Containers; using osu.Game.Overlays; using osuTK; @@ -156,18 +157,18 @@ namespace osu.Game.Graphics.UserInterfaceV2 descriptionText.Colour = osuColour.Yellow; } - public string Label + public LocalisableString Label { set => labelText.Text = value; } - public string Description + public LocalisableString Description { set { descriptionText.Text = value; - if (!string.IsNullOrEmpty(value)) + if (!string.IsNullOrEmpty(value.ToString())) descriptionText.Show(); else descriptionText.Hide(); diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index d60c9cfe65..69d72226ba 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -107,7 +107,8 @@ namespace osu.Game.Online.API WebRequest = CreateWebRequest(); WebRequest.Failed += Fail; WebRequest.AllowRetryOnTimeout = false; - WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}"); + if (!string.IsNullOrEmpty(API.AccessToken)) + WebRequest.AddHeader("Authorization", $"Bearer {API.AccessToken}"); if (isFailing) return; diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 790a247ccb..8e447390e6 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -14,15 +14,15 @@ namespace osu.Game.Online.API.Requests { public class GetScoresRequest : APIRequest { - private readonly BeatmapInfo beatmapInfo; + private readonly IBeatmapInfo beatmapInfo; private readonly BeatmapLeaderboardScope scope; - private readonly RulesetInfo ruleset; + private readonly IRulesetInfo ruleset; private readonly IEnumerable mods; - public GetScoresRequest(BeatmapInfo beatmapInfo, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null) + public GetScoresRequest(IBeatmapInfo beatmapInfo, IRulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null) { - if (!beatmapInfo.OnlineBeatmapID.HasValue) - throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); + if (beatmapInfo.OnlineID <= 0) + throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(IBeatmapInfo.OnlineID)}."); if (scope == BeatmapLeaderboardScope.Local) throw new InvalidOperationException("Should not attempt to request online scores for a local scoped leaderboard"); @@ -33,7 +33,7 @@ namespace osu.Game.Online.API.Requests this.mods = mods ?? Array.Empty(); } - protected override string Target => $@"beatmaps/{beatmapInfo.OnlineBeatmapID}/scores{createQueryParameters()}"; + protected override string Target => $@"beatmaps/{beatmapInfo.OnlineID}/scores{createQueryParameters()}"; private string createQueryParameters() { diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index e65dca752b..653b011f73 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -43,16 +43,16 @@ namespace osu.Game.Online.API.Requests.Responses public double StarRating { get; set; } [JsonProperty(@"drain")] - private float drainRate { get; set; } + public float DrainRate { get; set; } [JsonProperty(@"cs")] - private float circleSize { get; set; } + public float CircleSize { get; set; } [JsonProperty(@"ar")] - private float approachRate { get; set; } + public float ApproachRate { get; set; } [JsonProperty(@"accuracy")] - private float overallDifficulty { get; set; } + public float OverallDifficulty { get; set; } [JsonIgnore] public double Length { get; set; } @@ -100,10 +100,10 @@ namespace osu.Game.Online.API.Requests.Responses MaxCombo = MaxCombo, BaseDifficulty = new BeatmapDifficulty { - DrainRate = drainRate, - CircleSize = circleSize, - ApproachRate = approachRate, - OverallDifficulty = overallDifficulty, + DrainRate = DrainRate, + CircleSize = CircleSize, + ApproachRate = ApproachRate, + OverallDifficulty = OverallDifficulty, }, OnlineInfo = this, }; @@ -115,10 +115,10 @@ namespace osu.Game.Online.API.Requests.Responses public IBeatmapDifficultyInfo Difficulty => new BeatmapDifficulty { - DrainRate = drainRate, - CircleSize = circleSize, - ApproachRate = approachRate, - OverallDifficulty = overallDifficulty, + DrainRate = DrainRate, + CircleSize = CircleSize, + ApproachRate = ApproachRate, + OverallDifficulty = OverallDifficulty, }; IBeatmapSetInfo? IBeatmapInfo.BeatmapSet => BeatmapSet; diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index d8efa20b39..c41271ad5c 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -119,7 +119,7 @@ namespace osu.Game.Online.API.Requests.Responses public string Tags { get; set; } = string.Empty; [JsonProperty(@"beatmaps")] - public IEnumerable Beatmaps { get; set; } = Array.Empty(); + public APIBeatmap[] Beatmaps { get; set; } = Array.Empty(); public virtual BeatmapSetInfo ToBeatmapSet(RulesetStore rulesets) { @@ -128,7 +128,6 @@ namespace osu.Game.Online.API.Requests.Responses OnlineBeatmapSetID = OnlineID, Metadata = metadata, Status = Status, - OnlineInfo = this }; beatmapSet.Beatmaps = Beatmaps.Select(b => diff --git a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs index 1b66a1dcc3..5395fe0429 100644 --- a/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APIScoreInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using osu.Game.Beatmaps; @@ -36,6 +37,7 @@ namespace osu.Game.Online.API.Requests.Responses public DateTimeOffset Date { get; set; } [JsonProperty(@"beatmap")] + [CanBeNull] public APIBeatmap Beatmap { get; set; } [JsonProperty("accuracy")] @@ -45,6 +47,7 @@ namespace osu.Game.Online.API.Requests.Responses public double? PP { get; set; } [JsonProperty(@"beatmapset")] + [CanBeNull] public APIBeatmapSet BeatmapSet { set @@ -62,10 +65,13 @@ namespace osu.Game.Online.API.Requests.Responses public Dictionary Statistics { get; set; } [JsonProperty(@"mode_int")] - public int OnlineRulesetID { get; set; } + public int RulesetID { get; set; } [JsonProperty(@"mods")] - public string[] Mods { get; set; } + private string[] mods { set => Mods = value.Select(acronym => new APIMod { Acronym = acronym }); } + + [NotNull] + public IEnumerable Mods { get; set; } = Array.Empty(); [JsonProperty("rank")] [JsonConverter(typeof(StringEnumConverter))] @@ -79,30 +85,30 @@ namespace osu.Game.Online.API.Requests.Responses /// public ScoreInfo CreateScoreInfo(RulesetStore rulesets, BeatmapInfo beatmap = null) { - var ruleset = rulesets.GetRuleset(OnlineRulesetID); + var ruleset = rulesets.GetRuleset(RulesetID); var rulesetInstance = ruleset.CreateInstance(); - var mods = Mods != null ? Mods.Select(acronym => rulesetInstance.CreateModFromAcronym(acronym)).Where(m => m != null).ToArray() : Array.Empty(); + var modInstances = Mods.Select(apiMod => rulesetInstance.CreateModFromAcronym(apiMod.Acronym)).Where(m => m != null).ToArray(); // all API scores provided by this class are considered to be legacy. - mods = mods.Append(rulesetInstance.CreateMod()).ToArray(); + modInstances = modInstances.Append(rulesetInstance.CreateMod()).ToArray(); var scoreInfo = new ScoreInfo { TotalScore = TotalScore, MaxCombo = MaxCombo, - BeatmapInfo = Beatmap.ToBeatmapInfo(rulesets), + BeatmapInfo = Beatmap?.ToBeatmapInfo(rulesets), User = User, Accuracy = Accuracy, OnlineScoreID = OnlineID, Date = Date, PP = PP, - RulesetID = OnlineRulesetID, + RulesetID = RulesetID, Hash = HasReplay ? "online" : string.Empty, // todo: temporary? Rank = Rank, Ruleset = ruleset, - Mods = mods, + Mods = modInstances, }; if (beatmap != null) @@ -144,7 +150,7 @@ namespace osu.Game.Online.API.Requests.Responses return scoreInfo; } - public IRulesetInfo Ruleset => new RulesetInfo { ID = OnlineRulesetID }; + public IRulesetInfo Ruleset => new RulesetInfo { ID = RulesetID }; IBeatmapInfo IScoreInfo.Beatmap => Beatmap; } diff --git a/osu.Game/Online/Multiplayer/MultiplayerClient.cs b/osu.Game/Online/Multiplayer/MultiplayerClient.cs index d3e1a7c91a..a058420f78 100644 --- a/osu.Game/Online/Multiplayer/MultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/MultiplayerClient.cs @@ -17,6 +17,7 @@ using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Online.Multiplayer.Queueing; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; using osu.Game.Online.Rooms.RoomStatuses; using osu.Game.Rulesets; @@ -148,6 +149,10 @@ namespace osu.Game.Online.Multiplayer { Room = joinedRoom; APIRoom = room; + + Debug.Assert(LocalUser != null); + addUserToAPIRoom(LocalUser); + foreach (var user in joinedRoom.Users) updateUserPlayingState(user.UserID, user.State); @@ -358,6 +363,8 @@ namespace osu.Game.Online.Multiplayer Room.Users.Add(user); + addUserToAPIRoom(user); + UserJoined?.Invoke(user); RoomUpdated?.Invoke(); }); @@ -377,6 +384,18 @@ namespace osu.Game.Online.Multiplayer return handleUserLeft(user, UserKicked); } + private void addUserToAPIRoom(MultiplayerRoomUser user) + { + Debug.Assert(APIRoom != null); + + APIRoom.RecentParticipants.Add(user.User ?? new User + { + Id = user.UserID, + Username = "[Unresolved]" + }); + APIRoom.ParticipantCount.Value++; + } + private Task handleUserLeft(MultiplayerRoomUser user, Action? callback) { if (Room == null) @@ -390,6 +409,10 @@ namespace osu.Game.Online.Multiplayer Room.Users.Remove(user); PlayingUserIds.Remove(user.UserID); + Debug.Assert(APIRoom != null); + APIRoom.RecentParticipants.RemoveAll(u => u.Id == user.UserID); + APIRoom.ParticipantCount.Value--; + callback?.Invoke(user); RoomUpdated?.Invoke(); }, false); @@ -667,20 +690,17 @@ namespace osu.Game.Online.Multiplayer CurrentMatchPlayingItem.Value = APIRoom.Playlist.SingleOrDefault(p => p.ID == settings.PlaylistItemId); }, cancellationToken); - /// - /// Retrieves a from an online source. - /// - /// The beatmap set ID. - /// A token to cancel the request. - /// The retrieval task. - protected abstract Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default); - private async Task createPlaylistItem(APIPlaylistItem item) { var set = await GetOnlineBeatmapSet(item.BeatmapID).ConfigureAwait(false); - var beatmap = set.Beatmaps.Single(b => b.OnlineBeatmapID == item.BeatmapID); - beatmap.MD5Hash = item.BeatmapChecksum; + // The incoming response is deserialised without circular reference handling currently. + // Because we require using metadata from this instance, populate the nested beatmaps' sets manually here. + foreach (var b in set.Beatmaps) + b.BeatmapSet = set; + + var beatmap = set.Beatmaps.Single(b => b.OnlineID == item.BeatmapID); + beatmap.Checksum = item.BeatmapChecksum; var ruleset = Rulesets.GetRuleset(item.RulesetID); var rulesetInstance = ruleset.CreateInstance(); @@ -699,6 +719,14 @@ namespace osu.Game.Online.Multiplayer return playlistItem; } + /// + /// Retrieves a from an online source. + /// + /// The beatmap set ID. + /// A token to cancel the request. + /// The retrieval task. + protected abstract Task GetOnlineBeatmapSet(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 f69464e37e..94225b6de9 100644 --- a/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs +++ b/osu.Game/Online/Multiplayer/OnlineMultiplayerClient.cs @@ -9,9 +9,9 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR.Client; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Rooms; namespace osu.Game.Online.Multiplayer @@ -167,9 +167,9 @@ namespace osu.Game.Online.Multiplayer return connection.InvokeAsync(nameof(IMultiplayerServer.RemovePlaylistItem), item); } - protected override Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default) + protected override Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default) { - var tcs = new TaskCompletionSource(); + var tcs = new TaskCompletionSource(); var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId); req.Success += res => @@ -180,7 +180,7 @@ namespace osu.Game.Online.Multiplayer return; } - tcs.SetResult(res.ToBeatmapSet(Rulesets)); + tcs.SetResult(res); }; req.Failure += e => tcs.SetException(e); diff --git a/osu.Game/Online/Placeholders/ClickablePlaceholder.cs b/osu.Game/Online/Placeholders/ClickablePlaceholder.cs index 936ad79c64..054a4a3c39 100644 --- a/osu.Game/Online/Placeholders/ClickablePlaceholder.cs +++ b/osu.Game/Online/Placeholders/ClickablePlaceholder.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -12,7 +13,7 @@ namespace osu.Game.Online.Placeholders { public Action Action; - public ClickablePlaceholder(string actionMessage, IconUsage icon) + public ClickablePlaceholder(LocalisableString actionMessage, IconUsage icon) { OsuTextFlowContainer textFlow; diff --git a/osu.Game/Online/Placeholders/MessagePlaceholder.cs b/osu.Game/Online/Placeholders/MessagePlaceholder.cs index 7342765ca4..1676ba6cf2 100644 --- a/osu.Game/Online/Placeholders/MessagePlaceholder.cs +++ b/osu.Game/Online/Placeholders/MessagePlaceholder.cs @@ -3,14 +3,15 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; namespace osu.Game.Online.Placeholders { public class MessagePlaceholder : Placeholder { - private readonly string message; + private readonly LocalisableString message; - public MessagePlaceholder(string message) + public MessagePlaceholder(LocalisableString message) { AddIcon(FontAwesome.Solid.ExclamationCircle, cp => { diff --git a/osu.Game/Online/Rooms/MultiplayerScore.cs b/osu.Game/Online/Rooms/MultiplayerScore.cs index 7ec34e70d5..375e0b6b9f 100644 --- a/osu.Game/Online/Rooms/MultiplayerScore.cs +++ b/osu.Game/Online/Rooms/MultiplayerScore.cs @@ -7,6 +7,7 @@ using System.Linq; using JetBrains.Annotations; using Newtonsoft.Json; using Newtonsoft.Json.Converters; +using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; @@ -61,7 +62,7 @@ namespace osu.Game.Online.Rooms [CanBeNull] public MultiplayerScoresAround ScoresAround { get; set; } - public ScoreInfo CreateScoreInfo(PlaylistItem playlistItem) + public ScoreInfo CreateScoreInfo(PlaylistItem playlistItem, [NotNull] BeatmapInfo beatmap) { var rulesetInstance = playlistItem.Ruleset.Value.CreateInstance(); @@ -70,7 +71,7 @@ namespace osu.Game.Online.Rooms OnlineScoreID = ID, TotalScore = TotalScore, MaxCombo = MaxCombo, - BeatmapInfo = playlistItem.Beatmap.Value, + BeatmapInfo = beatmap, BeatmapInfoID = playlistItem.BeatmapID, Ruleset = playlistItem.Ruleset.Value, RulesetID = playlistItem.RulesetID, diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index 6cd735af23..eb9ea608f7 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -52,8 +53,13 @@ namespace osu.Game.Online.Rooms downloadTracker?.RemoveAndDisposeImmediately(); + Debug.Assert(item.NewValue.Beatmap.Value.BeatmapSet != null); + downloadTracker = new BeatmapDownloadTracker(item.NewValue.Beatmap.Value.BeatmapSet); - downloadTracker.State.BindValueChanged(_ => updateAvailability()); + + AddInternal(downloadTracker); + + downloadTracker.State.BindValueChanged(_ => updateAvailability(), true); downloadTracker.Progress.BindValueChanged(_ => { if (downloadTracker.State.Value != DownloadState.Downloading) @@ -63,9 +69,7 @@ namespace osu.Game.Online.Rooms // we don't want to flood the network with this, so rate limit how often we send progress updates. if (progressUpdate?.Completed != false) progressUpdate = Scheduler.AddDelayed(updateAvailability, progressUpdate == null ? 0 : 500); - }); - - AddInternal(downloadTracker); + }, true); }, true); } diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index e2a47ed744..e6eae4ae2a 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -31,7 +31,7 @@ namespace osu.Game.Online.Rooms public bool Expired { get; set; } [JsonIgnore] - public readonly Bindable Beatmap = new Bindable(); + public readonly Bindable Beatmap = new Bindable(); [JsonIgnore] public readonly Bindable Ruleset = new Bindable(); @@ -65,13 +65,13 @@ namespace osu.Game.Online.Rooms public PlaylistItem() { - Beatmap.BindValueChanged(beatmap => BeatmapID = beatmap.NewValue?.OnlineBeatmapID ?? 0); + Beatmap.BindValueChanged(beatmap => BeatmapID = beatmap.NewValue?.OnlineID ?? -1); Ruleset.BindValueChanged(ruleset => RulesetID = ruleset.NewValue?.ID ?? 0); } - public void MapObjects(BeatmapManager beatmaps, RulesetStore rulesets) + public void MapObjects(RulesetStore rulesets) { - Beatmap.Value ??= apiBeatmap.ToBeatmapInfo(rulesets); + Beatmap.Value ??= apiBeatmap; Ruleset.Value ??= rulesets.GetRuleset(RulesetID); Ruleset rulesetInstance = Ruleset.Value.CreateInstance(); diff --git a/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs b/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs index 9e432fa99e..d5da6c401c 100644 --- a/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs +++ b/osu.Game/Online/Rooms/SubmitRoomScoreRequest.cs @@ -5,6 +5,7 @@ using System.Net.Http; using Newtonsoft.Json; using osu.Framework.IO.Network; using osu.Game.Online.API; +using osu.Game.Online.Solo; using osu.Game.Scoring; namespace osu.Game.Online.Rooms @@ -14,14 +15,14 @@ namespace osu.Game.Online.Rooms private readonly long scoreId; private readonly long roomId; private readonly long playlistItemId; - private readonly ScoreInfo scoreInfo; + private readonly SubmittableScore score; public SubmitRoomScoreRequest(long scoreId, long roomId, long playlistItemId, ScoreInfo scoreInfo) { this.scoreId = scoreId; this.roomId = roomId; this.playlistItemId = playlistItemId; - this.scoreInfo = scoreInfo; + score = new SubmittableScore(scoreInfo); } protected override WebRequest CreateWebRequest() @@ -31,7 +32,7 @@ namespace osu.Game.Online.Rooms req.ContentType = "application/json"; req.Method = HttpMethod.Put; - req.AddRaw(JsonConvert.SerializeObject(scoreInfo, new JsonSerializerSettings + req.AddRaw(JsonConvert.SerializeObject(score, new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore })); diff --git a/osu.Game/Online/ScoreDownloadTracker.cs b/osu.Game/Online/ScoreDownloadTracker.cs index 675dbf608c..e679071ac1 100644 --- a/osu.Game/Online/ScoreDownloadTracker.cs +++ b/osu.Game/Online/ScoreDownloadTracker.cs @@ -35,7 +35,11 @@ namespace osu.Game.Online return; // Used to interact with manager classes that don't support interface types. Will eventually be replaced. - var scoreInfo = new ScoreInfo { OnlineScoreID = TrackedItem.OnlineScoreID }; + var scoreInfo = new ScoreInfo + { + ID = TrackedItem.ID, + OnlineScoreID = TrackedItem.OnlineScoreID + }; if (Manager.IsAvailableLocally(scoreInfo)) UpdateState(DownloadState.LocallyAvailable); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index f9e080a93c..a7ed7fedf5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -436,11 +436,15 @@ namespace osu.Game /// first beatmap from any ruleset. /// /// - public void PresentBeatmap(BeatmapSetInfo beatmap, Predicate difficultyCriteria = null) + public void PresentBeatmap(IBeatmapSetInfo beatmap, Predicate difficultyCriteria = null) { - var databasedSet = beatmap.OnlineBeatmapSetID != null - ? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID) - : BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash); + BeatmapSetInfo databasedSet = null; + + if (beatmap.OnlineID > 0) + databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineID); + + if (beatmap is BeatmapSetInfo localBeatmap) + databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash); if (databasedSet == null) { diff --git a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs index 8ee3b1cb2e..2ba8fc3ae2 100644 --- a/osu.Game/Overlays/AccountCreation/ScreenEntry.cs +++ b/osu.Game/Overlays/AccountCreation/ScreenEntry.cs @@ -5,7 +5,6 @@ using System; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; @@ -135,7 +134,16 @@ namespace osu.Game.Overlays.AccountCreation characterCheckText = passwordDescription.AddText("8 characters long"); passwordDescription.AddText(". Choose something long but also something you will remember, like a line from your favourite song."); - passwordTextBox.Current.ValueChanged += password => { characterCheckText.Drawables.ForEach(s => s.Colour = password.NewValue.Length == 0 ? Color4.White : Interpolation.ValueAt(password.NewValue.Length, Color4.OrangeRed, Color4.YellowGreen, 0, 8, Easing.In)); }; + passwordTextBox.Current.BindValueChanged(_ => updateCharacterCheckTextColour(), true); + characterCheckText.DrawablePartsRecreated += _ => updateCharacterCheckTextColour(); + } + + private void updateCharacterCheckTextColour() + { + string password = passwordTextBox.Text; + + foreach (var d in characterCheckText.Drawables) + d.Colour = password.Length == 0 ? Color4.White : Interpolation.ValueAt(password.Length, Color4.OrangeRed, Color4.YellowGreen, 0, 8, Easing.In); } public override void OnEntering(IScreen last) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 03d36ff5df..fa57191ef3 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -12,9 +12,9 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Framework.Threading; -using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Resources.Localisation.Web; using osuTK; @@ -206,7 +206,7 @@ namespace osu.Game.Overlays.BeatmapListing getSetsRequest.Success += response => { - var sets = response.BeatmapSets.Select(responseJson => responseJson.ToBeatmapSet(rulesets)).ToList(); + var sets = response.BeatmapSets.ToList(); // If the previous request returned a null cursor, the API is indicating we can't paginate further (maybe there are no more beatmaps left). if (sets.Count == 0 || response.Cursor == null) @@ -289,7 +289,7 @@ namespace osu.Game.Overlays.BeatmapListing /// Contains the beatmap sets returned from API. /// Valid for read if and only if is . /// - public List Results { get; private set; } + public List Results { get; private set; } /// /// Contains the names of supporter-only filters requested by the user. @@ -297,7 +297,7 @@ namespace osu.Game.Overlays.BeatmapListing /// public List SupporterOnlyFiltersUsed { get; private set; } - public static SearchResult ResultsReturned(List results) => new SearchResult + public static SearchResult ResultsReturned(List results) => new SearchResult { Type = SearchResultType.ResultsReturned, Results = results diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs index 776a8e73b0..2474515802 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingSearchControl.cs @@ -8,12 +8,12 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input.Bindings; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osu.Game.Scoring; @@ -49,17 +49,17 @@ namespace osu.Game.Overlays.BeatmapListing public Bindable ExplicitContent => explicitContentFilter.Current; - public BeatmapSetInfo BeatmapSet + public APIBeatmapSet BeatmapSet { set { - if (value == null || string.IsNullOrEmpty(value.OnlineInfo.Covers.Cover)) + if (value == null || string.IsNullOrEmpty(value.Covers.Cover)) { beatmapCover.FadeOut(600, Easing.OutQuint); return; } - beatmapCover.OnlineInfo = value.OnlineInfo; + beatmapCover.OnlineInfo = value; beatmapCover.FadeTo(0.1f, 200, Easing.OutQuint); } } diff --git a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanel.cs index c7fa98f159..34086c214f 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanel.cs @@ -23,6 +23,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; using osuTK; using osuTK.Graphics; @@ -30,7 +31,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { public abstract class BeatmapPanel : OsuClickableContainer, IHasContextMenu { - public readonly BeatmapSetInfo SetInfo; + public readonly APIBeatmapSet SetInfo; private const double hover_transition_time = 400; private const int maximum_difficulty_icons = 10; @@ -49,10 +50,10 @@ namespace osu.Game.Overlays.BeatmapListing.Panels protected Action ViewBeatmap; - protected BeatmapPanel(BeatmapSetInfo setInfo) + protected BeatmapPanel(APIBeatmapSet setInfo) : base(HoverSampleSet.Submit) { - Debug.Assert(setInfo.OnlineBeatmapSetID != null); + Debug.Assert(setInfo.OnlineID > 0); SetInfo = setInfo; } @@ -95,8 +96,8 @@ namespace osu.Game.Overlays.BeatmapListing.Panels Action = ViewBeatmap = () => { - Debug.Assert(SetInfo.OnlineBeatmapSetID != null); - beatmapSetOverlay?.FetchAndShowBeatmapSet(SetInfo.OnlineBeatmapSetID.Value); + Debug.Assert(SetInfo.OnlineID > 0); + beatmapSetOverlay?.FetchAndShowBeatmapSet(SetInfo.OnlineID); }; } @@ -146,14 +147,14 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { var icons = new List(); - if (SetInfo.Beatmaps.Count > maximum_difficulty_icons) + if (SetInfo.Beatmaps.Length > maximum_difficulty_icons) { foreach (var ruleset in SetInfo.Beatmaps.Select(b => b.Ruleset).Distinct()) - icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is ListBeatmapPanel ? Color4.White : colours.Gray5)); + icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.Where(b => b.RulesetID == ruleset.OnlineID).ToList(), ruleset, this is ListBeatmapPanel ? Color4.White : colours.Gray5)); } else { - foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.Ruleset.ID).ThenBy(beatmap => beatmap.StarDifficulty)) + foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.RulesetID).ThenBy(beatmap => beatmap.StarRating)) icons.Add(new DifficultyIcon(b)); } @@ -163,7 +164,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels protected Drawable CreateBackground() => new UpdateableOnlineBeatmapSetCover { RelativeSizeAxes = Axes.Both, - OnlineInfo = SetInfo.OnlineInfo, + OnlineInfo = SetInfo, }; public class Statistic : FillFlowContainer diff --git a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs index dd12e8e467..5ed49cf384 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs @@ -11,6 +11,7 @@ using osu.Game.Configuration; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.BeatmapListing.Panels { @@ -21,7 +22,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels /// /// Currently selected beatmap. Used to present the correct difficulty after completing a download. /// - public readonly IBindable SelectedBeatmap = new Bindable(); + public readonly IBindable SelectedBeatmap = new Bindable(); private readonly ShakeContainer shakeContainer; private readonly DownloadButton button; @@ -31,9 +32,9 @@ namespace osu.Game.Overlays.BeatmapListing.Panels protected readonly Bindable State = new Bindable(); - private readonly BeatmapSetInfo beatmapSet; + private readonly IBeatmapSetInfo beatmapSet; - public BeatmapPanelDownloadButton(BeatmapSetInfo beatmapSet) + public BeatmapPanelDownloadButton(IBeatmapSetInfo beatmapSet) { this.beatmapSet = beatmapSet; @@ -79,7 +80,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels case DownloadState.LocallyAvailable: Predicate findPredicate = null; if (SelectedBeatmap.Value != null) - findPredicate = b => b.OnlineBeatmapID == SelectedBeatmap.Value.OnlineBeatmapID; + findPredicate = b => b.OnlineBeatmapID == SelectedBeatmap.Value.OnlineID; game?.PresentBeatmap(beatmapSet, findPredicate); break; @@ -100,7 +101,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels break; default: - if (beatmapSet.OnlineInfo?.Availability.DownloadDisabled ?? false) + if ((beatmapSet as IBeatmapSetOnlineInfo)?.Availability.DownloadDisabled == true) { button.Enabled.Value = false; button.TooltipText = "this beatmap is currently not available for download."; diff --git a/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs b/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs index 24f929f55e..93eaf775e0 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/DownloadProgressBar.cs @@ -18,7 +18,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels private readonly ProgressBar progressBar; private readonly BeatmapDownloadTracker downloadTracker; - public DownloadProgressBar(BeatmapSetInfo beatmapSet) + public DownloadProgressBar(IBeatmapSetInfo beatmapSet) { InternalChildren = new Drawable[] { diff --git a/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs index 4a0fa59c31..770e5af7bd 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/GridBeatmapPanel.cs @@ -9,11 +9,11 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet; using osuTK; using osuTK.Graphics; @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels protected override PlayButton PlayButton => playButton; protected override Box PreviewBar => progressBar; - public GridBeatmapPanel(BeatmapSetInfo beatmap) + public GridBeatmapPanel(APIBeatmapSet beatmap) : base(beatmap) { Width = 380; @@ -84,7 +84,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { new OsuSpriteText { - Text = new RomanisableString(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), + Text = new RomanisableString(SetInfo.TitleUnicode, SetInfo.Title), Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true) }, } @@ -97,7 +97,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { new OsuSpriteText { - Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), + Text = new RomanisableString(SetInfo.ArtistUnicode, SetInfo.Artist), Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) } } @@ -145,7 +145,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { d.AutoSizeAxes = Axes.Both; d.AddText("mapped by ", t => t.Colour = colours.Gray5); - d.AddUserLink(SetInfo.Metadata.Author); + d.AddUserLink(SetInfo.Author); }), new Container { @@ -155,11 +155,11 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { new OsuSpriteText { - Text = SetInfo.Metadata.Source, + Text = SetInfo.Source, Font = OsuFont.GetFont(size: 14), Shadow = false, Colour = colours.Gray5, - Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f, + Alpha = string.IsNullOrEmpty(SetInfo.Source) ? 0f : 1f, }, }, }, @@ -193,8 +193,8 @@ namespace osu.Game.Overlays.BeatmapListing.Panels Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding }, Children = new[] { - new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.OnlineInfo?.PlayCount ?? 0), - new Statistic(FontAwesome.Solid.Heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), + new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.PlayCount), + new Statistic(FontAwesome.Solid.Heart, SetInfo.FavouriteCount), }, }, statusContainer = new FillFlowContainer @@ -211,7 +211,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }, }); - if (SetInfo.OnlineInfo?.HasExplicitContent ?? false) + if (SetInfo.HasExplicitContent) { titleContainer.Add(new ExplicitContentBeatmapPill { @@ -221,7 +221,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }); } - if (SetInfo.OnlineInfo?.TrackId != null) + if (SetInfo.TrackId != null) { artistContainer.Add(new FeaturedArtistBeatmapPill { @@ -231,12 +231,12 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }); } - if (SetInfo.OnlineInfo?.HasVideo ?? false) + if (SetInfo.HasVideo) { statusContainer.Add(new IconPill(FontAwesome.Solid.Film)); } - if (SetInfo.OnlineInfo?.HasStoryboard ?? false) + if (SetInfo.HasStoryboard) { statusContainer.Add(new IconPill(FontAwesome.Solid.Image)); } @@ -246,7 +246,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels AutoSizeAxes = Axes.Both, TextSize = 12, TextPadding = new MarginPadding { Horizontal = 10, Vertical = 5 }, - Status = SetInfo.OnlineInfo?.Status ?? BeatmapSetOnlineStatus.None, + Status = SetInfo.Status, }); PreviewPlaying.ValueChanged += _ => updateStatusContainer(); diff --git a/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs index 63d651f9de..dcd676724a 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/ListBeatmapPanel.cs @@ -9,11 +9,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet; using osuTK; using osuTK.Graphics; @@ -37,7 +37,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels protected override PlayButton PlayButton => playButton; protected override Box PreviewBar => progressBar; - public ListBeatmapPanel(BeatmapSetInfo beatmap) + public ListBeatmapPanel(APIBeatmapSet beatmap) : base(beatmap) { RelativeSizeAxes = Axes.X; @@ -107,7 +107,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { new OsuSpriteText { - Text = new RomanisableString(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title), + Text = new RomanisableString(SetInfo.TitleUnicode, SetInfo.Title), Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true) }, } @@ -120,7 +120,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { new OsuSpriteText { - Text = new RomanisableString(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist), + Text = new RomanisableString(SetInfo.ArtistUnicode, SetInfo.Artist), Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true) }, }, @@ -182,8 +182,8 @@ namespace osu.Game.Overlays.BeatmapListing.Panels Direction = FillDirection.Vertical, Children = new Drawable[] { - new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.OnlineInfo?.PlayCount ?? 0), - new Statistic(FontAwesome.Solid.Heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), + new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.PlayCount), + new Statistic(FontAwesome.Solid.Heart, SetInfo.FavouriteCount), new LinkFlowContainer(s => { s.Shadow = false; @@ -197,15 +197,15 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { d.AutoSizeAxes = Axes.Both; d.AddText("mapped by "); - d.AddUserLink(SetInfo.Metadata.Author); + d.AddUserLink(SetInfo.Author); }), new OsuSpriteText { - Text = SetInfo.Metadata.Source, + Text = SetInfo.Source, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Font = OsuFont.GetFont(size: 14), - Alpha = string.IsNullOrEmpty(SetInfo.Metadata.Source) ? 0f : 1f, + Alpha = string.IsNullOrEmpty(SetInfo.Source) ? 0f : 1f, }, }, }, @@ -225,7 +225,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }, }); - if (SetInfo.OnlineInfo?.HasExplicitContent ?? false) + if (SetInfo.HasExplicitContent) { titleContainer.Add(new ExplicitContentBeatmapPill { @@ -235,7 +235,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }); } - if (SetInfo.OnlineInfo?.TrackId != null) + if (SetInfo.TrackId != null) { artistContainer.Add(new FeaturedArtistBeatmapPill { @@ -245,12 +245,12 @@ namespace osu.Game.Overlays.BeatmapListing.Panels }); } - if (SetInfo.OnlineInfo?.HasVideo ?? false) + if (SetInfo.HasVideo) { statusContainer.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); } - if (SetInfo.OnlineInfo?.HasStoryboard ?? false) + if (SetInfo.HasStoryboard) { statusContainer.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) }); } @@ -260,7 +260,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels AutoSizeAxes = Axes.Both, TextSize = 12, TextPadding = new MarginPadding { Horizontal = 10, Vertical = 4 }, - Status = SetInfo.OnlineInfo?.Status ?? BeatmapSetOnlineStatus.None, + Status = SetInfo.Status, }); } } diff --git a/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs b/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs index 3aa9aa5ca5..c352fe0223 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/PlayButton.cs @@ -8,9 +8,9 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Game.Audio; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; using osuTK; using osuTK.Graphics; @@ -24,9 +24,9 @@ namespace osu.Game.Overlays.BeatmapListing.Panels public PreviewTrack Preview { get; private set; } - private BeatmapSetInfo beatmapSet; + private APIBeatmapSet beatmapSet; - public BeatmapSetInfo BeatmapSet + public APIBeatmapSet BeatmapSet { get => beatmapSet; set @@ -66,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels } } - public PlayButton(BeatmapSetInfo setInfo = null) + public PlayButton(APIBeatmapSet setInfo = null) { BeatmapSet = setInfo; AddRange(new Drawable[] diff --git a/osu.Game/Overlays/BeatmapListingOverlay.cs b/osu.Game/Overlays/BeatmapListingOverlay.cs index 935a89b99b..e08af52a72 100644 --- a/osu.Game/Overlays/BeatmapListingOverlay.cs +++ b/osu.Game/Overlays/BeatmapListingOverlay.cs @@ -15,10 +15,10 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osu.Game.Audio; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapListing; using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Resources.Localisation.Web; @@ -136,7 +136,7 @@ namespace osu.Game.Overlays return; } - var newPanels = searchResult.Results.Select(b => new GridBeatmapPanel(b) + var newPanels = searchResult.Results.Select(b => new GridBeatmapPanel(b) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs index 265d9bf125..c90e2e2085 100644 --- a/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs +++ b/osu.Game/Overlays/BeatmapSet/AuthorInfo.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Users.Drawables; using osuTK; @@ -16,6 +15,7 @@ using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Users; using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.BeatmapSet { @@ -26,9 +26,9 @@ namespace osu.Game.Overlays.BeatmapSet private UpdateableAvatar avatar; private FillFlowContainer fields; - private BeatmapSetInfo beatmapSet; + private APIBeatmapSet beatmapSet; - public BeatmapSetInfo BeatmapSet + public APIBeatmapSet BeatmapSet { get => beatmapSet; set @@ -78,30 +78,28 @@ namespace osu.Game.Overlays.BeatmapSet private void updateDisplay() { - avatar.User = BeatmapSet?.Metadata.Author; + avatar.User = BeatmapSet?.Author; fields.Clear(); if (BeatmapSet == null) return; - var online = BeatmapSet.OnlineInfo; - fields.Children = new Drawable[] { - new Field("mapped by", BeatmapSet.Metadata.Author, OsuFont.GetFont(weight: FontWeight.Regular, italics: true)), - new Field("submitted", online.Submitted, OsuFont.GetFont(weight: FontWeight.Bold)) + new Field("mapped by", BeatmapSet.Author, OsuFont.GetFont(weight: FontWeight.Regular, italics: true)), + new Field("submitted", BeatmapSet.Submitted, OsuFont.GetFont(weight: FontWeight.Bold)) { Margin = new MarginPadding { Top = 5 }, }, }; - if (online.Ranked.HasValue) + if (BeatmapSet.Ranked.HasValue) { - fields.Add(new Field(online.Status.ToString().ToLowerInvariant(), online.Ranked.Value, OsuFont.GetFont(weight: FontWeight.Bold))); + fields.Add(new Field(BeatmapSet.Status.ToString().ToLowerInvariant(), BeatmapSet.Ranked.Value, OsuFont.GetFont(weight: FontWeight.Bold))); } - else if (online.LastUpdated.HasValue) + else if (BeatmapSet.LastUpdated.HasValue) { - fields.Add(new Field("last updated", online.LastUpdated.Value, OsuFont.GetFont(weight: FontWeight.Bold))); + fields.Add(new Field("last updated", BeatmapSet.LastUpdated.Value, OsuFont.GetFont(weight: FontWeight.Bold))); } } diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index 683f4f0c49..8f848edf24 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -14,6 +14,7 @@ using osu.Game.Beatmaps; using osu.Game.Extensions; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; using osuTK; @@ -23,9 +24,9 @@ namespace osu.Game.Overlays.BeatmapSet { private readonly Statistic length, bpm, circleCount, sliderCount; - private BeatmapSetInfo beatmapSet; + private APIBeatmapSet beatmapSet; - public BeatmapSetInfo BeatmapSet + public APIBeatmapSet BeatmapSet { get => beatmapSet; set @@ -38,9 +39,9 @@ namespace osu.Game.Overlays.BeatmapSet } } - private BeatmapInfo beatmapInfo; + private IBeatmapInfo beatmapInfo; - public BeatmapInfo BeatmapInfo + public IBeatmapInfo BeatmapInfo { get => beatmapInfo; set @@ -55,7 +56,7 @@ namespace osu.Game.Overlays.BeatmapSet private void updateDisplay() { - bpm.Value = BeatmapSet?.OnlineInfo?.BPM.ToLocalisableString(@"0.##") ?? (LocalisableString)"-"; + bpm.Value = BeatmapSet?.BPM.ToLocalisableString(@"0.##") ?? (LocalisableString)"-"; if (beatmapInfo == null) { @@ -68,8 +69,10 @@ namespace osu.Game.Overlays.BeatmapSet length.TooltipText = BeatmapsetsStrings.ShowStatsTotalLength(TimeSpan.FromMilliseconds(beatmapInfo.Length).ToFormattedDuration()); length.Value = TimeSpan.FromMilliseconds(beatmapInfo.Length).ToFormattedDuration(); - circleCount.Value = beatmapInfo.OnlineInfo.CircleCount.ToLocalisableString(@"N0"); - sliderCount.Value = beatmapInfo.OnlineInfo.SliderCount.ToLocalisableString(@"N0"); + var onlineInfo = beatmapInfo as IBeatmapOnlineInfo; + + circleCount.Value = (onlineInfo?.CircleCount ?? 0).ToLocalisableString(@"N0"); + sliderCount.Value = (onlineInfo?.SliderCount ?? 0).ToLocalisableString(@"N0"); } } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapAvailability.cs b/osu.Game/Overlays/BeatmapSet/BeatmapAvailability.cs index f005a37eaa..dc46452dcb 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapAvailability.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapAvailability.cs @@ -5,19 +5,19 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; using osuTK.Graphics; namespace osu.Game.Overlays.BeatmapSet { public class BeatmapAvailability : Container { - private BeatmapSetInfo beatmapSet; + private APIBeatmapSet beatmapSet; - private bool downloadDisabled => BeatmapSet?.OnlineInfo.Availability.DownloadDisabled ?? false; - private bool hasExternalLink => !string.IsNullOrEmpty(BeatmapSet?.OnlineInfo.Availability.ExternalLink); + private bool downloadDisabled => BeatmapSet?.Availability.DownloadDisabled ?? false; + private bool hasExternalLink => !string.IsNullOrEmpty(BeatmapSet?.Availability.ExternalLink); private readonly LinkFlowContainer textContainer; @@ -44,7 +44,7 @@ namespace osu.Game.Overlays.BeatmapSet }; } - public BeatmapSetInfo BeatmapSet + public APIBeatmapSet BeatmapSet { get => beatmapSet; @@ -76,7 +76,7 @@ namespace osu.Game.Overlays.BeatmapSet { textContainer.NewParagraph(); textContainer.NewParagraph(); - textContainer.AddLink("Check here for more information.", BeatmapSet.OnlineInfo.Availability.ExternalLink, creationParameters: t => t.Font = OsuFont.GetFont(size: 10)); + textContainer.AddLink("Check here for more information.", BeatmapSet.Availability.ExternalLink, creationParameters: t => t.Font = OsuFont.GetFont(size: 10)); } } } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 3df275c6d3..b152375062 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -17,6 +17,7 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osuTK; @@ -34,10 +35,10 @@ namespace osu.Game.Overlays.BeatmapSet public readonly DifficultiesContainer Difficulties; - public readonly Bindable Beatmap = new Bindable(); - private BeatmapSetInfo beatmapSet; + public readonly Bindable Beatmap = new Bindable(); + private APIBeatmapSet beatmapSet; - public BeatmapSetInfo BeatmapSet + public APIBeatmapSet BeatmapSet { get => beatmapSet; set @@ -164,35 +165,38 @@ namespace osu.Game.Overlays.BeatmapSet if (BeatmapSet != null) { - Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.Where(b => b.Ruleset.Equals(ruleset.Value)).OrderBy(b => b.StarDifficulty).Select(b => new DifficultySelectorButton(b) - { - State = DifficultySelectorState.NotSelected, - OnHovered = beatmap => - { - showBeatmap(beatmap); - starRating.Text = beatmap.StarDifficulty.ToLocalisableString(@"0.##"); - starRatingContainer.FadeIn(100); - }, - OnClicked = beatmap => { Beatmap.Value = beatmap; }, - }); + Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps + .Where(b => b.Ruleset.OnlineID == ruleset.Value?.OnlineID) + .OrderBy(b => b.StarRating) + .Select(b => new DifficultySelectorButton(b) + { + State = DifficultySelectorState.NotSelected, + OnHovered = beatmap => + { + showBeatmap(beatmap); + starRating.Text = beatmap.StarRating.ToLocalisableString(@"0.##"); + starRatingContainer.FadeIn(100); + }, + OnClicked = beatmap => { Beatmap.Value = beatmap; }, + }); } starRatingContainer.FadeOut(100); - Beatmap.Value = Difficulties.FirstOrDefault()?.BeatmapInfo; - plays.Value = BeatmapSet?.OnlineInfo.PlayCount ?? 0; - favourites.Value = BeatmapSet?.OnlineInfo.FavouriteCount ?? 0; + Beatmap.Value = Difficulties.FirstOrDefault()?.Beatmap; + plays.Value = BeatmapSet?.PlayCount ?? 0; + favourites.Value = BeatmapSet?.FavouriteCount ?? 0; updateDifficultyButtons(); } - private void showBeatmap(BeatmapInfo beatmapInfo) + private void showBeatmap(IBeatmapInfo beatmapInfo) { - version.Text = beatmapInfo?.Version; + version.Text = beatmapInfo?.DifficultyName; } private void updateDifficultyButtons() { - Difficulties.Children.ToList().ForEach(diff => diff.State = diff.BeatmapInfo == Beatmap.Value ? DifficultySelectorState.Selected : DifficultySelectorState.NotSelected); + Difficulties.Children.ToList().ForEach(diff => diff.State = diff.Beatmap == Beatmap.Value ? DifficultySelectorState.Selected : DifficultySelectorState.NotSelected); } public class DifficultiesContainer : FillFlowContainer @@ -216,10 +220,10 @@ namespace osu.Game.Overlays.BeatmapSet private readonly Box backgroundBox; private readonly DifficultyIcon icon; - public readonly BeatmapInfo BeatmapInfo; + public readonly APIBeatmap Beatmap; - public Action OnHovered; - public Action OnClicked; + public Action OnHovered; + public Action OnClicked; public event Action StateChanged; private DifficultySelectorState state; @@ -241,9 +245,9 @@ namespace osu.Game.Overlays.BeatmapSet } } - public DifficultySelectorButton(BeatmapInfo beatmapInfo) + public DifficultySelectorButton(APIBeatmap beatmapInfo) { - BeatmapInfo = beatmapInfo; + Beatmap = beatmapInfo; Size = new Vector2(size); Margin = new MarginPadding { Horizontal = tile_spacing / 2 }; @@ -273,7 +277,7 @@ namespace osu.Game.Overlays.BeatmapSet protected override bool OnHover(HoverEvent e) { fadeIn(); - OnHovered?.Invoke(BeatmapInfo); + OnHovered?.Invoke(Beatmap); return base.OnHover(e); } @@ -286,7 +290,7 @@ namespace osu.Game.Overlays.BeatmapSet protected override bool OnClick(ClickEvent e) { - OnClicked?.Invoke(BeatmapInfo); + OnClicked?.Invoke(Beatmap); return base.OnClick(e); } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs index 005d21726b..6564ca3d41 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs @@ -3,17 +3,17 @@ using osu.Framework.Bindables; using osu.Framework.Graphics.UserInterface; -using osu.Game.Beatmaps; using osu.Game.Rulesets; using System.Linq; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.BeatmapSet { public class BeatmapRulesetSelector : OverlayRulesetSelector { - private readonly Bindable beatmapSet = new Bindable(); + private readonly Bindable beatmapSet = new Bindable(); - public BeatmapSetInfo BeatmapSet + public APIBeatmapSet BeatmapSet { get => beatmapSet.Value; set diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs index 79c95d6646..b3b3d1980b 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs @@ -1,22 +1,22 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; -using System.Linq; namespace osu.Game.Overlays.BeatmapSet { public class BeatmapRulesetTabItem : OverlayRulesetTabItem { - public readonly Bindable BeatmapSet = new Bindable(); + public readonly Bindable BeatmapSet = new Bindable(); [Resolved] private OverlayColourProvider colourProvider { get; set; } @@ -64,7 +64,7 @@ namespace osu.Game.Overlays.BeatmapSet BeatmapSet.BindValueChanged(setInfo => { - int beatmapsCount = setInfo.NewValue?.Beatmaps.Count(b => b.Ruleset.Equals(Value)) ?? 0; + int beatmapsCount = setInfo.NewValue?.Beatmaps.Count(b => b.Ruleset.OnlineID == Value.OnlineID) ?? 0; count.Text = beatmapsCount.ToString(); countContainer.FadeTo(beatmapsCount > 0 ? 1 : 0); diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs index 4a0c0e9f75..102cddfa92 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeader.cs @@ -6,7 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Effects; -using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; using osu.Game.Rulesets; using osuTK; @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.BeatmapSet { public class BeatmapSetHeader : OverlayHeader { - public readonly Bindable BeatmapSet = new Bindable(); + public readonly Bindable BeatmapSet = new Bindable(); public BeatmapSetHeaderContent HeaderContent { get; private set; } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index 1bfa7d1c47..547b8a6ec3 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -10,13 +10,13 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; -using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Overlays.BeatmapSet.Buttons; using osuTK; @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.BeatmapSet { public class BeatmapSetHeaderContent : CompositeDrawable { - public readonly Bindable BeatmapSet = new Bindable(); + public readonly Bindable BeatmapSet = new Bindable(); private const float transition_duration = 200; private const float buttons_height = 45; @@ -219,7 +219,7 @@ namespace osu.Game.Overlays.BeatmapSet Picker.Beatmap.ValueChanged += b => { Details.BeatmapInfo = b.NewValue; - externalLink.Link = $@"{api.WebsiteRootUrl}/beatmapsets/{BeatmapSet.Value?.OnlineBeatmapSetID}#{b.NewValue?.Ruleset.ShortName}/{b.NewValue?.OnlineBeatmapID}"; + externalLink.Link = $@"{api.WebsiteRootUrl}/beatmapsets/{BeatmapSet.Value?.OnlineID}#{b.NewValue?.Ruleset.ShortName}/{b.NewValue?.OnlineID}"; }; } @@ -231,7 +231,7 @@ namespace osu.Game.Overlays.BeatmapSet BeatmapSet.BindValueChanged(setInfo => { Picker.BeatmapSet = rulesetSelector.BeatmapSet = author.BeatmapSet = beatmapAvailability.BeatmapSet = Details.BeatmapSet = setInfo.NewValue; - cover.OnlineInfo = setInfo.NewValue?.OnlineInfo; + cover.OnlineInfo = setInfo.NewValue; downloadTracker?.RemoveAndDisposeImmediately(); @@ -255,14 +255,14 @@ namespace osu.Game.Overlays.BeatmapSet loading.Hide(); - title.Text = new RomanisableString(setInfo.NewValue.Metadata.TitleUnicode, setInfo.NewValue.Metadata.Title); - artist.Text = new RomanisableString(setInfo.NewValue.Metadata.ArtistUnicode, setInfo.NewValue.Metadata.Artist); + title.Text = new RomanisableString(setInfo.NewValue.TitleUnicode, setInfo.NewValue.Title); + artist.Text = new RomanisableString(setInfo.NewValue.ArtistUnicode, setInfo.NewValue.Artist); - explicitContentPill.Alpha = setInfo.NewValue.OnlineInfo.HasExplicitContent ? 1 : 0; - featuredArtistPill.Alpha = setInfo.NewValue.OnlineInfo.TrackId != null ? 1 : 0; + explicitContentPill.Alpha = setInfo.NewValue.HasExplicitContent ? 1 : 0; + featuredArtistPill.Alpha = setInfo.NewValue.TrackId != null ? 1 : 0; onlineStatusPill.FadeIn(500, Easing.OutQuint); - onlineStatusPill.Status = setInfo.NewValue.OnlineInfo.Status; + onlineStatusPill.Status = setInfo.NewValue.Status; downloadButtonsContainer.FadeIn(transition_duration); favouriteButton.FadeIn(transition_duration); @@ -276,7 +276,7 @@ namespace osu.Game.Overlays.BeatmapSet { if (BeatmapSet.Value == null) return; - if (BeatmapSet.Value.OnlineInfo.Availability.DownloadDisabled && downloadTracker.State.Value != DownloadState.LocallyAvailable) + if (BeatmapSet.Value.Availability.DownloadDisabled && downloadTracker.State.Value != DownloadState.LocallyAvailable) { downloadButtonsContainer.Clear(); return; @@ -302,7 +302,7 @@ namespace osu.Game.Overlays.BeatmapSet default: downloadButtonsContainer.Child = new HeaderDownloadButton(BeatmapSet.Value); - if (BeatmapSet.Value.OnlineInfo.HasVideo) + if (BeatmapSet.Value.HasVideo) downloadButtonsContainer.Add(new HeaderDownloadButton(BeatmapSet.Value, true)); break; } diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs index 43dd1438f1..d4873f241c 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/FavouriteButton.cs @@ -8,10 +8,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; using osu.Framework.Localisation; -using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Notifications; using osu.Game.Resources.Localisation.Web; using osu.Game.Users; @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { public class FavouriteButton : HeaderButton, IHasTooltip { - public readonly Bindable BeatmapSet = new Bindable(); + public readonly Bindable BeatmapSet = new Bindable(); private readonly BindableBool favourited = new BindableBool(); @@ -61,13 +61,13 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons Action = () => { // guaranteed by disabled state above. - Debug.Assert(BeatmapSet.Value.OnlineBeatmapSetID != null); + Debug.Assert(BeatmapSet.Value.OnlineID > 0); loading.Show(); request?.Cancel(); - request = new PostBeatmapFavouriteRequest(BeatmapSet.Value.OnlineBeatmapSetID.Value, favourited.Value ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite); + request = new PostBeatmapFavouriteRequest(BeatmapSet.Value.OnlineID, favourited.Value ? BeatmapFavouriteAction.UnFavourite : BeatmapFavouriteAction.Favourite); request.Success += () => { @@ -98,11 +98,11 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons BeatmapSet.BindValueChanged(setInfo => { updateEnabled(); - favourited.Value = setInfo.NewValue?.OnlineInfo?.HasFavourited ?? false; + favourited.Value = setInfo.NewValue?.HasFavourited ?? false; }, true); } - private void updateEnabled() => Enabled.Value = !(localUser.Value is GuestUser) && BeatmapSet.Value?.OnlineBeatmapSetID > 0; + private void updateEnabled() => Enabled.Value = !(localUser.Value is GuestUser) && BeatmapSet.Value?.OnlineID > 0; protected override void UpdateAfterChildren() { diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs index 88d0778ae4..bd7723d3c0 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/HeaderDownloadButton.cs @@ -14,11 +14,13 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Online; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Resources.Localisation.Web; using osu.Game.Users; using osuTK; using osuTK.Graphics; +using CommonStrings = osu.Game.Localisation.CommonStrings; namespace osu.Game.Overlays.BeatmapSet.Buttons { @@ -36,9 +38,10 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private HeaderButton button; private BeatmapDownloadTracker downloadTracker; - private readonly BeatmapSetInfo beatmapSet; - public HeaderDownloadButton(BeatmapSetInfo beatmapSet, bool noVideo = false) + private readonly APIBeatmapSet beatmapSet; + + public HeaderDownloadButton(APIBeatmapSet beatmapSet, bool noVideo = false) { this.beatmapSet = beatmapSet; this.noVideo = noVideo; @@ -121,7 +124,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { new OsuSpriteText { - Text = Localisation.CommonStrings.Downloading, + Text = CommonStrings.Downloading, Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) }, }; @@ -132,7 +135,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons { new OsuSpriteText { - Text = Localisation.CommonStrings.Importing, + Text = CommonStrings.Importing, Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) }, }; @@ -168,7 +171,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons private LocalisableString getVideoSuffixText() { - if (!beatmapSet.OnlineInfo.HasVideo) + if (!beatmapSet.HasVideo) return string.Empty; return noVideo ? BeatmapsetsStrings.ShowDetailsDownloadNoVideo : BeatmapsetsStrings.ShowDetailsDownloadVideo; diff --git a/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs index 5b3c142a66..6bcdb7bdc5 100644 --- a/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs +++ b/osu.Game/Overlays/BeatmapSet/Buttons/PreviewButton.cs @@ -8,9 +8,9 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Audio; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapListing.Panels; using osuTK; @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons public IBindable Playing => playButton.Playing; - public BeatmapSetInfo BeatmapSet + public APIBeatmapSet BeatmapSet { get => playButton.BeatmapSet; set => playButton.BeatmapSet = value; diff --git a/osu.Game/Overlays/BeatmapSet/Details.cs b/osu.Game/Overlays/BeatmapSet/Details.cs index d6720e5f35..dfc8d5e680 100644 --- a/osu.Game/Overlays/BeatmapSet/Details.cs +++ b/osu.Game/Overlays/BeatmapSet/Details.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet.Buttons; using osu.Game.Screens.Select.Details; using osuTK; @@ -21,9 +22,9 @@ namespace osu.Game.Overlays.BeatmapSet private readonly AdvancedStats advanced; private readonly DetailBox ratingBox; - private BeatmapSetInfo beatmapSet; + private APIBeatmapSet beatmapSet; - public BeatmapSetInfo BeatmapSet + public APIBeatmapSet BeatmapSet { get => beatmapSet; set @@ -37,9 +38,9 @@ namespace osu.Game.Overlays.BeatmapSet } } - private BeatmapInfo beatmapInfo; + private IBeatmapInfo beatmapInfo; - public BeatmapInfo BeatmapInfo + public IBeatmapInfo BeatmapInfo { get => beatmapInfo; set @@ -53,7 +54,7 @@ namespace osu.Game.Overlays.BeatmapSet private void updateDisplay() { Ratings.Ratings = BeatmapSet?.Ratings; - ratingBox.Alpha = BeatmapSet?.OnlineInfo?.Status > 0 ? 1 : 0; + ratingBox.Alpha = BeatmapSet?.Status > 0 ? 1 : 0; } public Details() diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index 970e9bbf42..3ef52d718d 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -6,9 +6,9 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; namespace osu.Game.Overlays.BeatmapSet { @@ -22,12 +22,12 @@ namespace osu.Game.Overlays.BeatmapSet private readonly Box background; private readonly SuccessRate successRate; - public readonly Bindable BeatmapSet = new Bindable(); + public readonly Bindable BeatmapSet = new Bindable(); - public BeatmapInfo BeatmapInfo + public APIBeatmap BeatmapInfo { - get => successRate.BeatmapInfo; - set => successRate.BeatmapInfo = value; + get => successRate.Beatmap; + set => successRate.Beatmap = value; } public Info() @@ -115,11 +115,11 @@ namespace osu.Game.Overlays.BeatmapSet BeatmapSet.ValueChanged += b => { - source.Text = b.NewValue?.Metadata.Source ?? string.Empty; - tags.Text = b.NewValue?.Metadata.Tags ?? string.Empty; - genre.Text = b.NewValue?.OnlineInfo?.Genre.Name ?? string.Empty; - language.Text = b.NewValue?.OnlineInfo?.Language.Name ?? string.Empty; - bool setHasLeaderboard = b.NewValue?.OnlineInfo?.Status > 0; + source.Text = b.NewValue?.Source ?? string.Empty; + tags.Text = b.NewValue?.Tags ?? string.Empty; + genre.Text = b.NewValue?.Genre.Name ?? string.Empty; + language.Text = b.NewValue?.Language.Name ?? string.Empty; + bool setHasLeaderboard = b.NewValue?.Status > 0; successRate.Alpha = setHasLeaderboard ? 1 : 0; notRankedPlaceholder.Alpha = setHasLeaderboard ? 0 : 1; Height = setHasLeaderboard ? 270 : base_height; diff --git a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs index 6349f115cb..a9723c9c62 100644 --- a/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/LeaderboardModSelector.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.UserInterface; using osuTK.Graphics; using System; using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; namespace osu.Game.Overlays.BeatmapSet @@ -20,7 +21,7 @@ namespace osu.Game.Overlays.BeatmapSet public class LeaderboardModSelector : CompositeDrawable { public readonly BindableList SelectedMods = new BindableList(); - public readonly Bindable Ruleset = new Bindable(); + public readonly Bindable Ruleset = new Bindable(); private readonly FillFlowContainer modsContainer; @@ -45,7 +46,10 @@ namespace osu.Game.Overlays.BeatmapSet Ruleset.BindValueChanged(onRulesetChanged, true); } - private void onRulesetChanged(ValueChangedEvent ruleset) + [Resolved] + private RulesetStore rulesets { get; set; } + + private void onRulesetChanged(ValueChangedEvent ruleset) { SelectedMods.Clear(); modsContainer.Clear(); @@ -54,7 +58,7 @@ namespace osu.Game.Overlays.BeatmapSet return; modsContainer.Add(new ModButton(new ModNoMod())); - modsContainer.AddRange(ruleset.NewValue.CreateInstance().AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m))); + modsContainer.AddRange(rulesets.GetRuleset(ruleset.NewValue.OnlineID).CreateInstance().AllMods.Where(m => m.UserPlayable).Select(m => new ModButton(m))); modsContainer.ForEach(button => { diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index c6eb516c7c..4a00c8b4a0 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -1,24 +1,24 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osuTK; using System.Linq; using System.Threading; using System.Threading.Tasks; -using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; -using osu.Framework.Bindables; -using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Users; +using osuTK; namespace osu.Game.Overlays.BeatmapSet.Scores { @@ -26,8 +26,8 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { private const int spacing = 15; - public readonly Bindable Beatmap = new Bindable(); - private readonly Bindable ruleset = new Bindable(); + public readonly Bindable Beatmap = new Bindable(); + private readonly Bindable ruleset = new Bindable(); private readonly Bindable scope = new Bindable(BeatmapLeaderboardScope.Global); private readonly IBindable user = new Bindable(); @@ -66,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores if (value?.Scores.Any() != true) return; - scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets, Beatmap.Value)).ToArray(), loadCancellationSource.Token) + scoreManager.OrderByTotalScoreAsync(value.Scores.Select(s => s.CreateScoreInfo(rulesets, Beatmap.Value.ToBeatmapInfo(rulesets))).ToArray(), loadCancellationSource.Token) .ContinueWith(ordered => Schedule(() => { if (loadCancellationSource.IsCancellationRequested) @@ -78,7 +78,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores scoreTable.Show(); var userScore = value.UserScore; - var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets, Beatmap.Value); + var userScoreInfo = userScore?.Score.CreateScoreInfo(rulesets, Beatmap.Value.ToBeatmapInfo(rulesets)); topScoresContainer.Add(new DrawableTopScore(topScore)); @@ -200,11 +200,11 @@ namespace osu.Game.Overlays.BeatmapSet.Scores user.BindValueChanged(onUserChanged, true); } - private void onBeatmapChanged(ValueChangedEvent beatmap) + private void onBeatmapChanged(ValueChangedEvent beatmap) { var beatmapRuleset = beatmap.NewValue?.Ruleset; - if (ruleset.Value?.Equals(beatmapRuleset) ?? false) + if (ruleset.Value?.OnlineID == beatmapRuleset?.OnlineID) { modSelector.DeselectAll(); ruleset.TriggerChange(); @@ -232,7 +232,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores noScoresPlaceholder.Hide(); - if (Beatmap.Value?.OnlineBeatmapID.HasValue != true || Beatmap.Value.Status <= BeatmapSetOnlineStatus.Pending) + if (Beatmap.Value == null || Beatmap.Value.OnlineID <= 0 || (Beatmap.Value?.BeatmapSet as IBeatmapSetOnlineInfo)?.Status <= BeatmapSetOnlineStatus.Pending) { Scores = null; Hide(); diff --git a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs index 40a3c9fe8b..e08f099226 100644 --- a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs +++ b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs @@ -5,10 +5,10 @@ using osu.Framework.Allocation; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; using osu.Game.Screens.Select.Details; @@ -23,16 +23,16 @@ namespace osu.Game.Overlays.BeatmapSet private readonly Bar successRate; private readonly Container percentContainer; - private BeatmapInfo beatmapInfo; + private APIBeatmap beatmap; - public BeatmapInfo BeatmapInfo + public APIBeatmap Beatmap { - get => beatmapInfo; + get => beatmap; set { - if (value == beatmapInfo) return; + if (value == beatmap) return; - beatmapInfo = value; + beatmap = value; updateDisplay(); } @@ -40,15 +40,15 @@ namespace osu.Game.Overlays.BeatmapSet private void updateDisplay() { - int passCount = beatmapInfo?.OnlineInfo?.PassCount ?? 0; - int playCount = beatmapInfo?.OnlineInfo?.PlayCount ?? 0; + int passCount = beatmap?.PassCount ?? 0; + int playCount = beatmap?.PlayCount ?? 0; float rate = playCount != 0 ? (float)passCount / playCount : 0; successPercent.Text = rate.ToLocalisableString(@"0.#%"); successRate.Length = rate; percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic); - Graph.FailTimes = beatmapInfo?.FailTimes; + Graph.FailTimes = beatmap?.FailTimes; } public SuccessRate() diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index f987b57d6e..fa5a7c66d0 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -7,8 +7,8 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.BeatmapSet; using osu.Game.Overlays.BeatmapSet.Scores; using osu.Game.Overlays.Comments; @@ -27,7 +27,7 @@ namespace osu.Game.Overlays [Resolved] private RulesetStore rulesets { get; set; } - private readonly Bindable beatmapSet = new Bindable(); + private readonly Bindable beatmapSet = new Bindable(); // receive input outside our bounds so we can trigger a close event on ourselves. public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; @@ -89,8 +89,8 @@ namespace osu.Game.Overlays var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId); req.Success += res => { - beatmapSet.Value = res.ToBeatmapSet(rulesets); - Header.HeaderContent.Picker.Beatmap.Value = Header.BeatmapSet.Value.Beatmaps.First(b => b.OnlineBeatmapID == beatmapId); + beatmapSet.Value = res; + Header.HeaderContent.Picker.Beatmap.Value = Header.BeatmapSet.Value.Beatmaps.First(b => b.OnlineID == beatmapId); }; API.Queue(req); @@ -102,7 +102,7 @@ namespace osu.Game.Overlays beatmapSet.Value = null; var req = new GetBeatmapSetRequest(beatmapSetId); - req.Success += res => beatmapSet.Value = res.ToBeatmapSet(rulesets); + req.Success += res => beatmapSet.Value = res; API.Queue(req); Show(); @@ -112,7 +112,7 @@ namespace osu.Game.Overlays /// Show an already fully-populated beatmap set. /// /// The set to show. - public void ShowBeatmapSet(BeatmapSetInfo set) + public void ShowBeatmapSet(APIBeatmapSet set) { beatmapSet.Value = set; Show(); @@ -120,7 +120,7 @@ namespace osu.Game.Overlays private class CommentsSection : BeatmapSetLayoutSection { - public readonly Bindable BeatmapSet = new Bindable(); + public readonly Bindable BeatmapSet = new Bindable(); public CommentsSection() { @@ -130,10 +130,10 @@ namespace osu.Game.Overlays BeatmapSet.BindValueChanged(beatmapSet => { - if (beatmapSet.NewValue?.OnlineBeatmapSetID is int onlineBeatmapSetID) + if (beatmapSet.NewValue?.OnlineID > 0) { Show(); - comments.ShowComments(CommentableType.Beatmapset, onlineBeatmapSetID); + comments.ShowComments(CommentableType.Beatmapset, beatmapSet.NewValue.OnlineID); } else { diff --git a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs index 5cc598ae70..e84eee15be 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSupporterPromo.cs @@ -101,7 +101,7 @@ namespace osu.Game.Overlays.Changelog t.Colour = colour.PinkLighter; }) { - Text = ChangelogStrings.SupportText2.ToString(), + Text = ChangelogStrings.SupportText2, Margin = new MarginPadding { Top = 10 }, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 78ef2ec795..0f953f92bb 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; using osuTK; @@ -42,9 +43,9 @@ namespace osu.Game.Overlays.Dialog set => icon.Icon = value; } - private string headerText; + private LocalisableString headerText; - public string HeaderText + public LocalisableString HeaderText { get => headerText; set @@ -57,9 +58,9 @@ namespace osu.Game.Overlays.Dialog } } - private string bodyText; + private LocalisableString bodyText; - public string BodyText + public LocalisableString BodyText { get => bodyText; set diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index eea2a9dc7e..04c12b8cd7 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -25,11 +25,8 @@ namespace osu.Game.Overlays.Music private TextFlowContainer text; private ITextPart titlePart; - private ILocalisedBindableString title; - private ILocalisedBindableString artist; - - private Color4 selectedColour; - private Color4 artistColour; + [Resolved] + private OsuColour colours { get; set; } public PlaylistItem(BeatmapSetInfo item) : base(item) @@ -40,22 +37,15 @@ namespace osu.Game.Overlays.Music } [BackgroundDependencyLoader] - private void load(OsuColour colours, LocalisationManager localisation) + private void load() { - selectedColour = colours.Yellow; - artistColour = colours.Gray9; HandleColour = colours.Gray5; - - title = localisation.GetLocalisedString(new RomanisableString(Model.Metadata.TitleUnicode, Model.Metadata.Title)); - artist = localisation.GetLocalisedString(new RomanisableString(Model.Metadata.ArtistUnicode, Model.Metadata.Artist)); } protected override void LoadComplete() { base.LoadComplete(); - artist.BindValueChanged(_ => recreateText(), true); - SelectedSet.BindValueChanged(set => { if (set.OldValue?.Equals(Model) != true && set.NewValue?.Equals(Model) != true) @@ -68,7 +58,7 @@ namespace osu.Game.Overlays.Music private void updateSelectionState(bool instant) { foreach (Drawable s in titlePart.Drawables) - s.FadeColour(SelectedSet.Value?.Equals(Model) == true ? selectedColour : Color4.White, instant ? 0 : FADE_DURATION); + s.FadeColour(SelectedSet.Value?.Equals(Model) == true ? colours.Yellow : Color4.White, instant ? 0 : FADE_DURATION); } protected override Drawable CreateContent() => text = new OsuTextFlowContainer @@ -77,18 +67,23 @@ namespace osu.Game.Overlays.Music AutoSizeAxes = Axes.Y, }; - private void recreateText() + protected override void LoadAsyncComplete() { - text.Clear(); + base.LoadAsyncComplete(); - // space after the title to put a space between the title and artist - titlePart = text.AddText(title.Value + @" ", sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); + var title = new RomanisableString(Model.Metadata.TitleUnicode, Model.Metadata.Title); + var artist = new RomanisableString(Model.Metadata.ArtistUnicode, Model.Metadata.Artist); + + titlePart = text.AddText(title, sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)); updateSelectionState(true); + titlePart.DrawablePartsRecreated += _ => updateSelectionState(true); - text.AddText(artist.Value, sprite => + text.AddText(@" "); // to separate the title from the artist. + + text.AddText(artist, sprite => { sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); - sprite.Colour = artistColour; + sprite.Colour = colours.Gray9; sprite.Padding = new MarginPadding { Top = 1 }; }); } diff --git a/osu.Game/Overlays/Notifications/ProgressNotification.cs b/osu.Game/Overlays/Notifications/ProgressNotification.cs index b27e15dd2c..c44b88ad29 100644 --- a/osu.Game/Overlays/Notifications/ProgressNotification.cs +++ b/osu.Game/Overlays/Notifications/ProgressNotification.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -22,7 +23,7 @@ namespace osu.Game.Overlays.Notifications { private const float loading_spinner_size = 22; - public string Text + public LocalisableString Text { set => Schedule(() => textDrawable.Text = value); } diff --git a/osu.Game/Overlays/Notifications/SimpleNotification.cs b/osu.Game/Overlays/Notifications/SimpleNotification.cs index 3a3136b1ea..17ec12a4ca 100644 --- a/osu.Game/Overlays/Notifications/SimpleNotification.cs +++ b/osu.Game/Overlays/Notifications/SimpleNotification.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osuTK; @@ -15,9 +16,9 @@ namespace osu.Game.Overlays.Notifications { public class SimpleNotification : Notification { - private string text = string.Empty; + private LocalisableString text; - public string Text + public LocalisableString Text { get => text; set diff --git a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs index c1e56facd9..fffa20dc11 100644 --- a/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Beatmaps/PaginatedBeatmapContainer.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays.Profile.Sections.Beatmaps new GetUserBeatmapsRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); protected override Drawable CreateDrawableItem(APIBeatmapSet model) => model.OnlineID > 0 - ? new GridBeatmapPanel(model.ToBeatmapSet(Rulesets)) + ? new GridBeatmapPanel(model) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index ac4299ae49..3ed4bd9e50 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -5,15 +5,15 @@ using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; -using osu.Framework.Graphics.Cursor; -using osu.Framework.Localisation; using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 7bfa2ee51e..fb464e1b41 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -12,9 +12,11 @@ using osu.Framework.Localisation; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Leaderboards; +using osu.Game.Rulesets; using osu.Game.Rulesets.UI; -using osu.Game.Scoring; +using osu.Game.Utils; using osuTK; namespace osu.Game.Overlays.Profile.Sections.Ranks @@ -26,7 +28,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private const float performance_background_shear = 0.45f; - protected readonly ScoreInfo Score; + protected readonly APIScoreInfo Score; [Resolved] private OsuColour colours { get; set; } @@ -34,7 +36,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks [Resolved] private OverlayColourProvider colourProvider { get; set; } - public DrawableProfileScore(ScoreInfo score) + public DrawableProfileScore(APIScoreInfo score) { Score = score; @@ -43,7 +45,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks } [BackgroundDependencyLoader] - private void load() + private void load(RulesetStore rulesets) { AddInternal(new ProfileItemContainer { @@ -79,7 +81,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Spacing = new Vector2(0, 2), Children = new Drawable[] { - new ScoreBeatmapMetadataContainer(Score.BeatmapInfo), + new ScoreBeatmapMetadataContainer(Score.Beatmap), new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -89,7 +91,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { new OsuSpriteText { - Text = $"{Score.BeatmapInfo.Version}", + Text = $"{Score.Beatmap?.DifficultyName}", Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), Colour = colours.Yellow }, @@ -129,7 +131,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Origin = Anchor.CentreRight, Direction = FillDirection.Horizontal, Spacing = new Vector2(2), - Children = Score.Mods.Select(mod => new ModIcon(mod) + Children = Score.Mods.Select(mod => new ModIcon(rulesets.GetRuleset(Score.RulesetID).CreateInstance().CreateModFromAcronym(mod.Acronym)) { Scale = new Vector2(0.35f) }).ToList(), @@ -198,7 +200,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks RelativeSizeAxes = Axes.Y, Child = new OsuSpriteText { - Text = Score.DisplayAccuracy, + Text = Score.Accuracy.FormatAccuracy(), Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true), Colour = colours.Yellow, Anchor = Anchor.CentreLeft, diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs index f77464ecb9..e653be5cfa 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileWeightedScore.cs @@ -6,8 +6,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Resources.Localisation.Web; -using osu.Game.Scoring; using osuTK; namespace osu.Game.Overlays.Profile.Sections.Ranks @@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { private readonly double weight; - public DrawableProfileWeightedScore(ScoreInfo score, double weight) + public DrawableProfileWeightedScore(APIScoreInfo 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 1fb6100a28..cde386bc7b 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/PaginatedScoreContainer.cs @@ -69,10 +69,10 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks switch (type) { default: - return new DrawableProfileScore(model.CreateScoreInfo(Rulesets)); + return new DrawableProfileScore(model); case ScoreType.Best: - return new DrawableProfileWeightedScore(model.CreateScoreInfo(Rulesets), Math.Pow(0.95, drawableItemIndex++)); + return new DrawableProfileWeightedScore(model, Math.Pow(0.95, drawableItemIndex++)); } } } diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs index b16e0a4908..b95b0a1afc 100644 --- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs +++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs @@ -143,7 +143,7 @@ namespace osu.Game.Overlays.Rankings AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, Spacing = new Vector2(10), - Children = response.BeatmapSets.Select(b => new GridBeatmapPanel(b.ToBeatmapSet(rulesets)) + Children = response.BeatmapSets.Select(b => new GridBeatmapPanel(b) { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs index e509cac2f1..0814e3c824 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/DirectorySelectScreen.cs @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance cp.Font = OsuFont.Default.With(size: 24); }) { - Text = HeaderText.ToString(), + Text = HeaderText, TextAnchor = Anchor.TopCentre, Margin = new MarginPadding(10), RelativeSizeAxes = Axes.X, diff --git a/osu.Game/Overlays/Settings/SettingsCheckbox.cs b/osu.Game/Overlays/Settings/SettingsCheckbox.cs index 8b7ac80a5b..8a8fed4d30 100644 --- a/osu.Game/Overlays/Settings/SettingsCheckbox.cs +++ b/osu.Game/Overlays/Settings/SettingsCheckbox.cs @@ -16,8 +16,7 @@ namespace osu.Game.Overlays.Settings public override LocalisableString LabelText { get => labelText; - // checkbox doesn't properly support localisation yet. - set => ((OsuCheckbox)Control).LabelText = (labelText = value).ToString(); + set => ((OsuCheckbox)Control).LabelText = labelText = value; } } } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index b593dea576..e709be1343 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -68,7 +68,7 @@ namespace osu.Game.Overlays.Settings { set { - bool hasValue = !string.IsNullOrWhiteSpace(value.ToString()); + bool hasValue = value != default; if (warningText == null) { @@ -80,7 +80,7 @@ namespace osu.Game.Overlays.Settings } warningText.Alpha = hasValue ? 1 : 0; - warningText.Text = value.ToString(); // TODO: Remove ToString() call after TextFlowContainer supports localisation (see https://github.com/ppy/osu-framework/issues/4636). + warningText.Text = value ?? default; } } diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index eab81186d5..5b4284dc2f 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -58,6 +58,11 @@ namespace osu.Game.Rulesets.Difficulty return CreateDifficultyAttributes(Beatmap, playableMods, skills, clockRate); } + /// + /// Calculates the difficulty of the beatmap and returns a set of representing the difficulty at every relevant time value in the beatmap. + /// + /// The mods that should be applied to the beatmap. + /// The set of . public List CalculateTimed(params Mod[] mods) { preProcess(mods); @@ -77,7 +82,7 @@ namespace osu.Game.Rulesets.Difficulty foreach (var skill in skills) skill.ProcessInternal(hitObject); - attribs.Add(new TimedDifficultyAttributes(hitObject.EndTime, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate))); + attribs.Add(new TimedDifficultyAttributes(hitObject.EndTime * clockRate, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate))); } return attribs; diff --git a/osu.Game/Rulesets/Difficulty/TimedDifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/TimedDifficultyAttributes.cs index 973b2dacb2..2509971389 100644 --- a/osu.Game/Rulesets/Difficulty/TimedDifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/TimedDifficultyAttributes.cs @@ -11,9 +11,21 @@ namespace osu.Game.Rulesets.Difficulty /// public class TimedDifficultyAttributes : IComparable { + /// + /// The non-clock-adjusted time value at which the attributes take effect. + /// public readonly double Time; + + /// + /// The attributes. + /// public readonly DifficultyAttributes Attributes; + /// + /// Creates new . + /// + /// The non-clock-adjusted time value at which the attributes take effect. + /// The attributes. public TimedDifficultyAttributes(double time, DifficultyAttributes attributes) { Time = time; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs b/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs index 08e0312b64..ec2ff68aad 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckAudioQuality.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Edit.Checks public IEnumerable Run(BeatmapVerifierContext context) { string audioFile = context.Beatmap.Metadata?.AudioFile; - if (audioFile == null) + if (string.IsNullOrEmpty(audioFile)) yield break; var track = context.WorkingBeatmap.Track; diff --git a/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs index abedee143a..33bcac1e75 100644 --- a/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs +++ b/osu.Game/Rulesets/Edit/Checks/CheckFilePresence.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Edit.Checks { string filename = GetFilename(context.Beatmap); - if (filename == null) + if (string.IsNullOrEmpty(filename)) { yield return new IssueTemplateNoneSet(this).Create(TypeOfFile); diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index d7d4642a39..29e3f12d03 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -108,11 +108,20 @@ namespace osu.Game.Screens.Edit.Compose.Components protected override bool OnMouseDown(MouseDownEvent e) { bool selectionPerformed = performMouseDownActions(e); - - // even if a selection didn't occur, a drag event may still move the selection. bool movementPossible = prepareSelectionMovement(); - return selectionPerformed || (e.Button == MouseButton.Left && movementPossible); + // check if selection has occurred + if (selectionPerformed) + { + // only unmodified right click should show context menu + bool shouldShowContextMenu = e.Button == MouseButton.Right && !e.ShiftPressed && !e.AltPressed && !e.SuperPressed; + + // stop propagation if not showing context menu + return !shouldShowContextMenu; + } + + // even if a selection didn't occur, a drag event may still move the selection. + return e.Button == MouseButton.Left && movementPossible; } protected SelectionBlueprint ClickedBlueprint { get; private set; } diff --git a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs index 10a5771520..6c004a7c8b 100644 --- a/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs +++ b/osu.Game/Screens/Edit/Timing/SliderWithTextBoxInput.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Localisation; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays.Settings; @@ -16,7 +17,7 @@ namespace osu.Game.Screens.Edit.Timing { private readonly SettingsSlider slider; - public SliderWithTextBoxInput(string labelText) + public SliderWithTextBoxInput(LocalisableString labelText) { LabelledTextBox textbox; diff --git a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs index 2901758332..e948c1adae 100644 --- a/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/OnlinePlay/Components/BeatmapTitle.cs @@ -7,7 +7,6 @@ using osu.Framework.Graphics; using osu.Framework.Localisation; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; namespace osu.Game.Screens.OnlinePlay.Components @@ -69,24 +68,14 @@ namespace osu.Game.Screens.OnlinePlay.Components } else { - textFlow.AddLink(new[] - { - new OsuSpriteText - { - Text = new RomanisableString(beatmap.Value.Metadata.ArtistUnicode, beatmap.Value.Metadata.Artist), - Font = OsuFont.GetFont(size: TextSize), - }, - new OsuSpriteText - { - Text = " - ", - Font = OsuFont.GetFont(size: TextSize), - }, - new OsuSpriteText - { - Text = new RomanisableString(beatmap.Value.Metadata.TitleUnicode, beatmap.Value.Metadata.Title), - Font = OsuFont.GetFont(size: TextSize), - } - }, LinkAction.OpenBeatmap, beatmap.Value.OnlineID.ToString(), "Open beatmap"); + var metadataInfo = beatmap.Value.Metadata; + + string artistUnicode = string.IsNullOrEmpty(metadataInfo.ArtistUnicode) ? metadataInfo.Artist : metadataInfo.ArtistUnicode; + string titleUnicode = string.IsNullOrEmpty(metadataInfo.TitleUnicode) ? metadataInfo.Title : metadataInfo.TitleUnicode; + + var title = new RomanisableString($"{artistUnicode} - {titleUnicode}".Trim(), $"{metadataInfo.Artist} - {metadataInfo.Title}".Trim()); + + textFlow.AddLink(title, LinkAction.OpenBeatmap, beatmap.Value.OnlineID.ToString(), "Open beatmap"); } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs index e2088c77d5..ffc5c07d4e 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundScreen.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Online.Rooms; using osuTK; using osuTK.Graphics; @@ -61,7 +62,10 @@ namespace osu.Game.Screens.OnlinePlay.Components { var beatmap = playlistItem?.Beatmap.Value; - if (background?.BeatmapInfo?.BeatmapSet?.OnlineInfo?.Covers.Cover == beatmap?.BeatmapSet?.OnlineInfo?.Covers.Cover) + string? lastCover = (background?.Beatmap?.BeatmapSet as IBeatmapSetOnlineInfo)?.Covers.Cover; + string? newCover = (beatmap?.BeatmapSet as IBeatmapSetOnlineInfo)?.Covers.Cover; + + if (lastCover == newCover) return; cancellationSource?.Cancel(); diff --git a/osu.Game/Screens/OnlinePlay/Components/PlaylistItemBackground.cs b/osu.Game/Screens/OnlinePlay/Components/PlaylistItemBackground.cs index 90ad6e0f6e..f3e90aa396 100644 --- a/osu.Game/Screens/OnlinePlay/Components/PlaylistItemBackground.cs +++ b/osu.Game/Screens/OnlinePlay/Components/PlaylistItemBackground.cs @@ -13,11 +13,11 @@ namespace osu.Game.Screens.OnlinePlay.Components { public class PlaylistItemBackground : Background { - public readonly BeatmapInfo? BeatmapInfo; + public readonly IBeatmapInfo? Beatmap; public PlaylistItemBackground(PlaylistItem? playlistItem) { - BeatmapInfo = playlistItem?.Beatmap.Value; + Beatmap = playlistItem?.Beatmap.Value; } [BackgroundDependencyLoader] @@ -26,8 +26,8 @@ namespace osu.Game.Screens.OnlinePlay.Components Texture? texture = null; // prefer online cover where available. - if (BeatmapInfo?.BeatmapSet?.OnlineInfo?.Covers.Cover != null) - texture = textures.Get(BeatmapInfo.BeatmapSet.OnlineInfo.Covers.Cover); + if (Beatmap?.BeatmapSet is IBeatmapSetOnlineInfo online) + texture = textures.Get(online.Covers.Cover); Sprite.Texture = texture ?? beatmaps.DefaultBeatmap.Background; } @@ -38,7 +38,7 @@ namespace osu.Game.Screens.OnlinePlay.Components if (ReferenceEquals(this, other)) return true; return other.GetType() == GetType() - && ((PlaylistItemBackground)other).BeatmapInfo == BeatmapInfo; + && ((PlaylistItemBackground)other).Beatmap == Beatmap; } } } diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index abda9e897b..ddfdab18f7 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -119,7 +119,7 @@ namespace osu.Game.Screens.OnlinePlay.Components try { foreach (var pi in room.Playlist) - pi.MapObjects(beatmaps, rulesets); + pi.MapObjects(rulesets); var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value); if (existing == null) diff --git a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs index 7b14acf924..fc029543bb 100644 --- a/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs +++ b/osu.Game/Screens/OnlinePlay/Components/StarRatingRangeDisplay.cs @@ -80,10 +80,10 @@ namespace osu.Game.Screens.OnlinePlay.Components private void updateRange(object sender, NotifyCollectionChangedEventArgs e) { - var orderedDifficulties = Playlist.Select(p => p.Beatmap.Value).OrderBy(b => b.StarDifficulty).ToArray(); + var orderedDifficulties = Playlist.Select(p => p.Beatmap.Value).OrderBy(b => b.StarRating).ToArray(); - StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarDifficulty : 0, 0); - StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarDifficulty : 0, 0); + StarDifficulty minDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[0].StarRating : 0, 0); + StarDifficulty maxDifficulty = new StarDifficulty(orderedDifficulties.Length > 0 ? orderedDifficulties[^1].StarRating : 0, 0); minDisplay.Current.Value = minDifficulty; maxDisplay.Current.Value = maxDifficulty; diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index fa2d0dd165..f4f0e5ce0b 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -27,6 +27,7 @@ using osu.Game.Overlays.BeatmapSet; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Play.HUD; +using osu.Game.Users; using osuTK; using osuTK.Graphics; @@ -47,7 +48,7 @@ namespace osu.Game.Screens.OnlinePlay private ExplicitContentBeatmapPill explicitContentPill; private ModDisplay modDisplay; - private readonly Bindable beatmap = new Bindable(); + private readonly Bindable beatmap = new Bindable(); private readonly Bindable ruleset = new Bindable(); private readonly BindableList requiredMods = new BindableList(); @@ -101,6 +102,7 @@ namespace osu.Game.Screens.OnlinePlay } private ScheduledDelegate scheduledRefresh; + private PanelBackground panelBackground; private void scheduleRefresh() { @@ -110,24 +112,25 @@ namespace osu.Game.Screens.OnlinePlay private void refresh() { - difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) }; + difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(32) }; + + panelBackground.Beatmap.Value = Item.Beatmap.Value; beatmapText.Clear(); - beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString(), null, text => + beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineID.ToString(), null, text => { text.Truncate = true; - text.RelativeSizeAxes = Axes.X; }); authorText.Clear(); - if (Item.Beatmap?.Value?.Metadata?.Author != null) + if (!string.IsNullOrEmpty(Item.Beatmap.Value?.Metadata.Author)) { authorText.AddText("mapped by "); - authorText.AddUserLink(Item.Beatmap.Value?.Metadata.Author); + authorText.AddUserLink(new User { Username = Item.Beatmap.Value.Metadata.Author }); } - bool hasExplicitContent = Item.Beatmap.Value.BeatmapSet.OnlineInfo?.HasExplicitContent == true; + bool hasExplicitContent = (Item.Beatmap.Value.BeatmapSet as IBeatmapSetOnlineInfo)?.HasExplicitContent == true; explicitContentPill.Alpha = hasExplicitContent ? 1 : 0; modDisplay.Current.Value = requiredMods.ToArray(); @@ -151,10 +154,9 @@ namespace osu.Game.Screens.OnlinePlay Alpha = 0, AlwaysPresent = true }, - new PanelBackground + panelBackground = new PanelBackground { RelativeSizeAxes = Axes.Both, - Beatmap = { BindTarget = beatmap } }, new GridContainer { @@ -187,8 +189,11 @@ namespace osu.Game.Screens.OnlinePlay { beatmapText = new LinkFlowContainer(fontParameters) { - AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, + // workaround to ensure only the first line of text shows, emulating truncation (but without ellipsis at the end). + // TODO: remove when text/link flow can support truncation with ellipsis natively. + Height = OsuFont.DEFAULT_FONT_SIZE, + Masking = true }, new FillFlowContainer { @@ -340,7 +345,7 @@ namespace osu.Game.Screens.OnlinePlay // 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) { - public readonly Bindable Beatmap = new Bindable(); + public readonly Bindable Beatmap = new Bindable(); public PanelBackground() { diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index acd87ed864..9920883078 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -353,7 +353,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components }) { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y + // workaround to ensure only the first line of text shows, emulating truncation (but without ellipsis at the end). + // TODO: remove when text/link flow can support truncation with ellipsis natively. + Height = 16, + Masking = true } } } @@ -381,11 +384,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components statusText.Text = "Currently playing "; beatmapText.AddLink(item.NewValue.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, - item.NewValue.Beatmap.Value.OnlineBeatmapID.ToString(), + item.NewValue.Beatmap.Value.OnlineID.ToString(), creationParameters: s => { s.Truncate = true; - s.RelativeSizeAxes = Axes.X; }); } } diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs index 2fe3c7b668..ef2c2df4a6 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/PlaylistCountPill.cs @@ -4,6 +4,7 @@ using System.Collections.Specialized; using Humanizer; using osu.Framework.Allocation; +using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -46,7 +47,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components private void updateCount(object sender, NotifyCollectionChangedEventArgs e) { count.Clear(); - count.AddText(Playlist.Count.ToString(), s => s.Font = s.Font.With(weight: FontWeight.Bold)); + count.AddText(Playlist.Count.ToLocalisableString(), s => s.Font = s.Font.With(weight: FontWeight.Bold)); count.AddText(" "); count.AddText("Beatmap".ToQuantity(Playlist.Count, ShowQuantityAs.None)); } diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index 2015a050bb..2cb29262e2 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -369,7 +369,7 @@ namespace osu.Game.Screens.OnlinePlay.Match var beatmap = SelectedItem.Value?.Beatmap.Value; // Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info - var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineBeatmapID); + var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineID); Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); } diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs index fa4a4d5112..bdb5ff9bb2 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsPlayer.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists private void load(IBindable ruleset) { // Sanity checks to ensure that PlaylistsPlayer matches the settings for the current PlaylistItem - if (Beatmap.Value.BeatmapInfo.OnlineBeatmapID != PlaylistItem.Beatmap.Value.OnlineBeatmapID) + if (Beatmap.Value.BeatmapInfo.OnlineBeatmapID != PlaylistItem.Beatmap.Value.OnlineID) throw new InvalidOperationException("Current Beatmap does not match PlaylistItem's Beatmap"); if (ruleset.Value.ID != PlaylistItem.Ruleset.Value.ID) diff --git a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs index 89bc659f63..34698fccab 100644 --- a/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Playlists/PlaylistsResultsScreen.cs @@ -169,7 +169,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists /// An optional pivot around which the scores were retrieved. private void performSuccessCallback([NotNull] Action> callback, [NotNull] List scores, [CanBeNull] MultiplayerScores pivot = null) { - var scoreInfos = scores.Select(s => s.CreateScoreInfo(playlistItem)).ToArray(); + var scoreInfos = scores.Select(s => s.CreateScoreInfo(playlistItem, Beatmap.Value.BeatmapInfo)).ToArray(); // Score panels calculate total score before displaying, which can take some time. In order to count that calculation as part of the loading spinner display duration, // calculate the total scores locally before invoking the success callback. diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index 9d4dad8bdc..250738df39 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -18,6 +18,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Spectator; using osu.Game.Overlays.BeatmapListing.Panels; using osu.Game.Overlays.Settings; @@ -50,7 +51,6 @@ namespace osu.Game.Screens.Play private Container beatmapPanelContainer; private TriangleButton watchButton; private SettingsCheckbox automaticDownload; - private BeatmapSetInfo onlineBeatmap; /// /// The player's immediate online gameplay state. @@ -60,6 +60,8 @@ namespace osu.Game.Screens.Play private GetBeatmapSetRequest onlineBeatmapRequest; + private APIBeatmapSet beatmapSet; + public SoloSpectator([NotNull] User targetUser) : base(targetUser.Id) { @@ -220,10 +222,10 @@ namespace osu.Game.Screens.Play Debug.Assert(state.BeatmapID != null); onlineBeatmapRequest = new GetBeatmapSetRequest(state.BeatmapID.Value, BeatmapSetLookupType.BeatmapId); - onlineBeatmapRequest.Success += res => Schedule(() => + onlineBeatmapRequest.Success += beatmapSet => Schedule(() => { - onlineBeatmap = res.ToBeatmapSet(rulesets); - beatmapPanelContainer.Child = new GridBeatmapPanel(onlineBeatmap); + this.beatmapSet = beatmapSet; + beatmapPanelContainer.Child = new GridBeatmapPanel(this.beatmapSet); checkForAutomaticDownload(); }); @@ -232,16 +234,16 @@ namespace osu.Game.Screens.Play private void checkForAutomaticDownload() { - if (onlineBeatmap == null) + if (beatmapSet == null) return; if (!automaticDownload.Current.Value) return; - if (beatmaps.IsAvailableLocally(onlineBeatmap)) + if (beatmaps.IsAvailableLocally(new BeatmapSetInfo { OnlineBeatmapSetID = beatmapSet.OnlineID })) return; - beatmaps.Download(onlineBeatmap); + beatmaps.Download(beatmapSet); } public override bool OnExiting(IScreen next) diff --git a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs index 5fb72e4151..307c2352e3 100644 --- a/osu.Game/Screens/Select/BeatmapDeleteDialog.cs +++ b/osu.Game/Screens/Select/BeatmapDeleteDialog.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select new PopupDialogOkButton { Text = @"Yes. Totally. Delete it.", - Action = () => manager.Delete(beatmap), + Action = () => manager?.Delete(beatmap), }, new PopupDialogCancelButton { diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index dfbaa9c6a5..6f215b9287 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -41,13 +41,13 @@ namespace osu.Game.Screens.Select [Resolved] private RulesetStore rulesets { get; set; } - private BeatmapInfo beatmapInfo; + private IBeatmapInfo beatmapInfo; private APIFailTimes failTimes; private int[] ratings; - public BeatmapInfo BeatmapInfo + public IBeatmapInfo BeatmapInfo { get => beatmapInfo; set @@ -56,8 +56,11 @@ namespace osu.Game.Screens.Select beatmapInfo = value; - failTimes = beatmapInfo?.OnlineInfo?.FailTimes; - ratings = beatmapInfo?.BeatmapSet?.Ratings; + var onlineInfo = beatmapInfo as IBeatmapOnlineInfo; + var onlineSetInfo = beatmapInfo.BeatmapSet as IBeatmapSetOnlineInfo; + + failTimes = onlineInfo?.FailTimes; + ratings = onlineSetInfo?.Ratings; Scheduler.AddOnce(updateStatistics); } @@ -178,11 +181,11 @@ namespace osu.Game.Screens.Select private void updateStatistics() { advanced.BeatmapInfo = BeatmapInfo; - description.Text = BeatmapInfo?.Version; - source.Text = BeatmapInfo?.Metadata?.Source; - tags.Text = BeatmapInfo?.Metadata?.Tags; + description.Text = BeatmapInfo?.DifficultyName; + source.Text = BeatmapInfo?.Metadata.Source; + tags.Text = BeatmapInfo?.Metadata.Tags; - // metrics may have been previously fetched + // failTimes may have been previously fetched if (ratings != null && failTimes != null) { updateMetrics(); @@ -190,7 +193,7 @@ namespace osu.Game.Screens.Select } // for now, let's early abort if an OnlineBeatmapID is not present (should have been populated at import time). - if (BeatmapInfo?.OnlineBeatmapID == null || api.State.Value == APIState.Offline) + if (BeatmapInfo == null || BeatmapInfo.OnlineID <= 0 || api.State.Value == APIState.Offline) { updateMetrics(); return; diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 2de72beaad..89eed14e6d 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -188,8 +188,8 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.Both; - titleBinding = localisation.GetLocalisedString(new RomanisableString(metadata.TitleUnicode, metadata.Title)); - artistBinding = localisation.GetLocalisedString(new RomanisableString(metadata.ArtistUnicode, metadata.Artist)); + titleBinding = localisation.GetLocalisedBindableString(new RomanisableString(metadata.TitleUnicode, metadata.Title)); + artistBinding = localisation.GetLocalisedBindableString(new RomanisableString(metadata.ArtistUnicode, metadata.Artist)); const float top_height = 0.7f; diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index e465f423bc..d41cb73a29 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Select.Carousel return string.Compare(BeatmapSet.Metadata.Title, otherSet.BeatmapSet.Metadata.Title, StringComparison.OrdinalIgnoreCase); case SortMode.Author: - return string.Compare(BeatmapSet.Metadata.Author.Username, otherSet.BeatmapSet.Metadata.Author.Username, StringComparison.OrdinalIgnoreCase); + return string.Compare(BeatmapSet.Metadata.Author?.Username, otherSet.BeatmapSet.Metadata.Author?.Username, StringComparison.OrdinalIgnoreCase); case SortMode.Source: return string.Compare(BeatmapSet.Metadata.Source, otherSet.BeatmapSet.Metadata.Source, StringComparison.OrdinalIgnoreCase); diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 5940911d4a..8a5dde961f 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -142,7 +142,7 @@ namespace osu.Game.Screens.Select.Carousel }, new OsuSpriteText { - Text = $"{(beatmapInfo.Metadata ?? beatmapInfo.BeatmapSet.Metadata).Author.Username}", + Text = $"{(beatmapInfo.Metadata ?? beatmapInfo.BeatmapSet.Metadata).Author?.Username ?? string.Empty}", Font = OsuFont.GetFont(italics: true), Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 25ca6ee264..91c8ce441e 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -38,9 +38,9 @@ namespace osu.Game.Screens.Select.Details protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate; private readonly StatisticRow starDifficulty; - private BeatmapInfo beatmapInfo; + private IBeatmapInfo beatmapInfo; - public BeatmapInfo BeatmapInfo + public IBeatmapInfo BeatmapInfo { get => beatmapInfo; set @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Select.Details private void updateStatistics() { - IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.BaseDifficulty; + IBeatmapDifficultyInfo baseDifficulty = BeatmapInfo?.Difficulty; BeatmapDifficulty adjustedDifficulty = null; if (baseDifficulty != null && mods.Value.Any(m => m is IApplicableToDifficulty)) @@ -114,7 +114,7 @@ namespace osu.Game.Screens.Select.Details mod.ApplyToDifficulty(adjustedDifficulty); } - switch (BeatmapInfo?.Ruleset?.ID ?? 0) + switch (BeatmapInfo?.Ruleset.OnlineID) { case 3: // Account for mania differences locally for now diff --git a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs index 0790faad34..01bd5e8196 100644 --- a/osu.Game/Skinning/Editor/SkinSelectionHandler.cs +++ b/osu.Game/Skinning/Editor/SkinSelectionHandler.cs @@ -70,14 +70,16 @@ namespace osu.Game.Skinning.Editor if (anchor.HasFlagFast(Anchor.x1)) scale.X = 0; if (anchor.HasFlagFast(Anchor.y1)) scale.Y = 0; - bool shouldAspectLock = - // for now aspect lock scale adjustments that occur at corners.. - (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1)) - // ..or if any of the selection have been rotated. - // this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway). - || SelectedBlueprints.Any(b => !Precision.AlmostEquals(((Drawable)b.Item).Rotation, 0)); - - if (shouldAspectLock) + // for now aspect lock scale adjustments that occur at corners.. + if (!anchor.HasFlagFast(Anchor.x1) && !anchor.HasFlagFast(Anchor.y1)) + { + // project scale vector along diagonal + Vector2 diag = (selectionRect.TopLeft - selectionRect.BottomRight).Normalized(); + scale = Vector2.Dot(scale, diag) * diag; + } + // ..or if any of the selection have been rotated. + // this is to avoid requiring skew logic (which would likely not be the user's expected transform anyway). + else if (SelectedBlueprints.Any(b => !Precision.AlmostEquals(((Drawable)b.Item).Rotation, 0))) { if (anchor.HasFlagFast(Anchor.x1)) // if dragging from the horizontal centre, only a vertical component is available. diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index 76d36ae7d9..0739026544 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -194,68 +194,61 @@ namespace osu.Game.Skinning string nameLine = @$"Name: {item.Name}"; string authorLine = @$"Author: {item.Creator}"; + string[] newLines = + { + @"// The following content was automatically added by osu! during import, based on filename / folder metadata.", + @"[General]", + nameLine, + authorLine, + }; + var existingFile = item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase)); - if (existingFile != null) + if (existingFile == null) { - List outputLines = new List(); + // In the case a skin doesn't have a skin.ini yet, let's create one. + writeNewSkinIni(); + return; + } - bool addedName = false; - bool addedAuthor = false; - - using (var stream = Files.Storage.GetStream(existingFile.FileInfo.StoragePath)) - using (var sr = new StreamReader(stream)) + using (Stream stream = new MemoryStream()) + { + using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) { - string line; - - while ((line = sr.ReadLine()) != null) + using (var existingStream = Files.Storage.GetStream(existingFile.FileInfo.StoragePath)) + using (var sr = new StreamReader(existingStream)) { - if (line.StartsWith(@"Name:", StringComparison.Ordinal)) - { - outputLines.Add(nameLine); - addedName = true; - } - else if (line.StartsWith(@"Author:", StringComparison.Ordinal)) - { - outputLines.Add(authorLine); - addedAuthor = true; - } - else - outputLines.Add(line); - } - } - - if (!addedName || !addedAuthor) - { - outputLines.AddRange(new[] - { - @"[General]", - nameLine, - authorLine, - }); - } - - using (Stream stream = new MemoryStream()) - { - using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - foreach (string line in outputLines) + string line; + while ((line = sr.ReadLine()) != null) sw.WriteLine(line); } - ReplaceFile(item, existingFile, stream); + sw.WriteLine(); + + foreach (string line in newLines) + sw.WriteLine(line); + } + + ReplaceFile(item, existingFile, stream); + + // can be removed 20220502. + if (!ensureIniWasUpdated(item)) + { + Logger.Log($"Skin {item}'s skin.ini had issues and has been removed. Please report this and provide the problematic skin.", LoggingTarget.Database, LogLevel.Important); + + DeleteFile(item, item.Files.SingleOrDefault(f => f.Filename.Equals(@"skin.ini", StringComparison.OrdinalIgnoreCase))); + writeNewSkinIni(); } } - else + + void writeNewSkinIni() { using (Stream stream = new MemoryStream()) { using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) { - sw.WriteLine(@"[General]"); - sw.WriteLine(nameLine); - sw.WriteLine(authorLine); - sw.WriteLine(@"Version: latest"); + foreach (string line in newLines) + sw.WriteLine(line); } AddFile(item, stream, @"skin.ini"); @@ -263,6 +256,17 @@ namespace osu.Game.Skinning } } + private bool ensureIniWasUpdated(SkinInfo item) + { + // This is a final consistency check to ensure that hash computation doesn't enter an infinite loop. + // With other changes to the surrounding code this should never be hit, but until we are 101% sure that there + // are no other cases let's avoid a hard startup crash by bailing and alerting. + + var instance = GetSkin(item); + + return instance.Configuration.SkinInfo.Name == item.Name; + } + protected override Task Populate(SkinInfo model, ArchiveReader archive, CancellationToken cancellationToken = default) { var instance = GetSkin(model); diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index a25bf24491..9e3d3df915 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -77,10 +77,14 @@ namespace osu.Game.Storyboards { get { - string backgroundPath = BeatmapInfo.BeatmapSet?.Metadata?.BackgroundFile?.ToLowerInvariant(); - if (backgroundPath == null) + string backgroundPath = BeatmapInfo.BeatmapSet?.Metadata?.BackgroundFile; + + if (string.IsNullOrEmpty(backgroundPath)) return false; + // Importantly, do this after the NullOrEmpty because EF may have stored the non-nullable value as null to the database, bypassing compile-time constraints. + backgroundPath = backgroundPath.ToLowerInvariant(); + return GetLayer("Background").Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); } } @@ -93,7 +97,7 @@ namespace osu.Game.Storyboards Drawable drawable = null; string storyboardPath = BeatmapInfo.BeatmapSet?.Files.Find(f => f.Filename.Equals(path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - if (storyboardPath != null) + if (!string.IsNullOrEmpty(storyboardPath)) drawable = new Sprite { Texture = textureStore.Get(storyboardPath) }; // if the texture isn't available locally in the beatmap, some storyboards choose to source from the underlying skin lookup hierarchy. else if (UseSkinSprites) diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index caf83973c4..b9eda5c06e 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -39,16 +39,6 @@ namespace osu.Game.Tests.Beatmaps BeatmapInfo.Length = 75000; BeatmapInfo.OnlineInfo = new APIBeatmap(); BeatmapInfo.OnlineBeatmapID = Interlocked.Increment(ref onlineBeatmapID); - BeatmapInfo.BeatmapSet.OnlineInfo = new APIBeatmapSet - { - Status = BeatmapSetOnlineStatus.Ranked, - Covers = new BeatmapSetOnlineCovers - { - Cover = "https://assets.ppy.sh/beatmaps/163112/covers/cover.jpg", - Card = "https://assets.ppy.sh/beatmaps/163112/covers/card.jpg", - List = "https://assets.ppy.sh/beatmaps/163112/covers/list.jpg" - } - }; } protected virtual Beatmap CreateBeatmap() => createTestBeatmap(); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 01481e68e6..d14303a7ab 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -13,6 +13,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Online.API; +using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus; using osu.Game.Online.Multiplayer.Queueing; @@ -63,7 +64,7 @@ namespace osu.Game.Tests.Visual.Multiplayer return roomUser; } - public void AddNullUser() => addUser(new MultiplayerRoomUser(TestUserLookupCache.NULL_USER_ID)); + public void TestAddUnresolvedUser() => addUser(new MultiplayerRoomUser(TestUserLookupCache.UNRESOLVED_USER_ID)); private void addUser(MultiplayerRoomUser user) { @@ -308,18 +309,24 @@ namespace osu.Game.Tests.Visual.Multiplayer return ((IMultiplayerClient)this).PlaylistItemRemoved(item); } - protected override Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default) + protected override Task GetOnlineBeatmapSet(int beatmapId, CancellationToken cancellationToken = default) { Debug.Assert(Room != null); var apiRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == Room.RoomID); - var set = apiRoom.Playlist.FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet - ?? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId)?.BeatmapSet; + IBeatmapSetInfo? set = apiRoom.Playlist.FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet + ?? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId)?.BeatmapSet; if (set == null) throw new InvalidOperationException("Beatmap not found."); - return Task.FromResult(set); + var apiSet = new APIBeatmapSet + { + OnlineID = set.OnlineID, + Beatmaps = set.Beatmaps.Select(b => new APIBeatmap { OnlineID = b.OnlineID }).ToArray(), + }; + + return Task.FromResult(apiSet); } private async Task changeMatchType(MatchType type) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 90e85f7716..153e2c487c 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -175,27 +175,42 @@ namespace osu.Game.Tests.Visual protected virtual IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset); - protected APIBeatmapSet CreateAPIBeatmapSet(RulesetInfo ruleset) + /// + /// Returns a sample API Beatmap with BeatmapSet populated. + /// + /// The ruleset to create the sample model using. osu! ruleset will be used if not specified. + protected APIBeatmap CreateAPIBeatmap(RulesetInfo ruleset = null) { - var beatmap = CreateBeatmap(ruleset).BeatmapInfo; + var beatmapSet = CreateAPIBeatmapSet(ruleset ?? Ruleset.Value); + + // Avoid circular reference. + var beatmap = beatmapSet.Beatmaps.First(); + beatmapSet.Beatmaps = Array.Empty(); + + // Populate the set as that's generally what we expect from the API. + beatmap.BeatmapSet = beatmapSet; + + return beatmap; + } + + /// + /// Returns a sample API BeatmapSet with beatmaps populated. + /// + /// The ruleset to create the sample model using. osu! ruleset will be used if not specified. + protected APIBeatmapSet CreateAPIBeatmapSet(RulesetInfo ruleset = null) + { + var beatmap = CreateBeatmap(ruleset ?? Ruleset.Value).BeatmapInfo; return new APIBeatmapSet { - Covers = beatmap.BeatmapSet.Covers, OnlineID = beatmap.BeatmapSet.OnlineID, - Status = beatmap.BeatmapSet.Status, - Preview = beatmap.BeatmapSet.Preview, - HasFavourited = beatmap.BeatmapSet.HasFavourited, - PlayCount = beatmap.BeatmapSet.PlayCount, - FavouriteCount = beatmap.BeatmapSet.FavouriteCount, - BPM = beatmap.BeatmapSet.BPM, - HasExplicitContent = beatmap.BeatmapSet.HasExplicitContent, - HasVideo = beatmap.BeatmapSet.HasVideo, - HasStoryboard = beatmap.BeatmapSet.HasStoryboard, - Submitted = beatmap.BeatmapSet.Submitted, - Ranked = beatmap.BeatmapSet.Ranked, - LastUpdated = beatmap.BeatmapSet.LastUpdated, - TrackId = beatmap.BeatmapSet.TrackId, + Status = BeatmapSetOnlineStatus.Ranked, + Covers = new BeatmapSetOnlineCovers + { + Cover = "https://assets.ppy.sh/beatmaps/163112/covers/cover.jpg", + Card = "https://assets.ppy.sh/beatmaps/163112/covers/card.jpg", + List = "https://assets.ppy.sh/beatmaps/163112/covers/list.jpg" + }, Title = beatmap.BeatmapSet.Metadata.Title, TitleUnicode = beatmap.BeatmapSet.Metadata.TitleUnicode, Artist = beatmap.BeatmapSet.Metadata.Artist, @@ -203,9 +218,6 @@ namespace osu.Game.Tests.Visual Author = beatmap.BeatmapSet.Metadata.Author, AuthorID = beatmap.BeatmapSet.Metadata.AuthorID, AuthorString = beatmap.BeatmapSet.Metadata.AuthorString, - Availability = beatmap.BeatmapSet.Availability, - Genre = beatmap.BeatmapSet.Genre, - Language = beatmap.BeatmapSet.Language, Source = beatmap.BeatmapSet.Metadata.Source, Tags = beatmap.BeatmapSet.Metadata.Tags, Beatmaps = new[] diff --git a/osu.Game/Tests/Visual/TestUserLookupCache.cs b/osu.Game/Tests/Visual/TestUserLookupCache.cs index b73e81d0dd..fcb9c070ff 100644 --- a/osu.Game/Tests/Visual/TestUserLookupCache.cs +++ b/osu.Game/Tests/Visual/TestUserLookupCache.cs @@ -14,11 +14,11 @@ namespace osu.Game.Tests.Visual /// A special user ID which would return a for. /// As a simulation to what a regular would return in the case of failing to fetch the user. /// - public const int NULL_USER_ID = -1; + public const int UNRESOLVED_USER_ID = -1; protected override Task ComputeValueAsync(int lookup, CancellationToken token = default) { - if (lookup == NULL_USER_ID) + if (lookup == UNRESOLVED_USER_ID) return Task.FromResult((User)null); return Task.FromResult(new User diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8052ab5254..6dda1f77c6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,9 +23,9 @@ - - - + + + @@ -36,10 +36,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + - - + + diff --git a/osu.iOS.props b/osu.iOS.props index d152cb7066..df24a57e90 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,8 +93,8 @@ - - + +