diff --git a/osu.Android.props b/osu.Android.props index 752eb160ed..f7b7b6fb23 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs new file mode 100644 index 0000000000..559d612037 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneSliderStreamConversion.cs @@ -0,0 +1,185 @@ +// 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.Utils; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Osu.Tests.Editor +{ + public class TestSceneSliderStreamConversion : TestSceneOsuEditor + { + private BindableBeatDivisor beatDivisor => (BindableBeatDivisor)Editor.Dependencies.Get(typeof(BindableBeatDivisor)); + + [Test] + public void TestSimpleConversion() + { + Slider slider = null; + + AddStep("select first slider", () => + { + slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider); + EditorClock.Seek(slider.StartTime); + EditorBeatmap.SelectedHitObjects.Add(slider); + }); + + convertToStream(); + + AddAssert("stream created", () => streamCreatedFor(slider, + (time: 0, pathPosition: 0), + (time: 0.25, pathPosition: 0.25), + (time: 0.5, pathPosition: 0.5), + (time: 0.75, pathPosition: 0.75), + (time: 1, pathPosition: 1))); + + AddStep("undo", () => Editor.Undo()); + AddAssert("slider restored", () => sliderRestored(slider)); + + AddStep("select first slider", () => + { + slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider); + EditorClock.Seek(slider.StartTime); + EditorBeatmap.SelectedHitObjects.Add(slider); + }); + AddStep("change beat divisor", () => beatDivisor.Value = 8); + + convertToStream(); + AddAssert("stream created", () => streamCreatedFor(slider, + (time: 0, pathPosition: 0), + (time: 0.125, pathPosition: 0.125), + (time: 0.25, pathPosition: 0.25), + (time: 0.375, pathPosition: 0.375), + (time: 0.5, pathPosition: 0.5), + (time: 0.625, pathPosition: 0.625), + (time: 0.75, pathPosition: 0.75), + (time: 0.875, pathPosition: 0.875), + (time: 1, pathPosition: 1))); + } + + [Test] + public void TestConversionWithNonMatchingDivisor() + { + Slider slider = null; + + AddStep("select second slider", () => + { + slider = (Slider)EditorBeatmap.HitObjects.Where(h => h is Slider).ElementAt(1); + EditorClock.Seek(slider.StartTime); + EditorBeatmap.SelectedHitObjects.Add(slider); + }); + AddStep("change beat divisor", () => beatDivisor.Value = 3); + + convertToStream(); + + AddAssert("stream created", () => streamCreatedFor(slider, + (time: 0, pathPosition: 0), + (time: 2 / 3d, pathPosition: 2 / 3d))); + } + + [Test] + public void TestConversionWithRepeats() + { + Slider slider = null; + + AddStep("select first slider with repeats", () => + { + slider = (Slider)EditorBeatmap.HitObjects.First(h => h is Slider s && s.RepeatCount > 0); + EditorClock.Seek(slider.StartTime); + EditorBeatmap.SelectedHitObjects.Add(slider); + }); + AddStep("change beat divisor", () => beatDivisor.Value = 2); + + convertToStream(); + + AddAssert("stream created", () => streamCreatedFor(slider, + (time: 0, pathPosition: 0), + (time: 0.25, pathPosition: 0.5), + (time: 0.5, pathPosition: 1), + (time: 0.75, pathPosition: 0.5), + (time: 1, pathPosition: 0))); + } + + [Test] + public void TestConversionPreservesSliderProperties() + { + Slider slider = null; + + AddStep("select second new-combo-starting slider", () => + { + slider = (Slider)EditorBeatmap.HitObjects.Where(h => h is Slider s && s.NewCombo).ElementAt(1); + EditorClock.Seek(slider.StartTime); + EditorBeatmap.SelectedHitObjects.Add(slider); + }); + + convertToStream(); + + AddAssert("stream created", () => streamCreatedFor(slider, + (time: 0, pathPosition: 0), + (time: 0.25, pathPosition: 0.25), + (time: 0.5, pathPosition: 0.5), + (time: 0.75, pathPosition: 0.75), + (time: 1, pathPosition: 1))); + + AddStep("undo", () => Editor.Undo()); + AddAssert("slider restored", () => sliderRestored(slider)); + } + + private void convertToStream() + { + AddStep("convert to stream", () => + { + InputManager.PressKey(Key.LControl); + InputManager.PressKey(Key.LShift); + InputManager.Key(Key.F); + InputManager.ReleaseKey(Key.LShift); + InputManager.ReleaseKey(Key.LControl); + }); + } + + private bool streamCreatedFor(Slider slider, params (double time, double pathPosition)[] expectedCircles) + { + if (EditorBeatmap.HitObjects.Contains(slider)) + return false; + + foreach ((double expectedTime, double expectedPathPosition) in expectedCircles) + { + double time = slider.StartTime + slider.Duration * expectedTime; + Vector2 position = slider.Position + slider.Path.PositionAt(expectedPathPosition); + + if (!EditorBeatmap.HitObjects.OfType().Any(h => matches(h, time, position, slider.NewCombo && expectedTime == 0))) + return false; + } + + return true; + + bool matches(HitCircle circle, double time, Vector2 position, bool startsNewCombo) => + Precision.AlmostEquals(circle.StartTime, time, 1) + && Precision.AlmostEquals(circle.Position, position, 0.01f) + && circle.NewCombo == startsNewCombo + && circle.Samples.SequenceEqual(slider.HeadCircle.Samples) + && circle.SampleControlPoint.IsRedundant(slider.SampleControlPoint); + } + + private bool sliderRestored(Slider slider) + { + var objects = EditorBeatmap.HitObjects.Where(h => h.StartTime >= slider.StartTime && h.GetEndTime() <= slider.EndTime).ToList(); + + if (objects.Count > 1) + return false; + + var hitObject = objects.Single(); + if (!(hitObject is Slider restoredSlider)) + return false; + + return Precision.AlmostEquals(slider.StartTime, restoredSlider.StartTime) + && Precision.AlmostEquals(slider.GetEndTime(), restoredSlider.GetEndTime()) + && Precision.AlmostEquals(slider.Position, restoredSlider.Position, 0.01f) + && Precision.AlmostEquals(slider.EndPosition, restoredSlider.EndPosition, 0.01f); + } + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs b/osu.Game.Rulesets.Osu.Tests/OsuDifficultyCalculatorTest.cs index b0e173e752..9148f0715c 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.6975550434910005d, "diffcalc-test")] - [TestCase(1.4670676815251105d, "zero-length-sliders")] + [TestCase(6.6972307565739273d, "diffcalc-test")] + [TestCase(1.4484754139145539d, "zero-length-sliders")] public void Test(double expected, string name) => base.Test(expected, name); - [TestCase(8.9389769779826267d, "diffcalc-test")] - [TestCase(1.7786917985891204d, "zero-length-sliders")] + [TestCase(8.9382559208689809d, "diffcalc-test")] + [TestCase(1.7548875851757628d, "zero-length-sliders")] public void TestClockRateAdjusted(double expected, string name) => Test(expected, name, new OsuModDoubleTime()); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index a08fe3b7c5..e0a216c8e0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -10,6 +10,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty public double AimStrain { get; set; } public double SpeedStrain { get; set; } public double FlashlightRating { get; set; } + public double SliderFactor { get; set; } public double ApproachRate { get; set; } public double OverallDifficulty { get; set; } public double DrainRate { get; set; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index b0a764dc4d..558ddc16ef 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -34,8 +34,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty return new OsuDifficultyAttributes { Mods = mods, Skills = skills }; double aimRating = Math.Sqrt(skills[0].DifficultyValue()) * difficulty_multiplier; - double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; - double flashlightRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; + double aimRatingNoSliders = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; + double speedRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; + double flashlightRating = Math.Sqrt(skills[3].DifficultyValue()) * difficulty_multiplier; + + double sliderFactor = aimRating > 0 ? aimRatingNoSliders / aimRating : 1; if (mods.Any(h => h is OsuModRelax)) speedRating = 0.0; @@ -74,6 +77,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty AimStrain = aimRating, SpeedStrain = speedRating, FlashlightRating = flashlightRating, + SliderFactor = sliderFactor, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, DrainRate = drainRate, @@ -108,7 +112,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty return new Skill[] { - new Aim(mods), + new Aim(mods, true), + new Aim(mods, false), new Speed(mods, hitWindowGreat), new Flashlight(mods) }; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index a9d9ff6985..8d45c7a8cc 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -125,6 +125,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= 1.0 + 0.04 * (12.0 - Attributes.ApproachRate); } + // We assume 15% of sliders in a map are difficult since there's no way to tell from the performance calculator. + double estimateDifficultSliders = Attributes.SliderCount * 0.15; + + if (Attributes.SliderCount > 0) + { + double estimateSliderEndsDropped = Math.Clamp(Math.Min(countOk + countMeh + countMiss, Attributes.MaxCombo - scoreMaxCombo), 0, estimateDifficultSliders); + double sliderNerfFactor = (1 - Attributes.SliderFactor) * Math.Pow(1 - estimateSliderEndsDropped / estimateDifficultSliders, 3) + Attributes.SliderFactor; + aimValue *= sliderNerfFactor; + } + aimValue *= accuracy; // It is important to also consider accuracy difficulty when doing that. aimValue *= 0.98 + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs index cbaad93bed..d073d751d0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Preprocessing/OsuDifficultyHitObject.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Preprocessing 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; private const float maximum_slider_radius = normalized_radius * 2.4f; - private const float assumed_slider_radius = normalized_radius * 1.65f; + private const float assumed_slider_radius = normalized_radius * 1.8f; protected new OsuHitObject BaseObject => (OsuHitObject)base.BaseObject; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs index 4c40a49396..2a8d2ce759 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Aim.cs @@ -14,11 +14,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills /// public class Aim : OsuStrainSkill { - public Aim(Mod[] mods) + public Aim(Mod[] mods, bool withSliders) : base(mods) { + this.withSliders = withSliders; } + private readonly bool withSliders; + protected override int HistoryLength => 2; private const double wide_angle_multiplier = 1.5; @@ -44,7 +47,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double currVelocity = osuCurrObj.JumpDistance / osuCurrObj.StrainTime; // But if the last object is a slider, then we extend the travel velocity through the slider into the current object. - if (osuLastObj.BaseObject is Slider) + if (osuLastObj.BaseObject is Slider && withSliders) { double movementVelocity = osuCurrObj.MovementDistance / osuCurrObj.MovementTime; // calculate the movement velocity from slider end to current object double travelVelocity = osuCurrObj.TravelDistance / osuCurrObj.TravelTime; // calculate the slider velocity from slider head to slider end. @@ -55,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // As above, do the same for the previous hitobject. double prevVelocity = osuLastObj.JumpDistance / osuLastObj.StrainTime; - if (osuLastLastObj.BaseObject is Slider) + if (osuLastLastObj.BaseObject is Slider && withSliders) { double movementVelocity = osuLastObj.MovementDistance / osuLastObj.MovementTime; double travelVelocity = osuLastObj.TravelDistance / osuLastObj.TravelTime; @@ -135,7 +138,8 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills aimStrain += Math.Max(acuteAngleBonus * acute_angle_multiplier, wideAngleBonus * wide_angle_multiplier + velocityChangeBonus * velocity_change_multiplier); // Add in additional slider velocity bonus. - aimStrain += sliderBonus * slider_multiplier; + if (withSliders) + aimStrain += sliderBonus * slider_multiplier; return aimStrain; } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index a7fadfb67f..17a62fc61c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -11,9 +11,12 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; +using osu.Framework.Utils; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -47,6 +50,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders [Resolved(CanBeNull = true)] private IEditorChangeHandler changeHandler { get; set; } + [Resolved(CanBeNull = true)] + private BindableBeatDivisor beatDivisor { get; set; } + public override Quad SelectionQuad => BodyPiece.ScreenSpaceDrawQuad; private readonly BindableList controlPoints = new BindableList(); @@ -173,6 +179,20 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders } } + protected override bool OnKeyDown(KeyDownEvent e) + { + if (!IsSelected) + return false; + + if (e.Key == Key.F && e.ControlPressed && e.ShiftPressed) + { + convertToStream(); + return true; + } + + return false; + } + private int addControlPoint(Vector2 position) { position -= HitObject.Position; @@ -234,9 +254,56 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders editorBeatmap?.Update(HitObject); } + private void convertToStream() + { + if (editorBeatmap == null || changeHandler == null || beatDivisor == null) + return; + + var timingPoint = editorBeatmap.ControlPointInfo.TimingPointAt(HitObject.StartTime); + double streamSpacing = timingPoint.BeatLength / beatDivisor.Value; + + changeHandler.BeginChange(); + + int i = 0; + double time = HitObject.StartTime; + + while (!Precision.DefinitelyBigger(time, HitObject.GetEndTime(), 1)) + { + // positionWithRepeats is a fractional number in the range of [0, HitObject.SpanCount()] + // and indicates how many fractional spans of a slider have passed up to time. + double positionWithRepeats = (time - HitObject.StartTime) / HitObject.Duration * HitObject.SpanCount(); + double pathPosition = positionWithRepeats - (int)positionWithRepeats; + // every second span is in the reverse direction - need to reverse the path position. + if (Precision.AlmostBigger(positionWithRepeats % 2, 1)) + pathPosition = 1 - pathPosition; + + Vector2 position = HitObject.Position + HitObject.Path.PositionAt(pathPosition); + + var samplePoint = (SampleControlPoint)HitObject.SampleControlPoint.DeepClone(); + samplePoint.Time = time; + + editorBeatmap.Add(new HitCircle + { + StartTime = time, + Position = position, + NewCombo = i == 0 && HitObject.NewCombo, + SampleControlPoint = samplePoint, + Samples = HitObject.HeadCircle.Samples.Select(s => s.With()).ToList() + }); + + i += 1; + time = HitObject.StartTime + i * streamSpacing; + } + + editorBeatmap.Remove(HitObject); + + changeHandler.EndChange(); + } + public override MenuItem[] ContextMenuItems => new MenuItem[] { new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)), + new OsuMenuItem("Convert to stream", MenuItemType.Destructive, convertToStream), }; // Always refer to the drawable object's slider body so subsequent movement deltas are calculated with updated positions. diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index 304a65e5c7..677aaf6f78 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -116,8 +116,8 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual("Insane", beatmapInfo.DifficultyName); Assert.AreEqual(string.Empty, metadata.Source); Assert.AreEqual("MBC7 Unisphere 地球ヤバイEP Chikyu Yabai", metadata.Tags); - Assert.AreEqual(557821, beatmapInfo.OnlineBeatmapID); - Assert.AreEqual(241526, beatmapInfo.BeatmapSet.OnlineBeatmapSetID); + Assert.AreEqual(557821, beatmapInfo.OnlineID); + Assert.AreEqual(241526, beatmapInfo.BeatmapSet.OnlineID); } } diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 37c1dfc657..bfd6ff0314 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { var beatmap = decodeAsJson(normal); var meta = beatmap.BeatmapInfo.Metadata; - Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID); + Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineID); Assert.AreEqual("Soleily", meta.Artist); Assert.AreEqual("Soleily", meta.ArtistUnicode); Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 3093a5719d..e00d7a1115 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -18,6 +18,7 @@ using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays.Notifications; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Scoring; @@ -387,6 +388,41 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [Test] + public async Task TestModelCreationFailureDoesntReturn() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(ImportBeatmapTest))) + { + try + { + var osu = LoadOsuIntoHost(host); + var importer = osu.Dependencies.Get(); + + var progressNotification = new ImportProgressNotification(); + + var zipStream = new MemoryStream(); + + using (var zip = ZipArchive.Create()) + zip.SaveTo(zipStream, new ZipWriterOptions(CompressionType.Deflate)); + + var imported = await importer.Import( + progressNotification, + new ImportTask(zipStream, string.Empty) + ); + + checkBeatmapSetCount(osu, 0); + checkBeatmapCount(osu, 0); + + Assert.IsEmpty(imported); + Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State); + } + finally + { + host.Exit(); + } + } + } + [Test] public async Task TestRollbackOnFailure() { @@ -506,7 +542,7 @@ namespace osu.Game.Tests.Beatmaps.IO var imported = await LoadOszIntoOsu(osu); foreach (var b in imported.Beatmaps) - b.OnlineBeatmapID = null; + b.OnlineID = null; osu.Dependencies.Get().Update(imported); @@ -545,19 +581,19 @@ namespace osu.Game.Tests.Beatmaps.IO var toImport = new BeatmapSetInfo { - OnlineBeatmapSetID = 1, + OnlineID = 1, Metadata = metadata, Beatmaps = new List { new BeatmapInfo { - OnlineBeatmapID = 2, + OnlineID = 2, Metadata = metadata, BaseDifficulty = difficulty }, new BeatmapInfo { - OnlineBeatmapID = 2, + OnlineID = 2, Metadata = metadata, Status = BeatmapSetOnlineStatus.Loved, BaseDifficulty = difficulty @@ -570,8 +606,8 @@ namespace osu.Game.Tests.Beatmaps.IO var imported = await manager.Import(toImport); Assert.NotNull(imported); - Assert.AreEqual(null, imported.Value.Beatmaps[0].OnlineBeatmapID); - Assert.AreEqual(null, imported.Value.Beatmaps[1].OnlineBeatmapID); + Assert.AreEqual(null, imported.Value.Beatmaps[0].OnlineID); + Assert.AreEqual(null, imported.Value.Beatmaps[1].OnlineID); } finally { @@ -1020,13 +1056,13 @@ namespace osu.Game.Tests.Beatmaps.IO { IEnumerable resultSets = null; var store = osu.Dependencies.Get(); - waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526)).Any(), + waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineID == 241526)).Any(), @"BeatmapSet did not import to the database in allocated time.", timeout); // ensure we were stored to beatmap database backing... Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1)."); - IEnumerable queryBeatmaps() => store.QueryBeatmaps(s => s.BeatmapSet.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0); - IEnumerable queryBeatmapSets() => store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526); + IEnumerable queryBeatmaps() => store.QueryBeatmaps(s => s.BeatmapSet.OnlineID == 241526 && s.BaseDifficultyID > 0); + IEnumerable queryBeatmapSets() => store.QueryBeatmapSets(s => s.OnlineID == 241526); // if we don't re-check here, the set will be inserted but the beatmaps won't be present yet. waitForOrAssert(() => queryBeatmaps().Count() == 12, @@ -1042,7 +1078,7 @@ namespace osu.Game.Tests.Beatmaps.IO var set = queryBeatmapSets().First(); foreach (BeatmapInfo b in set.Beatmaps) - Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineBeatmapID == b.OnlineBeatmapID)); + Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID)); Assert.IsTrue(set.Beatmaps.Count > 0); var beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 0))?.Beatmap; Assert.IsTrue(beatmap?.HitObjects.Any() == true); diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index eaf5d107ca..b2ab1eeaa6 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -56,7 +56,7 @@ namespace osu.Game.Tests.Beatmaps.IO var meta = beatmap.Metadata; - Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID); + Assert.AreEqual(241526, beatmap.BeatmapInfo.BeatmapSet.OnlineID); Assert.AreEqual("Soleily", meta.Artist); Assert.AreEqual("Soleily", meta.ArtistUnicode); Assert.AreEqual("03. Renatus - Soleily 192kbps.mp3", meta.AudioFile); diff --git a/osu.Game.Tests/Database/BeatmapImporterTests.cs b/osu.Game.Tests/Database/BeatmapImporterTests.cs index e1fe1e224e..88d16c8a36 100644 --- a/osu.Game.Tests/Database/BeatmapImporterTests.cs +++ b/osu.Game.Tests/Database/BeatmapImporterTests.cs @@ -16,6 +16,7 @@ using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.IO.Archives; using osu.Game.Models; +using osu.Game.Overlays.Notifications; using osu.Game.Stores; using osu.Game.Tests.Resources; using Realms; @@ -367,6 +368,34 @@ namespace osu.Game.Tests.Database }); } + [Test] + public void TestModelCreationFailureDoesntReturn() + { + RunTestWithRealmAsync(async (realmFactory, storage) => + { + using var importer = new BeatmapImporter(realmFactory, storage); + using var store = new RealmRulesetStore(realmFactory, storage); + + var progressNotification = new ImportProgressNotification(); + + var zipStream = new MemoryStream(); + + using (var zip = ZipArchive.Create()) + zip.SaveTo(zipStream, new ZipWriterOptions(CompressionType.Deflate)); + + var imported = await importer.Import( + progressNotification, + new ImportTask(zipStream, string.Empty) + ); + + checkBeatmapSetCount(realmFactory.Context, 0); + checkBeatmapCount(realmFactory.Context, 0); + + Assert.IsEmpty(imported); + Assert.AreEqual(ProgressNotificationState.Cancelled, progressNotification.State); + }); + } + [Test] public void TestRollbackOnFailure() { diff --git a/osu.Game.Tests/Models/DisplayStringTest.cs b/osu.Game.Tests/Models/DisplayStringTest.cs index cac5dd1aaa..95af21eb5f 100644 --- a/osu.Game.Tests/Models/DisplayStringTest.cs +++ b/osu.Game.Tests/Models/DisplayStringTest.cs @@ -10,19 +10,28 @@ using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Users; +#nullable enable + namespace osu.Game.Tests.Models { [TestFixture] public class DisplayStringTest { + [Test] + public void TestNull() + { + IBeatmapSetInfo? beatmap = null; + Assert.That(beatmap.GetDisplayString(), Is.EqualTo("null")); + } + [Test] public void TestBeatmapSet() { var mock = new Mock(); - mock.Setup(m => m.Metadata.Artist).Returns("artist"); - mock.Setup(m => m.Metadata.Title).Returns("title"); - mock.Setup(m => m.Metadata.Author.Username).Returns("author"); + mock.Setup(m => m.Metadata!.Artist).Returns("artist"); + mock.Setup(m => m.Metadata!.Title).Returns("title"); + mock.Setup(m => m.Metadata!.Author.Username).Returns("author"); Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title (author)")); } @@ -32,9 +41,9 @@ namespace osu.Game.Tests.Models { var mock = new Mock(); - mock.Setup(m => m.Metadata.Artist).Returns("artist"); - mock.Setup(m => m.Metadata.Title).Returns("title"); - mock.Setup(m => m.Metadata.Author.Username).Returns(string.Empty); + mock.Setup(m => m.Metadata!.Artist).Returns("artist"); + mock.Setup(m => m.Metadata!.Title).Returns("title"); + mock.Setup(m => m.Metadata!.Author.Username).Returns(string.Empty); Assert.That(mock.Object.GetDisplayString(), Is.EqualTo("artist - title")); } diff --git a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs index 9ce7e0a0e0..938edf07c6 100644 --- a/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs +++ b/osu.Game.Tests/NonVisual/BeatmapSetInfoEqualityTest.cs @@ -12,8 +12,8 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestOnlineWithOnline() { - var ourInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 }; - var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 123 }; + var ourInfo = new BeatmapSetInfo { OnlineID = 123 }; + var otherInfo = new BeatmapSetInfo { OnlineID = 123 }; Assert.AreEqual(ourInfo, otherInfo); } @@ -30,8 +30,8 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestDatabasedWithOnline() { - var ourInfo = new BeatmapSetInfo { ID = 123, OnlineBeatmapSetID = 12 }; - var otherInfo = new BeatmapSetInfo { OnlineBeatmapSetID = 12 }; + var ourInfo = new BeatmapSetInfo { ID = 123, OnlineID = 12 }; + var otherInfo = new BeatmapSetInfo { OnlineID = 12 }; Assert.AreEqual(ourInfo, otherInfo); } diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index ec97948532..ee1feeca8d 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -207,8 +207,8 @@ namespace osu.Game.Tests.NonVisual.Filtering public void TestCriteriaMatchingBeatmapIDs(string query, bool filtered) { var beatmap = getExampleBeatmap(); - beatmap.OnlineBeatmapID = 20201010; - beatmap.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = 1535 }; + beatmap.OnlineID = 20201010; + beatmap.BeatmapSet = new BeatmapSetInfo { OnlineID = 1535 }; var criteria = new FilterCriteria { SearchText = query }; var carouselItem = new CarouselBeatmap(beatmap); diff --git a/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs index ad6f01881b..103831822c 100644 --- a/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs +++ b/osu.Game.Tests/NonVisual/Ranking/UnstableRateTest.cs @@ -22,7 +22,8 @@ namespace osu.Game.Tests.NonVisual.Ranking var unstableRate = new UnstableRate(events); - Assert.IsTrue(Precision.AlmostEquals(unstableRate.Value, 10 * Math.Sqrt(10))); + Assert.IsNotNull(unstableRate.Value); + Assert.IsTrue(Precision.AlmostEquals(unstableRate.Value.Value, 10 * Math.Sqrt(10))); } [Test] diff --git a/osu.Game.Tests/Online/TestSceneBeatmapManager.cs b/osu.Game.Tests/Online/TestSceneBeatmapManager.cs index 4d5bee13f2..fc1b4f224d 100644 --- a/osu.Game.Tests/Online/TestSceneBeatmapManager.cs +++ b/osu.Game.Tests/Online/TestSceneBeatmapManager.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Online private static readonly BeatmapSetInfo test_db_model = new BeatmapSetInfo { - OnlineBeatmapSetID = 1, + OnlineID = 1, Metadata = new BeatmapMetadata { Artist = "test author", diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index e3e2304990..b66da028f1 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Online testBeatmapInfo = getTestBeatmapInfo(testBeatmapFile); testBeatmapSet = testBeatmapInfo.BeatmapSet; - var existing = beatmaps.QueryBeatmapSet(s => s.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID); + var existing = beatmaps.QueryBeatmapSet(s => s.OnlineID == testBeatmapSet.OnlineID); if (existing != null) beatmaps.Delete(existing); @@ -101,10 +101,10 @@ namespace osu.Game.Tests.Online AddStep("import beatmap", () => beatmaps.Import(testBeatmapFile).Wait()); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); - AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID))); + AddStep("delete beatmap", () => beatmaps.Delete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID))); addAvailabilityCheckStep("state not downloaded", BeatmapAvailability.NotDownloaded); - AddStep("undelete beatmap", () => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == testBeatmapSet.OnlineBeatmapSetID))); + AddStep("undelete beatmap", () => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.OnlineID == testBeatmapSet.OnlineID))); addAvailabilityCheckStep("state locally available", BeatmapAvailability.LocallyAvailable); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs new file mode 100644 index 0000000000..db42667033 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorTestGameplay.cs @@ -0,0 +1,123 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Edit.Components.Timelines.Summary; +using osu.Game.Tests.Beatmaps.IO; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneEditorTestGameplay : EditorTestScene + { + protected override bool IsolateSavingFromDatabase => false; + + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + + [Resolved] + private OsuGameBase game { get; set; } + + [Resolved] + private BeatmapManager beatmaps { get; set; } + + private BeatmapSetInfo importedBeatmapSet; + + public override void SetUpSteps() + { + AddStep("import test beatmap", () => importedBeatmapSet = ImportBeatmapTest.LoadOszIntoOsu(game).Result); + base.SetUpSteps(); + } + + protected override void LoadEditor() + { + Beatmap.Value = beatmaps.GetWorkingBeatmap(importedBeatmapSet.Beatmaps.First(b => b.RulesetID == 0)); + base.LoadEditor(); + } + + [Test] + public void TestBasicGameplayTest() + { + AddStep("click test gameplay button", () => + { + var button = Editor.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + + EditorPlayer editorPlayer = null; + AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null); + AddStep("exit player", () => editorPlayer.Exit()); + AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor); + } + + [Test] + public void TestCancelGameplayTestWithUnsavedChanges() + { + AddStep("delete all but first object", () => EditorBeatmap.RemoveRange(EditorBeatmap.HitObjects.Skip(1).ToList())); + + AddStep("click test gameplay button", () => + { + var button = Editor.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("save prompt shown", () => DialogOverlay.CurrentDialog is SaveBeforeGameplayTestDialog); + + AddStep("dismiss prompt", () => + { + var button = DialogOverlay.CurrentDialog.Buttons.Last(); + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + + AddWaitStep("wait some", 3); + AddAssert("stayed in editor", () => Stack.CurrentScreen is Editor); + } + + [Test] + public void TestSaveChangesBeforeGameplayTest() + { + AddStep("delete all but first object", () => EditorBeatmap.RemoveRange(EditorBeatmap.HitObjects.Skip(1).ToList())); + // bit of a hack to ensure this test can be ran multiple times without running into UNIQUE constraint failures + AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = Guid.NewGuid().ToString()); + + AddStep("click test gameplay button", () => + { + var button = Editor.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(button); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("save prompt shown", () => DialogOverlay.CurrentDialog is SaveBeforeGameplayTestDialog); + + AddStep("save changes", () => DialogOverlay.CurrentDialog.PerformOkAction()); + + EditorPlayer editorPlayer = null; + AddUntilStep("player pushed", () => (editorPlayer = Stack.CurrentScreen as EditorPlayer) != null); + AddAssert("beatmap has 1 object", () => editorPlayer.Beatmap.Value.Beatmap.HitObjects.Count == 1); + + AddUntilStep("wait for return to editor", () => Stack.CurrentScreen is Editor); + AddAssert("track stopped", () => !Beatmap.Value.Track.IsRunning); + } + + public override void TearDownSteps() + { + base.TearDownSteps(); + AddStep("delete imported", () => + { + beatmaps.Delete(importedBeatmapSet); + }); + } + } +} diff --git a/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs index c3c803ff23..03e78ce854 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneSetupScreen.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Edit; @@ -23,6 +24,9 @@ namespace osu.Game.Tests.Visual.Editing [Cached(typeof(IBeatSnapProvider))] private readonly EditorBeatmap editorBeatmap; + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + public TestSceneSetupScreen() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs index f961fff1e5..4bbffbdc7a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneTimingScreen.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; +using osu.Game.Overlays; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Edit; @@ -18,6 +19,9 @@ namespace osu.Game.Tests.Visual.Editing [Cached(typeof(IBeatSnapProvider))] private readonly EditorBeatmap editorBeatmap; + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + protected override bool ScrollUsingMouseWheel => false; public TestSceneTimingScreen() diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs index 4c48d52acd..84c7f611af 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs @@ -1,9 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Diagnostics; using NUnit.Framework; -using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Rulesets; @@ -20,16 +21,17 @@ namespace osu.Game.Tests.Visual.Gameplay { public class TestScenePerformancePointsCounter : OsuTestScene { - [Cached] - private GameplayState gameplayState; + private DependencyProvidingContainer dependencyContainer; - [Cached] + private GameplayState gameplayState; private ScoreProcessor scoreProcessor; private int iteration; + private Bindable lastJudgementResult = new Bindable(); private PerformancePointsCounter counter; - public TestScenePerformancePointsCounter() + [SetUpSteps] + public void SetUpSteps() => AddStep("create components", () => { var ruleset = CreateRuleset(); @@ -38,32 +40,43 @@ namespace osu.Game.Tests.Visual.Gameplay var beatmap = CreateWorkingBeatmap(ruleset.RulesetInfo) .GetPlayableBeatmap(ruleset.RulesetInfo); + lastJudgementResult = new Bindable(); + gameplayState = new GameplayState(beatmap, ruleset); + gameplayState.LastJudgementResult.BindTo(lastJudgementResult); + scoreProcessor = new ScoreProcessor(); - } + + Child = dependencyContainer = new DependencyProvidingContainer + { + RelativeSizeAxes = Axes.Both, + CachedDependencies = new (Type, object)[] + { + (typeof(GameplayState), gameplayState), + (typeof(ScoreProcessor), scoreProcessor) + } + }; + + iteration = 0; + }); protected override Ruleset CreateRuleset() => new OsuRuleset(); - [SetUpSteps] - public void SetUpSteps() + private void createCounter() => AddStep("Create counter", () => { - AddStep("Create counter", () => + dependencyContainer.Child = counter = new PerformancePointsCounter { - iteration = 0; - - Child = counter = new PerformancePointsCounter - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Scale = new Vector2(5), - }; - }); - } + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(5), + }; + }); [Test] public void TestBasicCounting() { int previousValue = 0; + createCounter(); AddAssert("counter displaying zero", () => counter.Current.Value == 0); @@ -86,6 +99,17 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("counter non-zero", () => counter.Current.Value > 0); } + [Test] + public void TestCounterUpdatesWithJudgementsBeforeCreation() + { + AddRepeatStep("Add judgement", applyOneJudgement, 10); + + createCounter(); + + AddUntilStep("counter non-zero", () => counter.Current.Value > 0); + AddUntilStep("counter opaque", () => counter.Child.Alpha == 1); + } + private void applyOneJudgement() { var scoreInfo = gameplayState.Score.ScoreInfo; @@ -94,13 +118,14 @@ namespace osu.Game.Tests.Visual.Gameplay scoreInfo.Accuracy = 1; scoreInfo.Statistics[HitResult.Great] = iteration * 1000; - scoreProcessor.ApplyResult(new OsuJudgementResult(new HitObject + lastJudgementResult.Value = new OsuJudgementResult(new HitObject { StartTime = iteration * 10000, }, new OsuJudgement()) { Type = HitResult.Perfect, - }); + }; + scoreProcessor.ApplyResult(lastJudgementResult.Value); iteration++; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs index bf864f844c..cb5058779c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerScoreSubmission.cs @@ -215,7 +215,7 @@ namespace osu.Game.Tests.Visual.Gameplay createPlayerTest(false, r => { var beatmap = createTestBeatmap(r); - beatmap.BeatmapInfo.OnlineBeatmapID = null; + beatmap.BeatmapInfo.OnlineID = null; return beatmap; }); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 20e859dd2b..9fadbe02bd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("import beatmap", () => { importedBeatmap = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result; - importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineBeatmapID ?? -1; + importedBeatmapId = importedBeatmap.Beatmaps.First(b => b.RulesetID == 0).OnlineID ?? -1; }); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs index 0dc25cf378..be20799479 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneUnstableRateCounter.cs @@ -61,6 +61,16 @@ namespace osu.Game.Tests.Visual.Gameplay AddRepeatStep("Bring UR to 100", () => applyJudgement(-10, false), 10); } + [Test] + public void TestCounterReceivesJudgementsBeforeCreation() + { + AddRepeatStep("Set UR to 250", () => applyJudgement(25, true), 4); + + AddStep("Create Display", recreateDisplay); + + AddUntilStep("UR = 250", () => counter.Current.Value == 250.0); + } + private void recreateDisplay() { Clear(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs index fdd01446b9..b10856b704 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiSpectatorScreen.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { importedSet = ImportBeatmapTest.LoadOszIntoOsu(game, virtualTrack: true).Result; importedBeatmap = importedSet.Beatmaps.First(b => b.RulesetID == 0); - importedBeatmapId = importedBeatmap.OnlineBeatmapID ?? -1; + importedBeatmapId = importedBeatmap.OnlineID ?? -1; } [SetUp] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs index 902629765f..25200560e4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboard.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (int user in users) { - SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); + SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID ?? 0); multiplayerUsers.Add(OnlinePlayDependencies.Client.AddUser(new APIUser { Id = user }, true)); } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs index af4e696fce..16a342df8c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerGameplayLeaderboardTeams.cs @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.Multiplayer foreach (int user in users) { - SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineBeatmapID ?? 0); + SpectatorClient.StartPlay(user, Beatmap.Value.BeatmapInfo.OnlineID ?? 0); var roomUser = OnlinePlayDependencies.Client.AddUser(new APIUser { Id = user }, true); roomUser.MatchState = new TeamVersusUserState diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs index 0d6b428cce..aef1fb31d6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerMatchSongSelect.cs @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmaps.Add(new BeatmapInfo { Ruleset = rulesets.GetRuleset(i % 4), - OnlineBeatmapID = beatmapId, + OnlineID = beatmapId, Length = length, BPM = bpm, BaseDifficulty = new BeatmapDifficulty() @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Multiplayer manager.Import(new BeatmapSetInfo { - OnlineBeatmapSetID = 10, + OnlineID = 10, Hash = Guid.NewGuid().ToString().ComputeMD5Hash(), Metadata = new BeatmapMetadata { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs index 5e55759e01..05f9a94cf7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestScenePlaylistsSongSelect.cs @@ -54,7 +54,7 @@ namespace osu.Game.Tests.Visual.Multiplayer beatmaps.Add(new BeatmapInfo { Ruleset = new OsuRuleset().RulesetInfo, - OnlineBeatmapID = beatmapId, + OnlineID = beatmapId, DifficultyName = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Length = length, BPM = bpm, @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Multiplayer manager.Import(new BeatmapSetInfo { - OnlineBeatmapSetID = 10, + OnlineID = 10, Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), Metadata = new BeatmapMetadata { diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs index 4ec76e1e4b..4e1b3bb9bf 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePerformFromScreen.cs @@ -174,7 +174,7 @@ namespace osu.Game.Tests.Visual.Navigation { AddStep("import beatmap", () => ImportBeatmapTest.LoadQuickOszIntoOsu(Game).Wait()); PushAndConfirm(() => new TestPlaySongSelect()); - AddUntilStep("beatmap updated", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineBeatmapSetID == 241526); + AddUntilStep("beatmap updated", () => Game.Beatmap.Value.BeatmapSetInfo.OnlineID == 241526); } public class DialogBlockingScreen : OsuScreen diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index 5f5ebfccfb..ff976c7bf6 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -107,20 +107,20 @@ namespace osu.Game.Tests.Visual.Navigation imported = Game.BeatmapManager.Import(new BeatmapSetInfo { Hash = Guid.NewGuid().ToString(), - OnlineBeatmapSetID = i, + OnlineID = i, Metadata = metadata, Beatmaps = new List { new BeatmapInfo { - OnlineBeatmapID = i * 1024, + OnlineID = i * 1024, Metadata = metadata, BaseDifficulty = difficulty, Ruleset = ruleset ?? new OsuRuleset().RulesetInfo }, new BeatmapInfo { - OnlineBeatmapID = i * 2048, + OnlineID = i * 2048, Metadata = metadata, BaseDifficulty = difficulty, Ruleset = ruleset ?? new OsuRuleset().RulesetInfo @@ -145,11 +145,11 @@ namespace osu.Game.Tests.Visual.Navigation private void presentSecondDifficultyAndConfirm(Func getImport, int importedID) { - Predicate pred = b => b.OnlineBeatmapID == importedID * 2048; + Predicate pred = b => b.OnlineID == importedID * 2048; AddStep("present difficulty", () => Game.PresentBeatmap(getImport(), pred)); AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect); - AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineBeatmapID == importedID * 2048); + AddUntilStep("correct beatmap displayed", () => Game.Beatmap.Value.BeatmapInfo.OnlineID == importedID * 2048); AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Beatmaps.First().Ruleset.ID); } } diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index aca7ada535..72f160c9a9 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -39,20 +39,20 @@ namespace osu.Game.Tests.Visual.Navigation beatmap = Game.BeatmapManager.Import(new BeatmapSetInfo { Hash = Guid.NewGuid().ToString(), - OnlineBeatmapSetID = 1, + OnlineID = 1, Metadata = metadata, Beatmaps = new List { new BeatmapInfo { - OnlineBeatmapID = 1 * 1024, + OnlineID = 1 * 1024, Metadata = metadata, BaseDifficulty = difficulty, Ruleset = new OsuRuleset().RulesetInfo }, new BeatmapInfo { - OnlineBeatmapID = 1 * 2048, + OnlineID = 1 * 2048, Metadata = metadata, BaseDifficulty = difficulty, Ruleset = new OsuRuleset().RulesetInfo diff --git a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs index a865bbe950..22a91fa9a3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneDirectDownloadButton.cs @@ -44,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online AddAssert("button state not downloaded", () => downloadButton.DownloadState == DownloadState.NotDownloaded); AddStep("import soleily", () => beatmaps.Import(TestResources.GetQuickTestBeatmapForImport())); - AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineBeatmapSetID == 241526)); + AddUntilStep("wait for beatmap import", () => beatmaps.GetAllUsableBeatmapSets().Any(b => b.OnlineID == 241526)); AddUntilStep("button state downloaded", () => downloadButton.DownloadState == DownloadState.LocallyAvailable); createButtonWithBeatmap(createSoleily()); @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Online { AddStep("remove soleily", () => { - var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineBeatmapSetID == 241526); + var beatmap = beatmaps.QueryBeatmapSet(b => b.OnlineID == 241526); if (beatmap != null) beatmaps.Delete(beatmap); }); diff --git a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs index 366fa8a4af..7a5ee84eb4 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNowPlayingCommand.cs @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null) { - BeatmapInfo = { OnlineBeatmapID = hasOnlineId ? 1234 : (int?)null } + BeatmapInfo = { OnlineID = hasOnlineId ? 1234 : (int?)null } }); AddStep("Run command", () => Add(new NowPlayingCommand())); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs index cda7e95a46..c5287d4257 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomCreation.cs @@ -121,8 +121,8 @@ namespace osu.Game.Tests.Visual.Playlists beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1; // intentionally increment online IDs to clash with import below. - beatmap.BeatmapInfo.OnlineBeatmapID++; - beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID++; + beatmap.BeatmapInfo.OnlineID++; + beatmap.BeatmapInfo.BeatmapSet.OnlineID++; importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result.Value; }); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 8d5d0ba8c7..423c0a048c 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -337,7 +337,7 @@ namespace osu.Game.Tests.Visual.Ranking public UnrankedSoloResultsScreen(ScoreInfo score) : base(score, true) { - Score.BeatmapInfo.OnlineBeatmapID = 0; + Score.BeatmapInfo.OnlineID = 0; Score.BeatmapInfo.Status = BeatmapSetOnlineStatus.Pending; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 03079fdc5f..534442c8b6 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -838,7 +838,7 @@ namespace osu.Game.Tests.Visual.SongSelect return new BeatmapSetInfo { ID = id, - OnlineBeatmapSetID = id, + OnlineID = id, Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), Metadata = new BeatmapMetadata { @@ -867,7 +867,7 @@ namespace osu.Game.Tests.Visual.SongSelect yield return new BeatmapInfo { - OnlineBeatmapID = id++ * 10, + OnlineID = id++ * 10, DifficultyName = version, StarRating = diff, Ruleset = new OsuRuleset().RulesetInfo, @@ -884,7 +884,7 @@ namespace osu.Game.Tests.Visual.SongSelect var toReturn = new BeatmapSetInfo { ID = id, - OnlineBeatmapSetID = id, + OnlineID = id, Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), Metadata = new BeatmapMetadata { @@ -900,7 +900,7 @@ namespace osu.Game.Tests.Visual.SongSelect { toReturn.Beatmaps.Add(new BeatmapInfo { - OnlineBeatmapID = b * 10, + OnlineID = b * 10, Path = $"extra{b}.osu", DifficultyName = $"Extra {b}", Ruleset = rulesets.GetRuleset((b - 1) % 4), diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 855a59b5f5..a8a7a5d350 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -388,7 +388,7 @@ namespace osu.Game.Tests.Visual.SongSelect { leaderboard.BeatmapInfo = new BeatmapInfo { - OnlineBeatmapID = 1113057, + OnlineID = 1113057, Status = status, }; } diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 57f2d436c5..a0742b862b 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -180,11 +180,11 @@ namespace osu.Game.Tests.Visual.SongSelect var beatmapSet = new BeatmapSetInfo { Hash = Guid.NewGuid().ToString(), - OnlineBeatmapSetID = importID, + OnlineID = importID, Metadata = metadata, Beatmaps = difficultyRulesets.Select((ruleset, difficultyIndex) => new BeatmapInfo { - OnlineBeatmapID = importID * 1024 + difficultyIndex, + OnlineID = importID * 1024 + difficultyIndex, Metadata = metadata, BaseDifficulty = new BeatmapDifficulty(), Ruleset = ruleset, @@ -205,8 +205,8 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is Screens.Select.SongSelect); AddUntilStep("recommended beatmap displayed", () => { - int? expectedID = getImport().Beatmaps[expectedDiff - 1].OnlineBeatmapID; - return Game.Beatmap.Value.BeatmapInfo.OnlineBeatmapID == expectedID; + int? expectedID = getImport().Beatmaps[expectedDiff - 1].OnlineID; + return Game.Beatmap.Value.BeatmapInfo.OnlineID == expectedID; }); } } diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index a0d78fff58..ee5a61f21f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -507,13 +507,13 @@ namespace osu.Game.Tests.Visual.SongSelect i.IsFiltered || i.Item.BeatmapInfo.Ruleset.ID == targetRuleset || i.Item.BeatmapInfo.Ruleset.ID == 0); }); - AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineBeatmapID == target.OnlineBeatmapID); - AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID); + AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineID == target.OnlineID); + AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineID == target.OnlineID); AddStep("reset filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = string.Empty); - AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID); - AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID); + AddAssert("game still correct", () => Beatmap.Value?.BeatmapInfo.OnlineID == target.OnlineID); + AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmapInfo.OnlineID == target.OnlineID); } [Test] @@ -544,8 +544,8 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); - AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineBeatmapID == target.OnlineBeatmapID); - AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID); + AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineID == target.OnlineID); + AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineID == target.OnlineID); AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = "nononoo"); @@ -918,7 +918,7 @@ namespace osu.Game.Tests.Visual.SongSelect beatmaps.Add(new BeatmapInfo { Ruleset = getRuleset(), - OnlineBeatmapID = beatmapId, + OnlineID = beatmapId, DifficultyName = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Length = length, BPM = bpm, @@ -931,7 +931,7 @@ namespace osu.Game.Tests.Visual.SongSelect return new BeatmapSetInfo { - OnlineBeatmapSetID = setId, + OnlineID = setId, Hash = new MemoryStream(Encoding.UTF8.GetBytes(Guid.NewGuid().ToString())).ComputeMD5Hash(), Metadata = new BeatmapMetadata { diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 035f438b89..7231409dc5 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -290,7 +290,7 @@ namespace osu.Game.Beatmaps catch (BeatmapInvalidForRulesetException e) { if (rulesetInfo.Equals(beatmapInfo.Ruleset)) - Logger.Error(e, $"Failed to convert {beatmapInfo.OnlineBeatmapID} to the beatmap's default ruleset ({beatmapInfo.Ruleset})."); + Logger.Error(e, $"Failed to convert {beatmapInfo.OnlineID} to the beatmap's default ruleset ({beatmapInfo.Ruleset})."); return new StarDifficulty(); } diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 5bbd48f26d..602bb22c40 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -23,13 +23,14 @@ namespace osu.Game.Beatmaps public int BeatmapVersion; - private int? onlineBeatmapID; + private int? onlineID; [JsonProperty("id")] - public int? OnlineBeatmapID + [Column("OnlineBeatmapID")] + public int? OnlineID { - get => onlineBeatmapID; - set => onlineBeatmapID = value > 0 ? value : null; + get => onlineID; + set => onlineID = value > 0 ? value : null; } [JsonIgnore] @@ -176,7 +177,7 @@ namespace osu.Game.Beatmaps #region Implementation of IHasOnlineID - public int OnlineID => OnlineBeatmapID ?? -1; + int IHasOnlineID.OnlineID => OnlineID ?? -1; #endregion diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index ff4305dc91..3becbee0ba 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -94,17 +94,17 @@ namespace osu.Game.Beatmaps validateOnlineIds(beatmapSet); - bool hadOnlineBeatmapIDs = beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0); + bool hadOnlineIDs = beatmapSet.Beatmaps.Any(b => b.OnlineID > 0); if (OnlineLookupQueue != null) await OnlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken).ConfigureAwait(false); // ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID. - if (hadOnlineBeatmapIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0)) + if (hadOnlineIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineID > 0)) { - if (beatmapSet.OnlineBeatmapSetID != null) + if (beatmapSet.OnlineID != null) { - beatmapSet.OnlineBeatmapSetID = null; + beatmapSet.OnlineID = null; LogForModel(beatmapSet, "Disassociating beatmap set ID due to loss of all beatmap IDs"); } } @@ -116,27 +116,27 @@ namespace osu.Game.Beatmaps throw new InvalidOperationException($"Cannot import {nameof(BeatmapInfo)} with null {nameof(BeatmapInfo.BaseDifficulty)}."); // check if a set already exists with the same online id, delete if it does. - if (beatmapSet.OnlineBeatmapSetID != null) + if (beatmapSet.OnlineID != null) { - var existingSetWithSameOnlineID = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == beatmapSet.OnlineBeatmapSetID); + var existingSetWithSameOnlineID = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineID == beatmapSet.OnlineID); if (existingSetWithSameOnlineID != null) { Delete(existingSetWithSameOnlineID); // in order to avoid a unique key constraint, immediately remove the online ID from the previous set. - existingSetWithSameOnlineID.OnlineBeatmapSetID = null; + existingSetWithSameOnlineID.OnlineID = null; foreach (var b in existingSetWithSameOnlineID.Beatmaps) - b.OnlineBeatmapID = null; + b.OnlineID = null; - LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineBeatmapSetID}). It has been deleted."); + LogForModel(beatmapSet, $"Found existing beatmap set with same OnlineBeatmapSetID ({beatmapSet.OnlineID}). It has been deleted."); } } } private void validateOnlineIds(BeatmapSetInfo beatmapSet) { - var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList(); + var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineID.HasValue).Select(b => b.OnlineID).ToList(); // ensure all IDs are unique if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1)) @@ -147,7 +147,7 @@ namespace osu.Game.Beatmaps } // find any existing beatmaps in the database that have matching online ids - var existingBeatmaps = QueryBeatmaps(b => beatmapIds.Contains(b.OnlineBeatmapID)).ToList(); + var existingBeatmaps = QueryBeatmaps(b => beatmapIds.Contains(b.OnlineID)).ToList(); if (existingBeatmaps.Count > 0) { @@ -162,7 +162,7 @@ namespace osu.Game.Beatmaps } } - void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineBeatmapID = null); + void resetIds() => beatmapSet.Beatmaps.ForEach(b => b.OnlineID = null); } /// @@ -242,7 +242,7 @@ namespace osu.Game.Beatmaps if (!base.CanSkipImport(existing, import)) return false; - return existing.Beatmaps.Any(b => b.OnlineBeatmapID != null); + return existing.Beatmaps.Any(b => b.OnlineID != null); } protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import) @@ -250,11 +250,11 @@ namespace osu.Game.Beatmaps if (!base.CanReuseExisting(existing, import)) return false; - var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); - var importIds = import.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); + var existingIds = existing.Beatmaps.Select(b => b.OnlineID).OrderBy(i => i); + var importIds = import.Beatmaps.Select(b => b.OnlineID).OrderBy(i => i); // force re-import if we are not in a sane state. - return existing.OnlineBeatmapSetID == import.OnlineBeatmapSetID && existingIds.SequenceEqual(importIds); + return existing.OnlineID == import.OnlineID && existingIds.SequenceEqual(importIds); } /// @@ -349,7 +349,7 @@ namespace osu.Game.Beatmaps protected override bool CheckLocalAvailability(BeatmapSetInfo model, IQueryable items) => base.CheckLocalAvailability(model, items) - || (model.OnlineBeatmapSetID != null && items.Any(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID)); + || (model.OnlineID != null && items.Any(b => b.OnlineID == model.OnlineID)); protected override BeatmapSetInfo CreateModel(ArchiveReader reader) { @@ -368,7 +368,7 @@ namespace osu.Game.Beatmaps return new BeatmapSetInfo { - OnlineBeatmapSetID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID, + OnlineID = beatmap.BeatmapInfo.BeatmapSet?.OnlineID, Beatmaps = new List(), Metadata = beatmap.Metadata, DateAdded = DateTimeOffset.UtcNow diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs index b05ad9a1dd..7c80d8ad56 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs @@ -84,8 +84,8 @@ namespace osu.Game.Beatmaps { beatmapInfo.Status = res.Status; beatmapInfo.BeatmapSet.Status = res.BeatmapSet?.Status ?? BeatmapSetOnlineStatus.None; - beatmapInfo.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; - beatmapInfo.OnlineBeatmapID = res.OnlineID; + beatmapInfo.BeatmapSet.OnlineID = res.OnlineBeatmapSetID; + beatmapInfo.OnlineID = res.OnlineID; if (beatmapInfo.Metadata != null) beatmapInfo.Metadata.AuthorID = res.AuthorID; @@ -103,7 +103,7 @@ namespace osu.Game.Beatmaps void fail(Exception e) { - beatmapInfo.OnlineBeatmapID = null; + beatmapInfo.OnlineID = null; logForModel(set, $"Online retrieval failed for {beatmapInfo} ({e.Message})"); } } @@ -161,7 +161,7 @@ namespace osu.Game.Beatmaps if (string.IsNullOrEmpty(beatmapInfo.MD5Hash) && string.IsNullOrEmpty(beatmapInfo.Path) - && beatmapInfo.OnlineBeatmapID == null) + && beatmapInfo.OnlineID == null) return false; try @@ -172,10 +172,10 @@ namespace osu.Game.Beatmaps using (var cmd = db.CreateCommand()) { - cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineBeatmapID OR filename = @Path"; + cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineID OR filename = @Path"; cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmapInfo.MD5Hash)); - cmd.Parameters.Add(new SqliteParameter("@OnlineBeatmapID", beatmapInfo.OnlineBeatmapID ?? (object)DBNull.Value)); + cmd.Parameters.Add(new SqliteParameter("@OnlineID", beatmapInfo.OnlineID ?? (object)DBNull.Value)); cmd.Parameters.Add(new SqliteParameter("@Path", beatmapInfo.Path)); using (var reader = cmd.ExecuteReader()) @@ -186,8 +186,8 @@ namespace osu.Game.Beatmaps beatmapInfo.Status = status; beatmapInfo.BeatmapSet.Status = status; - beatmapInfo.BeatmapSet.OnlineBeatmapSetID = reader.GetInt32(0); - beatmapInfo.OnlineBeatmapID = reader.GetInt32(1); + beatmapInfo.BeatmapSet.OnlineID = reader.GetInt32(0); + beatmapInfo.OnlineID = reader.GetInt32(1); if (beatmapInfo.Metadata != null) beatmapInfo.Metadata.AuthorID = reader.GetInt32(3); diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 0c93c4b9db..f42e6876e3 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -16,12 +16,13 @@ namespace osu.Game.Beatmaps { public int ID { get; set; } - private int? onlineBeatmapSetID; + private int? onlineID; - public int? OnlineBeatmapSetID + [Column("OnlineBeatmapSetID")] + public int? OnlineID { - get => onlineBeatmapSetID; - set => onlineBeatmapSetID = value > 0 ? value : null; + get => onlineID; + set => onlineID = value > 0 ? value : null; } public DateTimeOffset DateAdded { get; set; } @@ -74,8 +75,8 @@ namespace osu.Game.Beatmaps if (ID != 0 && other.ID != 0) return ID == other.ID; - if (OnlineBeatmapSetID.HasValue && other.OnlineBeatmapSetID.HasValue) - return OnlineBeatmapSetID == other.OnlineBeatmapSetID; + if (OnlineID.HasValue && other.OnlineID.HasValue) + return OnlineID == other.OnlineID; if (!string.IsNullOrEmpty(Hash) && !string.IsNullOrEmpty(other.Hash)) return Hash == other.Hash; @@ -85,7 +86,7 @@ namespace osu.Game.Beatmaps #region Implementation of IHasOnlineID - public int OnlineID => OnlineBeatmapSetID ?? -1; + int IHasOnlineID.OnlineID => OnlineID ?? -1; #endregion diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index f0c19f80f3..65d050e608 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -263,11 +263,11 @@ namespace osu.Game.Beatmaps.Formats break; case @"BeatmapID": - beatmap.BeatmapInfo.OnlineBeatmapID = Parsing.ParseInt(pair.Value); + beatmap.BeatmapInfo.OnlineID = Parsing.ParseInt(pair.Value); break; case @"BeatmapSetID": - beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = Parsing.ParseInt(pair.Value) }; + beatmap.BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineID = Parsing.ParseInt(pair.Value) }; break; } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index bcb14526c7..49853418d6 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -133,8 +133,8 @@ namespace osu.Game.Beatmaps.Formats writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.DifficultyName}")); 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}")); + if (beatmap.BeatmapInfo.OnlineID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineID}")); + if (beatmap.BeatmapInfo.BeatmapSet?.OnlineID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineID}")); } private void handleDifficulty(TextWriter writer) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 320f108886..e8b6996869 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -264,7 +264,7 @@ namespace osu.Game.Database model = CreateModel(archive); if (model == null) - return Task.FromResult>(new EntityFrameworkLive(null)); + return Task.FromResult>(null); } catch (TaskCanceledException) { diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index 5d0a044578..d00cfb2035 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.cs @@ -7,6 +7,8 @@ using System.Threading.Tasks; using osu.Game.IO.Archives; using osu.Game.Overlays.Notifications; +#nullable enable + namespace osu.Game.Database { /// @@ -26,7 +28,7 @@ namespace osu.Game.Database /// Whether this is a low priority import. /// An optional cancellation token. /// The imported model, if successful. - Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default); + Task?> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default); /// /// Silently import an item from an . @@ -34,7 +36,7 @@ namespace osu.Game.Database /// The archive to be imported. /// Whether this is a low priority import. /// An optional cancellation token. - Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default); + Task?> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default); /// /// Silently import an item from a . @@ -43,7 +45,7 @@ namespace osu.Game.Database /// An optional archive to use for model population. /// Whether this is a low priority import. /// An optional cancellation token. - Task> Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default); + Task?> Import(TModel item, ArchiveReader? archive = null, bool lowPriority = false, CancellationToken cancellationToken = default); /// /// A user displayable name for the model type associated with this manager. diff --git a/osu.Game/Database/ImportTask.cs b/osu.Game/Database/ImportTask.cs index 1433a567a9..1fb5a42630 100644 --- a/osu.Game/Database/ImportTask.cs +++ b/osu.Game/Database/ImportTask.cs @@ -47,10 +47,30 @@ namespace osu.Game.Database /// public ArchiveReader GetReader() { - if (Stream != null) - return new ZipArchiveReader(Stream, Path); + return Stream != null + ? getReaderFrom(Stream) + : getReaderFrom(Path); + } - return getReaderFrom(Path); + /// + /// Creates an from a stream. + /// + /// A seekable stream containing the archive content. + /// A reader giving access to the archive's content. + private ArchiveReader getReaderFrom(Stream stream) + { + if (!(stream is MemoryStream memoryStream)) + { + // This isn't used in any current path. May need to reconsider for performance reasons (ie. if we don't expect the incoming stream to be copied out). + byte[] buffer = new byte[stream.Length]; + stream.Read(buffer, 0, (int)stream.Length); + memoryStream = new MemoryStream(buffer); + } + + if (ZipUtils.IsZipArchive(memoryStream)) + return new ZipArchiveReader(memoryStream, Path); + + return new LegacyByteArrayReader(memoryStream.ToArray(), Path); } /// diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index 1d8322aadd..d8d2cb8981 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -125,11 +125,11 @@ namespace osu.Game.Database { base.OnModelCreating(modelBuilder); - modelBuilder.Entity().HasIndex(b => b.OnlineBeatmapID).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.OnlineID).IsUnique(); modelBuilder.Entity().HasIndex(b => b.MD5Hash); modelBuilder.Entity().HasIndex(b => b.Hash); - modelBuilder.Entity().HasIndex(b => b.OnlineBeatmapSetID).IsUnique(); + modelBuilder.Entity().HasIndex(b => b.OnlineID).IsUnique(); modelBuilder.Entity().HasIndex(b => b.DeletePending); modelBuilder.Entity().HasIndex(b => b.Hash).IsUnique(); diff --git a/osu.Game/Extensions/ModelExtensions.cs b/osu.Game/Extensions/ModelExtensions.cs index 5c96add076..d8e0938d46 100644 --- a/osu.Game/Extensions/ModelExtensions.cs +++ b/osu.Game/Extensions/ModelExtensions.cs @@ -54,7 +54,7 @@ namespace osu.Game.Extensions } // fallback in case none of the above happens to match. - result ??= model.ToString(); + result ??= model?.ToString() ?? @"null"; return result; } } diff --git a/osu.Game/IO/Archives/LegacyByteArrayReader.cs b/osu.Game/IO/Archives/LegacyByteArrayReader.cs index 0c3620403f..ea8ff3bbe0 100644 --- a/osu.Game/IO/Archives/LegacyByteArrayReader.cs +++ b/osu.Game/IO/Archives/LegacyByteArrayReader.cs @@ -7,7 +7,7 @@ using System.IO; namespace osu.Game.IO.Archives { /// - /// Allows reading a single file from the provided stream. + /// Allows reading a single file from the provided byte array. /// public class LegacyByteArrayReader : ArchiveReader { diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 22446634c1..c71cb6a00a 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -76,6 +76,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft), new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight), new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode), + new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay), }; public IEnumerable InGameKeyBindings => new[] @@ -288,6 +289,9 @@ namespace osu.Game.Input.Bindings ToggleChatFocus, [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCycleGridDisplayMode))] - EditorCycleGridDisplayMode + EditorCycleGridDisplayMode, + + [LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTestGameplay))] + EditorTestGameplay } } diff --git a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs index 06f1b094bf..35a0c2ae74 100644 --- a/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs +++ b/osu.Game/Localisation/GlobalActionKeyBindingStrings.cs @@ -169,6 +169,11 @@ namespace osu.Game.Localisation /// public static LocalisableString EditorCycleGridDisplayMode => new TranslatableString(getKey(@"editor_cycle_grid_display_mode"), @"Cycle grid display mode"); + /// + /// "Test gameplay" + /// + public static LocalisableString EditorTestGameplay => new TranslatableString(getKey(@"editor_test_gameplay"), @"Test gameplay"); + /// /// "Hold for HUD" /// diff --git a/osu.Game/Online/BeatmapDownloadTracker.cs b/osu.Game/Online/BeatmapDownloadTracker.cs index 592dc60d20..77a8fca1e4 100644 --- a/osu.Game/Online/BeatmapDownloadTracker.cs +++ b/osu.Game/Online/BeatmapDownloadTracker.cs @@ -29,7 +29,7 @@ namespace osu.Game.Online return; // Used to interact with manager classes that don't support interface types. Will eventually be replaced. - var beatmapSetInfo = new BeatmapSetInfo { OnlineBeatmapSetID = TrackedItem.OnlineID }; + var beatmapSetInfo = new BeatmapSetInfo { OnlineID = TrackedItem.OnlineID }; if (Manager.IsAvailableLocally(beatmapSetInfo)) UpdateState(DownloadState.LocallyAvailable); diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs index adb3d88df6..34b12c23e6 100644 --- a/osu.Game/Online/Chat/NowPlayingCommand.cs +++ b/osu.Game/Online/Chat/NowPlayingCommand.cs @@ -57,7 +57,7 @@ namespace osu.Game.Online.Chat break; } - string beatmapString = beatmapInfo.OnlineBeatmapID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmapInfo.OnlineBeatmapID} {beatmapInfo}]" : beatmapInfo.ToString(); + string beatmapString = beatmapInfo.OnlineID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmapInfo.OnlineID} {beatmapInfo}]" : beatmapInfo.ToString(); channelManager.PostMessage($"is {verb} {beatmapString}", true, target); Expire(); diff --git a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs index eb9ea608f7..aa0e37363b 100644 --- a/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game/Online/Rooms/OnlinePlayBeatmapAvailabilityTracker.cs @@ -113,7 +113,7 @@ namespace osu.Game.Online.Rooms int onlineId = SelectedItem.Value.Beatmap.Value.OnlineID; string checksum = SelectedItem.Value.Beatmap.Value.MD5Hash; - return beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending) != null; + return beatmapManager.QueryBeatmap(b => b.OnlineID == onlineId && b.MD5Hash == checksum && !b.BeatmapSet.DeletePending) != null; } } } diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index f9366674d8..6b95d288c5 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -144,7 +144,7 @@ namespace osu.Game.Online.Spectator IsPlaying = true; // transfer state at point of beginning play - currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineBeatmapID; + currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineID; currentState.RulesetID = score.ScoreInfo.RulesetID; currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index bf757a153f..095add399c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -443,7 +443,7 @@ namespace osu.Game BeatmapSetInfo databasedSet = null; if (beatmap.OnlineID > 0) - databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineID); + databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineID == beatmap.OnlineID); if (beatmap is BeatmapSetInfo localBeatmap) databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash); diff --git a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs index 5ed49cf384..8c7846783d 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs @@ -80,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.OnlineID; + findPredicate = b => b.OnlineID == SelectedBeatmap.Value.OnlineID; game?.PresentBeatmap(beatmapSet, findPredicate); break; diff --git a/osu.Game/Rulesets/Scoring/HitEventExtensions.cs b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs new file mode 100644 index 0000000000..f645b12483 --- /dev/null +++ b/osu.Game/Rulesets/Scoring/HitEventExtensions.cs @@ -0,0 +1,37 @@ +// 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; + +namespace osu.Game.Rulesets.Scoring +{ + public static class HitEventExtensions + { + /// + /// Calculates the "unstable rate" for a sequence of s. + /// + /// + /// A non-null value if unstable rate could be calculated, + /// and if unstable rate cannot be calculated due to being empty. + /// + public static double? CalculateUnstableRate(this IEnumerable hitEvents) + { + double[] timeOffsets = hitEvents.Where(affectsUnstableRate).Select(ev => ev.TimeOffset).ToArray(); + return 10 * standardDeviation(timeOffsets); + } + + private static bool affectsUnstableRate(HitEvent e) => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit(); + + private static double? standardDeviation(double[] timeOffsets) + { + if (timeOffsets.Length == 0) + return null; + + double mean = timeOffsets.Average(); + double squares = timeOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum(); + return Math.Sqrt(squares / timeOffsets.Length); + } + } +} diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index c1234f8fb3..fc24972b8e 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -53,6 +53,12 @@ namespace osu.Game.Rulesets.Scoring /// public readonly Bindable Mode = new Bindable(); + /// + /// The s collected during gameplay thus far. + /// Intended for use with various statistics displays. + /// + public IReadOnlyList HitEvents => hitEvents; + /// /// The default portion of awarded for hitting s accurately. Defaults to 30%. /// diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/TestGameplayButton.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/TestGameplayButton.cs new file mode 100644 index 0000000000..0d7a4ad057 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/TestGameplayButton.cs @@ -0,0 +1,34 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary +{ + public class TestGameplayButton : OsuButton + { + protected override SpriteText CreateText() => new OsuSpriteText + { + Depth = -1, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Font = OsuFont.TorusAlternate.With(weight: FontWeight.Light, size: 24), + Shadow = false + }; + + [BackgroundDependencyLoader] + private void load(OsuColour colours, OverlayColourProvider colourProvider) + { + BackgroundColour = colours.Orange1; + SpriteText.Colour = colourProvider.Background6; + + Text = "Test!"; + } + } +} diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 0ca7038580..738f607cc8 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -36,6 +36,7 @@ using osu.Game.Screens.Edit.Timing; using osu.Game.Screens.Edit.Verify; using osu.Game.Screens.Play; using osu.Game.Users; +using osuTK; using osuTK.Graphics; using osuTK.Input; @@ -90,6 +91,8 @@ namespace osu.Game.Screens.Edit private DependencyContainer dependencies; + private TestGameplayButton testGameplayButton; + private bool isNewBeatmap; protected override UserActivity InitialActivity => new UserActivity.Editing(Beatmap.Value.BeatmapInfo); @@ -106,6 +109,9 @@ namespace osu.Game.Screens.Edit [Cached] public readonly EditorClipboard Clipboard = new EditorClipboard(); + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + public Editor(EditorLoader loader = null) { this.loader = loader; @@ -262,7 +268,8 @@ namespace osu.Game.Screens.Edit { new Dimension(GridSizeMode.Absolute, 220), new Dimension(), - new Dimension(GridSizeMode.Absolute, 220) + new Dimension(GridSizeMode.Absolute, 220), + new Dimension(GridSizeMode.Absolute, 120), }, Content = new[] { @@ -283,6 +290,13 @@ namespace osu.Game.Screens.Edit RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = 10 }, Child = new PlaybackControl { RelativeSizeAxes = Axes.Both }, + }, + testGameplayButton = new TestGameplayButton + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 10 }, + Size = new Vector2(1), + Action = testGameplay } }, } @@ -456,6 +470,10 @@ namespace osu.Game.Screens.Edit menuBar.Mode.Value = EditorScreenMode.Verify; return true; + case GlobalAction.EditorTestGameplay: + testGameplayButton.TriggerClick(); + return true; + default: return false; } @@ -510,7 +528,21 @@ namespace osu.Game.Screens.Edit ApplyToBackground(b => b.FadeColour(Color4.White, 500)); resetTrack(); - // To update the game-wide beatmap with any changes, perform a re-fetch on exit. + refetchBeatmap(); + + return base.OnExiting(next); + } + + public override void OnSuspending(IScreen next) + { + refetchBeatmap(); + + base.OnSuspending(next); + } + + private void refetchBeatmap() + { + // To update the game-wide beatmap with any changes, perform a re-fetch on exit/suspend. // This is required as the editor makes its local changes via EditorBeatmap // (which are not propagated outwards to a potentially cached WorkingBeatmap). var refetchedBeatmap = beatmapManager.GetWorkingBeatmap(Beatmap.Value.BeatmapInfo); @@ -520,8 +552,6 @@ namespace osu.Game.Screens.Edit Logger.Log("Editor providing re-fetched beatmap post edit session"); Beatmap.Value = refetchedBeatmap; } - - return base.OnExiting(next); } private void confirmExitWithSave() @@ -752,6 +782,24 @@ namespace osu.Game.Screens.Edit loader?.CancelPendingDifficultySwitch(); } + private void testGameplay() + { + if (HasUnsavedChanges) + { + dialogOverlay.Push(new SaveBeforeGameplayTestDialog(() => + { + Save(); + pushEditorPlayer(); + })); + } + else + { + pushEditorPlayer(); + } + + void pushEditorPlayer() => this.Push(new PlayerLoader(() => new EditorPlayer())); + } + public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime); diff --git a/osu.Game/Screens/Edit/EditorPlayer.cs b/osu.Game/Screens/Edit/EditorPlayer.cs new file mode 100644 index 0000000000..b2fab3fefc --- /dev/null +++ b/osu.Game/Screens/Edit/EditorPlayer.cs @@ -0,0 +1,44 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Screens; +using osu.Game.Overlays; +using osu.Game.Screens.Play; + +namespace osu.Game.Screens.Edit +{ + public class EditorPlayer : Player + { + public EditorPlayer() + : base(new PlayerConfiguration { ShowResults = false }) + { + } + + [Resolved] + private MusicController musicController { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + ScoreProcessor.HasCompleted.BindValueChanged(completed => + { + if (completed.NewValue) + Scheduler.AddDelayed(this.Exit, RESULTS_DISPLAY_DELAY); + }); + } + + protected override void PrepareReplay() + { + // don't record replays. + } + + protected override bool CheckModsAllowFailure() => false; // never fail. + + public override bool OnExiting(IScreen next) + { + musicController.Stop(); + return base.OnExiting(next); + } + } +} diff --git a/osu.Game/Screens/Edit/EditorRoundedScreen.cs b/osu.Game/Screens/Edit/EditorRoundedScreen.cs index 508663224d..7f7b3abc2a 100644 --- a/osu.Game/Screens/Edit/EditorRoundedScreen.cs +++ b/osu.Game/Screens/Edit/EditorRoundedScreen.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; +using osu.Game.Overlays; namespace osu.Game.Screens.Edit { @@ -26,7 +27,7 @@ namespace osu.Game.Screens.Edit } [BackgroundDependencyLoader] - private void load() + private void load(OverlayColourProvider colourProvider) { base.Content.Add(new Container { @@ -41,7 +42,7 @@ namespace osu.Game.Screens.Edit { new Box { - Colour = ColourProvider.Background3, + Colour = colourProvider.Background3, RelativeSizeAxes = Axes.Both, }, roundedContent = new Container diff --git a/osu.Game/Screens/Edit/EditorScreen.cs b/osu.Game/Screens/Edit/EditorScreen.cs index 516d7a23e0..2837cdcd9a 100644 --- a/osu.Game/Screens/Edit/EditorScreen.cs +++ b/osu.Game/Screens/Edit/EditorScreen.cs @@ -6,7 +6,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Game.Overlays; namespace osu.Game.Screens.Edit { @@ -18,9 +17,6 @@ namespace osu.Game.Screens.Edit [Resolved] protected EditorBeatmap EditorBeatmap { get; private set; } - [Cached] - protected readonly OverlayColourProvider ColourProvider; - protected override Container Content => content; private readonly Container content; @@ -34,8 +30,6 @@ namespace osu.Game.Screens.Edit Origin = Anchor.Centre; RelativeSizeAxes = Axes.Both; - ColourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); - InternalChild = content = new PopoverContainer { RelativeSizeAxes = Axes.Both }; } diff --git a/osu.Game/Screens/Edit/SaveBeforeGameplayTestDialog.cs b/osu.Game/Screens/Edit/SaveBeforeGameplayTestDialog.cs new file mode 100644 index 0000000000..7c03664c66 --- /dev/null +++ b/osu.Game/Screens/Edit/SaveBeforeGameplayTestDialog.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics.Sprites; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.Edit +{ + public class SaveBeforeGameplayTestDialog : PopupDialog + { + public SaveBeforeGameplayTestDialog(Action saveAndPreview) + { + HeaderText = "The beatmap will be saved in order to test it."; + + Icon = FontAwesome.Regular.Save; + + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = "Sounds good, let's go!", + Action = saveAndPreview + }, + new PopupDialogCancelButton + { + Text = "Oops, continue editing", + }, + }; + } + } +} diff --git a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs index d6016ec4b9..dc928d90e9 100644 --- a/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Match/RoomSubScreen.cs @@ -366,7 +366,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.OnlineID); + var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineID == 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 bdb5ff9bb2..22537c3ce0 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.OnlineID) + if (Beatmap.Value.BeatmapInfo.OnlineID != 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/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index ef289c2a20..b4359fe518 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -92,6 +92,9 @@ namespace osu.Game.Screens.Play.HUD scoreProcessor.NewJudgement += onJudgementChanged; scoreProcessor.JudgementReverted += onJudgementChanged; } + + if (gameplayState?.LastJudgementResult.Value != null) + onJudgementChanged(gameplayState.LastJudgementResult.Value); } private bool isValid; @@ -155,7 +158,10 @@ namespace osu.Game.Screens.Play.HUD base.Dispose(isDisposing); if (scoreProcessor != null) + { scoreProcessor.NewJudgement -= onJudgementChanged; + scoreProcessor.JudgementReverted -= onJudgementChanged; + } loadCancellationSource?.Cancel(); } @@ -184,7 +190,7 @@ namespace osu.Game.Screens.Play.HUD { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Font = OsuFont.Numeric.With(size: 16) + Font = OsuFont.Numeric.With(size: 16, fixedWidth: true) }, new OsuSpriteText { diff --git a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs index 89ae4f8a21..235f0f01fd 100644 --- a/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs +++ b/osu.Game/Screens/Play/HUD/UnstableRateCounter.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -17,6 +15,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Screens.Play.HUD { @@ -29,8 +28,6 @@ namespace osu.Game.Screens.Play.HUD private const float alpha_when_invalid = 0.3f; private readonly Bindable valid = new Bindable(); - private readonly List hitOffsets = new List(); - [Resolved] private ScoreProcessor scoreProcessor { get; set; } @@ -54,41 +51,20 @@ namespace osu.Game.Screens.Play.HUD { base.LoadComplete(); - scoreProcessor.NewJudgement += onJudgementAdded; - scoreProcessor.JudgementReverted += onJudgementReverted; - } - - private void onJudgementAdded(JudgementResult judgement) - { - if (!changesUnstableRate(judgement)) return; - - hitOffsets.Add(judgement.TimeOffset); + scoreProcessor.NewJudgement += updateDisplay; + scoreProcessor.JudgementReverted += updateDisplay; updateDisplay(); } - private void onJudgementReverted(JudgementResult judgement) - { - if (judgement.FailedAtJudgement || !changesUnstableRate(judgement)) return; - - hitOffsets.RemoveAt(hitOffsets.Count - 1); - updateDisplay(); - } + private void updateDisplay(JudgementResult _) => Scheduler.AddOnce(updateDisplay); private void updateDisplay() { - // At Count = 0, we get NaN, While we are allowing count = 1, it will be 0 since average = offset. - if (hitOffsets.Count > 0) - { - double mean = hitOffsets.Average(); - double squares = hitOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum(); - Current.Value = (int)(Math.Sqrt(squares / hitOffsets.Count) * 10); - valid.Value = true; - } - else - { - Current.Value = 0; - valid.Value = false; - } + double? unstableRate = scoreProcessor.HitEvents.CalculateUnstableRate(); + + valid.Value = unstableRate != null; + if (unstableRate != null) + Current.Value = (int)Math.Round(unstableRate.Value); } protected override IHasText CreateText() => new TextComponent @@ -102,8 +78,8 @@ namespace osu.Game.Screens.Play.HUD if (scoreProcessor == null) return; - scoreProcessor.NewJudgement -= onJudgementAdded; - scoreProcessor.JudgementReverted -= onJudgementReverted; + scoreProcessor.NewJudgement -= updateDisplay; + scoreProcessor.JudgementReverted -= updateDisplay; } private class TextComponent : CompositeDrawable, IHasText @@ -123,6 +99,7 @@ namespace osu.Game.Screens.Play.HUD InternalChild = new FillFlowContainer { AutoSizeAxes = Axes.Both, + Spacing = new Vector2(2), Children = new Drawable[] { text = new OsuSpriteText @@ -136,8 +113,8 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Font = OsuFont.Numeric.With(size: 8, fixedWidth: true), - Text = "UR", - Padding = new MarginPadding { Bottom = 1.5f }, + Text = @"UR", + Padding = new MarginPadding { Bottom = 1.5f }, // align baseline better } } }; diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index d852ac2940..ba34df0e59 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Threading.Tasks; using JetBrains.Annotations; +using ManagedBass.Fx; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Bindables; @@ -67,6 +68,7 @@ namespace osu.Game.Screens.Play private readonly BindableDouble volumeAdjustment = new BindableDouble(1); private AudioFilter lowPassFilter; + private AudioFilter highPassFilter; protected bool BackgroundBrightnessReduction { @@ -168,7 +170,8 @@ namespace osu.Game.Screens.Play }, idleTracker = new IdleTracker(750), }), - lowPassFilter = new AudioFilter(audio.TrackMixer) + lowPassFilter = new AudioFilter(audio.TrackMixer), + highPassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass) }; if (Beatmap.Value.BeatmapInfo.EpilepsyWarning) @@ -239,6 +242,7 @@ namespace osu.Game.Screens.Play Beatmap.Value.Track.Stop(); Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment); lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF); + highPassFilter.CutoffTo(0); } public override bool OnExiting(IScreen next) @@ -266,9 +270,9 @@ namespace osu.Game.Screens.Play const double duration = 300; - if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.In); + if (!resuming) logo.MoveTo(new Vector2(0.5f), duration, Easing.OutQuint); - logo.ScaleTo(new Vector2(0.15f), duration, Easing.In); + logo.ScaleTo(new Vector2(0.15f), duration, Easing.OutQuint); logo.FadeIn(350); Scheduler.AddDelayed(() => @@ -352,6 +356,7 @@ namespace osu.Game.Screens.Play content.FadeInFromZero(400); content.ScaleTo(1, 650, Easing.OutQuint).Then().Schedule(prepareNewPlayer); lowPassFilter.CutoffTo(1000, 650, Easing.OutQuint); + highPassFilter.CutoffTo(300).Then().CutoffTo(0, 1250); // 1250 is to line up with the appearance of MetadataInfo (750 delay + 500 fade-in) ApplyToBackground(b => b?.FadeColour(Color4.White, 800, Easing.OutQuint)); } @@ -364,6 +369,7 @@ namespace osu.Game.Screens.Play content.ScaleTo(0.7f, content_out_duration * 2, Easing.OutQuint); content.FadeOut(content_out_duration, Easing.OutQuint); lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, content_out_duration); + highPassFilter.CutoffTo(0, content_out_duration); } private void pushWhenLoaded() diff --git a/osu.Game/Screens/Play/SkipOverlay.cs b/osu.Game/Screens/Play/SkipOverlay.cs index b04fcba0c6..c35548c6b4 100644 --- a/osu.Game/Screens/Play/SkipOverlay.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -270,7 +270,7 @@ namespace osu.Game.Screens.Play colourNormal = colours.Yellow; colourHover = colours.YellowDark; - sampleConfirm = audio.Samples.Get(@"SongSelect/confirm-selection"); + sampleConfirm = audio.Samples.Get(@"UI/submit-select"); Children = new Drawable[] { diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index 675cb71311..6cea75af0a 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Play protected override APIRequest CreateTokenRequest() { - if (!(Beatmap.Value.BeatmapInfo.OnlineBeatmapID is int beatmapId)) + if (!(Beatmap.Value.BeatmapInfo.OnlineID is int beatmapId)) return null; if (!(Ruleset.Value.ID is int rulesetId) || Ruleset.Value.ID > ILegacyRuleset.MAX_LEGACY_RULESET_ID) @@ -40,9 +40,9 @@ namespace osu.Game.Screens.Play { var beatmap = score.ScoreInfo.BeatmapInfo; - Debug.Assert(beatmap.OnlineBeatmapID != null); + Debug.Assert(beatmap.OnlineID != null); - int beatmapId = beatmap.OnlineBeatmapID.Value; + int beatmapId = beatmap.OnlineID.Value; return new SubmitSoloScoreRequest(beatmapId, token, score.ScoreInfo); } diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index 1dcc191c0f..c65d4af2ae 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -241,7 +241,7 @@ namespace osu.Game.Screens.Play if (!automaticDownload.Current.Value) return; - if (beatmaps.IsAvailableLocally(new BeatmapSetInfo { OnlineBeatmapSetID = beatmapSet.OnlineID })) + if (beatmaps.IsAvailableLocally(new BeatmapSetInfo { OnlineID = beatmapSet.OnlineID })) return; beatmaps.Download(beatmapSet); diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 4f4dfa4909..425e6f983b 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Ranking protected override APIRequest FetchScores(Action> scoresCallback) { - if (Score.BeatmapInfo.OnlineBeatmapID == null || Score.BeatmapInfo.Status <= BeatmapSetOnlineStatus.Pending) + if (Score.BeatmapInfo.OnlineID == null || Score.BeatmapInfo.Status <= BeatmapSetOnlineStatus.Pending) return null; getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset); diff --git a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs index cd2b292547..0d23490f40 100644 --- a/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs +++ b/osu.Game/Screens/Ranking/Statistics/UnstableRate.cs @@ -1,9 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; -using System.Linq; using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Ranking.Statistics @@ -11,7 +9,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// /// Displays the unstable rate statistic for a given play. /// - public class UnstableRate : SimpleStatisticItem + public class UnstableRate : SimpleStatisticItem { /// /// Creates and computes an statistic. @@ -20,21 +18,9 @@ namespace osu.Game.Screens.Ranking.Statistics public UnstableRate(IEnumerable hitEvents) : base("Unstable Rate") { - double[] timeOffsets = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows) && e.Result.IsHit()) - .Select(ev => ev.TimeOffset).ToArray(); - Value = 10 * standardDeviation(timeOffsets); + Value = hitEvents.CalculateUnstableRate(); } - private static double standardDeviation(double[] timeOffsets) - { - if (timeOffsets.Length == 0) - return double.NaN; - - double mean = timeOffsets.Average(); - double squares = timeOffsets.Select(offset => Math.Pow(offset - mean, 2)).Sum(); - return Math.Sqrt(squares / timeOffsets.Length); - } - - protected override string DisplayValue(double value) => double.IsNaN(value) ? "(not available)" : value.ToString("N2"); + protected override string DisplayValue(double? value) => value == null ? "(not available)" : value.Value.ToString(@"N2"); } } diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 6f215b9287..7543c89f17 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -192,7 +192,7 @@ namespace osu.Game.Screens.Select return; } - // for now, let's early abort if an OnlineBeatmapID is not present (should have been populated at import time). + // for now, let's early abort if an OnlineID is not present (should have been populated at import time). if (BeatmapInfo == null || BeatmapInfo.OnlineID <= 0 || api.State.Value == APIState.Offline) { updateMetrics(); diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 1334784613..cc2db6ed31 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -66,8 +66,8 @@ namespace osu.Game.Screens.Select.Carousel // this should be done after text matching so we can prioritise matching numbers in metadata. if (!match && criteria.SearchNumber.HasValue) { - match = (BeatmapInfo.OnlineBeatmapID == criteria.SearchNumber.Value) || - (BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID == criteria.SearchNumber.Value); + match = (BeatmapInfo.OnlineID == criteria.SearchNumber.Value) || + (BeatmapInfo.BeatmapSet?.OnlineID == criteria.SearchNumber.Value); } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs index 40ca3e0764..ed3aea3445 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselHeader.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselHeader.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Select.Carousel RelativeSizeAxes = Axes.Both, }; - sampleHover = audio.Samples.Get("SongSelect/song-ping"); + sampleHover = audio.Samples.Get("UI/default-hover"); } public bool InsetForBorder diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index f30bec5d2b..d0f9d835fd 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -238,8 +238,8 @@ namespace osu.Game.Screens.Select.Carousel if (editRequested != null) items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested(beatmapInfo))); - if (beatmapInfo.OnlineBeatmapID.HasValue && beatmapOverlay != null) - items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmapInfo.OnlineBeatmapID.Value))); + if (beatmapInfo.OnlineID.HasValue && beatmapOverlay != null) + items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmapInfo.OnlineID.Value))); if (collectionManager != null) { diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs index 173b804d90..619b1e0fd0 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmapSet.cs @@ -214,8 +214,8 @@ namespace osu.Game.Screens.Select.Carousel if (Item.State.Value == CarouselItemState.NotSelected) items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => Item.State.Value = CarouselItemState.Selected)); - if (beatmapSet.OnlineBeatmapSetID != null && viewDetails != null) - items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineBeatmapSetID.Value))); + if (beatmapSet.OnlineID != null && viewDetails != null) + items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => viewDetails(beatmapSet.OnlineID.Value))); if (collectionManager != null) { diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 9205c6c0d2..9c8ccee99b 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -152,7 +152,7 @@ namespace osu.Game.Screens.Select.Leaderboards return null; } - if (BeatmapInfo.OnlineBeatmapID == null || BeatmapInfo?.Status <= BeatmapSetOnlineStatus.Pending) + if (BeatmapInfo.OnlineID == null || BeatmapInfo?.Status <= BeatmapSetOnlineStatus.Pending) { PlaceholderState = PlaceholderState.Unavailable; return null; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index a2dea355ac..4da15ee53c 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -105,6 +105,8 @@ namespace osu.Game.Screens.Select private readonly Bindable decoupledRuleset = new Bindable(); + private double audioFeedbackLastPlaybackTime; + [Resolved] private MusicController music { get; set; } @@ -435,6 +437,7 @@ namespace osu.Game.Screens.Select } // We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds. + private BeatmapInfo beatmapInfoPrevious; private BeatmapInfo beatmapInfoNoDebounce; private RulesetInfo rulesetNoDebounce; @@ -477,6 +480,21 @@ namespace osu.Game.Screens.Select else selectionChangedDebounce = Scheduler.AddDelayed(run, 200); + if (beatmap != beatmapInfoPrevious) + { + if (beatmap != null && beatmapInfoPrevious != null && Time.Current - audioFeedbackLastPlaybackTime >= 50) + { + if (beatmap.BeatmapSetInfoID == beatmapInfoPrevious.BeatmapSetInfoID) + sampleChangeDifficulty.Play(); + else + sampleChangeBeatmap.Play(); + + audioFeedbackLastPlaybackTime = Time.Current; + } + + beatmapInfoPrevious = beatmap; + } + void run() { // clear pending task immediately to track any potential nested debounce operation. @@ -508,18 +526,7 @@ namespace osu.Game.Screens.Select if (!EqualityComparer.Default.Equals(beatmap, Beatmap.Value.BeatmapInfo)) { Logger.Log($"beatmap changed from \"{Beatmap.Value.BeatmapInfo}\" to \"{beatmap}\""); - - int? lastSetID = Beatmap.Value?.BeatmapInfo.BeatmapSetInfoID; - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap); - - if (beatmap != null) - { - if (beatmap.BeatmapSetInfoID == lastSetID) - sampleChangeDifficulty.Play(); - else - sampleChangeBeatmap.Play(); - } } if (this.IsCurrentScreen()) diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 3bcfcb2a0b..1f07042ede 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -84,7 +84,7 @@ namespace osu.Game.Screens.Spectate if (!playingUserStates.TryGetValue(userId, out var userState)) continue; - if (beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == userState.BeatmapID)) + if (beatmapSet.Beatmaps.Any(b => b.OnlineID == userState.BeatmapID)) updateGameplayState(userId); } } @@ -150,7 +150,7 @@ namespace osu.Game.Screens.Spectate if (resolvedRuleset == null) return; - var resolvedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == spectatorState.BeatmapID); + var resolvedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineID == spectatorState.BeatmapID); if (resolvedBeatmap == null) return; diff --git a/osu.Game/Stores/BeatmapImporter.cs b/osu.Game/Stores/BeatmapImporter.cs index 01bf925547..a9605f1fc3 100644 --- a/osu.Game/Stores/BeatmapImporter.cs +++ b/osu.Game/Stores/BeatmapImporter.cs @@ -63,7 +63,7 @@ namespace osu.Game.Stores validateOnlineIds(beatmapSet, realm); - bool hadOnlineBeatmapIDs = beatmapSet.Beatmaps.Any(b => b.OnlineID > 0); + bool hadOnlineIDs = beatmapSet.Beatmaps.Any(b => b.OnlineID > 0); if (onlineLookupQueue != null) { @@ -72,7 +72,7 @@ namespace osu.Game.Stores } // ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID. - if (hadOnlineBeatmapIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineID > 0)) + if (hadOnlineIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineID > 0)) { if (beatmapSet.OnlineID > 0) { @@ -182,7 +182,7 @@ namespace osu.Game.Stores return new RealmBeatmapSet { - OnlineID = beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID ?? -1, + OnlineID = beatmap.BeatmapInfo.BeatmapSet?.OnlineID ?? -1, // Metadata = beatmap.Metadata, DateAdded = DateTimeOffset.UtcNow }; @@ -254,7 +254,7 @@ namespace osu.Game.Stores { Hash = hash, DifficultyName = decodedInfo.DifficultyName, - OnlineID = decodedInfo.OnlineBeatmapID ?? -1, + OnlineID = decodedInfo.OnlineID ?? -1, AudioLeadIn = decodedInfo.AudioLeadIn, StackLeniency = decodedInfo.StackLeniency, SpecialStyle = decodedInfo.SpecialStyle, diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index b9eda5c06e..32a9b0b9d1 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -35,10 +35,10 @@ namespace osu.Game.Tests.Beatmaps BeatmapInfo.RulesetID = ruleset.ID ?? 0; BeatmapInfo.BeatmapSet.Metadata = BeatmapInfo.Metadata; BeatmapInfo.BeatmapSet.Beatmaps = new List { BeatmapInfo }; - BeatmapInfo.BeatmapSet.OnlineBeatmapSetID = Interlocked.Increment(ref onlineSetID); + BeatmapInfo.BeatmapSet.OnlineID = Interlocked.Increment(ref onlineSetID); BeatmapInfo.Length = 75000; BeatmapInfo.OnlineInfo = new APIBeatmap(); - BeatmapInfo.OnlineBeatmapID = Interlocked.Increment(ref onlineBeatmapID); + BeatmapInfo.OnlineID = Interlocked.Increment(ref onlineBeatmapID); } protected virtual Beatmap CreateBeatmap() => createTestBeatmap(); diff --git a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs index 036afb26b4..8f7e6d7f5c 100644 --- a/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs +++ b/osu.Game/Tests/Visual/Multiplayer/TestMultiplayerClient.cs @@ -311,7 +311,7 @@ namespace osu.Game.Tests.Visual.Multiplayer var apiRoom = roomManager.ServerSideRooms.Single(r => r.RoomID.Value == Room.RoomID); IBeatmapSetInfo? set = apiRoom.Playlist.FirstOrDefault(p => p.BeatmapID == beatmapId)?.Beatmap.Value.BeatmapSet - ?? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId)?.BeatmapSet; + ?? beatmaps.QueryBeatmap(b => b.OnlineID == beatmapId)?.BeatmapSet; if (set == null) throw new InvalidOperationException("Beatmap not found."); diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index a4cef02395..fc7bc324ca 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -203,7 +203,7 @@ namespace osu.Game.Tests.Visual return new APIBeatmapSet { - OnlineID = beatmap.BeatmapSet.OnlineID, + OnlineID = ((IBeatmapSetInfo)beatmap.BeatmapSet).OnlineID, Status = BeatmapSetOnlineStatus.Ranked, Covers = new BeatmapSetOnlineCovers { @@ -222,8 +222,8 @@ namespace osu.Game.Tests.Visual { new APIBeatmap { - OnlineID = beatmap.OnlineID, - OnlineBeatmapSetID = beatmap.BeatmapSet.OnlineID, + OnlineID = ((IBeatmapInfo)beatmap).OnlineID, + OnlineBeatmapSetID = ((IBeatmapSetInfo)beatmap.BeatmapSet).OnlineID, Status = beatmap.Status, Checksum = beatmap.MD5Hash, AuthorID = beatmap.Metadata.Author.OnlineID, diff --git a/osu.Game/Utils/ZipUtils.cs b/osu.Game/Utils/ZipUtils.cs index cd4d876451..eb2d2d3b80 100644 --- a/osu.Game/Utils/ZipUtils.cs +++ b/osu.Game/Utils/ZipUtils.cs @@ -9,6 +9,34 @@ namespace osu.Game.Utils { public static class ZipUtils { + public static bool IsZipArchive(MemoryStream stream) + { + try + { + stream.Seek(0, SeekOrigin.Begin); + + using (var arc = ZipArchive.Open(stream)) + { + foreach (var entry in arc.Entries) + { + using (entry.OpenEntryStream()) + { + } + } + } + + return true; + } + catch (Exception) + { + return false; + } + finally + { + stream.Seek(0, SeekOrigin.Begin); + } + } + public static bool IsZipArchive(string path) { if (!File.Exists(path)) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index df3c9b355a..93fb729f46 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -37,7 +37,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 1852957e87..3a2a3fd65e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - +