From f148fbcc94774816b607a33307e7627279e12631 Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Wed, 29 Sep 2021 00:59:08 +0200 Subject: [PATCH 001/183] Cap LoopCount to at least 1 --- osu.Game/Storyboards/CommandLoop.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/CommandLoop.cs b/osu.Game/Storyboards/CommandLoop.cs index c22ca0d8c0..c17436d813 100644 --- a/osu.Game/Storyboards/CommandLoop.cs +++ b/osu.Game/Storyboards/CommandLoop.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; namespace osu.Game.Storyboards @@ -16,7 +17,7 @@ namespace osu.Game.Storyboards public CommandLoop(double startTime, int loopCount) { LoopStartTime = startTime; - LoopCount = loopCount; + LoopCount = Math.Max(1, loopCount); } public override IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0) From 94e2dbd7e76f4da32b6be693d7d17ae740ca83b5 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 23 Sep 2021 20:14:32 +0900 Subject: [PATCH 002/183] Add a low-pass filter effect to music when certain popup dialogs are shown --- osu.Game/Audio/Effects/LowPassFilter.cs | 75 +++++++++++++++++++++++++ osu.Game/Overlays/DialogOverlay.cs | 15 +++++ 2 files changed, 90 insertions(+) create mode 100644 osu.Game/Audio/Effects/LowPassFilter.cs diff --git a/osu.Game/Audio/Effects/LowPassFilter.cs b/osu.Game/Audio/Effects/LowPassFilter.cs new file mode 100644 index 0000000000..b1e78e4324 --- /dev/null +++ b/osu.Game/Audio/Effects/LowPassFilter.cs @@ -0,0 +1,75 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using ManagedBass.Fx; +using osu.Framework.Audio.Mixing; +using osu.Framework.Bindables; +using osu.Framework.Graphics; + +namespace osu.Game.Audio.Effects +{ + public class LowPassFilter : Component + { + private const float filter_cutoff_start = 2000; + private const float filter_cutoff_end = 150; + private const float filter_sweep_duration = 100; + private readonly Bindable filterFreq = new Bindable(filter_cutoff_start); + private readonly AudioMixer mixer; + private readonly BQFParameters filter; + + /// + /// A toggle-able low-pass filter with a subtle filter-sweep effect when toggled that can be attached to an . + /// + public LowPassFilter(AudioMixer mixer) + { + this.mixer = mixer; + filter = new BQFParameters + { + lFilter = BQFType.LowPass, + fCenter = filterFreq.Value + }; + } + + public void Enable() + { + attachFilter(); + this.TransformBindableTo(filterFreq, filter_cutoff_end, filter_sweep_duration); + } + + public void Disable() + { + this.TransformBindableTo(filterFreq, filter_cutoff_start, filter_sweep_duration) + .OnComplete(_ => detatchFilter()); + } + + private void attachFilter() + { + mixer.Effects.Add(filter); + filterFreq.ValueChanged += updateFilter; + } + + private void detatchFilter() + { + filterFreq.ValueChanged -= updateFilter; + mixer.Effects.Remove(filter); + } + + private void updateFilter(ValueChangedEvent cutoff) + { + var filterIndex = mixer.Effects.IndexOf(filter); + if (filterIndex < 0) return; + + var existingFilter = mixer.Effects[filterIndex] as BQFParameters; + if (existingFilter == null) return; + + existingFilter.fCenter = cutoff.NewValue; + mixer.Effects[filterIndex] = existingFilter; + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + detatchFilter(); + } + } +} diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index f051e09c08..d653ef3888 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -7,7 +7,10 @@ using osu.Game.Overlays.Dialog; using osu.Game.Graphics.Containers; using osu.Game.Input.Bindings; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Input.Events; +using osu.Game.Audio.Effects; namespace osu.Game.Overlays { @@ -18,6 +21,8 @@ namespace osu.Game.Overlays protected override string PopInSampleName => "UI/dialog-pop-in"; protected override string PopOutSampleName => "UI/dialog-pop-out"; + private LowPassFilter filter; + public PopupDialog CurrentDialog { get; private set; } public DialogOverlay() @@ -34,6 +39,12 @@ namespace osu.Game.Overlays Origin = Anchor.BottomCentre; } + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + AddInternal(filter = new LowPassFilter(audio.TrackMixer)); + } + public void Push(PopupDialog dialog) { if (dialog == CurrentDialog || dialog.State.Value != Visibility.Visible) return; @@ -71,12 +82,16 @@ namespace osu.Game.Overlays { base.PopIn(); this.FadeIn(PopupDialog.ENTER_DURATION, Easing.OutQuint); + filter.Enable(); } protected override void PopOut() { base.PopOut(); + if (IsLoaded) + filter.Disable(); + if (CurrentDialog?.State.Value == Visibility.Visible) { CurrentDialog.Hide(); From 2608d193a9518e2403cc3c1053a93e5149a3fb5d Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 29 Sep 2021 19:19:33 +0900 Subject: [PATCH 003/183] Refactor `Filter` component to be more re-usable --- .../Effects/{LowPassFilter.cs => Filter.cs} | 44 ++++++++++++------- osu.Game/Overlays/DialogOverlay.cs | 4 +- 2 files changed, 30 insertions(+), 18 deletions(-) rename osu.Game/Audio/Effects/{LowPassFilter.cs => Filter.cs} (60%) diff --git a/osu.Game/Audio/Effects/LowPassFilter.cs b/osu.Game/Audio/Effects/Filter.cs similarity index 60% rename from osu.Game/Audio/Effects/LowPassFilter.cs rename to osu.Game/Audio/Effects/Filter.cs index b1e78e4324..37a2fa93db 100644 --- a/osu.Game/Audio/Effects/LowPassFilter.cs +++ b/osu.Game/Audio/Effects/Filter.cs @@ -8,50 +8,62 @@ using osu.Framework.Graphics; namespace osu.Game.Audio.Effects { - public class LowPassFilter : Component + public class Filter : Component { - private const float filter_cutoff_start = 2000; - private const float filter_cutoff_end = 150; - private const float filter_sweep_duration = 100; - private readonly Bindable filterFreq = new Bindable(filter_cutoff_start); + public BQFType FilterType = BQFType.LowPass; + public float SweepCutoffStart = 2000; + public float SweepCutoffEnd = 150; + public float SweepDuration = 100; + public Easing SweepEasing = Easing.None; + + public bool IsActive { get; private set; } + + private readonly Bindable filterFreq = new Bindable(); private readonly AudioMixer mixer; - private readonly BQFParameters filter; + private BQFParameters filter; /// - /// A toggle-able low-pass filter with a subtle filter-sweep effect when toggled that can be attached to an . + /// A BiQuad filter that performs a filter-sweep when toggled on or off. /// - public LowPassFilter(AudioMixer mixer) + /// The mixer this effect should be attached to. + public Filter(AudioMixer mixer) { this.mixer = mixer; - filter = new BQFParameters - { - lFilter = BQFType.LowPass, - fCenter = filterFreq.Value - }; } public void Enable() { attachFilter(); - this.TransformBindableTo(filterFreq, filter_cutoff_end, filter_sweep_duration); + this.TransformBindableTo(filterFreq, SweepCutoffEnd, SweepDuration, SweepEasing); } public void Disable() { - this.TransformBindableTo(filterFreq, filter_cutoff_start, filter_sweep_duration) - .OnComplete(_ => detatchFilter()); + this.TransformBindableTo(filterFreq, SweepCutoffStart, SweepDuration, SweepEasing).OnComplete(_ => detatchFilter()); } private void attachFilter() { + if (IsActive) return; + + filter = new BQFParameters + { + lFilter = FilterType, + fCenter = filterFreq.Value = SweepCutoffStart + }; + mixer.Effects.Add(filter); filterFreq.ValueChanged += updateFilter; + IsActive = true; } private void detatchFilter() { + if (!IsActive) return; + filterFreq.ValueChanged -= updateFilter; mixer.Effects.Remove(filter); + IsActive = false; } private void updateFilter(ValueChangedEvent cutoff) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index d653ef3888..3fade81cec 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays protected override string PopInSampleName => "UI/dialog-pop-in"; protected override string PopOutSampleName => "UI/dialog-pop-out"; - private LowPassFilter filter; + private Filter filter; public PopupDialog CurrentDialog { get; private set; } @@ -42,7 +42,7 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load(AudioManager audio) { - AddInternal(filter = new LowPassFilter(audio.TrackMixer)); + AddInternal(filter = new Filter(audio.TrackMixer)); } public void Push(PopupDialog dialog) From 909f8d1d9800eb7ffd820463865b38ea4e6f5264 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 29 Sep 2021 19:20:05 +0900 Subject: [PATCH 004/183] Add visual tests for `Filter` --- osu.Game.Tests/Audio/TestSceneFilter.cs | 95 +++++++++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 osu.Game.Tests/Audio/TestSceneFilter.cs diff --git a/osu.Game.Tests/Audio/TestSceneFilter.cs b/osu.Game.Tests/Audio/TestSceneFilter.cs new file mode 100644 index 0000000000..fd4fe530eb --- /dev/null +++ b/osu.Game.Tests/Audio/TestSceneFilter.cs @@ -0,0 +1,95 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using ManagedBass.Fx; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Graphics; +using osu.Game.Audio.Effects; +using osu.Game.Beatmaps; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Audio +{ + public class TestSceneFilter : OsuTestScene + { + [Resolved] + private AudioManager audio { get; set; } + + private WorkingBeatmap testBeatmap; + private Filter lowPassFilter; + private Filter highPassFilter; + private Filter bandPassFilter; + + [BackgroundDependencyLoader] + private void load() + { + testBeatmap = new WaveformTestBeatmap(audio); + AddRange(new Drawable[] + { + lowPassFilter = new Filter(audio.TrackMixer) + { + FilterType = BQFType.LowPass, + SweepCutoffStart = 2000, + SweepCutoffEnd = 150, + SweepDuration = 1000 + }, + highPassFilter = new Filter(audio.TrackMixer) + { + FilterType = BQFType.HighPass, + SweepCutoffStart = 150, + SweepCutoffEnd = 2000, + SweepDuration = 1000 + }, + bandPassFilter = new Filter(audio.TrackMixer) + { + FilterType = BQFType.BandPass, + SweepCutoffStart = 150, + SweepCutoffEnd = 20000, + SweepDuration = 1000 + }, + }); + } + + [Test] + public void TestLowPass() + { + testFilter(lowPassFilter); + } + + [Test] + public void TestHighPass() + { + testFilter(highPassFilter); + } + + [Test] + public void TestBandPass() + { + testFilter(bandPassFilter); + } + + private void testFilter(Filter filter) + { + AddStep("Prepare Track", () => + { + testBeatmap = new WaveformTestBeatmap(audio); + testBeatmap.LoadTrack(); + }); + AddStep("Play Track", () => + { + testBeatmap.Track.Start(); + }); + AddWaitStep("Let track play", 10); + AddStep("Enable Filter", filter.Enable); + AddWaitStep("Let track play", 10); + AddStep("Disable Filter", filter.Disable); + AddWaitStep("Let track play", 10); + AddStep("Stop Track", () => + { + testBeatmap.Track.Stop(); + }); + } + } +} From 4d91204faff435d6104a1333800f8b94e248a7d1 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 29 Sep 2021 20:29:27 +0900 Subject: [PATCH 005/183] Pause `Editor` sample playback while popup dialog is shown --- osu.Game/Screens/Edit/Editor.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 2ff0101dc0..1170658abb 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -508,6 +508,7 @@ namespace osu.Game.Screens.Edit if (isNewBeatmap || HasUnsavedChanges) { + samplePlaybackDisabled.Value = true; dialogOverlay?.Push(new PromptForSaveDialog(confirmExit, confirmExitWithSave, cancelExit)); return true; } @@ -756,7 +757,11 @@ namespace osu.Game.Screens.Edit ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? clipboard.Value : string.Empty }); - private void cancelExit() => loader?.CancelPendingDifficultySwitch(); + private void cancelExit() + { + samplePlaybackDisabled.Value = false; + loader?.CancelPendingDifficultySwitch(); + } public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); From f55c4ac6406cabd1997f14e7643cf4a2156f8062 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 29 Sep 2021 21:05:46 +0900 Subject: [PATCH 006/183] IsLoaded check no longer required --- osu.Game/Overlays/DialogOverlay.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 3fade81cec..8a274968b7 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -89,8 +89,7 @@ namespace osu.Game.Overlays { base.PopOut(); - if (IsLoaded) - filter.Disable(); + filter.Disable(); if (CurrentDialog?.State.Value == Visibility.Visible) { From 968826c9e7b0b65015c9d26c250695ab2887c973 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 30 Sep 2021 15:17:39 +0900 Subject: [PATCH 007/183] Fix typo --- osu.Game/Audio/Effects/Filter.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Audio/Effects/Filter.cs b/osu.Game/Audio/Effects/Filter.cs index 37a2fa93db..04ad862879 100644 --- a/osu.Game/Audio/Effects/Filter.cs +++ b/osu.Game/Audio/Effects/Filter.cs @@ -39,7 +39,7 @@ namespace osu.Game.Audio.Effects public void Disable() { - this.TransformBindableTo(filterFreq, SweepCutoffStart, SweepDuration, SweepEasing).OnComplete(_ => detatchFilter()); + this.TransformBindableTo(filterFreq, SweepCutoffStart, SweepDuration, SweepEasing).OnComplete(_ => detachFilter()); } private void attachFilter() @@ -57,7 +57,7 @@ namespace osu.Game.Audio.Effects IsActive = true; } - private void detatchFilter() + private void detachFilter() { if (!IsActive) return; @@ -81,7 +81,7 @@ namespace osu.Game.Audio.Effects protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - detatchFilter(); + detachFilter(); } } } From 1304b55c4182db06c22b5830cb449584c3e18dde Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 30 Sep 2021 15:19:09 +0900 Subject: [PATCH 008/183] Move visual test to correct namespace --- osu.Game.Tests/{ => Visual}/Audio/TestSceneFilter.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename osu.Game.Tests/{ => Visual}/Audio/TestSceneFilter.cs (97%) diff --git a/osu.Game.Tests/Audio/TestSceneFilter.cs b/osu.Game.Tests/Visual/Audio/TestSceneFilter.cs similarity index 97% rename from osu.Game.Tests/Audio/TestSceneFilter.cs rename to osu.Game.Tests/Visual/Audio/TestSceneFilter.cs index fd4fe530eb..6a7a404d8a 100644 --- a/osu.Game.Tests/Audio/TestSceneFilter.cs +++ b/osu.Game.Tests/Visual/Audio/TestSceneFilter.cs @@ -8,9 +8,8 @@ using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Game.Audio.Effects; using osu.Game.Beatmaps; -using osu.Game.Tests.Visual; -namespace osu.Game.Tests.Audio +namespace osu.Game.Tests.Visual.Audio { public class TestSceneFilter : OsuTestScene { From 84bddf0885893d700addf2cbc00900dc408a0f8e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 30 Sep 2021 17:00:15 +0900 Subject: [PATCH 009/183] Initial PP counter implementation --- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- .../Difficulty/DifficultyCalculator.cs | 126 +++++++++++++++--- .../HUD/DefaultPerformancePointsCounter.cs | 108 +++++++++++++++ osu.Game/Screens/Play/Player.cs | 8 +- 4 files changed, 218 insertions(+), 26 deletions(-) create mode 100644 osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 61760e69b0..c4c5c89f28 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -81,7 +81,7 @@ namespace osu.Game.Beatmaps /// The applicable . protected virtual IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap, Ruleset ruleset) => ruleset.CreateBeatmapConverter(beatmap); - public IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null) + public virtual IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null) { using (var cancellationSource = createCancellationTokenSource(timeout)) { diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 224c9178ae..f38949d982 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -7,6 +7,8 @@ using System.Linq; using osu.Framework.Audio.Track; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Mods; @@ -19,6 +21,10 @@ namespace osu.Game.Rulesets.Difficulty private readonly Ruleset ruleset; private readonly WorkingBeatmap beatmap; + private IBeatmap playableBeatmap; + private Mod[] playableMods; + private double clockRate; + protected DifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) { this.ruleset = ruleset; @@ -32,14 +38,41 @@ namespace osu.Game.Rulesets.Difficulty /// A structure describing the difficulty of the beatmap. public DifficultyAttributes Calculate(params Mod[] mods) { - mods = mods.Select(m => m.DeepClone()).ToArray(); + preProcess(mods); - IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); + var skills = CreateSkills(playableBeatmap, playableMods, clockRate); - var track = new TrackVirtual(10000); - mods.OfType().ForEach(m => m.ApplyToTrack(track)); + if (!playableBeatmap.HitObjects.Any()) + return CreateDifficultyAttributes(playableBeatmap, playableMods, skills, clockRate); - return calculate(playableBeatmap, mods, track.Rate); + foreach (var hitObject in getDifficultyHitObjects()) + { + foreach (var skill in skills) + skill.ProcessInternal(hitObject); + } + + return CreateDifficultyAttributes(playableBeatmap, playableMods, skills, clockRate); + } + + public IEnumerable CalculateTimed(params Mod[] mods) + { + preProcess(mods); + + if (!playableBeatmap.HitObjects.Any()) + yield break; + + var skills = CreateSkills(playableBeatmap, playableMods, clockRate); + var progressiveBeatmap = new ProgressiveCalculationBeatmap(playableBeatmap); + + foreach (var hitObject in getDifficultyHitObjects()) + { + progressiveBeatmap.HitObjects.Add(hitObject.BaseObject); + + foreach (var skill in skills) + skill.ProcessInternal(hitObject); + + yield return new TimedDifficultyAttributes(hitObject.EndTime, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate)); + } } /// @@ -57,24 +90,23 @@ namespace osu.Game.Rulesets.Difficulty } } - private DifficultyAttributes calculate(IBeatmap beatmap, Mod[] mods, double clockRate) + /// + /// Retrieves the s to calculate against. + /// + private IEnumerable getDifficultyHitObjects() => SortObjects(CreateDifficultyHitObjects(playableBeatmap, clockRate)); + + /// + /// Performs required tasks before every calculation. + /// + /// The original list of s. + private void preProcess(Mod[] mods) { - var skills = CreateSkills(beatmap, mods, clockRate); + playableMods = mods.Select(m => m.DeepClone()).ToArray(); + playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); - if (!beatmap.HitObjects.Any()) - return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); - - var difficultyHitObjects = SortObjects(CreateDifficultyHitObjects(beatmap, clockRate)).ToList(); - - foreach (var hitObject in difficultyHitObjects) - { - foreach (var skill in skills) - { - skill.ProcessInternal(hitObject); - } - } - - return CreateDifficultyAttributes(beatmap, mods, skills, clockRate); + var track = new TrackVirtual(10000); + mods.OfType().ForEach(m => m.ApplyToTrack(track)); + clockRate = track.Rate; } /// @@ -183,5 +215,57 @@ namespace osu.Game.Rulesets.Difficulty /// Clockrate to calculate difficulty with. /// The s. protected abstract Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate); + + public class TimedDifficultyAttributes : IComparable + { + public readonly double Time; + public readonly DifficultyAttributes Attributes; + + public TimedDifficultyAttributes(double time, DifficultyAttributes attributes) + { + Time = time; + Attributes = attributes; + } + + public int CompareTo(TimedDifficultyAttributes other) => Time.CompareTo(other.Time); + } + + private class ProgressiveCalculationBeatmap : IBeatmap + { + private readonly IBeatmap baseBeatmap; + + public ProgressiveCalculationBeatmap(IBeatmap baseBeatmap) + { + this.baseBeatmap = baseBeatmap; + } + + public BeatmapInfo BeatmapInfo + { + get => baseBeatmap.BeatmapInfo; + set => baseBeatmap.BeatmapInfo = value; + } + + public BeatmapMetadata Metadata => baseBeatmap.Metadata; + + public ControlPointInfo ControlPointInfo + { + get => baseBeatmap.ControlPointInfo; + set => baseBeatmap.ControlPointInfo = value; + } + + public List Breaks => baseBeatmap.Breaks; + + public double TotalBreakTime => baseBeatmap.TotalBreakTime; + + public readonly List HitObjects = new List(); + + IReadOnlyList IBeatmap.HitObjects => HitObjects; + + public IEnumerable GetStatistics() => baseBeatmap.GetStatistics(); + + public double GetMostCommonBeatLength() => baseBeatmap.GetMostCommonBeatLength(); + + public IBeatmap Clone() => new ProgressiveCalculationBeatmap(baseBeatmap.Clone()); + } } } diff --git a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs new file mode 100644 index 0000000000..18bb621dd1 --- /dev/null +++ b/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs @@ -0,0 +1,108 @@ +// 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.IO; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; +using osu.Framework.Localisation; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; + +namespace osu.Game.Screens.Play.HUD +{ + public class DefaultPerformancePointsCounter : RollingCounter, ISkinnableDrawable + { + public bool UsesFixedAnchor { get; set; } + + [Resolved] + private ScoreProcessor scoreProcessor { get; set; } + + [Resolved] + private Player player { get; set; } + + private DifficultyCalculator.TimedDifficultyAttributes[] timedAttributes; + private Ruleset gameplayRuleset; + + public DefaultPerformancePointsCounter() + { + Current.Value = DisplayedCount = 0; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.BlueLighter; + + gameplayRuleset = player.GameplayRuleset; + timedAttributes = gameplayRuleset.CreateDifficultyCalculator(new GameplayWorkingBeatmap(player.GameplayBeatmap)).CalculateTimed(player.Mods.Value.ToArray()).ToArray(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + scoreProcessor.NewJudgement += onNewJudgement; + } + + private void onNewJudgement(JudgementResult judgement) + { + var attribIndex = Array.BinarySearch(timedAttributes, 0, timedAttributes.Length, new DifficultyCalculator.TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null)); + if (attribIndex < 0) + attribIndex = ~attribIndex - 1; + attribIndex = Math.Clamp(attribIndex, 0, timedAttributes.Length - 1); + + var ppProcessor = gameplayRuleset.CreatePerformanceCalculator(timedAttributes[attribIndex].Attributes, player.Score.ScoreInfo); + Current.Value = (int)(ppProcessor?.Calculate() ?? 0); + } + + protected override LocalisableString FormatCount(int count) => $@"{count}pp"; + + protected override OsuSpriteText CreateSpriteText() + => base.CreateSpriteText().With(s => s.Font = s.Font.With(size: 20f)); + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (scoreProcessor != null) + scoreProcessor.NewJudgement -= onNewJudgement; + } + + private class GameplayWorkingBeatmap : WorkingBeatmap + { + private readonly GameplayBeatmap gameplayBeatmap; + + public GameplayWorkingBeatmap(GameplayBeatmap gameplayBeatmap) + : base(gameplayBeatmap.BeatmapInfo, null) + { + this.gameplayBeatmap = gameplayBeatmap; + } + + public override IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null) + => gameplayBeatmap; + + protected override IBeatmap GetBeatmap() => gameplayBeatmap; + + protected override Texture GetBackground() => throw new NotImplementedException(); + + protected override Track GetBeatmapTrack() => throw new NotImplementedException(); + + protected internal override ISkin GetSkin() => throw new NotImplementedException(); + + public override Stream GetStream(string storagePath) => throw new NotImplementedException(); + } + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9927467bd6..4018650093 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -93,9 +93,9 @@ namespace osu.Game.Screens.Play [Resolved] private SpectatorClient spectatorClient { get; set; } - protected Ruleset GameplayRuleset { get; private set; } + public Ruleset GameplayRuleset { get; private set; } - protected GameplayBeatmap GameplayBeatmap { get; private set; } + public GameplayBeatmap GameplayBeatmap { get; private set; } private Sample sampleRestart; @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Play [Cached] [Cached(Type = typeof(IBindable>))] - protected new readonly Bindable> Mods = new Bindable>(Array.Empty()); + public new readonly Bindable> Mods = new Bindable>(Array.Empty()); /// /// Whether failing should be allowed. @@ -137,7 +137,7 @@ namespace osu.Game.Screens.Play public readonly PlayerConfiguration Configuration; - protected Score Score { get; private set; } + public Score Score { get; private set; } /// /// Create a new player instance. From d2a8f35b4c5428118d306174cb72503dd2ff66c1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 30 Sep 2021 17:54:52 +0900 Subject: [PATCH 010/183] Update styling --- .../Graphics/UserInterface/RollingCounter.cs | 8 ++-- .../HUD/DefaultPerformancePointsCounter.cs | 43 +++++++++++++++++-- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index 244658b75e..f03287e7de 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -25,7 +25,7 @@ namespace osu.Game.Graphics.UserInterface set => current.Current = value; } - private SpriteText displayedCountSpriteText; + private IHasText displayedCountSpriteText; /// /// If true, the roll-up duration will be proportional to change in value. @@ -72,10 +72,10 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load() { - displayedCountSpriteText = CreateSpriteText(); + displayedCountSpriteText = CreateText(); UpdateDisplay(); - Child = displayedCountSpriteText; + Child = (Drawable)displayedCountSpriteText; } protected void UpdateDisplay() @@ -160,6 +160,8 @@ namespace osu.Game.Graphics.UserInterface this.TransformTo(nameof(DisplayedCount), newValue, rollingTotalDuration, RollingEasing); } + protected virtual IHasText CreateText() => CreateSpriteText(); + protected virtual OsuSpriteText CreateSpriteText() => new OsuSpriteText { Font = OsuFont.Numeric.With(size: 40f), diff --git a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs index 18bb621dd1..a7651187c2 100644 --- a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs @@ -68,10 +68,9 @@ namespace osu.Game.Screens.Play.HUD Current.Value = (int)(ppProcessor?.Calculate() ?? 0); } - protected override LocalisableString FormatCount(int count) => $@"{count}pp"; + protected override LocalisableString FormatCount(int count) => count.ToString(@"D"); - protected override OsuSpriteText CreateSpriteText() - => base.CreateSpriteText().With(s => s.Font = s.Font.With(size: 20f)); + protected override IHasText CreateText() => new TextComponent(); protected override void Dispose(bool isDisposing) { @@ -81,6 +80,44 @@ namespace osu.Game.Screens.Play.HUD scoreProcessor.NewJudgement -= onNewJudgement; } + private class TextComponent : CompositeDrawable, IHasText + { + public LocalisableString Text + { + get => text.Text; + set => text.Text = value; + } + + private readonly OsuSpriteText text; + + public TextComponent() + { + AutoSizeAxes = Axes.Both; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(2), + Children = new Drawable[] + { + text = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Font = OsuFont.Numeric.With(size: 16) + }, + new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Text = @"pp", + Font = OsuFont.Numeric.With(size: 8) + } + } + }; + } + } + private class GameplayWorkingBeatmap : WorkingBeatmap { private readonly GameplayBeatmap gameplayBeatmap; From 4d8418e0720a43f620381142c6b05a4e1ba95a69 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 30 Sep 2021 17:54:56 +0900 Subject: [PATCH 011/183] Fix possible nullrefs --- .../HUD/DefaultPerformancePointsCounter.cs | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs index a7651187c2..563032b4ea 100644 --- a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs @@ -5,9 +5,12 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Framework.Localisation; using osu.Game.Beatmaps; @@ -21,6 +24,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Screens.Play.HUD { @@ -28,10 +32,12 @@ namespace osu.Game.Screens.Play.HUD { public bool UsesFixedAnchor { get; set; } - [Resolved] + [CanBeNull] + [Resolved(CanBeNull = true)] private ScoreProcessor scoreProcessor { get; set; } - [Resolved] + [CanBeNull] + [Resolved(CanBeNull = true)] private Player player { get; set; } private DifficultyCalculator.TimedDifficultyAttributes[] timedAttributes; @@ -47,18 +53,26 @@ namespace osu.Game.Screens.Play.HUD { Colour = colours.BlueLighter; - gameplayRuleset = player.GameplayRuleset; - timedAttributes = gameplayRuleset.CreateDifficultyCalculator(new GameplayWorkingBeatmap(player.GameplayBeatmap)).CalculateTimed(player.Mods.Value.ToArray()).ToArray(); + if (player != null) + { + gameplayRuleset = player.GameplayRuleset; + timedAttributes = gameplayRuleset.CreateDifficultyCalculator(new GameplayWorkingBeatmap(player.GameplayBeatmap)).CalculateTimed(player.Mods.Value.ToArray()).ToArray(); + } } protected override void LoadComplete() { base.LoadComplete(); - scoreProcessor.NewJudgement += onNewJudgement; + + if (scoreProcessor != null) + scoreProcessor.NewJudgement += onNewJudgement; } private void onNewJudgement(JudgementResult judgement) { + if (player == null) + return; + var attribIndex = Array.BinarySearch(timedAttributes, 0, timedAttributes.Length, new DifficultyCalculator.TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null)); if (attribIndex < 0) attribIndex = ~attribIndex - 1; From fab0d531bec9bf64687e927b937f27e861b4b7ea Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 30 Sep 2021 17:55:00 +0900 Subject: [PATCH 012/183] Add counter to HUD --- osu.Game/Skinning/DefaultSkin.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index d3adae5c8c..41b7875cd1 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -68,6 +68,7 @@ namespace osu.Game.Skinning var score = container.OfType().FirstOrDefault(); var accuracy = container.OfType().FirstOrDefault(); var combo = container.OfType().FirstOrDefault(); + var ppCounter = container.OfType().FirstOrDefault(); if (score != null) { @@ -81,6 +82,13 @@ namespace osu.Game.Skinning score.Position = new Vector2(0, vertical_offset); + if (ppCounter != null) + { + ppCounter.Y = score.Position.Y + score.ScreenSpaceDrawQuad.Size.Y; + ppCounter.Origin = Anchor.TopCentre; + ppCounter.Anchor = Anchor.TopCentre; + } + if (accuracy != null) { accuracy.Position = new Vector2(-accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 - horizontal_padding, vertical_offset + 5); @@ -123,6 +131,7 @@ namespace osu.Game.Skinning new SongProgress(), new BarHitErrorMeter(), new BarHitErrorMeter(), + new DefaultPerformancePointsCounter() } }; From c05a8fc4a2ec1f598a6e317974d9293c34d780b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Sep 2021 18:52:09 +0900 Subject: [PATCH 013/183] Split importer interface out of `IModelManager` --- osu.Game/Database/IModelImporter.cs | 65 +++++++++++++++++++++++++++++ osu.Game/Database/IModelManager.cs | 51 +--------------------- 2 files changed, 66 insertions(+), 50 deletions(-) create mode 100644 osu.Game/Database/IModelImporter.cs diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs new file mode 100644 index 0000000000..fa3b4d9152 --- /dev/null +++ b/osu.Game/Database/IModelImporter.cs @@ -0,0 +1,65 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using osu.Game.IO.Archives; +using osu.Game.Overlays.Notifications; + +namespace osu.Game.Database +{ + /// + /// A class which handles importing of asociated models to the game store. + /// + /// The model type. + public interface IModelImporter : IPostNotifications + where TModel : class + { + /// + /// Import one or more items from filesystem . + /// + /// + /// This will be treated as a low priority import if more than one path is specified; use to always import at standard priority. + /// This will post notifications tracking progress. + /// + /// One or more archive locations on disk. + Task Import(params string[] paths); + + Task Import(params ImportTask[] tasks); + + Task> Import(ProgressNotification notification, params ImportTask[] tasks); + + /// + /// Import one from the filesystem and delete the file on success. + /// Note that this bypasses the UI flow and should only be used for special cases or testing. + /// + /// The containing data about the to import. + /// Whether this is a low priority import. + /// An optional cancellation token. + /// The imported model, if successful. + Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default); + + /// + /// Silently import an item from an . + /// + /// 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); + + /// + /// Silently import an item from a . + /// + /// The model to be imported. + /// 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); + + /// + /// A user displayable name for the model type associated with this manager. + /// + string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}"; + } +} diff --git a/osu.Game/Database/IModelManager.cs b/osu.Game/Database/IModelManager.cs index 7bfc8dbee3..2b1e574176 100644 --- a/osu.Game/Database/IModelManager.cs +++ b/osu.Game/Database/IModelManager.cs @@ -4,12 +4,9 @@ using System; using System.Collections.Generic; using System.IO; -using System.Threading; using System.Threading.Tasks; using osu.Framework.Bindables; using osu.Game.IO; -using osu.Game.IO.Archives; -using osu.Game.Overlays.Notifications; namespace osu.Game.Database { @@ -17,7 +14,7 @@ namespace osu.Game.Database /// Represents a model manager that publishes events when s are added or removed. /// /// The model type. - public interface IModelManager : IPostNotifications + public interface IModelManager : IModelImporter, IPostNotifications where TModel : class { /// @@ -83,57 +80,11 @@ namespace osu.Game.Database /// The item to restore void Undelete(TModel item); - /// - /// Import one or more items from filesystem . - /// - /// - /// This will be treated as a low priority import if more than one path is specified; use to always import at standard priority. - /// This will post notifications tracking progress. - /// - /// One or more archive locations on disk. - Task Import(params string[] paths); - - Task Import(params ImportTask[] tasks); - - Task> Import(ProgressNotification notification, params ImportTask[] tasks); - - /// - /// Import one from the filesystem and delete the file on success. - /// Note that this bypasses the UI flow and should only be used for special cases or testing. - /// - /// The containing data about the to import. - /// Whether this is a low priority import. - /// An optional cancellation token. - /// The imported model, if successful. - Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default); - - /// - /// Silently import an item from an . - /// - /// 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); - - /// - /// Silently import an item from a . - /// - /// The model to be imported. - /// 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); - /// /// Checks whether a given is already available in the local store. /// /// The whose existence needs to be checked. /// Whether the exists. bool IsAvailableLocally(TModel model); - - /// - /// A user displayable name for the model type associated with this manager. - /// - string HumanisedModelName => $"{typeof(TModel).Name.Replace(@"Info", "").ToLower()}"; } } From 66409147dc3ff3cc3eac9afa10841ef35e6eef98 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Sep 2021 19:25:08 +0900 Subject: [PATCH 014/183] Remove duplicate interface specification --- osu.Game/Database/IModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/IModelManager.cs b/osu.Game/Database/IModelManager.cs index 2b1e574176..f5e401cdfb 100644 --- a/osu.Game/Database/IModelManager.cs +++ b/osu.Game/Database/IModelManager.cs @@ -14,7 +14,7 @@ namespace osu.Game.Database /// Represents a model manager that publishes events when s are added or removed. /// /// The model type. - public interface IModelManager : IModelImporter, IPostNotifications + public interface IModelManager : IModelImporter where TModel : class { /// From a2e61883e3b00dc205ecefc5870d9d2343ced7bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Sep 2021 19:33:12 +0900 Subject: [PATCH 015/183] Initial push to use `ILive` in import process --- .../Beatmaps/IO/ImportBeatmapTest.cs | 34 +++++++-------- ...eneOnlinePlayBeatmapAvailabilityTracker.cs | 4 +- osu.Game.Tests/Skins/IO/ImportSkinTest.cs | 2 +- .../Skins/TestSceneBeatmapSkinResources.cs | 2 +- .../Skins/TestSceneSkinResources.cs | 2 +- .../Menus/TestSceneMusicActionHandling.cs | 2 +- .../Navigation/TestScenePresentBeatmap.cs | 2 +- .../Navigation/TestScenePresentScore.cs | 4 +- .../TestScenePlaylistsRoomSubScreen.cs | 2 +- .../TestSceneBeatmapRecommendations.cs | 2 +- .../SongSelect/TestScenePlaySongSelect.cs | 2 +- .../TestSceneDeleteLocalScore.cs | 4 +- osu.Game/Beatmaps/BeatmapManager.cs | 15 +++---- osu.Game/Database/ArchiveModelManager.cs | 26 ++++++------ osu.Game/Database/EntityFrameworkLive.cs | 34 +++++++++++++++ .../Database/EntityFrameworkLiveExtensions.cs | 14 +++++++ osu.Game/Database/ILive.cs | 42 +++++++++++++++++++ osu.Game/Database/IModelImporter.cs | 8 ++-- osu.Game/Database/IPresentImports.cs | 2 +- osu.Game/FodyWeavers.xml | 3 ++ osu.Game/OsuGame.cs | 4 +- osu.Game/Scoring/ScoreManager.cs | 10 ++--- osu.Game/Screens/Menu/IntroScreen.cs | 8 +++- osu.Game/Skinning/SkinManager.cs | 2 +- 24 files changed, 164 insertions(+), 66 deletions(-) create mode 100644 osu.Game/Database/EntityFrameworkLive.cs create mode 100644 osu.Game/Database/EntityFrameworkLiveExtensions.cs create mode 100644 osu.Game/Database/ILive.cs create mode 100644 osu.Game/FodyWeavers.xml diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index cba7f34ede..fc2a3792cb 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Beatmaps.IO var manager = osu.Dependencies.Get(); - BeatmapSetInfo importedSet; + ILive importedSet; using (var stream = File.OpenRead(tempPath)) { @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(File.Exists(tempPath), "Stream source file somehow went missing"); File.Delete(tempPath); - var imported = manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID); + var imported = manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID); deleteBeatmapSet(imported, osu); } @@ -172,8 +172,8 @@ namespace osu.Game.Tests.Beatmaps.IO ensureLoaded(osu); // but contents doesn't, so existing should still be used. - Assert.IsTrue(imported.ID == importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID); + Assert.IsTrue(imported.ID == importedSecondTime.Value.ID); + Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Value.Beatmaps.First().ID); } finally { @@ -226,8 +226,8 @@ namespace osu.Game.Tests.Beatmaps.IO ensureLoaded(osu); // check the newly "imported" beatmap is not the original. - Assert.IsTrue(imported.ID != importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); + Assert.IsTrue(imported.ID != importedSecondTime.Value.ID); + Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID); } finally { @@ -278,8 +278,8 @@ namespace osu.Game.Tests.Beatmaps.IO ensureLoaded(osu); // check the newly "imported" beatmap is not the original. - Assert.IsTrue(imported.ID != importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); + Assert.IsTrue(imported.ID != importedSecondTime.Value.ID); + Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID); } finally { @@ -329,8 +329,8 @@ namespace osu.Game.Tests.Beatmaps.IO ensureLoaded(osu); // check the newly "imported" beatmap is not the original. - Assert.IsTrue(imported.ID != importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Beatmaps.First().ID); + Assert.IsTrue(imported.ID != importedSecondTime.Value.ID); + Assert.IsTrue(imported.Beatmaps.First().ID != importedSecondTime.Value.Beatmaps.First().ID); } finally { @@ -570,8 +570,8 @@ namespace osu.Game.Tests.Beatmaps.IO var imported = await manager.Import(toImport); Assert.NotNull(imported); - Assert.AreEqual(null, imported.Beatmaps[0].OnlineBeatmapID); - Assert.AreEqual(null, imported.Beatmaps[1].OnlineBeatmapID); + Assert.AreEqual(null, imported.Value.Beatmaps[0].OnlineBeatmapID); + Assert.AreEqual(null, imported.Value.Beatmaps[1].OnlineBeatmapID); } finally { @@ -706,7 +706,7 @@ namespace osu.Game.Tests.Beatmaps.IO ensureLoaded(osu); - Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder"); + Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("subfolder")), "Files contain common subfolder"); } finally { @@ -759,8 +759,8 @@ namespace osu.Game.Tests.Beatmaps.IO ensureLoaded(osu); - Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored"); - Assert.IsFalse(imported.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder"); + Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("__MACOSX")), "Files contain resource fork folder, which should be ignored"); + Assert.IsFalse(imported.Value.Files.Any(f => f.Filename.Contains("actual_data")), "Files contain common subfolder"); } finally { @@ -915,7 +915,7 @@ namespace osu.Game.Tests.Beatmaps.IO waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); - return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID); + return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID); } public static async Task LoadOszIntoOsu(OsuGameBase osu, string path = null, bool virtualTrack = false) @@ -930,7 +930,7 @@ namespace osu.Game.Tests.Beatmaps.IO waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); - return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.ID); + return manager.GetAllUsableBeatmapSets().Find(beatmapSet => beatmapSet.ID == importedSet.Value.ID); } private void deleteBeatmapSet(BeatmapSetInfo imported, OsuGameBase osu) diff --git a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs index d38294aba9..79767bc671 100644 --- a/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs +++ b/osu.Game.Tests/Online/TestSceneOnlinePlayBeatmapAvailabilityTracker.cs @@ -156,7 +156,7 @@ namespace osu.Game.Tests.Online { public TaskCompletionSource AllowImport = new TaskCompletionSource(); - public Task CurrentImportTask { get; private set; } + public Task> CurrentImportTask { get; private set; } public TestBeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null) : base(storage, contextFactory, rulesets, api, audioManager, resources, host, defaultBeatmap) @@ -194,7 +194,7 @@ namespace osu.Game.Tests.Online this.testBeatmapManager = testBeatmapManager; } - public override async Task Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public override async Task> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { await testBeatmapManager.AllowImport.Task.ConfigureAwait(false); return await (testBeatmapManager.CurrentImportTask = base.Import(item, archive, lowPriority, cancellationToken)).ConfigureAwait(false); diff --git a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs index 7a9fc20426..b2600bb887 100644 --- a/osu.Game.Tests/Skins/IO/ImportSkinTest.cs +++ b/osu.Game.Tests/Skins/IO/ImportSkinTest.cs @@ -196,7 +196,7 @@ namespace osu.Game.Tests.Skins.IO private async Task loadSkinIntoOsu(OsuGameBase osu, ArchiveReader archive = null) { var skinManager = osu.Dependencies.Get(); - return await skinManager.Import(archive); + return (await skinManager.Import(archive)).Value; } } } diff --git a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs index eff430ac25..f03cda1489 100644 --- a/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneBeatmapSkinResources.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Skins private void load() { var imported = beatmaps.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-beatmap.osz"))).Result; - beatmap = beatmaps.GetWorkingBeatmap(imported.Beatmaps[0]); + beatmap = beatmaps.GetWorkingBeatmap(imported.Value.Beatmaps[0]); beatmap.LoadTrack(); } diff --git a/osu.Game.Tests/Skins/TestSceneSkinResources.cs b/osu.Game.Tests/Skins/TestSceneSkinResources.cs index 107a96292f..10f1ab31df 100644 --- a/osu.Game.Tests/Skins/TestSceneSkinResources.cs +++ b/osu.Game.Tests/Skins/TestSceneSkinResources.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Skins private void load() { var imported = skins.Import(new ZipArchiveReader(TestResources.OpenResource("Archives/ogg-skin.osk"))).Result; - skin = skins.GetSkin(imported); + skin = skins.GetSkin(imported.Value); } [Test] diff --git a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs index 9037338e23..79dfe79299 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneMusicActionHandling.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Visual.Menus AddStep("import beatmap with track", () => { var setWithTrack = Game.BeatmapManager.Import(new ImportTask(TestResources.GetTestBeatmapForImport())).Result; - Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Beatmaps.First()); + Beatmap.Value = Game.BeatmapManager.GetWorkingBeatmap(setWithTrack.Value.Beatmaps.First()); }); AddStep("bind to track change", () => diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs index f0ddefa51d..5f5ebfccfb 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentBeatmap.cs @@ -126,7 +126,7 @@ namespace osu.Game.Tests.Visual.Navigation Ruleset = ruleset ?? new OsuRuleset().RulesetInfo }, } - }).Result; + }).Result.Value; }); AddAssert($"import {i} succeeded", () => imported != null); diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 52b577b402..2ea765a1a9 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Navigation Ruleset = new OsuRuleset().RulesetInfo }, } - }).Result; + }).Result.Value; }); } @@ -132,7 +132,7 @@ namespace osu.Game.Tests.Visual.Navigation OnlineScoreID = i, Beatmap = beatmap.Beatmaps.First(), Ruleset = ruleset ?? new OsuRuleset().RulesetInfo - }).Result; + }).Result.Value; }); AddAssert($"import {i} succeeded", () => imported != null); diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index 9051c71fc6..d8ec89a94e 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -117,7 +117,7 @@ namespace osu.Game.Tests.Visual.Playlists { beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1; - importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result; + importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result.Value; }); AddStep("load room", () => diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs index 53cb628bb3..c22b6a54e9 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapRecommendations.cs @@ -192,7 +192,7 @@ namespace osu.Game.Tests.Visual.SongSelect }).ToList() }; - return Game.BeatmapManager.Import(beatmapSet).Result; + return Game.BeatmapManager.Import(beatmapSet).Result.Value; } private bool ensureAllBeatmapSetsImported(IEnumerable beatmapSets) => beatmapSets.All(set => set != null); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 102e5ee425..19aa91a38f 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -751,7 +751,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("import huge difficulty count map", () => { var usableRulesets = rulesets.AvailableRulesets.Where(r => r.ID != 2).ToArray(); - imported = manager.Import(createTestBeatmapSet(usableRulesets, 50)).Result; + imported = manager.Import(createTestBeatmapSet(usableRulesets, 50)).Result.Value; }); AddStep("select the first beatmap of import", () => Beatmap.Value = manager.GetWorkingBeatmap(imported.Beatmaps.First())); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 2e30ed9827..3c69db032e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory, Scheduler)); - beatmap = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Beatmaps[0]; + beatmap = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Value.Beatmaps[0]; for (int i = 0; i < 50; i++) { @@ -100,7 +100,7 @@ namespace osu.Game.Tests.Visual.UserInterface User = new User { Username = "TestUser" }, }; - importedScores.Add(scoreManager.Import(score).Result); + importedScores.Add(scoreManager.Import(score).Result.Value); } return dependencies; diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 8dfd895987..1bf4feb6a3 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -89,8 +89,9 @@ namespace osu.Game.Beatmaps } }; - var working = beatmapModelManager.Import(set).Result; - return GetWorkingBeatmap(working.Beatmaps.First()); + var imported = beatmapModelManager.Import(set).Result.Value; + + return GetWorkingBeatmap(imported.Beatmaps.First()); } #region Delegation to BeatmapModelManager (methods which previously existed locally). @@ -176,7 +177,7 @@ namespace osu.Game.Beatmaps /// /// Fired when the user requests to view the resulting import. /// - public Action> PresentImport { set => beatmapModelManager.PresentImport = value; } + public Action>> PresentImport { set => beatmapModelManager.PresentImport = value; } /// /// Delete a beatmap difficulty. @@ -275,22 +276,22 @@ namespace osu.Game.Beatmaps return beatmapModelManager.Import(tasks); } - public Task> Import(ProgressNotification notification, params ImportTask[] tasks) + public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) { return beatmapModelManager.Import(notification, tasks); } - public Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) { return beatmapModelManager.Import(task, lowPriority, cancellationToken); } - public Task Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) { return beatmapModelManager.Import(archive, lowPriority, cancellationToken); } - public Task Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(BeatmapSetInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return beatmapModelManager.Import(item, archive, lowPriority, cancellationToken); } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 0c309bbddb..403bfdf621 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -132,13 +132,13 @@ namespace osu.Game.Database return Import(notification, tasks); } - public async Task> Import(ProgressNotification notification, params ImportTask[] tasks) + public async Task>> Import(ProgressNotification notification, params ImportTask[] tasks) { if (tasks.Length == 0) { notification.CompletionText = $"No {HumanisedModelName}s were found to import!"; notification.State = ProgressNotificationState.Completed; - return Enumerable.Empty(); + return Enumerable.Empty>(); } notification.Progress = 0; @@ -146,7 +146,7 @@ namespace osu.Game.Database int current = 0; - var imported = new List(); + var imported = new List>(); bool isLowPriorityImport = tasks.Length > low_priority_import_batch_size; @@ -224,11 +224,11 @@ namespace osu.Game.Database /// Whether this is a low priority import. /// An optional cancellation token. /// The imported model, if successful. - public async Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) + public async Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); - TModel import; + ILive import; using (ArchiveReader reader = task.GetReader()) import = await Import(reader, lowPriority, cancellationToken).ConfigureAwait(false); @@ -243,13 +243,13 @@ namespace osu.Game.Database } catch (Exception e) { - LogForModel(import, $@"Could not delete original file after import ({task})", e); + LogForModel(import?.Value, $@"Could not delete original file after import ({task})", e); } return import; } - public Action> PresentImport { protected get; set; } + public Action>> PresentImport { protected get; set; } /// /// Silently import an item from an . @@ -257,7 +257,7 @@ namespace osu.Game.Database /// The archive to be imported. /// Whether this is a low priority import. /// An optional cancellation token. - public Task Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); @@ -268,7 +268,7 @@ namespace osu.Game.Database model = CreateModel(archive); if (model == null) - return Task.FromResult(null); + return Task.FromResult>(new EntityFrameworkLive(null)); } catch (TaskCanceledException) { @@ -343,7 +343,7 @@ namespace osu.Game.Database /// An optional archive to use for model population. /// Whether this is a low priority import. /// An optional cancellation token. - public virtual async Task Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () => + public virtual async Task> Import(TModel item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) => await Task.Factory.StartNew(async () => { cancellationToken.ThrowIfCancellationRequested(); @@ -369,7 +369,7 @@ namespace osu.Game.Database { LogForModel(item, @$"Found existing (optimised) {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); Undelete(existing); - return existing; + return existing.ToEntityFrameworkLive(); } LogForModel(item, @"Found existing (optimised) but failed pre-check."); @@ -415,7 +415,7 @@ namespace osu.Game.Database // existing item will be used; rollback new import and exit early. rollback(); flushEvents(true); - return existing; + return existing.ToEntityFrameworkLive(); } LogForModel(item, @"Found existing but failed re-use check."); @@ -448,7 +448,7 @@ namespace osu.Game.Database } flushEvents(true); - return item; + return item.ToEntityFrameworkLive(); }, cancellationToken, TaskCreationOptions.HideScheduler, lowPriority ? import_scheduler_low_priority : import_scheduler).Unwrap().ConfigureAwait(false); /// diff --git a/osu.Game/Database/EntityFrameworkLive.cs b/osu.Game/Database/EntityFrameworkLive.cs new file mode 100644 index 0000000000..1d7b53911a --- /dev/null +++ b/osu.Game/Database/EntityFrameworkLive.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 System; + +namespace osu.Game.Database +{ + public class EntityFrameworkLive : ILive where T : class + { + public EntityFrameworkLive(T item) + { + Value = item; + } + + public Guid ID => throw new InvalidOperationException(); + + public void PerformRead(Action perform) + { + perform(Value); + } + + public TReturn PerformRead(Func perform) + { + return perform(Value); + } + + public void PerformWrite(Action perform) + { + perform(Value); + } + + public T Value { get; } + } +} diff --git a/osu.Game/Database/EntityFrameworkLiveExtensions.cs b/osu.Game/Database/EntityFrameworkLiveExtensions.cs new file mode 100644 index 0000000000..cd0673675e --- /dev/null +++ b/osu.Game/Database/EntityFrameworkLiveExtensions.cs @@ -0,0 +1,14 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +namespace osu.Game.Database +{ + public static class EntityFrameworkLiveExtensions + { + public static ILive ToEntityFrameworkLive(this T item) + where T : class + { + return new EntityFrameworkLive(item); + } + } +} diff --git a/osu.Game/Database/ILive.cs b/osu.Game/Database/ILive.cs new file mode 100644 index 0000000000..29e5756dba --- /dev/null +++ b/osu.Game/Database/ILive.cs @@ -0,0 +1,42 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Database +{ + /// + /// A wrapper to provide access to database backed classes in a thread-safe manner. + /// + /// The databased type. + public interface ILive where T : class + { + Guid ID { get; } + + /// + /// Perform a read operation on this live object. + /// + /// The action to perform. + void PerformRead(Action perform); + + /// + /// Perform a read operation on this live object. + /// + /// The action to perform. + TReturn PerformRead(Func perform); + + /// + /// Perform a write operation on this live object. + /// + /// The action to perform. + void PerformWrite(Action perform); + + /// + /// Resolve the value of this instance on the current thread's context. + /// + /// + /// After resolving the data should not be passed between threads. + /// + T Value { get; } + } +} diff --git a/osu.Game/Database/IModelImporter.cs b/osu.Game/Database/IModelImporter.cs index fa3b4d9152..e94af01772 100644 --- a/osu.Game/Database/IModelImporter.cs +++ b/osu.Game/Database/IModelImporter.cs @@ -28,7 +28,7 @@ namespace osu.Game.Database Task Import(params ImportTask[] tasks); - Task> Import(ProgressNotification notification, params ImportTask[] tasks); + Task>> Import(ProgressNotification notification, params ImportTask[] tasks); /// /// Import one from the filesystem and delete the file on success. @@ -38,7 +38,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 . @@ -46,7 +46,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 . @@ -55,7 +55,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/IPresentImports.cs b/osu.Game/Database/IPresentImports.cs index 39b495ebd5..6aa29a5083 100644 --- a/osu.Game/Database/IPresentImports.cs +++ b/osu.Game/Database/IPresentImports.cs @@ -12,6 +12,6 @@ namespace osu.Game.Database /// /// Fired when the user requests to view the resulting import. /// - public Action> PresentImport { set; } + public Action>> PresentImport { set; } } } diff --git a/osu.Game/FodyWeavers.xml b/osu.Game/FodyWeavers.xml new file mode 100644 index 0000000000..cc07b89533 --- /dev/null +++ b/osu.Game/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 99925bb1fb..35ec213755 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -624,10 +624,10 @@ namespace osu.Game SkinManager.PostNotification = n => Notifications.Post(n); BeatmapManager.PostNotification = n => Notifications.Post(n); - BeatmapManager.PresentImport = items => PresentBeatmap(items.First()); + BeatmapManager.PresentImport = items => PresentBeatmap(items.First().Value); ScoreManager.PostNotification = n => Notifications.Post(n); - ScoreManager.PresentImport = items => PresentScore(items.First()); + ScoreManager.PresentImport = items => PresentScore(items.First().Value); // make config aware of how to lookup skins for on-screen display purposes. // if this becomes a more common thing, tracked settings should be reconsidered to allow local DI. diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index d83b4e3f1d..aa0ee4bbbb 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -299,22 +299,22 @@ namespace osu.Game.Scoring public IEnumerable HandledExtensions => scoreModelManager.HandledExtensions; - public Task> Import(ProgressNotification notification, params ImportTask[] tasks) + public Task>> Import(ProgressNotification notification, params ImportTask[] tasks) { return scoreModelManager.Import(notification, tasks); } - public Task Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ImportTask task, bool lowPriority = false, CancellationToken cancellationToken = default) { return scoreModelManager.Import(task, lowPriority, cancellationToken); } - public Task Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ArchiveReader archive, bool lowPriority = false, CancellationToken cancellationToken = default) { return scoreModelManager.Import(archive, lowPriority, cancellationToken); } - public Task Import(ScoreInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) + public Task> Import(ScoreInfo item, ArchiveReader archive = null, bool lowPriority = false, CancellationToken cancellationToken = default) { return scoreModelManager.Import(item, archive, lowPriority, cancellationToken); } @@ -365,7 +365,7 @@ namespace osu.Game.Scoring #region Implementation of IPresentImports - public Action> PresentImport + public Action>> PresentImport { set => scoreModelManager.PresentImport = value; } diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index cfe14eab92..fbd33cad67 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -101,8 +101,12 @@ namespace osu.Game.Screens.Menu // if we detect that the theme track or beatmap is unavailable this is either first startup or things are in a bad state. // this could happen if a user has nuked their files store. for now, reimport to repair this. var import = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).Result; - import.Protected = true; - beatmaps.Update(import); + + import.PerformWrite(b => + { + b.Protected = true; + beatmaps.Update(b); + }); loadThemedIntro(); } diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index edeb17cbad..3842acab74 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -207,7 +207,7 @@ namespace osu.Game.Skinning Name = skin.SkinInfo.Name + " (modified)", Creator = skin.SkinInfo.Creator, InstantiationInfo = skin.SkinInfo.InstantiationInfo, - }).Result; + }).Result.Value; } public void Save(Skin skin) From 38cd383aaf474f91fa7668718de9923f4fd087cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Oct 2021 00:27:54 +0900 Subject: [PATCH 016/183] Remove local handling of realm when switching thread modes --- osu.Game/OsuGameBase.cs | 37 +++++++------------------------------ 1 file changed, 7 insertions(+), 30 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 7aa460981a..d8cf8c729e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -13,24 +13,23 @@ using osu.Framework.Development; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.IO.Stores; -using osu.Framework.Platform; -using osu.Game.Beatmaps; -using osu.Game.Configuration; -using osu.Game.Graphics; -using osu.Game.Graphics.Cursor; -using osu.Game.Online.API; using osu.Framework.Graphics.Performance; using osu.Framework.Graphics.Textures; using osu.Framework.Input; +using osu.Framework.IO.Stores; using osu.Framework.Logging; -using osu.Framework.Threading; +using osu.Framework.Platform; using osu.Game.Audio; +using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Database; +using osu.Game.Graphics; +using osu.Game.Graphics.Cursor; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.IO; using osu.Game.Online; +using osu.Game.Online.API; using osu.Game.Online.Chat; using osu.Game.Online.Multiplayer; using osu.Game.Online.Spectator; @@ -160,8 +159,6 @@ namespace osu.Game private readonly BindableNumber globalTrackVolumeAdjust = new BindableNumber(GLOBAL_TRACK_VOLUME_ADJUST); - private IBindable updateThreadState; - public OsuGameBase() { UseDevelopmentServer = DebugUtils.IsDebugBuild; @@ -189,9 +186,6 @@ namespace osu.Game dependencies.Cache(realmFactory = new RealmContextFactory(Storage)); - updateThreadState = Host.UpdateThread.State.GetBoundCopy(); - updateThreadState.BindValueChanged(updateThreadStateChanged); - AddInternal(realmFactory); dependencies.CacheAs(Storage); @@ -367,23 +361,6 @@ namespace osu.Game AddFont(Resources, @"Fonts/Venera/Venera-Black"); } - private IDisposable blocking; - - private void updateThreadStateChanged(ValueChangedEvent state) - { - switch (state.NewValue) - { - case GameThreadState.Running: - blocking?.Dispose(); - blocking = null; - break; - - case GameThreadState.Paused: - blocking = realmFactory.BlockAllOperations(); - break; - } - } - protected override void LoadComplete() { base.LoadComplete(); From ca7346e01fc5280ebe53106146f4434958c75c4d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Oct 2021 00:34:09 +0900 Subject: [PATCH 017/183] Add test coverage --- osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs index e2baa82ba0..7327d4053a 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; @@ -123,6 +124,13 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("ruleset unchanged", () => ReferenceEquals(Ruleset.Value, ruleset)); } + [Test] + public void TestSwitchThreadExecutionMode() + { + AddStep("Change thread mode to multi threaded", () => { game.Dependencies.Get().SetValue(FrameworkSetting.ExecutionMode, ExecutionMode.MultiThreaded); }); + AddStep("Change thread mode to single thread", () => { game.Dependencies.Get().SetValue(FrameworkSetting.ExecutionMode, ExecutionMode.SingleThread); }); + } + [Test] public void TestUnavailableRulesetHandled() { From 9c0abae2b0836dd7cb9e3584be85ccf34ad03615 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 30 Sep 2021 23:59:26 +0900 Subject: [PATCH 018/183] Add failing test coverage of realm blocking behaviour --- osu.Game.Tests/Database/GeneralUsageTests.cs | 64 ++++++++++++++++++ osu.Game.Tests/Database/RealmTest.cs | 70 ++++++++++++++++++++ osu.Game.Tests/osu.Game.Tests.csproj | 1 + 3 files changed, 135 insertions(+) create mode 100644 osu.Game.Tests/Database/GeneralUsageTests.cs create mode 100644 osu.Game.Tests/Database/RealmTest.cs diff --git a/osu.Game.Tests/Database/GeneralUsageTests.cs b/osu.Game.Tests/Database/GeneralUsageTests.cs new file mode 100644 index 0000000000..245981cd9b --- /dev/null +++ b/osu.Game.Tests/Database/GeneralUsageTests.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; + +#nullable enable + +namespace osu.Game.Tests.Database +{ + [TestFixture] + public class GeneralUsageTests : RealmTest + { + /// + /// Just test the construction of a new database works. + /// + [Test] + public void TestConstructRealm() + { + RunTestWithRealm((realmFactory, _) => { realmFactory.CreateContext().Refresh(); }); + } + + [Test] + public void TestBlockOperations() + { + RunTestWithRealm((realmFactory, _) => + { + using (realmFactory.BlockAllOperations()) + { + } + }); + } + + [Test] + public void TestBlockOperationsWithContention() + { + RunTestWithRealm((realmFactory, _) => + { + ManualResetEventSlim stopThreadedUsage = new ManualResetEventSlim(); + ManualResetEventSlim hasThreadedUsage = new ManualResetEventSlim(); + + Task.Factory.StartNew(() => + { + using (realmFactory.CreateContext()) + { + hasThreadedUsage.Set(); + + stopThreadedUsage.Wait(); + } + }, TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler); + + hasThreadedUsage.Wait(); + + Assert.Throws(() => + { + using (realmFactory.BlockAllOperations()) + { + } + }); + + stopThreadedUsage.Set(); + }); + } + } +} diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs new file mode 100644 index 0000000000..2f4838cb67 --- /dev/null +++ b/osu.Game.Tests/Database/RealmTest.cs @@ -0,0 +1,70 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Nito.AsyncEx; +using NUnit.Framework; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Framework.Testing; +using osu.Game.Database; + +#nullable enable + +namespace osu.Game.Tests.Database +{ + [TestFixture] + public abstract class RealmTest + { + private static readonly TemporaryNativeStorage storage; + + static RealmTest() + { + storage = new TemporaryNativeStorage("realm-test"); + storage.DeleteDirectory(string.Empty); + } + + protected void RunTestWithRealm(Action testAction, [CallerMemberName] string caller = "") + { + AsyncContext.Run(() => + { + var testStorage = storage.GetStorageForDirectory(caller); + + using (var realmFactory = new RealmContextFactory(testStorage, caller)) + { + Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}"); + testAction(realmFactory, testStorage); + + realmFactory.Dispose(); + Logger.Log($"Final database size: {testStorage.GetStream(realmFactory.Filename)?.Length ?? 0}"); + + realmFactory.Compact(); + Logger.Log($"Final database size after compact: {testStorage.GetStream(realmFactory.Filename)?.Length ?? 0}"); + } + }); + } + + protected void RunTestWithRealmAsync(Func testAction, [CallerMemberName] string caller = "") + { + AsyncContext.Run(async () => + { + var testStorage = storage.GetStorageForDirectory(caller); + + using (var realmFactory = new RealmContextFactory(testStorage, caller)) + { + Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}"); + + await testAction(realmFactory, testStorage); + + realmFactory.Dispose(); + Logger.Log($"Final database size: {testStorage.GetStream(realmFactory.Filename)?.Length ?? 0}"); + + realmFactory.Compact(); + Logger.Log($"Final database size after compact: {testStorage.GetStream(realmFactory.Filename)?.Length ?? 0}"); + } + }); + } + } +} diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 696f930467..cd56cb51ae 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -4,6 +4,7 @@ + From cfd3bdf888fc24df4bc8eeb0f8def24471352bbb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Oct 2021 01:32:28 +0900 Subject: [PATCH 019/183] Ensure realm blocks until all threaded usages are completed --- osu.Game/Database/RealmContextFactory.cs | 39 +++++++++++++++++++----- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/RealmContextFactory.cs b/osu.Game/Database/RealmContextFactory.cs index c51ac095bb..e3b0764721 100644 --- a/osu.Game/Database/RealmContextFactory.cs +++ b/osu.Game/Database/RealmContextFactory.cs @@ -133,20 +133,43 @@ namespace osu.Game.Database if (IsDisposed) throw new ObjectDisposedException(nameof(RealmContextFactory)); + // TODO: this can be added for safety once we figure how to bypass in test + // if (!ThreadSafety.IsUpdateThread) + // throw new InvalidOperationException($"{nameof(BlockAllOperations)} must be called from the update thread."); + Logger.Log(@"Blocking realm operations.", LoggingTarget.Database); - contextCreationLock.Wait(); + try + { + contextCreationLock.Wait(); - context?.Dispose(); - context = null; + const int sleep_length = 200; + int timeout = 5000; - return new InvokeOnDisposal(this, endBlockingSection); + context?.Dispose(); + context = null; - static void endBlockingSection(RealmContextFactory factory) + // see https://github.com/realm/realm-dotnet/discussions/2657 + while (!Compact()) + { + Thread.Sleep(sleep_length); + timeout -= sleep_length; + + if (timeout < 0) + throw new TimeoutException("Took too long to acquire lock"); + } + } + catch + { + contextCreationLock.Release(); + throw; + } + + return new InvokeOnDisposal(this, factory => { factory.contextCreationLock.Release(); Logger.Log(@"Restoring realm operations.", LoggingTarget.Database); - } + }); } protected override void Dispose(bool isDisposing) @@ -155,8 +178,8 @@ namespace osu.Game.Database if (!IsDisposed) { - // intentionally block all operations indefinitely. this ensures that nothing can start consuming a new context after disposal. - BlockAllOperations(); + // intentionally block context creation indefinitely. this ensures that nothing can start consuming a new context after disposal. + contextCreationLock.Wait(); contextCreationLock.Dispose(); } From b5345235cae7edb8430f31e97139f1bbe016ee95 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Oct 2021 10:40:55 +0900 Subject: [PATCH 020/183] Handle window file access errors --- osu.Game.Tests/Database/RealmTest.cs | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs index 2f4838cb67..b7658d6408 100644 --- a/osu.Game.Tests/Database/RealmTest.cs +++ b/osu.Game.Tests/Database/RealmTest.cs @@ -38,10 +38,26 @@ namespace osu.Game.Tests.Database testAction(realmFactory, testStorage); realmFactory.Dispose(); - Logger.Log($"Final database size: {testStorage.GetStream(realmFactory.Filename)?.Length ?? 0}"); + + try + { + Logger.Log($"Final database size: {testStorage.GetStream(realmFactory.Filename)?.Length ?? 0}"); + } + catch + { + // windows runs may error due to file still being open. + } realmFactory.Compact(); - Logger.Log($"Final database size after compact: {testStorage.GetStream(realmFactory.Filename)?.Length ?? 0}"); + + try + { + Logger.Log($"Final database size after compact: {testStorage.GetStream(realmFactory.Filename)?.Length ?? 0}"); + } + catch + { + // windows runs may error due to file still being open. + } } }); } From 619dfe06907d74d3054dbdb3f23c9d0444b505d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Oct 2021 14:34:11 +0900 Subject: [PATCH 021/183] Add new interface base types for models --- osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs | 90 +++++++++++++++ osu.Game/Beatmaps/IBeatmapInfo.cs | 74 +++++++++++++ osu.Game/Beatmaps/IBeatmapMetadataInfo.cs | 117 ++++++++++++++++++++ osu.Game/Beatmaps/IBeatmapSetInfo.cs | 57 ++++++++++ osu.Game/Beatmaps/IFileInfo.cs | 18 +++ osu.Game/Beatmaps/IHasOnlineID.cs | 15 +++ osu.Game/Beatmaps/INamedFileUsage.cs | 23 ++++ osu.Game/Beatmaps/IRulesetInfo.cs | 46 ++++++++ 8 files changed, 440 insertions(+) create mode 100644 osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs create mode 100644 osu.Game/Beatmaps/IBeatmapInfo.cs create mode 100644 osu.Game/Beatmaps/IBeatmapMetadataInfo.cs create mode 100644 osu.Game/Beatmaps/IBeatmapSetInfo.cs create mode 100644 osu.Game/Beatmaps/IFileInfo.cs create mode 100644 osu.Game/Beatmaps/IHasOnlineID.cs create mode 100644 osu.Game/Beatmaps/INamedFileUsage.cs create mode 100644 osu.Game/Beatmaps/IRulesetInfo.cs diff --git a/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs b/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs new file mode 100644 index 0000000000..6d9fcfcb06 --- /dev/null +++ b/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs @@ -0,0 +1,90 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +namespace osu.Game.Beatmaps +{ + /// + /// A representation of all top-level difficulty settings for a beatmap. + /// + public interface IBeatmapDifficultyInfo + { + /// + /// The default value used for all difficulty settings except and . + /// + const float DEFAULT_DIFFICULTY = 5; + + /// + /// The drain rate of the associated beatmap. + /// + float DrainRate { get; } + + /// + /// The circle size of the associated beatmap. + /// + float CircleSize { get; } + + /// + /// The overall difficulty of the associated beatmap. + /// + float OverallDifficulty { get; } + + /// + /// The approach rate of the associated beatmap. + /// + float ApproachRate { get; } + + /// + /// The slider multiplier of the associated beatmap. + /// + double SliderMultiplier { get; } + + /// + /// The slider tick rate of the associated beatmap. + /// + double SliderTickRate { get; } + + /// + /// Maps a difficulty value [0, 10] to a two-piece linear range of values. + /// + /// The difficulty value to be mapped. + /// Minimum of the resulting range which will be achieved by a difficulty value of 0. + /// Midpoint of the resulting range which will be achieved by a difficulty value of 5. + /// Maximum of the resulting range which will be achieved by a difficulty value of 10. + /// Value to which the difficulty value maps in the specified range. + static double DifficultyRange(double difficulty, double min, double mid, double max) + { + if (difficulty > 5) + return mid + (max - mid) * (difficulty - 5) / 5; + if (difficulty < 5) + return mid - (mid - min) * (5 - difficulty) / 5; + + return mid; + } + + /// + /// Maps a difficulty value [0, 10] to a two-piece linear range of values. + /// + /// The difficulty value to be mapped. + /// The values that define the two linear ranges. + /// + /// + /// od0 + /// Minimum of the resulting range which will be achieved by a difficulty value of 0. + /// + /// + /// od5 + /// Midpoint of the resulting range which will be achieved by a difficulty value of 5. + /// + /// + /// od10 + /// Maximum of the resulting range which will be achieved by a difficulty value of 10. + /// + /// + /// + /// Value to which the difficulty value maps in the specified range. + public static double DifficultyRange(double difficulty, (double od0, double od5, double od10) range) + => DifficultyRange(difficulty, range.od0, range.od5, range.od10); + } +} diff --git a/osu.Game/Beatmaps/IBeatmapInfo.cs b/osu.Game/Beatmaps/IBeatmapInfo.cs new file mode 100644 index 0000000000..72a04621f2 --- /dev/null +++ b/osu.Game/Beatmaps/IBeatmapInfo.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Localisation; + +#nullable enable + +namespace osu.Game.Beatmaps +{ + /// + /// A single beatmap difficulty. + /// + public interface IBeatmapInfo : IHasOnlineID + { + /// + /// The user-specified name given to this beatmap. + /// + string DifficultyName { get; } + + /// + /// The metadata representing this beatmap. May be shared between multiple beatmaps. + /// + IBeatmapMetadataInfo Metadata { get; } + + /// + /// The difficulty settings for this beatmap. + /// + IBeatmapDifficultyInfo Difficulty { get; } + + /// + /// The playable length in milliseconds of this beatmap. + /// + double Length { get; } + + /// + /// The most common BPM of this beatmap. + /// + double BPM { get; } + + /// + /// The SHA-256 hash representing this beatmap's contents. + /// + string Hash { get; } + + /// + /// MD5 is kept for legacy support (matching against replays, osu-web-10 etc.). + /// + string MD5Hash { get; } + + /// + /// The ruleset this beatmap was made for. + /// + IRulesetInfo Ruleset { get; } + + /// + /// The basic star rating for this beatmap (with no mods applied). + /// + double StarRating { get; } + + string DisplayTitle => $"{Metadata} {versionString}".Trim(); + + RomanisableString DisplayTitleRomanisable + { + get + { + var metadata = Metadata.DisplayTitleRomanisable; + + return new RomanisableString($"{metadata.GetPreferred(true)} {versionString}".Trim(), $"{metadata.GetPreferred(false)} {versionString}".Trim()); + } + } + + private string versionString => string.IsNullOrEmpty(DifficultyName) ? string.Empty : $"[{DifficultyName}]"; + } +} diff --git a/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs b/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs new file mode 100644 index 0000000000..18dd38b404 --- /dev/null +++ b/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs @@ -0,0 +1,117 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using osu.Framework.Localisation; + +#nullable enable + +namespace osu.Game.Beatmaps +{ + /// + /// Metadata representing a beatmap. May be shared between multiple beatmap difficulties. + /// + public interface IBeatmapMetadataInfo : IEquatable + { + /// + /// The romanised title of this beatmap. + /// + string Title { get; } + + /// + /// The unicode title of this beatmap. + /// + string TitleUnicode { get; } + + /// + /// The romanised artist of this beatmap. + /// + string Artist { get; } + + /// + /// The unicode artist of this beatmap. + /// + string ArtistUnicode { get; } + + /// + /// The author of this beatmap. + /// + string Author { get; } // eventually should be linked to a persisted User. + + /// + /// The source of this beatmap. + /// + string Source { get; } + + /// + /// The tags of this beatmap. + /// + string Tags { get; } + + /// + /// The time in milliseconds to begin playing the track for preview purposes. + /// If -1, the track should begin playing at 40% of its length. + /// + int PreviewTime { get; } + + /// + /// The filename of the audio file consumed by this beatmap. + /// + string AudioFile { get; } + + /// + /// The filename of the background image file consumed by this beatmap. + /// + string BackgroundFile { get; } + + string DisplayTitle + { + get + { + string author = string.IsNullOrEmpty(Author) ? string.Empty : $"({Author})"; + return $"{Artist} - {Title} {author}".Trim(); + } + } + + RomanisableString DisplayTitleRomanisable + { + get + { + string author = string.IsNullOrEmpty(Author) ? string.Empty : $"({Author})"; + var artistUnicode = string.IsNullOrEmpty(ArtistUnicode) ? Artist : ArtistUnicode; + var titleUnicode = string.IsNullOrEmpty(TitleUnicode) ? Title : TitleUnicode; + + return new RomanisableString($"{artistUnicode} - {titleUnicode} {author}".Trim(), $"{Artist} - {Title} {author}".Trim()); + } + } + + string[] SearchableTerms => new[] + { + Author, + Artist, + ArtistUnicode, + Title, + TitleUnicode, + Source, + Tags + }.Where(s => !string.IsNullOrEmpty(s)).ToArray(); + + bool IEquatable.Equals(IBeatmapMetadataInfo? other) + { + if (other == null) + return false; + + return Title == other.Title + && TitleUnicode == other.TitleUnicode + && Artist == other.Artist + && ArtistUnicode == other.ArtistUnicode + && Author == other.Author + && Source == other.Source + && Tags == other.Tags + && PreviewTime == other.PreviewTime + && AudioFile == other.AudioFile + && BackgroundFile == other.BackgroundFile; + } + } +} diff --git a/osu.Game/Beatmaps/IBeatmapSetInfo.cs b/osu.Game/Beatmaps/IBeatmapSetInfo.cs new file mode 100644 index 0000000000..f22115e08f --- /dev/null +++ b/osu.Game/Beatmaps/IBeatmapSetInfo.cs @@ -0,0 +1,57 @@ +// 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; + +#nullable enable + +namespace osu.Game.Beatmaps +{ + /// + /// A representation of a collection of beatmap difficulties, generally packaged as an ".osz" archive. + /// + public interface IBeatmapSetInfo : IHasOnlineID + { + /// + /// The date when this beatmap was imported. + /// + DateTimeOffset DateAdded { get; } + + /// + /// The best-effort metadata representing this set. In the case metadata differs between contained beatmaps, one is arbitrarily chosen. + /// + IBeatmapMetadataInfo? Metadata { get; } + + /// + /// All beatmaps contained in this set. + /// + IEnumerable Beatmaps { get; } + + /// + /// All files used by this set. + /// + IEnumerable Files { get; } + + /// + /// The maximum star difficulty of all beatmaps in this set. + /// + double MaxStarDifficulty { get; } + + /// + /// The maximum playable length in milliseconds of all beatmaps in this set. + /// + double MaxLength { get; } + + /// + /// The maximum BPM of all beatmaps in this set. + /// + double MaxBPM { get; } + + /// + /// The filename for the storyboard. + /// + string StoryboardFile => Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename ?? string.Empty; + } +} diff --git a/osu.Game/Beatmaps/IFileInfo.cs b/osu.Game/Beatmaps/IFileInfo.cs new file mode 100644 index 0000000000..50eb223fc4 --- /dev/null +++ b/osu.Game/Beatmaps/IFileInfo.cs @@ -0,0 +1,18 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +namespace osu.Game.Beatmaps +{ + /// + /// A representation of a tracked file. + /// + public interface IFileInfo + { + /// + /// SHA-256 hash of the file content. + /// + string Hash { get; } + } +} diff --git a/osu.Game/Beatmaps/IHasOnlineID.cs b/osu.Game/Beatmaps/IHasOnlineID.cs new file mode 100644 index 0000000000..dc2793afe5 --- /dev/null +++ b/osu.Game/Beatmaps/IHasOnlineID.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +namespace osu.Game.Beatmaps +{ + public interface IHasOnlineID + { + /// + /// The server-side ID representing this instance, if one exists. + /// + int? OnlineID { get; } + } +} diff --git a/osu.Game/Beatmaps/INamedFileUsage.cs b/osu.Game/Beatmaps/INamedFileUsage.cs new file mode 100644 index 0000000000..aa7a3852a7 --- /dev/null +++ b/osu.Game/Beatmaps/INamedFileUsage.cs @@ -0,0 +1,23 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +namespace osu.Game.Beatmaps +{ + /// + /// A usage of a file, with a local filename attached. + /// + public interface INamedFileUsage + { + /// + /// The underlying file on disk. + /// + IFileInfo File { get; } + + /// + /// The filename for this usage. + /// + string Filename { get; } + } +} diff --git a/osu.Game/Beatmaps/IRulesetInfo.cs b/osu.Game/Beatmaps/IRulesetInfo.cs new file mode 100644 index 0000000000..b31ebdfbfd --- /dev/null +++ b/osu.Game/Beatmaps/IRulesetInfo.cs @@ -0,0 +1,46 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Rulesets; + +#nullable enable + +namespace osu.Game.Beatmaps +{ + /// + /// A representation of a ruleset's metadata. + /// + public interface IRulesetInfo : IHasOnlineID + { + /// + /// The user-exposed name of this ruleset. + /// + string Name { get; } + + /// + /// An acronym defined by the ruleset that can be used as a permanent identifier. + /// + string ShortName { get; } + + /// + /// A string representation of this ruleset, to be used with reflection to instantiate the ruleset represented by this metadata. + /// + string InstantiationInfo { get; } + + public Ruleset? CreateInstance() + { + var type = Type.GetType(InstantiationInfo); + + if (type == null) + return null; + + var ruleset = Activator.CreateInstance(type) as Ruleset; + + // overwrite the pre-populated RulesetInfo with a potentially database attached copy. + // ruleset.RulesetInfo = this; + + return ruleset; + } + } +} From d30963646077df2eb6f1d2377d7bd56585071e8c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Oct 2021 16:31:11 +0900 Subject: [PATCH 022/183] Update all EF based models to implement new read only interfaces --- osu.Game/Beatmaps/BeatmapInfo.cs | 19 ++++++++++++++++++- osu.Game/Beatmaps/BeatmapMetadata.cs | 4 +++- osu.Game/Beatmaps/BeatmapSetFileInfo.cs | 4 +++- osu.Game/Beatmaps/BeatmapSetInfo.cs | 16 +++++++++++++++- osu.Game/Beatmaps/IBeatmapInfo.cs | 2 ++ osu.Game/Beatmaps/IBeatmapSetInfo.cs | 1 + .../{Beatmaps => Database}/IHasOnlineID.cs | 2 +- .../{Beatmaps => Database}/INamedFileUsage.cs | 4 +++- osu.Game/IO/FileInfo.cs | 2 +- osu.Game/{Beatmaps => IO}/IFileInfo.cs | 2 +- .../{Beatmaps => Rulesets}/IRulesetInfo.cs | 4 ++-- osu.Game/Rulesets/RulesetInfo.cs | 8 +++++++- 12 files changed, 57 insertions(+), 11 deletions(-) rename osu.Game/{Beatmaps => Database}/IHasOnlineID.cs (92%) rename osu.Game/{Beatmaps => Database}/INamedFileUsage.cs (92%) rename osu.Game/{Beatmaps => IO}/IFileInfo.cs (93%) rename osu.Game/{Beatmaps => Rulesets}/IRulesetInfo.cs (96%) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 8cb5da8083..d2b47ef1a4 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -17,13 +17,14 @@ namespace osu.Game.Beatmaps { [ExcludeFromDynamicCompile] [Serializable] - public class BeatmapInfo : IEquatable, IHasPrimaryKey + public class BeatmapInfo : IEquatable, IHasPrimaryKey, IBeatmapInfo { public int ID { get; set; } public int BeatmapVersion; private int? onlineBeatmapID; + private IRulesetInfo ruleset; [JsonProperty("id")] public int? OnlineBeatmapID @@ -187,5 +188,21 @@ namespace osu.Game.Beatmaps /// Returns a shallow-clone of this . /// public BeatmapInfo Clone() => (BeatmapInfo)MemberwiseClone(); + + #region Implementation of IHasOnlineID + + public int? OnlineID => ID; + + #endregion + + #region Implementation of IBeatmapInfo + + public string DifficultyName => Version; + IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata; + public IBeatmapDifficultyInfo Difficulty => BaseDifficulty; + IRulesetInfo IBeatmapInfo.Ruleset => Ruleset; + public double StarRating => StarDifficulty; + + #endregion } } diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 713f80d1fe..fbd47d2614 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -15,7 +15,7 @@ namespace osu.Game.Beatmaps { [ExcludeFromDynamicCompile] [Serializable] - public class BeatmapMetadata : IEquatable, IHasPrimaryKey + public class BeatmapMetadata : IEquatable, IHasPrimaryKey, IBeatmapMetadataInfo { public int ID { get; set; } @@ -128,5 +128,7 @@ namespace osu.Game.Beatmaps && AudioFile == other.AudioFile && BackgroundFile == other.BackgroundFile; } + + string IBeatmapMetadataInfo.Author => AuthorString; } } diff --git a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs index 3a55dc1577..ce50463f05 100644 --- a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs @@ -7,7 +7,7 @@ using osu.Game.IO; namespace osu.Game.Beatmaps { - public class BeatmapSetFileInfo : INamedFileInfo, IHasPrimaryKey + public class BeatmapSetFileInfo : INamedFileInfo, IHasPrimaryKey, INamedFileUsage { public int ID { get; set; } @@ -19,5 +19,7 @@ namespace osu.Game.Beatmaps [Required] public string Filename { get; set; } + + public IFileInfo File => FileInfo; } } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 3b1ff4ced0..739acb9a8d 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -12,7 +12,7 @@ using osu.Game.Database; namespace osu.Game.Beatmaps { [ExcludeFromDynamicCompile] - public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete, IEquatable + public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete, IEquatable, IBeatmapSetInfo { public int ID { get; set; } @@ -90,5 +90,19 @@ namespace osu.Game.Beatmaps return ReferenceEquals(this, other); } + + #region Implementation of IHasOnlineID + + public int? OnlineID => ID; + + #endregion + + #region Implementation of IBeatmapSetInfo + + IBeatmapMetadataInfo IBeatmapSetInfo.Metadata => Metadata; + IEnumerable IBeatmapSetInfo.Beatmaps => Beatmaps; + IEnumerable IBeatmapSetInfo.Files => Files; + + #endregion } } diff --git a/osu.Game/Beatmaps/IBeatmapInfo.cs b/osu.Game/Beatmaps/IBeatmapInfo.cs index 72a04621f2..8ba8f316ed 100644 --- a/osu.Game/Beatmaps/IBeatmapInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapInfo.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Localisation; +using osu.Game.Database; +using osu.Game.Rulesets; #nullable enable diff --git a/osu.Game/Beatmaps/IBeatmapSetInfo.cs b/osu.Game/Beatmaps/IBeatmapSetInfo.cs index f22115e08f..548a48367c 100644 --- a/osu.Game/Beatmaps/IBeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapSetInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Game.Database; #nullable enable diff --git a/osu.Game/Beatmaps/IHasOnlineID.cs b/osu.Game/Database/IHasOnlineID.cs similarity index 92% rename from osu.Game/Beatmaps/IHasOnlineID.cs rename to osu.Game/Database/IHasOnlineID.cs index dc2793afe5..c55c461d2d 100644 --- a/osu.Game/Beatmaps/IHasOnlineID.cs +++ b/osu.Game/Database/IHasOnlineID.cs @@ -3,7 +3,7 @@ #nullable enable -namespace osu.Game.Beatmaps +namespace osu.Game.Database { public interface IHasOnlineID { diff --git a/osu.Game/Beatmaps/INamedFileUsage.cs b/osu.Game/Database/INamedFileUsage.cs similarity index 92% rename from osu.Game/Beatmaps/INamedFileUsage.cs rename to osu.Game/Database/INamedFileUsage.cs index aa7a3852a7..e558ffe0fb 100644 --- a/osu.Game/Beatmaps/INamedFileUsage.cs +++ b/osu.Game/Database/INamedFileUsage.cs @@ -1,9 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Game.IO; + #nullable enable -namespace osu.Game.Beatmaps +namespace osu.Game.Database { /// /// A usage of a file, with a local filename attached. diff --git a/osu.Game/IO/FileInfo.cs b/osu.Game/IO/FileInfo.cs index e04bfb46cc..331546f9f8 100644 --- a/osu.Game/IO/FileInfo.cs +++ b/osu.Game/IO/FileInfo.cs @@ -6,7 +6,7 @@ using osu.Game.Database; namespace osu.Game.IO { - public class FileInfo : IHasPrimaryKey + public class FileInfo : IHasPrimaryKey, IFileInfo { public int ID { get; set; } diff --git a/osu.Game/Beatmaps/IFileInfo.cs b/osu.Game/IO/IFileInfo.cs similarity index 93% rename from osu.Game/Beatmaps/IFileInfo.cs rename to osu.Game/IO/IFileInfo.cs index 50eb223fc4..080d8e57f5 100644 --- a/osu.Game/Beatmaps/IFileInfo.cs +++ b/osu.Game/IO/IFileInfo.cs @@ -3,7 +3,7 @@ #nullable enable -namespace osu.Game.Beatmaps +namespace osu.Game.IO { /// /// A representation of a tracked file. diff --git a/osu.Game/Beatmaps/IRulesetInfo.cs b/osu.Game/Rulesets/IRulesetInfo.cs similarity index 96% rename from osu.Game/Beatmaps/IRulesetInfo.cs rename to osu.Game/Rulesets/IRulesetInfo.cs index b31ebdfbfd..d4dec0de64 100644 --- a/osu.Game/Beatmaps/IRulesetInfo.cs +++ b/osu.Game/Rulesets/IRulesetInfo.cs @@ -2,11 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Game.Rulesets; +using osu.Game.Database; #nullable enable -namespace osu.Game.Beatmaps +namespace osu.Game.Rulesets { /// /// A representation of a ruleset's metadata. diff --git a/osu.Game/Rulesets/RulesetInfo.cs b/osu.Game/Rulesets/RulesetInfo.cs index 59ec9cdd7e..ca6a083a58 100644 --- a/osu.Game/Rulesets/RulesetInfo.cs +++ b/osu.Game/Rulesets/RulesetInfo.cs @@ -10,7 +10,7 @@ using osu.Framework.Testing; namespace osu.Game.Rulesets { [ExcludeFromDynamicCompile] - public class RulesetInfo : IEquatable + public class RulesetInfo : IEquatable, IRulesetInfo { public int? ID { get; set; } @@ -54,5 +54,11 @@ namespace osu.Game.Rulesets } public override string ToString() => Name ?? $"{Name} ({ShortName}) ID: {ID}"; + + #region Implementation of IHasOnlineID + + public int? OnlineID => ID; + + #endregion } } From 8595eb2d11d8540b01587efe5bd2ec0494639c34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Oct 2021 14:38:30 +0900 Subject: [PATCH 023/183] Switch `BeatmapDifficulty` usages to use interface type --- osu.Game/Beatmaps/BeatmapDifficulty.cs | 45 +------------------ osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs | 2 +- .../Scoring/DrainingHealthProcessor.cs | 3 +- osu.Game/Rulesets/Scoring/HitWindows.cs | 4 +- 4 files changed, 7 insertions(+), 47 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 1844b193f2..0443422b31 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -2,10 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Database; +using osu.Game.Models.Interfaces; namespace osu.Game.Beatmaps { - public class BeatmapDifficulty : IHasPrimaryKey + public class BeatmapDifficulty : IHasPrimaryKey, IBeatmapDifficultyInfo { /// /// The default value used for all difficulty settings except and . @@ -49,47 +50,5 @@ namespace osu.Game.Beatmaps difficulty.SliderMultiplier = SliderMultiplier; difficulty.SliderTickRate = SliderTickRate; } - - /// - /// Maps a difficulty value [0, 10] to a two-piece linear range of values. - /// - /// The difficulty value to be mapped. - /// Minimum of the resulting range which will be achieved by a difficulty value of 0. - /// Midpoint of the resulting range which will be achieved by a difficulty value of 5. - /// Maximum of the resulting range which will be achieved by a difficulty value of 10. - /// Value to which the difficulty value maps in the specified range. - public static double DifficultyRange(double difficulty, double min, double mid, double max) - { - if (difficulty > 5) - return mid + (max - mid) * (difficulty - 5) / 5; - if (difficulty < 5) - return mid - (mid - min) * (5 - difficulty) / 5; - - return mid; - } - - /// - /// Maps a difficulty value [0, 10] to a two-piece linear range of values. - /// - /// The difficulty value to be mapped. - /// The values that define the two linear ranges. - /// - /// - /// od0 - /// Minimum of the resulting range which will be achieved by a difficulty value of 0. - /// - /// - /// od5 - /// Midpoint of the resulting range which will be achieved by a difficulty value of 5. - /// - /// - /// od10 - /// Maximum of the resulting range which will be achieved by a difficulty value of 10. - /// - /// - /// - /// Value to which the difficulty value maps in the specified range. - public static double DifficultyRange(double difficulty, (double od0, double od5, double od10) range) - => DifficultyRange(difficulty, range.od0, range.od5, range.od10); } } diff --git a/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs b/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs index 6d9fcfcb06..339364d442 100644 --- a/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapDifficultyInfo.cs @@ -84,7 +84,7 @@ namespace osu.Game.Beatmaps /// /// /// Value to which the difficulty value maps in the specified range. - public static double DifficultyRange(double difficulty, (double od0, double od5, double od10) range) + static double DifficultyRange(double difficulty, (double od0, double od5, double od10) range) => DifficultyRange(difficulty, range.od0, range.od5, range.od10); } } diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index cae41e22f4..785a729c6d 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; +using osu.Game.Models.Interfaces; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Utils; @@ -100,7 +101,7 @@ namespace osu.Game.Rulesets.Scoring .First() ))); - targetMinimumHealth = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, min_health_target, mid_health_target, max_health_target); + targetMinimumHealth = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, min_health_target, mid_health_target, max_health_target); // Add back a portion of the amount of HP to be drained, depending on the lenience requested. targetMinimumHealth += drainLenience * (1 - targetMinimumHealth); diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs index 410614de07..71a2af03fc 100644 --- a/osu.Game/Rulesets/Scoring/HitWindows.cs +++ b/osu.Game/Rulesets/Scoring/HitWindows.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using osu.Game.Beatmaps; +using osu.Game.Models.Interfaces; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Scoring @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Scoring { foreach (var range in GetRanges()) { - var value = BeatmapDifficulty.DifficultyRange(difficulty, (range.Min, range.Average, range.Max)); + var value = IBeatmapDifficultyInfo.DifficultyRange(difficulty, (range.Min, range.Average, range.Max)); switch (range.Result) { From a92d499d7a33f938cbeb91a1d174ff6d669c1cd2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Oct 2021 14:56:42 +0900 Subject: [PATCH 024/183] Convert usages of `BeatmapDifficulty` to `IBeatmapDifficultyInfo` --- .../TestSceneCatcher.cs | 4 ++-- .../TestSceneCatcherArea.cs | 4 ++-- .../Difficulty/CatchDifficultyCalculator.cs | 2 +- .../Edit/CatchEditorPlayfield.cs | 2 +- .../Objects/CatchHitObject.cs | 4 ++-- .../Objects/JuiceStream.cs | 2 +- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 4 ++-- osu.Game.Rulesets.Catch/UI/Catcher.cs | 6 +++--- .../UI/DrawableCatchRuleset.cs | 2 +- .../Beatmaps/ManiaBeatmapConverter.cs | 2 +- .../Patterns/Legacy/PatternGenerator.cs | 2 +- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 2 +- .../Checks/CheckTooShortSpinnersTest.cs | 12 +++++------ .../TestSceneObjectOrderedHitPolicy.cs | 2 +- .../TestSceneStartTimeOrderedHitPolicy.cs | 2 +- .../Difficulty/OsuDifficultyCalculator.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 2 +- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 4 ++-- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- .../Objects/SliderEndCircle.cs | 2 +- osu.Game.Rulesets.Osu/Objects/SliderTick.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 4 ++-- .../Beatmaps/TaikoBeatmapConverter.cs | 7 ++++--- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 2 +- .../Scoring/TaikoHealthProcessor.cs | 4 ++-- osu.Game.Tournament/Components/SongBar.cs | 2 +- osu.Game/Beatmaps/BeatmapDifficulty.cs | 21 ++++++++++++++++++- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 2 +- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 2 +- osu.Game/Rulesets/Mods/ModHardRock.cs | 2 +- osu.Game/Rulesets/Objects/HitObject.cs | 4 ++-- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 2 +- .../Scoring/DrainingHealthProcessor.cs | 1 - osu.Game/Rulesets/Scoring/HitWindows.cs | 2 +- .../Screens/Select/Details/AdvancedStats.cs | 4 ++-- osu.Game/osu.Game.csproj | 3 +++ 36 files changed, 75 insertions(+), 53 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs index 540f02580f..f291bfed13 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcher.cs @@ -290,7 +290,7 @@ namespace osu.Game.Rulesets.Catch.Tests { public IEnumerable CaughtObjects => this.ChildrenOfType(); - public TestCatcher(DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty) + public TestCatcher(DroppedObjectContainer droppedObjectTarget, IBeatmapDifficultyInfo difficulty) : base(droppedObjectTarget, difficulty) { } @@ -298,7 +298,7 @@ namespace osu.Game.Rulesets.Catch.Tests public class TestKiaiFruit : Fruit { - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true }); base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index 6abfbdbe21..7cae9b18b9 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Tests private ScheduledDelegate addManyFruit; - private BeatmapDifficulty beatmapDifficulty; + private IBeatmapDifficultyInfo beatmapDifficulty; public TestSceneCatcherArea() { @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Catch.Tests private class TestCatcherArea : CatcherArea { - public TestCatcherArea(BeatmapDifficulty beatmapDifficulty) + public TestCatcherArea(IBeatmapDifficultyInfo beatmapDifficulty) { var droppedObjectContainer = new DroppedObjectContainer(); Add(droppedObjectContainer); diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 82d76252d2..5b1f613f8d 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty return new CatchDifficultyAttributes { Mods = mods, Skills = skills }; // this is the same as osu!, so there's potential to share the implementation... maybe - double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; + double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; return new CatchDifficultyAttributes { diff --git a/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs b/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs index 8c9f292aa9..046ba0ebce 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchEditorPlayfield.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Catch.Edit public class CatchEditorPlayfield : CatchPlayfield { // TODO fixme: the size of the catcher is not changed when circle size is changed in setup screen. - public CatchEditorPlayfield(BeatmapDifficulty difficulty) + public CatchEditorPlayfield(IBeatmapDifficultyInfo difficulty) : base(difficulty) { } diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index d43e6f1c8b..ee10cf9711 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -128,11 +128,11 @@ namespace osu.Game.Rulesets.Catch.Objects /// public int RandomSeed => (int)StartTime; - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); + TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; } diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index a8ad34fcbe..0d6925a83d 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Catch.Objects /// public double SpanDuration => Duration / this.SpanCount(); - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 1e20643a08..df32d917ce 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -34,9 +34,9 @@ namespace osu.Game.Rulesets.Catch.UI internal CatcherArea CatcherArea { get; private set; } - private readonly BeatmapDifficulty difficulty; + private readonly IBeatmapDifficultyInfo difficulty; - public CatchPlayfield(BeatmapDifficulty difficulty) + public CatchPlayfield(IBeatmapDifficultyInfo difficulty) { this.difficulty = difficulty; } diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 5cd85aac56..3745099010 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -124,7 +124,7 @@ namespace osu.Game.Rulesets.Catch.UI private readonly DrawablePool caughtBananaPool; private readonly DrawablePool caughtDropletPool; - public Catcher([NotNull] DroppedObjectContainer droppedObjectTarget, BeatmapDifficulty difficulty = null) + public Catcher([NotNull] DroppedObjectContainer droppedObjectTarget, IBeatmapDifficultyInfo difficulty = null) { this.droppedObjectTarget = droppedObjectTarget; @@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.Catch.UI /// /// Calculates the scale of the catcher based off the provided beatmap difficulty. /// - private static Vector2 calculateScale(BeatmapDifficulty difficulty) => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5); + private static Vector2 calculateScale(IBeatmapDifficultyInfo difficulty) => new Vector2(1.0f - 0.7f * (difficulty.CircleSize - 5) / 5); /// /// Calculates the width of the area used for attempting catches in gameplay. @@ -184,7 +184,7 @@ namespace osu.Game.Rulesets.Catch.UI /// Calculates the width of the area used for attempting catches in gameplay. /// /// The beatmap difficulty. - public static float CalculateCatchWidth(BeatmapDifficulty difficulty) => CalculateCatchWidth(calculateScale(difficulty)); + public static float CalculateCatchWidth(IBeatmapDifficultyInfo difficulty) => CalculateCatchWidth(calculateScale(difficulty)); /// /// Determine if this catcher can catch a in the current position. diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 8b6a074426..ba6e9224c9 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.UI : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Down; - TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); + TimeRange.Value = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); } protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 26393c8edb..9745285b38 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps protected override Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken) { - BeatmapDifficulty difficulty = original.BeatmapInfo.BaseDifficulty; + IBeatmapDifficultyInfo difficulty = original.BeatmapInfo.BaseDifficulty; int seed = (int)MathF.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)MathF.Round(difficulty.ApproachRate); Random = new FastRandom(seed); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index e643b82271..d65e78bb49 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (drainTime == 0) drainTime = 10000; - BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty; + IBeatmapDifficultyInfo difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty; conversionDifficulty = ((difficulty.DrainRate + Math.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15; conversionDifficulty = Math.Min(conversionDifficulty.Value, 12); diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index 43e876b7aa..c1937af7e4 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Mania.Objects /// private double tickSpacing = 50; - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs index 6a3f168ee1..787807a8ea 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/Checks/CheckTooShortSpinnersTest.cs @@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks public class CheckTooShortSpinnersTest { private CheckTooShortSpinners check; - private BeatmapDifficulty difficulty; + private IBeatmapDifficultyInfo difficulty; [SetUp] public void Setup() @@ -81,12 +81,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks assertTooShort(new List { spinnerHighOd }, difficultyHighOd); } - private void assertOk(List hitObjects, BeatmapDifficulty beatmapDifficulty) + private void assertOk(List hitObjects, IBeatmapDifficultyInfo beatmapDifficulty) { Assert.That(check.Run(getContext(hitObjects, beatmapDifficulty)), Is.Empty); } - private void assertVeryShort(List hitObjects, BeatmapDifficulty beatmapDifficulty) + private void assertVeryShort(List hitObjects, IBeatmapDifficultyInfo beatmapDifficulty) { var issues = check.Run(getContext(hitObjects, beatmapDifficulty)).ToList(); @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Assert.That(issues.First().Template is CheckTooShortSpinners.IssueTemplateVeryShort); } - private void assertTooShort(List hitObjects, BeatmapDifficulty beatmapDifficulty) + private void assertTooShort(List hitObjects, IBeatmapDifficultyInfo beatmapDifficulty) { var issues = check.Run(getContext(hitObjects, beatmapDifficulty)).ToList(); @@ -102,12 +102,12 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor.Checks Assert.That(issues.First().Template is CheckTooShortSpinners.IssueTemplateTooShort); } - private BeatmapVerifierContext getContext(List hitObjects, BeatmapDifficulty beatmapDifficulty) + private BeatmapVerifierContext getContext(List hitObjects, IBeatmapDifficultyInfo beatmapDifficulty) { var beatmap = new Beatmap { HitObjects = hitObjects, - BeatmapInfo = new BeatmapInfo { BaseDifficulty = beatmapDifficulty } + BeatmapInfo = new BeatmapInfo { BaseDifficulty = new BeatmapDifficulty(beatmapDifficulty) } }; return new BeatmapVerifierContext(beatmap, new TestWorkingBeatmap(beatmap)); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index 77a68b714b..cfce80a2b2 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -452,7 +452,7 @@ namespace osu.Game.Rulesets.Osu.Tests private class TestSpinner : Spinner { - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); SpinsRequired = 1; diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs index 177a4f50a1..1b85e0efde 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneStartTimeOrderedHitPolicy.cs @@ -412,7 +412,7 @@ namespace osu.Game.Rulesets.Osu.Tests private class TestSpinner : Spinner { - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); SpinsRequired = 1; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 4c8d0b2ce6..a8f10f44dc 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; - double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; + double preempt = (int)IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; int maxCombo = beatmap.HitObjects.Count; // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index 210d5e0403..b0c655b106 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Osu.Mods #region Reduce AR (IApplicableToDifficulty) - public void ReadFromDifficulty(BeatmapDifficulty difficulty) + public void ReadFromDifficulty(IBeatmapDifficultyInfo difficulty) { } diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 36629fa41e..7c45b2bc07 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -122,11 +122,11 @@ namespace osu.Game.Rulesets.Osu.Objects }); } - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, PREEMPT_MIN); + TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, PREEMPT_MIN); // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above. diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index c4420b1e87..1d2666f46b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Osu.Objects Path.Version.ValueChanged += _ => updateNestedPositions(); } - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs index a6aed2c00e..f893559548 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderEndCircle.cs @@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Osu.Objects public double SpanDuration => slider.SpanDuration; - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs index 725dbe81fb..e7e64954e9 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTick.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Objects public int SpanIndex { get; set; } public double SpanStartTime { get; set; } - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 194aa640f9..f85dc0d391 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects /// public int MaximumBonusSpins { get; protected set; } = 1; - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects double secondsDuration = Duration / 1000; - double minimumRotationsPerSecond = stable_matching_fudge * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5); + double minimumRotationsPerSecond = stable_matching_fudge * IBeatmapDifficultyInfo.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5); SpinsRequired = (int)(secondsDuration * minimumRotationsPerSecond); MaximumBonusSpins = (int)((maximum_rotations_per_second - minimumRotationsPerSecond) * secondsDuration); diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 9b73e644c5..3b5b972c01 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps case IHasDuration endTimeData: { - double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; + double hitMultiplier = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; yield return new Swell { @@ -193,9 +193,10 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps private class TaikoMutliplierAppliedDifficulty : BeatmapDifficulty { - public TaikoMutliplierAppliedDifficulty(BeatmapDifficulty difficulty) + public TaikoMutliplierAppliedDifficulty(IBeatmapDifficultyInfo difficulty) { - difficulty.CopyTo(this); + CopyFrom(difficulty); + SliderMultiplier *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; } } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index b0634295d0..0318e32991 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Taiko.Objects private float overallDifficulty = BeatmapDifficulty.DEFAULT_DIFFICULTY; - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs index f7a1d130eb..94cd411d7b 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs @@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Taiko.Scoring { base.ApplyBeatmap(beatmap); - hpMultiplier = 1 / (object_count_factor * Math.Max(1, beatmap.HitObjects.OfType().Count()) * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98)); - hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120); + hpMultiplier = 1 / (object_count_factor * Math.Max(1, beatmap.HitObjects.OfType().Count()) * IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98)); + hpMissMultiplier = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120); } protected override double GetHealthIncreaseFor(JudgementResult result) diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 6080f7b636..7bb01ddc6d 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -117,7 +117,7 @@ namespace osu.Game.Tournament.Components if ((mods & LegacyMods.DoubleTime) > 0) { // temporary local calculation (taken from OsuDifficultyCalculator) - double preempt = (int)BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450) / 1.5; + double preempt = (int)IBeatmapDifficultyInfo.DifficultyRange(ar, 1800, 1200, 450) / 1.5; ar = (float)(preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5); bpm *= 1.5f; diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 0443422b31..2bb0787b4c 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Database; -using osu.Game.Models.Interfaces; namespace osu.Game.Beatmaps { @@ -21,6 +20,15 @@ namespace osu.Game.Beatmaps private float? approachRate; + public BeatmapDifficulty() + { + } + + public BeatmapDifficulty(IBeatmapDifficultyInfo source) + { + CopyFrom(source); + } + public float ApproachRate { get => approachRate ?? OverallDifficulty; @@ -40,6 +48,17 @@ namespace osu.Game.Beatmaps return diff; } + public void CopyFrom(IBeatmapDifficultyInfo difficulty) + { + ApproachRate = difficulty.ApproachRate; + DrainRate = difficulty.DrainRate; + CircleSize = difficulty.CircleSize; + OverallDifficulty = difficulty.OverallDifficulty; + + SliderMultiplier = difficulty.SliderMultiplier; + SliderTickRate = difficulty.SliderTickRate; + } + public void CopyTo(BeatmapDifficulty difficulty) { difficulty.ApproachRate = ApproachRate; diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 82e90399c9..b7529f39ca 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -109,7 +109,7 @@ namespace osu.Game.Rulesets.Edit } /// - /// Invokes , + /// Invokes , /// refreshing and parameters for the . /// protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index b78c30e8a5..eefa1531c4 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.Mods } } - public void ReadFromDifficulty(BeatmapDifficulty difficulty) + public void ReadFromDifficulty(IBeatmapDifficultyInfo difficulty) { } diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index 4edcb0b074..da838f9ea6 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Mods public override string Description => "Everything just got a bit harder..."; public override Type[] IncompatibleMods => new[] { typeof(ModEasy), typeof(ModDifficultyAdjust) }; - public void ReadFromDifficulty(BeatmapDifficulty difficulty) + public void ReadFromDifficulty(IBeatmapDifficultyInfo difficulty) { } diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index ae0cb895bc..0b159819d4 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Objects /// The control points. /// The difficulty settings to use. /// The cancellation token. - public void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty, CancellationToken cancellationToken = default) + public void ApplyDefaults(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty, CancellationToken cancellationToken = default) { ApplyDefaultsToSelf(controlPointInfo, difficulty); @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Objects DefaultsApplied?.Invoke(this); } - protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected virtual void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { Kiai = controlPointInfo.EffectPointAt(StartTime + control_point_leniency).KiaiMode; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index df569b91c1..e1de82ade7 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public double Velocity = 1; - protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, IBeatmapDifficultyInfo difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index 785a729c6d..85693abb93 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; -using osu.Game.Models.Interfaces; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Utils; diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs index 71a2af03fc..3ffd1eb66b 100644 --- a/osu.Game/Rulesets/Scoring/HitWindows.cs +++ b/osu.Game/Rulesets/Scoring/HitWindows.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using osu.Game.Models.Interfaces; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Scoring diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 53e30fd9ca..a855322e57 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -106,12 +106,12 @@ namespace osu.Game.Screens.Select.Details private void updateStatistics() { - BeatmapDifficulty baseDifficulty = Beatmap?.BaseDifficulty; + IBeatmapDifficultyInfo baseDifficulty = Beatmap?.BaseDifficulty; BeatmapDifficulty adjustedDifficulty = null; if (baseDifficulty != null && mods.Value.Any(m => m is IApplicableToDifficulty)) { - adjustedDifficulty = baseDifficulty.Clone(); + adjustedDifficulty = new BeatmapDifficulty(baseDifficulty); foreach (var mod in mods.Value.OfType()) mod.ApplyToDifficulty(adjustedDifficulty); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ba118c5240..c8914353f8 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -43,4 +43,7 @@ + + + From 05996cc2e9232e7c54e1892ca2c3a253e7783513 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Oct 2021 17:03:08 +0900 Subject: [PATCH 025/183] Add changes that got forgotted in branch surgery --- .../Difficulty/CatchDifficultyCalculator.cs | 2 +- osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs | 2 +- osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs | 2 +- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 2 +- osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 2 +- osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs | 4 ++-- osu.Game.Tournament/Components/SongBar.cs | 2 +- osu.Game/Beatmaps/BeatmapDifficulty.cs | 1 - osu.Game/Beatmaps/BeatmapInfo.cs | 1 - osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs | 1 - osu.Game/Rulesets/Scoring/HitWindows.cs | 2 +- 13 files changed, 11 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 82d76252d2..5b1f613f8d 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty return new CatchDifficultyAttributes { Mods = mods, Skills = skills }; // this is the same as osu!, so there's potential to share the implementation... maybe - double preempt = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; + double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; return new CatchDifficultyAttributes { diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index d43e6f1c8b..32fdc9f62d 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -132,7 +132,7 @@ namespace osu.Game.Rulesets.Catch.Objects { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); + TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450); Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; } diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index 8b6a074426..ba6e9224c9 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.UI : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Down; - TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); + TimeRange.Value = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); } protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 4c8d0b2ce6..a8f10f44dc 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; - double preempt = (int)BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; + double preempt = (int)IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; int maxCombo = beatmap.HitObjects.Count; // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 36629fa41e..7015c5bce2 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Osu.Objects { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); - TimePreempt = (float)BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, PREEMPT_MIN); + TimePreempt = (float)IBeatmapDifficultyInfo.DifficultyRange(difficulty.ApproachRate, 1800, 1200, PREEMPT_MIN); // Preempt time can go below 450ms. Normally, this is achieved via the DT mod which uniformly speeds up all animations game wide regardless of AR. // This uniform speedup is hard to match 1:1, however we can at least make AR>10 (via mods) feel good by extending the upper linear function above. diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 194aa640f9..d5f08f049c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects double secondsDuration = Duration / 1000; - double minimumRotationsPerSecond = stable_matching_fudge * BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5); + double minimumRotationsPerSecond = stable_matching_fudge * IBeatmapDifficultyInfo.DifficultyRange(difficulty.OverallDifficulty, 3, 5, 7.5); SpinsRequired = (int)(secondsDuration * minimumRotationsPerSecond); MaximumBonusSpins = (int)((maximum_rotations_per_second - minimumRotationsPerSecond) * secondsDuration); diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 9b73e644c5..7068e469d2 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps case IHasDuration endTimeData: { - double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; + double hitMultiplier = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; yield return new Swell { diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs index f7a1d130eb..94cd411d7b 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs @@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Taiko.Scoring { base.ApplyBeatmap(beatmap); - hpMultiplier = 1 / (object_count_factor * Math.Max(1, beatmap.HitObjects.OfType().Count()) * BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98)); - hpMissMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120); + hpMultiplier = 1 / (object_count_factor * Math.Max(1, beatmap.HitObjects.OfType().Count()) * IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98)); + hpMissMultiplier = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120); } protected override double GetHealthIncreaseFor(JudgementResult result) diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 6080f7b636..7bb01ddc6d 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -117,7 +117,7 @@ namespace osu.Game.Tournament.Components if ((mods & LegacyMods.DoubleTime) > 0) { // temporary local calculation (taken from OsuDifficultyCalculator) - double preempt = (int)BeatmapDifficulty.DifficultyRange(ar, 1800, 1200, 450) / 1.5; + double preempt = (int)IBeatmapDifficultyInfo.DifficultyRange(ar, 1800, 1200, 450) / 1.5; ar = (float)(preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5); bpm *= 1.5f; diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 0443422b31..1bb1c86c1f 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Database; -using osu.Game.Models.Interfaces; namespace osu.Game.Beatmaps { diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index d2b47ef1a4..d6f3bf0de4 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -24,7 +24,6 @@ namespace osu.Game.Beatmaps public int BeatmapVersion; private int? onlineBeatmapID; - private IRulesetInfo ruleset; [JsonProperty("id")] public int? OnlineBeatmapID diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index 785a729c6d..85693abb93 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; -using osu.Game.Models.Interfaces; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Utils; diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs index 71a2af03fc..3ffd1eb66b 100644 --- a/osu.Game/Rulesets/Scoring/HitWindows.cs +++ b/osu.Game/Rulesets/Scoring/HitWindows.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using osu.Game.Models.Interfaces; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Scoring From 00e33a1da75496db67ccea0269e7f5d174b02f3d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Oct 2021 17:06:09 +0900 Subject: [PATCH 026/183] Fix incorrect `OnlineID` mappings --- osu.Game/Beatmaps/BeatmapInfo.cs | 2 +- osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index d6f3bf0de4..1b3e67e2e7 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -190,7 +190,7 @@ namespace osu.Game.Beatmaps #region Implementation of IHasOnlineID - public int? OnlineID => ID; + public int? OnlineID => OnlineBeatmapID; #endregion diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 739acb9a8d..7e26b154a9 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -93,7 +93,7 @@ namespace osu.Game.Beatmaps #region Implementation of IHasOnlineID - public int? OnlineID => ID; + public int? OnlineID => OnlineBeatmapSetID; #endregion From 9dae92e78cf9db6caf0b1e659d17be04017630d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Oct 2021 17:22:25 +0900 Subject: [PATCH 027/183] Add missing backlink to `BeatmapSet` from `Beatmap` and fix non-explicit implementations --- osu.Game/Beatmaps/BeatmapInfo.cs | 7 ++++--- osu.Game/Beatmaps/IBeatmapInfo.cs | 5 +++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 1b3e67e2e7..83e547218b 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -196,11 +196,12 @@ namespace osu.Game.Beatmaps #region Implementation of IBeatmapInfo - public string DifficultyName => Version; + string IBeatmapInfo.DifficultyName => Version; IBeatmapMetadataInfo IBeatmapInfo.Metadata => Metadata; - public IBeatmapDifficultyInfo Difficulty => BaseDifficulty; + IBeatmapDifficultyInfo IBeatmapInfo.Difficulty => BaseDifficulty; + IBeatmapSetInfo IBeatmapInfo.BeatmapSet => BeatmapSet; IRulesetInfo IBeatmapInfo.Ruleset => Ruleset; - public double StarRating => StarDifficulty; + double IBeatmapInfo.StarRating => StarDifficulty; #endregion } diff --git a/osu.Game/Beatmaps/IBeatmapInfo.cs b/osu.Game/Beatmaps/IBeatmapInfo.cs index 8ba8f316ed..fa2ce2949b 100644 --- a/osu.Game/Beatmaps/IBeatmapInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapInfo.cs @@ -29,6 +29,11 @@ namespace osu.Game.Beatmaps /// IBeatmapDifficultyInfo Difficulty { get; } + /// + /// The beatmap set this beatmap is part of. + /// + IBeatmapSetInfo BeatmapSet { get; } + /// /// The playable length in milliseconds of this beatmap. /// From d6618a99a3b885e0163043c76b011d648c5367af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Oct 2021 17:31:27 +0900 Subject: [PATCH 028/183] Redirect more methods to interface implementations --- osu.Game/Beatmaps/BeatmapInfo.cs | 8 ++--- osu.Game/Beatmaps/BeatmapMetadata.cs | 46 +++------------------------- osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 +- 3 files changed, 8 insertions(+), 48 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 83e547218b..09f237a5de 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -157,13 +157,9 @@ namespace osu.Game.Beatmaps Version }.Concat(Metadata?.SearchableTerms ?? Enumerable.Empty()).Where(s => !string.IsNullOrEmpty(s)).ToArray(); - public override string ToString() => $"{Metadata ?? BeatmapSet?.Metadata} {versionString}".Trim(); + public override string ToString() => ((IBeatmapInfo)this).DisplayTitle; - public RomanisableString ToRomanisableString() - { - var metadata = (Metadata ?? BeatmapSet?.Metadata)?.ToRomanisableString() ?? new RomanisableString(null, null); - return new RomanisableString($"{metadata.GetPreferred(true)} {versionString}".Trim(), $"{metadata.GetPreferred(false)} {versionString}".Trim()); - } + public RomanisableString ToRomanisableString() => ((IBeatmapInfo)this).DisplayTitleRomanisable; public bool Equals(BeatmapInfo other) { diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index fbd47d2614..3da80580cb 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; -using System.Linq; using Newtonsoft.Json; using osu.Framework.Localisation; using osu.Framework.Testing; @@ -83,51 +82,16 @@ namespace osu.Game.Beatmaps public int PreviewTime { get; set; } public string AudioFile { get; set; } + public string BackgroundFile { get; set; } - public override string ToString() - { - string author = Author == null ? string.Empty : $"({Author})"; - return $"{Artist} - {Title} {author}".Trim(); - } + public bool Equals(BeatmapMetadata other) => ((IBeatmapMetadataInfo)this).Equals(other); - public RomanisableString ToRomanisableString() - { - string author = Author == null ? string.Empty : $"({Author})"; - var artistUnicode = string.IsNullOrEmpty(ArtistUnicode) ? Artist : ArtistUnicode; - var titleUnicode = string.IsNullOrEmpty(TitleUnicode) ? Title : TitleUnicode; + public override string ToString() => ((IBeatmapMetadataInfo)this).DisplayTitle; - return new RomanisableString($"{artistUnicode} - {titleUnicode} {author}".Trim(), $"{Artist} - {Title} {author}".Trim()); - } + public RomanisableString ToRomanisableString() => ((IBeatmapMetadataInfo)this).DisplayTitleRomanisable; - [JsonIgnore] - public string[] SearchableTerms => new[] - { - Author?.Username, - Artist, - ArtistUnicode, - Title, - TitleUnicode, - Source, - Tags - }.Where(s => !string.IsNullOrEmpty(s)).ToArray(); - - public bool Equals(BeatmapMetadata other) - { - if (other == null) - return false; - - return Title == other.Title - && TitleUnicode == other.TitleUnicode - && Artist == other.Artist - && ArtistUnicode == other.ArtistUnicode - && AuthorString == other.AuthorString - && Source == other.Source - && Tags == other.Tags - && PreviewTime == other.PreviewTime - && AudioFile == other.AudioFile - && BackgroundFile == other.BackgroundFile; - } + public IEnumerable SearchableTerms => ((IBeatmapMetadataInfo)this).SearchableTerms; string IBeatmapMetadataInfo.Author => AuthorString; } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 7e26b154a9..4804c7032c 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -61,7 +61,7 @@ namespace osu.Game.Beatmaps public string Hash { get; set; } - public string StoryboardFile => Files.Find(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; + public string StoryboardFile => ((IBeatmapSetInfo)this).StoryboardFile; /// /// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null. From 0daf8937e3a954f0ea595e5a2c4131bdb616e0a2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Oct 2021 17:33:57 +0900 Subject: [PATCH 029/183] Add missing xmldoc --- osu.Game/Beatmaps/IBeatmapInfo.cs | 6 ++++++ osu.Game/Beatmaps/IBeatmapMetadataInfo.cs | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/osu.Game/Beatmaps/IBeatmapInfo.cs b/osu.Game/Beatmaps/IBeatmapInfo.cs index fa2ce2949b..6153a0af08 100644 --- a/osu.Game/Beatmaps/IBeatmapInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapInfo.cs @@ -64,8 +64,14 @@ namespace osu.Game.Beatmaps /// double StarRating { get; } + /// + /// A user-presentable display title representing this metadata. + /// string DisplayTitle => $"{Metadata} {versionString}".Trim(); + /// + /// A user-presentable display title representing this beatmap, with localisation handling for potentially romanisable fields. + /// RomanisableString DisplayTitleRomanisable { get diff --git a/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs b/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs index 18dd38b404..d0dae296a0 100644 --- a/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs @@ -65,6 +65,9 @@ namespace osu.Game.Beatmaps /// string BackgroundFile { get; } + /// + /// A user-presentable display title representing this metadata. + /// string DisplayTitle { get @@ -74,6 +77,9 @@ namespace osu.Game.Beatmaps } } + /// + /// A user-presentable display title representing this metadata, with localisation handling for potentially romanisable fields. + /// RomanisableString DisplayTitleRomanisable { get @@ -86,6 +92,9 @@ namespace osu.Game.Beatmaps } } + /// + /// An array of all searchable terms provided in contained metadata. + /// string[] SearchableTerms => new[] { Author, From 3faafd7200576bee1016ba6c75d2643e1d7af751 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Oct 2021 18:24:46 +0900 Subject: [PATCH 030/183] Rename parameter to `repeatCount` and add guards --- .../Formats/LegacyStoryboardDecoder.cs | 4 ++-- osu.Game/Storyboards/CommandLoop.cs | 23 ++++++++++++++----- osu.Game/Storyboards/StoryboardSprite.cs | 10 ++++---- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 6301c42deb..5b03212da4 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -176,8 +176,8 @@ namespace osu.Game.Beatmaps.Formats case "L": { var startTime = Parsing.ParseDouble(split[1]); - var loopCount = Parsing.ParseInt(split[2]); - timelineGroup = storyboardSprite?.AddLoop(startTime, loopCount); + var repeatCount = Parsing.ParseInt(split[2]); + timelineGroup = storyboardSprite?.AddLoop(startTime, Math.Max(0, repeatCount)); break; } diff --git a/osu.Game/Storyboards/CommandLoop.cs b/osu.Game/Storyboards/CommandLoop.cs index c17436d813..66db965803 100644 --- a/osu.Game/Storyboards/CommandLoop.cs +++ b/osu.Game/Storyboards/CommandLoop.cs @@ -9,20 +9,31 @@ namespace osu.Game.Storyboards public class CommandLoop : CommandTimelineGroup { public double LoopStartTime; - public int LoopCount; + + /// + /// The total number of times this loop is played back. Always greater than zero. + /// + public readonly int TotalIterations; public override double StartTime => LoopStartTime + CommandsStartTime; - public override double EndTime => StartTime + CommandsDuration * LoopCount; + public override double EndTime => StartTime + CommandsDuration * TotalIterations; - public CommandLoop(double startTime, int loopCount) + /// + /// Construct a new command loop. + /// + /// The start time of the loop. + /// The number of times the loop should repeat. Should be greater than zero. Zero means a single playback. + public CommandLoop(double startTime, int repeatCount) { + if (repeatCount < 0) throw new ArgumentException("Repeat count must be zero or above.", nameof(repeatCount)); + LoopStartTime = startTime; - LoopCount = Math.Max(1, loopCount); + TotalIterations = repeatCount + 1; } public override IEnumerable.TypedCommand> GetCommands(CommandTimelineSelector timelineSelector, double offset = 0) { - for (var loop = 0; loop < LoopCount; loop++) + for (var loop = 0; loop < TotalIterations; loop++) { var loopOffset = LoopStartTime + loop * CommandsDuration; foreach (var command in base.GetCommands(timelineSelector, offset + loopOffset)) @@ -31,6 +42,6 @@ namespace osu.Game.Storyboards } public override string ToString() - => $"{LoopStartTime} x{LoopCount}"; + => $"{LoopStartTime} x{TotalIterations}"; } } diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index bf87e7d10e..6fb2f5994b 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -1,13 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; -using osu.Framework.Graphics; -using osu.Game.Storyboards.Drawables; using System; using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; +using osu.Framework.Graphics; +using osu.Game.Storyboards.Drawables; +using osuTK; namespace osu.Game.Storyboards { @@ -78,9 +78,9 @@ namespace osu.Game.Storyboards InitialPosition = initialPosition; } - public CommandLoop AddLoop(double startTime, int loopCount) + public CommandLoop AddLoop(double startTime, int repeatCount) { - var loop = new CommandLoop(startTime, loopCount); + var loop = new CommandLoop(startTime, repeatCount); loops.Add(loop); return loop; } From 4c28749d7310a658e9a8329f39064afb03306311 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Oct 2021 19:05:08 +0900 Subject: [PATCH 031/183] Fix incorrect legacy decoder usage --- osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs index 5b03212da4..0f15e28c00 100644 --- a/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyStoryboardDecoder.cs @@ -177,7 +177,7 @@ namespace osu.Game.Beatmaps.Formats { var startTime = Parsing.ParseDouble(split[1]); var repeatCount = Parsing.ParseInt(split[2]); - timelineGroup = storyboardSprite?.AddLoop(startTime, Math.Max(0, repeatCount)); + timelineGroup = storyboardSprite?.AddLoop(startTime, Math.Max(0, repeatCount - 1)); break; } From 5820a7165268fcd9776e9fd1b33c27648884349d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Oct 2021 19:57:45 +0900 Subject: [PATCH 032/183] Fix mania difficulty calculator crashing --- .../Difficulty/ManiaDifficultyCalculator.cs | 8 ++-- .../Difficulty/DifficultyCalculator.cs | 38 +++++++++++-------- .../HUD/DefaultPerformancePointsCounter.cs | 4 +- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index a7a6677b68..fc29eadedc 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty Mods = mods, // Todo: This int cast is temporary to achieve 1:1 results with osu!stable, and should be removed in the future GreatHitWindow = (int)Math.Ceiling(getHitWindow300(mods) / clockRate), - ScoreMultiplier = getScoreMultiplier(beatmap, mods), + ScoreMultiplier = getScoreMultiplier(mods), MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1), Skills = skills }; @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) => new Skill[] { - new Strain(mods, ((ManiaBeatmap)beatmap).TotalColumns) + new Strain(mods, ((ManiaBeatmap)Beatmap).TotalColumns) }; protected override Mod[] DifficultyAdjustmentMods @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty } } - private double getScoreMultiplier(IBeatmap beatmap, Mod[] mods) + private double getScoreMultiplier(Mod[] mods) { double scoreMultiplier = 1; @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty } } - var maniaBeatmap = (ManiaBeatmap)beatmap; + var maniaBeatmap = (ManiaBeatmap)Beatmap; int diff = maniaBeatmap.TotalColumns - maniaBeatmap.OriginalTotalColumns; if (diff > 0) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index f38949d982..780c2ad491 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -18,13 +18,17 @@ namespace osu.Game.Rulesets.Difficulty { public abstract class DifficultyCalculator { - private readonly Ruleset ruleset; - private readonly WorkingBeatmap beatmap; + /// + /// The beatmap for which difficulty will be calculated. + /// + protected IBeatmap Beatmap { get; private set; } - private IBeatmap playableBeatmap; private Mod[] playableMods; private double clockRate; + private readonly Ruleset ruleset; + private readonly WorkingBeatmap beatmap; + protected DifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap) { this.ruleset = ruleset; @@ -40,10 +44,10 @@ namespace osu.Game.Rulesets.Difficulty { preProcess(mods); - var skills = CreateSkills(playableBeatmap, playableMods, clockRate); + var skills = CreateSkills(Beatmap, playableMods, clockRate); - if (!playableBeatmap.HitObjects.Any()) - return CreateDifficultyAttributes(playableBeatmap, playableMods, skills, clockRate); + if (!Beatmap.HitObjects.Any()) + return CreateDifficultyAttributes(Beatmap, playableMods, skills, clockRate); foreach (var hitObject in getDifficultyHitObjects()) { @@ -51,18 +55,18 @@ namespace osu.Game.Rulesets.Difficulty skill.ProcessInternal(hitObject); } - return CreateDifficultyAttributes(playableBeatmap, playableMods, skills, clockRate); + return CreateDifficultyAttributes(Beatmap, playableMods, skills, clockRate); } public IEnumerable CalculateTimed(params Mod[] mods) { preProcess(mods); - if (!playableBeatmap.HitObjects.Any()) + if (!Beatmap.HitObjects.Any()) yield break; - var skills = CreateSkills(playableBeatmap, playableMods, clockRate); - var progressiveBeatmap = new ProgressiveCalculationBeatmap(playableBeatmap); + var skills = CreateSkills(Beatmap, playableMods, clockRate); + var progressiveBeatmap = new ProgressiveCalculationBeatmap(Beatmap); foreach (var hitObject in getDifficultyHitObjects()) { @@ -93,7 +97,7 @@ namespace osu.Game.Rulesets.Difficulty /// /// Retrieves the s to calculate against. /// - private IEnumerable getDifficultyHitObjects() => SortObjects(CreateDifficultyHitObjects(playableBeatmap, clockRate)); + private IEnumerable getDifficultyHitObjects() => SortObjects(CreateDifficultyHitObjects(Beatmap, clockRate)); /// /// Performs required tasks before every calculation. @@ -102,7 +106,7 @@ namespace osu.Game.Rulesets.Difficulty private void preProcess(Mod[] mods) { playableMods = mods.Select(m => m.DeepClone()).ToArray(); - playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); + Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); var track = new TrackVirtual(10000); mods.OfType().ForEach(m => m.ApplyToTrack(track)); @@ -118,7 +122,7 @@ namespace osu.Game.Rulesets.Difficulty => input.OrderBy(h => h.BaseObject.StartTime); /// - /// Creates all combinations which adjust the difficulty. + /// Creates all combinations which adjust the difficulty. /// public Mod[] CreateDifficultyAdjustmentModCombinations() { @@ -186,14 +190,15 @@ namespace osu.Game.Rulesets.Difficulty } /// - /// Retrieves all s which adjust the difficulty. + /// Retrieves all s which adjust the difficulty. /// protected virtual Mod[] DifficultyAdjustmentMods => Array.Empty(); /// /// Creates to describe beatmap's calculated difficulty. /// - /// The whose difficulty was calculated. + /// The whose difficulty was calculated. + /// This may differ from in the case of timed calculation. /// The s that difficulty was calculated with. /// The skills which processed the beatmap. /// The rate at which the gameplay clock is run at. @@ -210,7 +215,8 @@ namespace osu.Game.Rulesets.Difficulty /// /// Creates the s to calculate the difficulty of an . /// - /// The whose difficulty will be calculated. + /// The whose difficulty will be calculated. + /// This may differ from in the case of timed calculation. /// Mods to calculate difficulty with. /// Clockrate to calculate difficulty with. /// The s. diff --git a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs index 563032b4ea..ff0c628aa6 100644 --- a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs @@ -143,9 +143,9 @@ namespace osu.Game.Screens.Play.HUD } public override IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null) - => gameplayBeatmap; + => gameplayBeatmap.PlayableBeatmap; - protected override IBeatmap GetBeatmap() => gameplayBeatmap; + protected override IBeatmap GetBeatmap() => gameplayBeatmap.PlayableBeatmap; protected override Texture GetBackground() => throw new NotImplementedException(); From 98badd644ff6b46d26500f48b125c65b2100410a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Oct 2021 20:09:39 +0900 Subject: [PATCH 033/183] Add xmldocs --- osu.Game/Graphics/UserInterface/RollingCounter.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index f03287e7de..67b0b6a06b 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -160,8 +160,15 @@ namespace osu.Game.Graphics.UserInterface this.TransformTo(nameof(DisplayedCount), newValue, rollingTotalDuration, RollingEasing); } + /// + /// Creates the text. Delegates to by default. + /// protected virtual IHasText CreateText() => CreateSpriteText(); + /// + /// Creates an which may be used to display this counter's text. + /// May not be called if is overridden. + /// protected virtual OsuSpriteText CreateSpriteText() => new OsuSpriteText { Font = OsuFont.Numeric.With(size: 40f), From d0081908c5af3f22aa3ef532b982e94390704910 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Oct 2021 20:52:48 +0900 Subject: [PATCH 034/183] Make Score internal --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4018650093..6b1186c5ce 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -137,7 +137,7 @@ namespace osu.Game.Screens.Play public readonly PlayerConfiguration Configuration; - public Score Score { get; private set; } + internal Score Score { get; private set; } /// /// Create a new player instance. From a1f880a36aede7bb087a9c25c0e618b17813e39a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Oct 2021 20:56:03 +0900 Subject: [PATCH 035/183] Split classes --- .../Difficulty/DifficultyCalculator.cs | 17 +++---------- .../Difficulty/TimedDifficultyAttributes.cs | 25 +++++++++++++++++++ .../HUD/DefaultPerformancePointsCounter.cs | 4 +-- 3 files changed, 30 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Rulesets/Difficulty/TimedDifficultyAttributes.cs diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 780c2ad491..3d90cc59f4 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -222,20 +222,9 @@ namespace osu.Game.Rulesets.Difficulty /// The s. protected abstract Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate); - public class TimedDifficultyAttributes : IComparable - { - public readonly double Time; - public readonly DifficultyAttributes Attributes; - - public TimedDifficultyAttributes(double time, DifficultyAttributes attributes) - { - Time = time; - Attributes = attributes; - } - - public int CompareTo(TimedDifficultyAttributes other) => Time.CompareTo(other.Time); - } - + /// + /// Used to calculate timed difficulty attributes, where only a subset of hitobjects should be visible at any point in time. + /// private class ProgressiveCalculationBeatmap : IBeatmap { private readonly IBeatmap baseBeatmap; diff --git a/osu.Game/Rulesets/Difficulty/TimedDifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/TimedDifficultyAttributes.cs new file mode 100644 index 0000000000..973b2dacb2 --- /dev/null +++ b/osu.Game/Rulesets/Difficulty/TimedDifficultyAttributes.cs @@ -0,0 +1,25 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; + +namespace osu.Game.Rulesets.Difficulty +{ + /// + /// Wraps a object and adds a time value for which the attribute is valid. + /// Output by . + /// + public class TimedDifficultyAttributes : IComparable + { + public readonly double Time; + public readonly DifficultyAttributes Attributes; + + public TimedDifficultyAttributes(double time, DifficultyAttributes attributes) + { + Time = time; + Attributes = attributes; + } + + public int CompareTo(TimedDifficultyAttributes other) => Time.CompareTo(other.Time); + } +} diff --git a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs index ff0c628aa6..2bc9f78548 100644 --- a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Play.HUD [Resolved(CanBeNull = true)] private Player player { get; set; } - private DifficultyCalculator.TimedDifficultyAttributes[] timedAttributes; + private TimedDifficultyAttributes[] timedAttributes; private Ruleset gameplayRuleset; public DefaultPerformancePointsCounter() @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Play.HUD if (player == null) return; - var attribIndex = Array.BinarySearch(timedAttributes, 0, timedAttributes.Length, new DifficultyCalculator.TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null)); + var attribIndex = Array.BinarySearch(timedAttributes, 0, timedAttributes.Length, new TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null)); if (attribIndex < 0) attribIndex = ~attribIndex - 1; attribIndex = Math.Clamp(attribIndex, 0, timedAttributes.Length - 1); From 0ee148b53f4bf72441f879950be6b76d610d9c8a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Oct 2021 21:31:26 +0900 Subject: [PATCH 036/183] Extra guard against no attributes --- osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs index 2bc9f78548..d93d626c72 100644 --- a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs @@ -70,7 +70,7 @@ namespace osu.Game.Screens.Play.HUD private void onNewJudgement(JudgementResult judgement) { - if (player == null) + if (player == null || timedAttributes.Length == 0) return; var attribIndex = Array.BinarySearch(timedAttributes, 0, timedAttributes.Length, new TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null)); From adff418fd26c7abdff11663eb49389ec9b400268 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Oct 2021 22:15:10 +0900 Subject: [PATCH 037/183] Guard against exception in skin deserialisation --- osu.Game/Skinning/Skin.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game/Skinning/Skin.cs b/osu.Game/Skinning/Skin.cs index b6cb8fc7a4..92441f40da 100644 --- a/osu.Game/Skinning/Skin.cs +++ b/osu.Game/Skinning/Skin.cs @@ -11,6 +11,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; +using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.IO; using osu.Game.Screens.Play.HUD; @@ -55,13 +56,20 @@ namespace osu.Game.Skinning if (bytes == null) continue; - string jsonContent = Encoding.UTF8.GetString(bytes); - var deserializedContent = JsonConvert.DeserializeObject>(jsonContent); + try + { + string jsonContent = Encoding.UTF8.GetString(bytes); + var deserializedContent = JsonConvert.DeserializeObject>(jsonContent); - if (deserializedContent == null) - continue; + if (deserializedContent == null) + continue; - DrawableComponentInfo[skinnableTarget] = deserializedContent.ToArray(); + DrawableComponentInfo[skinnableTarget] = deserializedContent.ToArray(); + } + catch (Exception ex) + { + Logger.Error(ex, "Failed to load skin configuration."); + } } } From 4e3d9da22d4422f2eb3b3e1f0c513ac95af45d4a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Oct 2021 22:58:40 +0900 Subject: [PATCH 038/183] Increase test lenience --- .../Gameplay/TestSceneBeatmapSkinFallbacks.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index e560c81fb2..7d4673c901 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -84,18 +84,18 @@ namespace osu.Game.Tests.Visual.Gameplay Remove(expectedComponentsAdjustmentContainer); return almostEqual(actualInfo, expectedInfo); - - static bool almostEqual(SkinnableInfo info, SkinnableInfo other) => - other != null - && info.Type == other.Type - && info.Anchor == other.Anchor - && info.Origin == other.Origin - && Precision.AlmostEquals(info.Position, other.Position) - && Precision.AlmostEquals(info.Scale, other.Scale) - && Precision.AlmostEquals(info.Rotation, other.Rotation) - && info.Children.SequenceEqual(other.Children, new FuncEqualityComparer(almostEqual)); } + private static bool almostEqual(SkinnableInfo info, SkinnableInfo other) => + other != null + && info.Type == other.Type + && info.Anchor == other.Anchor + && info.Origin == other.Origin + && Precision.AlmostEquals(info.Position, other.Position, 1) + && Precision.AlmostEquals(info.Scale, other.Scale) + && Precision.AlmostEquals(info.Rotation, other.Rotation) + && info.Children.SequenceEqual(other.Children, new FuncEqualityComparer(almostEqual)); + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, Audio, currentBeatmapSkin); From 2a4a376b87ef04d7eb25e689159570e42acbd422 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Sat, 2 Oct 2021 01:21:55 +0900 Subject: [PATCH 039/183] Refactor Filter to behave closer to a Transformable --- .../Visual/Audio/TestSceneFilter.cs | 108 ++++++++++-------- osu.Game/Audio/Effects/Filter.cs | 97 ++++++++++------ .../Audio/Effects/ITransformableFilter.cs | 54 +++++++++ osu.Game/Overlays/DialogOverlay.cs | 9 +- 4 files changed, 175 insertions(+), 93 deletions(-) create mode 100644 osu.Game/Audio/Effects/ITransformableFilter.cs diff --git a/osu.Game.Tests/Visual/Audio/TestSceneFilter.cs b/osu.Game.Tests/Visual/Audio/TestSceneFilter.cs index 6a7a404d8a..79d8f7da8c 100644 --- a/osu.Game.Tests/Visual/Audio/TestSceneFilter.cs +++ b/osu.Game.Tests/Visual/Audio/TestSceneFilter.cs @@ -6,74 +6,77 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Game.Audio.Effects; using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Tests.Visual.Audio { public class TestSceneFilter : OsuTestScene { - [Resolved] - private AudioManager audio { get; set; } - private WorkingBeatmap testBeatmap; - private Filter lowPassFilter; - private Filter highPassFilter; - private Filter bandPassFilter; + + private OsuSpriteText lowpassText; + private OsuSpriteText highpassText; + private Filter lowpassFilter; + private Filter highpassFilter; [BackgroundDependencyLoader] - private void load() + private void load(AudioManager audio) { testBeatmap = new WaveformTestBeatmap(audio); - AddRange(new Drawable[] + lowpassFilter = new Filter(audio.TrackMixer); + highpassFilter = new Filter(audio.TrackMixer, BQFType.HighPass); + Add(new FillFlowContainer { - lowPassFilter = new Filter(audio.TrackMixer) + Children = new Drawable[] { - FilterType = BQFType.LowPass, - SweepCutoffStart = 2000, - SweepCutoffEnd = 150, - SweepDuration = 1000 - }, - highPassFilter = new Filter(audio.TrackMixer) - { - FilterType = BQFType.HighPass, - SweepCutoffStart = 150, - SweepCutoffEnd = 2000, - SweepDuration = 1000 - }, - bandPassFilter = new Filter(audio.TrackMixer) - { - FilterType = BQFType.BandPass, - SweepCutoffStart = 150, - SweepCutoffEnd = 20000, - SweepDuration = 1000 - }, + lowpassText = new OsuSpriteText + { + Padding = new MarginPadding(20), + Text = $"Low Pass: {lowpassFilter.Cutoff.Value}hz", + Font = new FontUsage(size: 40) + }, + new OsuSliderBar + { + Width = 500, + Height = 50, + Padding = new MarginPadding(20), + Current = { BindTarget = lowpassFilter.Cutoff } + }, + highpassText = new OsuSpriteText + { + Padding = new MarginPadding(20), + Text = $"High Pass: {highpassFilter.Cutoff.Value}hz", + Font = new FontUsage(size: 40) + }, + new OsuSliderBar + { + Width = 500, + Height = 50, + Padding = new MarginPadding(20), + Current = { BindTarget = highpassFilter.Cutoff } + } + } }); + lowpassFilter.Cutoff.ValueChanged += e => lowpassText.Text = $"Low Pass: {e.NewValue}hz"; + highpassFilter.Cutoff.ValueChanged += e => highpassText.Text = $"High Pass: {e.NewValue}hz"; } [Test] - public void TestLowPass() - { - testFilter(lowPassFilter); - } + public void TestLowPass() => testFilter(lowpassFilter, lowpassFilter.MaxCutoff, 0); [Test] - public void TestHighPass() - { - testFilter(highPassFilter); - } + public void TestHighPass() => testFilter(highpassFilter, 0, highpassFilter.MaxCutoff); - [Test] - public void TestBandPass() - { - testFilter(bandPassFilter); - } - - private void testFilter(Filter filter) + private void testFilter(Filter filter, int cutoffFrom, int cutoffTo) { + Add(filter); AddStep("Prepare Track", () => { - testBeatmap = new WaveformTestBeatmap(audio); testBeatmap.LoadTrack(); }); AddStep("Play Track", () => @@ -81,14 +84,19 @@ namespace osu.Game.Tests.Visual.Audio testBeatmap.Track.Start(); }); AddWaitStep("Let track play", 10); - AddStep("Enable Filter", filter.Enable); - AddWaitStep("Let track play", 10); - AddStep("Disable Filter", filter.Disable); - AddWaitStep("Let track play", 10); - AddStep("Stop Track", () => + AddStep("Filter Sweep", () => { - testBeatmap.Track.Stop(); + filter.CutoffTo(cutoffFrom).Then() + .CutoffTo(cutoffTo, 2000, cutoffFrom > cutoffTo ? Easing.OutCubic : Easing.InCubic); }); + AddWaitStep("Let track play", 10); + AddStep("Filter Sweep (reverse)", () => + { + filter.CutoffTo(cutoffTo).Then() + .CutoffTo(cutoffFrom, 2000, cutoffTo > cutoffFrom ? Easing.OutCubic : Easing.InCubic); + }); + AddWaitStep("Let track play", 10); + AddStep("Stop track", () => testBeatmap.Track.Stop()); } } } diff --git a/osu.Game/Audio/Effects/Filter.cs b/osu.Game/Audio/Effects/Filter.cs index 04ad862879..142e6b8fff 100644 --- a/osu.Game/Audio/Effects/Filter.cs +++ b/osu.Game/Audio/Effects/Filter.cs @@ -8,66 +8,87 @@ using osu.Framework.Graphics; namespace osu.Game.Audio.Effects { - public class Filter : Component + public class Filter : Component, ITransformableFilter { - public BQFType FilterType = BQFType.LowPass; - public float SweepCutoffStart = 2000; - public float SweepCutoffEnd = 150; - public float SweepDuration = 100; - public Easing SweepEasing = Easing.None; - - public bool IsActive { get; private set; } - - private readonly Bindable filterFreq = new Bindable(); + public readonly int MaxCutoff; private readonly AudioMixer mixer; - private BQFParameters filter; + private readonly BQFParameters filter; + private readonly BQFType type; + + public BindableNumber Cutoff { get; } /// /// A BiQuad filter that performs a filter-sweep when toggled on or off. /// /// The mixer this effect should be attached to. - public Filter(AudioMixer mixer) + /// The type of filter (e.g. LowPass, HighPass, etc) + public Filter(AudioMixer mixer, BQFType type = BQFType.LowPass) { this.mixer = mixer; - } + this.type = type; - public void Enable() - { - attachFilter(); - this.TransformBindableTo(filterFreq, SweepCutoffEnd, SweepDuration, SweepEasing); - } + var initialCutoff = 1; - public void Disable() - { - this.TransformBindableTo(filterFreq, SweepCutoffStart, SweepDuration, SweepEasing).OnComplete(_ => detachFilter()); - } + // These max cutoff values are a work-around for BASS' BiQuad filters behaving weirdly when approaching nyquist. + // Note that these values assume a sample rate of 44100 (as per BassAudioMixer in osu.Framework) + // See also https://www.un4seen.com/forum/?topic=19542.0 for more information. + switch (type) + { + case BQFType.HighPass: + MaxCutoff = 21968; // beyond this value, the high-pass cuts out + break; - private void attachFilter() - { - if (IsActive) return; + case BQFType.LowPass: + MaxCutoff = initialCutoff = 14000; // beyond (roughly) this value, the low-pass filter audibly wraps/reflects + break; + case BQFType.BandPass: + MaxCutoff = 16000; // beyond (roughly) this value, the band-pass filter audibly wraps/reflects + break; + + default: + MaxCutoff = 22050; // default to nyquist for other filter types, TODO: handle quirks of other filter types + break; + } + + Cutoff = new BindableNumber + { + MinValue = 1, + MaxValue = MaxCutoff + }; filter = new BQFParameters { - lFilter = FilterType, - fCenter = filterFreq.Value = SweepCutoffStart + lFilter = type, + fCenter = initialCutoff }; - mixer.Effects.Add(filter); - filterFreq.ValueChanged += updateFilter; - IsActive = true; + attachFilter(); + + Cutoff.ValueChanged += updateFilter; + Cutoff.Value = initialCutoff; } - private void detachFilter() - { - if (!IsActive) return; + private void attachFilter() => mixer.Effects.Add(filter); - filterFreq.ValueChanged -= updateFilter; - mixer.Effects.Remove(filter); - IsActive = false; - } + private void detachFilter() => mixer.Effects.Remove(filter); - private void updateFilter(ValueChangedEvent cutoff) + private void updateFilter(ValueChangedEvent cutoff) { + // This is another workaround for quirks in BASS' BiQuad filters. + // Because the cutoff can't be set above ~14khz (i.e. outside of human hearing range) without the aforementioned wrapping/reflecting quirk occuring, we instead + // remove the effect from the mixer when the cutoff is at maximum so that a LowPass filter isn't always attenuating high frequencies just by existing. + if (type == BQFType.LowPass) + { + if (cutoff.NewValue >= MaxCutoff) + { + detachFilter(); + return; + } + + if (cutoff.OldValue >= MaxCutoff && cutoff.NewValue < MaxCutoff) + attachFilter(); + } + var filterIndex = mixer.Effects.IndexOf(filter); if (filterIndex < 0) return; diff --git a/osu.Game/Audio/Effects/ITransformableFilter.cs b/osu.Game/Audio/Effects/ITransformableFilter.cs new file mode 100644 index 0000000000..e4de4cf8ff --- /dev/null +++ b/osu.Game/Audio/Effects/ITransformableFilter.cs @@ -0,0 +1,54 @@ +// 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.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Transforms; + +namespace osu.Game.Audio.Effects +{ + public interface ITransformableFilter + { + /// + /// The filter cutoff. + /// + BindableNumber Cutoff { get; } + } + + public static class FilterableAudioComponentExtensions + { + /// + /// Smoothly adjusts filter cutoff over time. + /// + /// A to which further transforms can be added. + public static TransformSequence CutoffTo(this T component, int newCutoff, double duration = 0, Easing easing = Easing.None) + where T : class, ITransformableFilter, IDrawable + => component.CutoffTo(newCutoff, duration, new DefaultEasingFunction(easing)); + + /// + /// Smoothly adjusts filter cutoff over time. + /// + /// A to which further transforms can be added. + public static TransformSequence CutoffTo(this TransformSequence sequence, int newCutoff, double duration = 0, Easing easing = Easing.None) + where T : class, ITransformableFilter, IDrawable + => sequence.CutoffTo(newCutoff, duration, new DefaultEasingFunction(easing)); + + /// + /// Smoothly adjusts filter cutoff over time. + /// + /// A to which further transforms can be added. + public static TransformSequence CutoffTo(this T component, int newCutoff, double duration, TEasing easing) + where T : class, ITransformableFilter, IDrawable + where TEasing : IEasingFunction + => component.TransformBindableTo(component.Cutoff, newCutoff, duration, easing); + + /// + /// Smoothly adjusts filter cutoff over time. + /// + /// A to which further transforms can be added. + public static TransformSequence CutoffTo(this TransformSequence sequence, int newCutoff, double duration, TEasing easing) + where T : class, ITransformableFilter, IDrawable + where TEasing : IEasingFunction + => sequence.Append(o => o.TransformBindableTo(o.Cutoff, newCutoff, duration, easing)); + } +} diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 8a274968b7..6016d16d29 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays protected override string PopInSampleName => "UI/dialog-pop-in"; protected override string PopOutSampleName => "UI/dialog-pop-out"; - private Filter filter; + private Filter lpFilter; public PopupDialog CurrentDialog { get; private set; } @@ -42,7 +42,7 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load(AudioManager audio) { - AddInternal(filter = new Filter(audio.TrackMixer)); + AddInternal(lpFilter = new Filter(audio.TrackMixer)); } public void Push(PopupDialog dialog) @@ -82,15 +82,13 @@ namespace osu.Game.Overlays { base.PopIn(); this.FadeIn(PopupDialog.ENTER_DURATION, Easing.OutQuint); - filter.Enable(); + lpFilter.CutoffTo(2000).Then().CutoffTo(150, 100, Easing.OutCubic); } protected override void PopOut() { base.PopOut(); - filter.Disable(); - if (CurrentDialog?.State.Value == Visibility.Visible) { CurrentDialog.Hide(); @@ -98,6 +96,7 @@ namespace osu.Game.Overlays } this.FadeOut(PopupDialog.EXIT_DURATION, Easing.InSine); + lpFilter.CutoffTo(2000, 100, Easing.InCubic).Then().CutoffTo(lpFilter.MaxCutoff); } public override bool OnPressed(KeyBindingPressEvent e) From 1eb67dc5941322db6b8b9ab5d8bdf87f10f1b579 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Oct 2021 17:01:28 +0000 Subject: [PATCH 040/183] Bump Microsoft.AspNetCore.SignalR.Client from 5.0.9 to 5.0.10 Bumps [Microsoft.AspNetCore.SignalR.Client](https://github.com/dotnet/aspnetcore) from 5.0.9 to 5.0.10. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.9...v5.0.10) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.SignalR.Client dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ba118c5240..9087e77a46 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + From 323a9a748dae5f5c133be297f6f4ae33c5cbe07b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Oct 2021 17:01:32 +0000 Subject: [PATCH 041/183] Bump HtmlAgilityPack from 1.11.36 to 1.11.37 Bumps [HtmlAgilityPack](https://github.com/zzzprojects/html-agility-pack) from 1.11.36 to 1.11.37. - [Release notes](https://github.com/zzzprojects/html-agility-pack/releases) - [Commits](https://github.com/zzzprojects/html-agility-pack/compare/v1.11.36...v1.11.37) --- updated-dependencies: - dependency-name: HtmlAgilityPack dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ba118c5240..850cce2d29 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -20,7 +20,7 @@ - + From 6de4e981ddb4d4fb7ef2cd6546a4b518312498f7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Oct 2021 17:01:37 +0000 Subject: [PATCH 042/183] Bump Realm from 10.5.0 to 10.6.0 Bumps [Realm](https://github.com/realm/realm-dotnet) from 10.5.0 to 10.6.0. - [Release notes](https://github.com/realm/realm-dotnet/releases) - [Changelog](https://github.com/realm/realm-dotnet/blob/master/CHANGELOG.md) - [Commits](https://github.com/realm/realm-dotnet/compare/10.5.0...10.6.0) --- updated-dependencies: - dependency-name: Realm dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 8fad10d247..b84f1730ac 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -56,6 +56,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ba118c5240..b1654655a2 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 37931d0c38..8597a06c03 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -99,6 +99,6 @@ - + From 05ca3aec4f7ed7f5ce56ab7a0e414bbcfb4d7afa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Oct 2021 02:08:56 +0900 Subject: [PATCH 043/183] Rename `GameplayState` to `SpectatorGameplayState` --- .../Spectate/MultiSpectatorScreen.cs | 4 ++-- osu.Game/Screens/Play/SoloSpectator.cs | 22 +++++++++---------- ...playState.cs => SpectatorGameplayState.cs} | 6 ++--- osu.Game/Screens/Spectate/SpectatorScreen.cs | 8 +++---- 4 files changed, 20 insertions(+), 20 deletions(-) rename osu.Game/Screens/Spectate/{GameplayState.cs => SpectatorGameplayState.cs} (81%) diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs index c45e3a79da..7bf8ce0e1a 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/MultiSpectatorScreen.cs @@ -213,8 +213,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate { } - protected override void StartGameplay(int userId, GameplayState gameplayState) - => instances.Single(i => i.UserId == userId).LoadScore(gameplayState.Score); + protected override void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState) + => instances.Single(i => i.UserId == userId).LoadScore(spectatorGameplayState.Score); protected override void EndGameplay(int userId) { diff --git a/osu.Game/Screens/Play/SoloSpectator.cs b/osu.Game/Screens/Play/SoloSpectator.cs index 4520e2e825..9d4dad8bdc 100644 --- a/osu.Game/Screens/Play/SoloSpectator.cs +++ b/osu.Game/Screens/Play/SoloSpectator.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Play /// The player's immediate online gameplay state. /// This doesn't always reflect the gameplay state being watched. /// - private GameplayState immediateGameplayState; + private SpectatorGameplayState immediateSpectatorGameplayState; private GetBeatmapSetRequest onlineBeatmapRequest; @@ -146,7 +146,7 @@ namespace osu.Game.Screens.Play Width = 250, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Action = () => scheduleStart(immediateGameplayState), + Action = () => scheduleStart(immediateSpectatorGameplayState), Enabled = { Value = false } } } @@ -167,18 +167,18 @@ namespace osu.Game.Screens.Play showBeatmapPanel(spectatorState); } - protected override void StartGameplay(int userId, GameplayState gameplayState) + protected override void StartGameplay(int userId, SpectatorGameplayState spectatorGameplayState) { - immediateGameplayState = gameplayState; + immediateSpectatorGameplayState = spectatorGameplayState; watchButton.Enabled.Value = true; - scheduleStart(gameplayState); + scheduleStart(spectatorGameplayState); } protected override void EndGameplay(int userId) { scheduledStart?.Cancel(); - immediateGameplayState = null; + immediateSpectatorGameplayState = null; watchButton.Enabled.Value = false; clearDisplay(); @@ -194,7 +194,7 @@ namespace osu.Game.Screens.Play private ScheduledDelegate scheduledStart; - private void scheduleStart(GameplayState gameplayState) + private void scheduleStart(SpectatorGameplayState spectatorGameplayState) { // This function may be called multiple times in quick succession once the screen becomes current again. scheduledStart?.Cancel(); @@ -203,15 +203,15 @@ namespace osu.Game.Screens.Play if (this.IsCurrentScreen()) start(); else - scheduleStart(gameplayState); + scheduleStart(spectatorGameplayState); }); void start() { - Beatmap.Value = gameplayState.Beatmap; - Ruleset.Value = gameplayState.Ruleset.RulesetInfo; + Beatmap.Value = spectatorGameplayState.Beatmap; + Ruleset.Value = spectatorGameplayState.Ruleset.RulesetInfo; - this.Push(new SpectatorPlayerLoader(gameplayState.Score, () => new SoloSpectatorPlayer(gameplayState.Score))); + this.Push(new SpectatorPlayerLoader(spectatorGameplayState.Score, () => new SoloSpectatorPlayer(spectatorGameplayState.Score))); } } diff --git a/osu.Game/Screens/Spectate/GameplayState.cs b/osu.Game/Screens/Spectate/SpectatorGameplayState.cs similarity index 81% rename from osu.Game/Screens/Spectate/GameplayState.cs rename to osu.Game/Screens/Spectate/SpectatorGameplayState.cs index 4579b9c07c..6ca1ac9a0a 100644 --- a/osu.Game/Screens/Spectate/GameplayState.cs +++ b/osu.Game/Screens/Spectate/SpectatorGameplayState.cs @@ -8,9 +8,9 @@ using osu.Game.Scoring; namespace osu.Game.Screens.Spectate { /// - /// The gameplay state of a spectated user. This class is immutable. + /// An immutable spectator gameplay state. /// - public class GameplayState + public class SpectatorGameplayState { /// /// The score which the user is playing. @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Spectate /// public readonly WorkingBeatmap Beatmap; - public GameplayState(Score score, Ruleset ruleset, WorkingBeatmap beatmap) + public SpectatorGameplayState(Score score, Ruleset ruleset, WorkingBeatmap beatmap) { Score = score; Ruleset = ruleset; diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index f0a68ea078..71bcc336f3 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Spectate private readonly IBindableDictionary playingUserStates = new BindableDictionary(); private readonly Dictionary userMap = new Dictionary(); - private readonly Dictionary gameplayStates = new Dictionary(); + private readonly Dictionary gameplayStates = new Dictionary(); private IBindable> managerUpdated; @@ -173,7 +173,7 @@ namespace osu.Game.Screens.Spectate Replay = new Replay { HasReceivedAllFrames = false }, }; - var gameplayState = new GameplayState(score, resolvedRuleset, beatmaps.GetWorkingBeatmap(resolvedBeatmap)); + var gameplayState = new SpectatorGameplayState(score, resolvedRuleset, beatmaps.GetWorkingBeatmap(resolvedBeatmap)); gameplayStates[userId] = gameplayState; Schedule(() => StartGameplay(userId, gameplayState)); @@ -190,8 +190,8 @@ namespace osu.Game.Screens.Spectate /// Starts gameplay for a user. /// /// The user to start gameplay for. - /// The gameplay state. - protected abstract void StartGameplay(int userId, [NotNull] GameplayState gameplayState); + /// The gameplay state. + protected abstract void StartGameplay(int userId, [NotNull] SpectatorGameplayState spectatorGameplayState); /// /// Ends gameplay for a user. From 32afd3f4267df99a69b5f11909d37d5e39a78525 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Oct 2021 02:22:23 +0900 Subject: [PATCH 044/183] Replace all basic usages --- .../Mods/TestSceneOsuModHidden.cs | 4 +- .../TestSceneGameplayCursor.cs | 10 ++-- .../Skinning/Legacy/LegacyCursorParticles.cs | 6 +- .../UI/Cursor/OsuCursorContainer.cs | 6 +- .../Skinning/Legacy/LegacyTaikoScroller.cs | 6 +- .../UI/DrawableTaikoMascot.cs | 6 +- .../Gameplay/TestSceneReplayRecorder.cs | 7 ++- .../Gameplay/TestSceneReplayRecording.cs | 7 ++- .../Gameplay/TestSceneSpectatorPlayback.cs | 4 +- osu.Game/Online/Spectator/SpectatorClient.cs | 4 +- osu.Game/Rulesets/UI/ReplayRecorder.cs | 4 +- osu.Game/Screens/Play/GameplayBeatmap.cs | 56 ------------------- osu.Game/Screens/Play/GameplayState.cs | 39 +++++++++++++ osu.Game/Screens/Play/Player.cs | 29 +++++----- osu.Game/Screens/Play/ReplayPlayer.cs | 4 +- osu.Game/Screens/Play/SpectatorPlayer.cs | 4 +- osu.Game/Tests/Visual/TestPlayer.cs | 2 +- 17 files changed, 94 insertions(+), 104 deletions(-) delete mode 100644 osu.Game/Screens/Play/GameplayBeatmap.cs create mode 100644 osu.Game/Screens/Play/GameplayState.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs index 1ac3ad9194..af64be78f8 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs @@ -4,13 +4,11 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Screens.Play; using osuTK; namespace osu.Game.Rulesets.Osu.Tests.Mods @@ -122,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods private bool checkSomeHit() => Player.ScoreProcessor.JudgedHits >= 4; private bool objectWithIncreasedVisibilityHasIndex(int index) - => Player.Mods.Value.OfType().Single().FirstObject == Player.ChildrenOfType().Single().HitObjects[index]; + => Player.Mods.Value.OfType().Single().FirstObject == Player.GameplayState.Beatmap.HitObjects[index]; private class TestOsuModHidden : OsuModHidden { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index f9dc9abd75..41d9bf7132 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -17,6 +17,7 @@ using osu.Framework.Testing.Input; using osu.Framework.Utils; using osu.Game.Audio; using osu.Game.Configuration; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Skinning; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Screens.Play; @@ -29,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Tests public class TestSceneGameplayCursor : OsuSkinnableTestScene { [Cached] - private GameplayBeatmap gameplayBeatmap; + private GameplayState gameplayState; private OsuCursorContainer lastContainer; @@ -40,7 +41,8 @@ namespace osu.Game.Rulesets.Osu.Tests public TestSceneGameplayCursor() { - gameplayBeatmap = new GameplayBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); + var ruleset = new OsuRuleset(); + gameplayState = new GameplayState(CreateBeatmap(ruleset.RulesetInfo), ruleset, Array.Empty()); AddStep("change background colour", () => { @@ -57,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddSliderStep("circle size", 0f, 10f, 0f, val => { config.SetValue(OsuSetting.AutoCursorSize, true); - gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = val; + gameplayState.Beatmap.BeatmapInfo.BaseDifficulty.CircleSize = val; Scheduler.AddOnce(() => loadContent(false)); }); @@ -73,7 +75,7 @@ namespace osu.Game.Rulesets.Osu.Tests public void TestSizing(int circleSize, float userScale) { AddStep($"set user scale to {userScale}", () => config.SetValue(OsuSetting.GameplayCursorSize, userScale)); - AddStep($"adjust cs to {circleSize}", () => gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSize); + AddStep($"adjust cs to {circleSize}", () => gameplayState.Beatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSize); AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true)); AddStep("load content", () => loadContent()); diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs index c2db5f3f82..611ddd08eb 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyCursorParticles.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private OsuPlayfield playfield { get; set; } [Resolved(canBeNull: true)] - private GameplayBeatmap gameplayBeatmap { get; set; } + private GameplayState gameplayState { get; set; } [BackgroundDependencyLoader] private void load(ISkinSource skin, OsuColour colours) @@ -75,12 +75,12 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected override void Update() { - if (playfield == null || gameplayBeatmap == null) return; + if (playfield == null || gameplayState == null) return; DrawableHitObject kiaiHitObject = null; // Check whether currently in a kiai section first. This is only done as an optimisation to avoid enumerating AliveObjects when not necessary. - if (gameplayBeatmap.ControlPointInfo.EffectPointAt(Time.Current).KiaiMode) + if (gameplayState.Beatmap.ControlPointInfo.EffectPointAt(Time.Current).KiaiMode) kiaiHitObject = playfield.HitObjectContainer.AliveObjects.FirstOrDefault(isTracking); kiaiSpewer.Active.Value = kiaiHitObject != null; diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 83bcc88e5f..cfe83d0106 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor } [Resolved(canBeNull: true)] - private GameplayBeatmap beatmap { get; set; } + private GameplayState state { get; set; } [Resolved] private OsuConfigManager config { get; set; } @@ -96,10 +96,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { float scale = userCursorScale.Value; - if (autoCursorScale.Value && beatmap != null) + if (autoCursorScale.Value && state != null) { // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. - scale *= GetScaleForCircleSize(beatmap.BeatmapInfo.BaseDifficulty.CircleSize); + scale *= GetScaleForCircleSize(state.Beatmap.BeatmapInfo.BaseDifficulty.CircleSize); } cursorScale.Value = scale; diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyTaikoScroller.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyTaikoScroller.cs index 6fc59ea0e8..fa49242675 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyTaikoScroller.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyTaikoScroller.cs @@ -25,10 +25,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy } [BackgroundDependencyLoader(true)] - private void load(GameplayBeatmap gameplayBeatmap) + private void load(GameplayState gameplayState) { - if (gameplayBeatmap != null) - ((IBindable)LastResult).BindTo(gameplayBeatmap.LastJudgementResult); + if (gameplayState != null) + ((IBindable)LastResult).BindTo(gameplayState.LastJudgementResult); } private bool passing; diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs index 6a16f311bf..e1063e1071 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.UI } [BackgroundDependencyLoader(true)] - private void load(TextureStore textures, GameplayBeatmap gameplayBeatmap) + private void load(TextureStore textures, GameplayState gameplayState) { InternalChildren = new[] { @@ -49,8 +49,8 @@ namespace osu.Game.Rulesets.Taiko.UI animations[TaikoMascotAnimationState.Fail] = new TaikoMascotAnimation(TaikoMascotAnimationState.Fail), }; - if (gameplayBeatmap != null) - ((IBindable)LastResult).BindTo(gameplayBeatmap.LastJudgementResult); + if (gameplayState != null) + ((IBindable)LastResult).BindTo(gameplayState.LastJudgementResult); } protected override void LoadComplete() diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index 0a3fedaf8e..d89fd322d1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using NUnit.Framework; @@ -17,6 +18,8 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Replays; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; using osu.Game.Scoring; @@ -38,7 +41,7 @@ namespace osu.Game.Tests.Visual.Gameplay private TestReplayRecorder recorder; [Cached] - private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap()); + private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty()); [SetUp] public void SetUp() => Schedule(() => @@ -57,7 +60,7 @@ namespace osu.Game.Tests.Visual.Gameplay Recorder = recorder = new TestReplayRecorder(new Score { Replay = replay, - ScoreInfo = { Beatmap = gameplayBeatmap.BeatmapInfo } + ScoreInfo = { Beatmap = gameplayState.Beatmap.BeatmapInfo } }) { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs index dfd5e2dc58..07514ad51a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -13,6 +14,8 @@ using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Replays; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.UI; using osu.Game.Scoring; @@ -30,7 +33,7 @@ namespace osu.Game.Tests.Visual.Gameplay private readonly TestRulesetInputManager recordingManager; [Cached] - private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap()); + private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty()); public TestSceneReplayRecording() { @@ -48,7 +51,7 @@ namespace osu.Game.Tests.Visual.Gameplay Recorder = new TestReplayRecorder(new Score { Replay = replay, - ScoreInfo = { Beatmap = gameplayBeatmap.BeatmapInfo } + ScoreInfo = { Beatmap = gameplayState.Beatmap.BeatmapInfo } }) { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 6f5f774758..07ff35f77b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -25,6 +25,8 @@ using osu.Game.Online.Spectator; using osu.Game.Replays; using osu.Game.Replays.Legacy; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Rulesets.UI; @@ -62,7 +64,7 @@ namespace osu.Game.Tests.Visual.Gameplay private SpectatorClient spectatorClient { get; set; } [Cached] - private GameplayBeatmap gameplayBeatmap = new GameplayBeatmap(new Beatmap()); + private GameplayState gameplayState = new GameplayState(new Beatmap(), new OsuRuleset(), Array.Empty()); [SetUp] public void SetUp() => Schedule(() => diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index 8c617784b9..d55ad45ff5 100644 --- a/osu.Game/Online/Spectator/SpectatorClient.cs +++ b/osu.Game/Online/Spectator/SpectatorClient.cs @@ -134,7 +134,7 @@ namespace osu.Game.Online.Spectator return Task.CompletedTask; } - public void BeginPlaying(GameplayBeatmap beatmap, Score score) + public void BeginPlaying(GameplayState state, Score score) { Debug.Assert(ThreadSafety.IsUpdateThread); @@ -148,7 +148,7 @@ namespace osu.Game.Online.Spectator currentState.RulesetID = score.ScoreInfo.RulesetID; currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray(); - currentBeatmap = beatmap.PlayableBeatmap; + currentBeatmap = state.Beatmap; currentScore = score; BeginPlayingInternal(currentState); diff --git a/osu.Game/Rulesets/UI/ReplayRecorder.cs b/osu.Game/Rulesets/UI/ReplayRecorder.cs index b57c224059..976f95cef8 100644 --- a/osu.Game/Rulesets/UI/ReplayRecorder.cs +++ b/osu.Game/Rulesets/UI/ReplayRecorder.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.UI private SpectatorClient spectatorClient { get; set; } [Resolved] - private GameplayBeatmap gameplayBeatmap { get; set; } + private GameplayState gameplayState { get; set; } protected ReplayRecorder(Score target) { @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.UI inputManager = GetContainingInputManager(); - spectatorClient?.BeginPlaying(gameplayBeatmap, target); + spectatorClient?.BeginPlaying(gameplayState, target); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Play/GameplayBeatmap.cs b/osu.Game/Screens/Play/GameplayBeatmap.cs deleted file mode 100644 index 74fbe540fa..0000000000 --- a/osu.Game/Screens/Play/GameplayBeatmap.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System.Collections.Generic; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps.Timing; -using osu.Game.Rulesets.Judgements; -using osu.Game.Rulesets.Objects; - -namespace osu.Game.Screens.Play -{ - public class GameplayBeatmap : Component, IBeatmap - { - public readonly IBeatmap PlayableBeatmap; - - public GameplayBeatmap(IBeatmap playableBeatmap) - { - PlayableBeatmap = playableBeatmap; - } - - public BeatmapInfo BeatmapInfo - { - get => PlayableBeatmap.BeatmapInfo; - set => PlayableBeatmap.BeatmapInfo = value; - } - - public BeatmapMetadata Metadata => PlayableBeatmap.Metadata; - - public ControlPointInfo ControlPointInfo - { - get => PlayableBeatmap.ControlPointInfo; - set => PlayableBeatmap.ControlPointInfo = value; - } - - public List Breaks => PlayableBeatmap.Breaks; - - public double TotalBreakTime => PlayableBeatmap.TotalBreakTime; - - public IReadOnlyList HitObjects => PlayableBeatmap.HitObjects; - - public IEnumerable GetStatistics() => PlayableBeatmap.GetStatistics(); - - public double GetMostCommonBeatLength() => PlayableBeatmap.GetMostCommonBeatLength(); - - public IBeatmap Clone() => PlayableBeatmap.Clone(); - - private readonly Bindable lastJudgementResult = new Bindable(); - - public IBindable LastJudgementResult => lastJudgementResult; - - public void ApplyResult(JudgementResult result) => lastJudgementResult.Value = result; - } -} diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs new file mode 100644 index 0000000000..4944d5b8e2 --- /dev/null +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -0,0 +1,39 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Bindables; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mods; + +#nullable enable + +namespace osu.Game.Screens.Play +{ + public class GameplayState + { + /// + /// The final post-convert post-mod-application beatmap. + /// + public readonly IBeatmap Beatmap; + + public readonly Ruleset Ruleset; + + public IReadOnlyList Mods; + + public GameplayState(IBeatmap beatmap, Ruleset ruleset, IReadOnlyList mods) + { + Beatmap = beatmap; + Ruleset = ruleset; + Mods = mods; + } + + private readonly Bindable lastJudgementResult = new Bindable(); + + public IBindable LastJudgementResult => lastJudgementResult; + + public void ApplyResult(JudgementResult result) => lastJudgementResult.Value = result; + } +} diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9927467bd6..a05a8f5056 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -93,9 +93,9 @@ namespace osu.Game.Screens.Play [Resolved] private SpectatorClient spectatorClient { get; set; } - protected Ruleset GameplayRuleset { get; private set; } + public GameplayState GameplayState { get; private set; } - protected GameplayBeatmap GameplayBeatmap { get; private set; } + private Ruleset ruleset; private Sample sampleRestart; @@ -165,7 +165,7 @@ namespace osu.Game.Screens.Play // ensure the score is in a consistent state with the current player. Score.ScoreInfo.Beatmap = Beatmap.Value.BeatmapInfo; - Score.ScoreInfo.Ruleset = GameplayRuleset.RulesetInfo; + Score.ScoreInfo.Ruleset = ruleset.RulesetInfo; Score.ScoreInfo.Mods = Mods.Value.ToArray(); PrepareReplay(); @@ -206,16 +206,16 @@ namespace osu.Game.Screens.Play if (game is OsuGame osuGame) LocalUserPlaying.BindTo(osuGame.LocalUserPlaying); - DrawableRuleset = GameplayRuleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); + DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); dependencies.CacheAs(DrawableRuleset); - ScoreProcessor = GameplayRuleset.CreateScoreProcessor(); + ScoreProcessor = ruleset.CreateScoreProcessor(); ScoreProcessor.ApplyBeatmap(playableBeatmap); ScoreProcessor.Mods.BindTo(Mods); dependencies.CacheAs(ScoreProcessor); - HealthProcessor = GameplayRuleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime); + HealthProcessor = ruleset.CreateHealthProcessor(playableBeatmap.HitObjects[0].StartTime); HealthProcessor.ApplyBeatmap(playableBeatmap); dependencies.CacheAs(HealthProcessor); @@ -225,12 +225,11 @@ namespace osu.Game.Screens.Play InternalChild = GameplayClockContainer = CreateGameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime); - AddInternal(GameplayBeatmap = new GameplayBeatmap(playableBeatmap)); + dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, Mods.Value)); + AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); - dependencies.CacheAs(GameplayBeatmap); - - var rulesetSkinProvider = new RulesetSkinProvidingContainer(GameplayRuleset, playableBeatmap, Beatmap.Value.Skin); + var rulesetSkinProvider = new RulesetSkinProvidingContainer(ruleset, playableBeatmap, Beatmap.Value.Skin); // load the skinning hierarchy first. // this is intentionally done in two stages to ensure things are in a loaded state before exposing the ruleset to skin sources. @@ -280,7 +279,7 @@ namespace osu.Game.Screens.Play { HealthProcessor.ApplyResult(r); ScoreProcessor.ApplyResult(r); - GameplayBeatmap.ApplyResult(r); + GameplayState.ApplyResult(r); }; DrawableRuleset.RevertResult += r => @@ -478,17 +477,17 @@ namespace osu.Game.Screens.Play throw new InvalidOperationException("Beatmap was not loaded"); var rulesetInfo = Ruleset.Value ?? Beatmap.Value.BeatmapInfo.Ruleset; - GameplayRuleset = rulesetInfo.CreateInstance(); + ruleset = rulesetInfo.CreateInstance(); try { - playable = Beatmap.Value.GetPlayableBeatmap(GameplayRuleset.RulesetInfo, Mods.Value); + playable = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Mods.Value); } catch (BeatmapInvalidForRulesetException) { // A playable beatmap may not be creatable with the user's preferred ruleset, so try using the beatmap's default ruleset rulesetInfo = Beatmap.Value.BeatmapInfo.Ruleset; - GameplayRuleset = rulesetInfo.CreateInstance(); + ruleset = rulesetInfo.CreateInstance(); playable = Beatmap.Value.GetPlayableBeatmap(rulesetInfo, Mods.Value); } @@ -1010,7 +1009,7 @@ namespace osu.Game.Screens.Play using (var stream = new MemoryStream()) { - new LegacyScoreEncoder(score, GameplayBeatmap.PlayableBeatmap).Encode(stream); + new LegacyScoreEncoder(score, GameplayState.Beatmap).Encode(stream); replayReader = new LegacyByteArrayReader(stream.ToArray(), "replay.osr"); } diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 0c6f1ed911..eefea737cf 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play DrawableRuleset?.SetReplayScore(Score); } - protected override Score CreateScore() => createScore(GameplayBeatmap.PlayableBeatmap, Mods.Value); + protected override Score CreateScore() => createScore(GameplayState.Beatmap, Mods.Value); // Don't re-import replay scores as they're already present in the database. protected override Task ImportScore(Score score) => Task.CompletedTask; @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Play void keyboardSeek(int direction) { - double target = Math.Clamp(GameplayClockContainer.CurrentTime + direction * keyboard_seek_amount, 0, GameplayBeatmap.HitObjects.Last().GetEndTime()); + double target = Math.Clamp(GameplayClockContainer.CurrentTime + direction * keyboard_seek_amount, 0, GameplayState.Beatmap.HitObjects.Last().GetEndTime()); Seek(target); } diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index d7e42a9cd1..fbb4fb5699 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -66,8 +66,8 @@ namespace osu.Game.Screens.Play foreach (var frame in bundle.Frames) { - IConvertibleReplayFrame convertibleFrame = GameplayRuleset.CreateConvertibleReplayFrame(); - convertibleFrame.FromLegacy(frame, GameplayBeatmap.PlayableBeatmap); + IConvertibleReplayFrame convertibleFrame = GameplayState.Ruleset.CreateConvertibleReplayFrame(); + convertibleFrame.FromLegacy(frame, GameplayState.Beatmap); var convertedFrame = (ReplayFrame)convertibleFrame; convertedFrame.Time = frame.Time; diff --git a/osu.Game/Tests/Visual/TestPlayer.cs b/osu.Game/Tests/Visual/TestPlayer.cs index 5e5f20b307..d68984b144 100644 --- a/osu.Game/Tests/Visual/TestPlayer.cs +++ b/osu.Game/Tests/Visual/TestPlayer.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual if (autoplayMod != null) { - DrawableRuleset?.SetReplayScore(autoplayMod.CreateReplayScore(GameplayBeatmap.PlayableBeatmap, Mods.Value)); + DrawableRuleset?.SetReplayScore(autoplayMod.CreateReplayScore(GameplayState.Beatmap, Mods.Value)); return; } From 7e009f616845718cc124c68b900ad4c57382a7fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Oct 2021 02:28:35 +0900 Subject: [PATCH 045/183] Add full xmldoc --- osu.Game/Screens/Play/GameplayState.cs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index 4944d5b8e2..ba08c946d2 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -12,6 +12,9 @@ using osu.Game.Rulesets.Mods; namespace osu.Game.Screens.Play { + /// + /// The state of an active gameplay session, generally constructed and exposed by . + /// public class GameplayState { /// @@ -19,10 +22,23 @@ namespace osu.Game.Screens.Play /// public readonly IBeatmap Beatmap; + /// + /// The ruleset used in gameplay. + /// public readonly Ruleset Ruleset; + /// + /// The mods applied to the gameplay. + /// public IReadOnlyList Mods; + /// + /// A bindable tracking the last judgement result applied to any hit object. + /// + public IBindable LastJudgementResult => lastJudgementResult; + + private readonly Bindable lastJudgementResult = new Bindable(); + public GameplayState(IBeatmap beatmap, Ruleset ruleset, IReadOnlyList mods) { Beatmap = beatmap; @@ -30,10 +46,10 @@ namespace osu.Game.Screens.Play Mods = mods; } - private readonly Bindable lastJudgementResult = new Bindable(); - - public IBindable LastJudgementResult => lastJudgementResult; - + /// + /// Applies the score change of a to this . + /// + /// The to apply. public void ApplyResult(JudgementResult result) => lastJudgementResult.Value = result; } } From 6d6fda833705efad08906e015c564f7a6b621f3f Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Fri, 1 Oct 2021 19:54:10 +0200 Subject: [PATCH 046/183] Fix some usages of `SettingsTextBox` using a bindable with `null` as default --- osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs | 6 +++++- osu.Game.Tournament/Components/DateTextBox.cs | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs b/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs index e3dae9c27e..d530e1f796 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneSettingsSource.cs @@ -53,7 +53,11 @@ namespace osu.Game.Tests.Visual.Settings }; [SettingSource("Sample string", "Change something for a mod")] - public Bindable StringBindable { get; } = new Bindable(); + public Bindable StringBindable { get; } = new Bindable + { + Default = string.Empty, + Value = "Sample text" + }; } private enum TestEnum diff --git a/osu.Game.Tournament/Components/DateTextBox.cs b/osu.Game.Tournament/Components/DateTextBox.cs index 5782301a65..2237e389d7 100644 --- a/osu.Game.Tournament/Components/DateTextBox.cs +++ b/osu.Game.Tournament/Components/DateTextBox.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tournament.Components public DateTextBox() { - base.Current = new Bindable(); + base.Current = new Bindable(string.Empty); ((OsuTextBox)Control).OnCommit += (sender, newText) => { From dcd7d7a709beb3b5b837b4c88c9861c764613072 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Oct 2021 03:05:04 +0900 Subject: [PATCH 047/183] Add `JsonIgnore` rule for `StoryboardFile` Not sure why this is required, doesn't make much sense. --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 4804c7032c..9f5a07ec43 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using JetBrains.Annotations; +using Newtonsoft.Json; using osu.Framework.Testing; using osu.Game.Database; @@ -61,6 +62,7 @@ namespace osu.Game.Beatmaps public string Hash { get; set; } + [JsonIgnore] public string StoryboardFile => ((IBeatmapSetInfo)this).StoryboardFile; /// From 5ea51f4a9ff1d001882f41409f2adef11df4e396 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Oct 2021 18:15:07 +0000 Subject: [PATCH 048/183] Bump Sentry from 3.9.0 to 3.9.4 Bumps [Sentry](https://github.com/getsentry/sentry-dotnet) from 3.9.0 to 3.9.4. - [Release notes](https://github.com/getsentry/sentry-dotnet/releases) - [Changelog](https://github.com/getsentry/sentry-dotnet/blob/main/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-dotnet/compare/3.9.0...3.9.4) --- updated-dependencies: - dependency-name: Sentry dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3e0809c359..ff89fadcc3 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -38,7 +38,7 @@ - + From 9517d69f21fdd51ce7539eed07b42f7ff6a9f4ca Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Oct 2021 00:59:31 +0000 Subject: [PATCH 049/183] Bump MessagePack from 2.3.75 to 2.3.85 Bumps [MessagePack](https://github.com/neuecc/MessagePack-CSharp) from 2.3.75 to 2.3.85. - [Release notes](https://github.com/neuecc/MessagePack-CSharp/releases) - [Changelog](https://github.com/neuecc/MessagePack-CSharp/blob/master/prepare_release.ps1) - [Commits](https://github.com/neuecc/MessagePack-CSharp/compare/v2.3.75...v2.3.85) --- updated-dependencies: - dependency-name: MessagePack dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ff89fadcc3..c110aadac1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -22,7 +22,7 @@ - + From f60f712bcc232177f909d98e4ab686478c6c364b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Oct 2021 00:59:32 +0000 Subject: [PATCH 050/183] Bump Microsoft.AspNetCore.SignalR.Protocols.MessagePack Bumps [Microsoft.AspNetCore.SignalR.Protocols.MessagePack](https://github.com/dotnet/aspnetcore) from 5.0.9 to 5.0.10. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.9...v5.0.10) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.SignalR.Protocols.MessagePack dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ff89fadcc3..868074a32f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + From cb8165ca504f426c9b0b2013d6c58a28d73bfcb5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 2 Oct 2021 00:59:32 +0000 Subject: [PATCH 051/183] Bump Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson Bumps [Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson](https://github.com/dotnet/aspnetcore) from 5.0.9 to 5.0.10. - [Release notes](https://github.com/dotnet/aspnetcore/releases) - [Commits](https://github.com/dotnet/aspnetcore/compare/v5.0.9...v5.0.10) --- updated-dependencies: - dependency-name: Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ff89fadcc3..73b95b60d5 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -25,7 +25,7 @@ - + From 973c31132be80ccfc14cac0621fc871db987f406 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Oct 2021 12:44:22 +0900 Subject: [PATCH 052/183] Rename `BeatmapInfo` variables which were named `beatmap` for clarity --- .../Background/TestSceneUserDimBackgrounds.cs | 2 +- .../SongSelect/TestSceneAdvancedStats.cs | 24 ++++---- .../SongSelect/TestSceneBeatmapCarousel.cs | 22 +++---- .../SongSelect/TestScenePlaySongSelect.cs | 60 +++++++++---------- osu.Game.Tournament/Components/SongBar.cs | 32 +++++----- .../Screens/BeatmapInfoScreen.cs | 2 +- osu.Game/Overlays/BeatmapSet/BasicStats.cs | 20 +++---- .../BeatmapSet/BeatmapSetHeaderContent.cs | 2 +- osu.Game/Overlays/BeatmapSet/Details.cs | 10 ++-- osu.Game/Screens/Select/BeatmapCarousel.cs | 14 ++--- osu.Game/Screens/Select/BeatmapDetails.cs | 2 +- .../Select/Carousel/CarouselBeatmap.cs | 54 ++++++++--------- .../Select/Carousel/CarouselBeatmapSet.cs | 6 +- .../Carousel/DrawableCarouselBeatmap.cs | 2 +- .../Carousel/FilterableDifficultyIcon.cs | 2 +- .../FilterableGroupedDifficultyIcon.cs | 2 +- .../Select/Carousel/SetPanelContent.cs | 2 +- .../Screens/Select/Details/AdvancedStats.cs | 20 +++---- osu.Game/Screens/Select/SongSelect.cs | 2 +- 19 files changed, 140 insertions(+), 140 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 1670d86545..12a85c3f26 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -286,7 +286,7 @@ namespace osu.Game.Tests.Visual.Background private void setupUserSettings() { AddUntilStep("Song select is current", () => songSelect.IsCurrentScreen()); - AddUntilStep("Song select has selection", () => songSelect.Carousel?.SelectedBeatmap != null); + AddUntilStep("Song select has selection", () => songSelect.Carousel?.SelectedBeatmapInfo != null); AddStep("Set default user settings", () => { SelectedMods.Value = SelectedMods.Value.Concat(new[] { new OsuModNoFail() }).ToArray(); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs index dcc2111ad3..4538e36c5e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneAdvancedStats.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestNoMod() { - AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo); + AddStep("set beatmap", () => advancedStats.BeatmapInfo = exampleBeatmapInfo); AddStep("no mods selected", () => SelectedMods.Value = Array.Empty()); @@ -65,7 +65,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestManiaFirstBarText() { - AddStep("set beatmap", () => advancedStats.Beatmap = new BeatmapInfo + AddStep("set beatmap", () => advancedStats.BeatmapInfo = new BeatmapInfo { Ruleset = rulesets.GetRuleset(3), BaseDifficulty = new BeatmapDifficulty @@ -84,11 +84,11 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestEasyMod() { - AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo); + AddStep("set beatmap", () => advancedStats.BeatmapInfo = exampleBeatmapInfo); AddStep("select EZ mod", () => { - var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); + var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance(); SelectedMods.Value = new[] { ruleset.CreateMod() }; }); @@ -101,11 +101,11 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestHardRockMod() { - AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo); + AddStep("set beatmap", () => advancedStats.BeatmapInfo = exampleBeatmapInfo); AddStep("select HR mod", () => { - var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); + var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance(); SelectedMods.Value = new[] { ruleset.CreateMod() }; }); @@ -118,13 +118,13 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestUnchangedDifficultyAdjustMod() { - AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo); + AddStep("set beatmap", () => advancedStats.BeatmapInfo = exampleBeatmapInfo); AddStep("select unchanged Difficulty Adjust mod", () => { - var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); + var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance(); var difficultyAdjustMod = ruleset.CreateMod(); - difficultyAdjustMod.ReadFromDifficulty(advancedStats.Beatmap.BaseDifficulty); + difficultyAdjustMod.ReadFromDifficulty(advancedStats.BeatmapInfo.BaseDifficulty); SelectedMods.Value = new[] { difficultyAdjustMod }; }); @@ -137,13 +137,13 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestChangedDifficultyAdjustMod() { - AddStep("set beatmap", () => advancedStats.Beatmap = exampleBeatmapInfo); + AddStep("set beatmap", () => advancedStats.BeatmapInfo = exampleBeatmapInfo); AddStep("select changed Difficulty Adjust mod", () => { - var ruleset = advancedStats.Beatmap.Ruleset.CreateInstance(); + var ruleset = advancedStats.BeatmapInfo.Ruleset.CreateInstance(); var difficultyAdjustMod = ruleset.CreateMod(); - var originalDifficulty = advancedStats.Beatmap.BaseDifficulty; + var originalDifficulty = advancedStats.BeatmapInfo.BaseDifficulty; difficultyAdjustMod.ReadFromDifficulty(originalDifficulty); difficultyAdjustMod.DrainRate.Value = originalDifficulty.DrainRate - 0.5f; diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 78ddfa9ed2..66f15670f5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.SongSelect private readonly Stack selectedSets = new Stack(); private readonly HashSet eagerSelectedIDs = new HashSet(); - private BeatmapInfo currentSelection => carousel.SelectedBeatmap; + private BeatmapInfo currentSelection => carousel.SelectedBeatmapInfo; private const int set_count = 5; @@ -75,11 +75,11 @@ namespace osu.Game.Tests.Visual.SongSelect { for (int i = 0; i < 3; i++) { - AddStep("store selection", () => selection = carousel.SelectedBeatmap); + AddStep("store selection", () => selection = carousel.SelectedBeatmapInfo); if (isIterating) - AddUntilStep("selection changed", () => carousel.SelectedBeatmap != selection); + AddUntilStep("selection changed", () => carousel.SelectedBeatmapInfo != selection); else - AddUntilStep("selection not changed", () => carousel.SelectedBeatmap == selection); + AddUntilStep("selection not changed", () => carousel.SelectedBeatmapInfo == selection); } } } @@ -387,7 +387,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Set non-empty mode filter", () => carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(1) }, false)); - AddAssert("Something is selected", () => carousel.SelectedBeatmap != null); + AddAssert("Something is selected", () => carousel.SelectedBeatmapInfo != null); } /// @@ -562,7 +562,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("filter to ruleset 0", () => carousel.Filter(new FilterCriteria { Ruleset = rulesets.AvailableRulesets.ElementAt(0) }, false)); AddStep("select filtered map skipping filtered", () => carousel.SelectBeatmap(testMixed.Beatmaps[1], false)); - AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmap.RulesetID == 0); + AddAssert("unfiltered beatmap not selected", () => carousel.SelectedBeatmapInfo.RulesetID == 0); AddStep("remove mixed set", () => { @@ -653,7 +653,7 @@ namespace osu.Game.Tests.Visual.SongSelect carousel.Filter(new FilterCriteria { SearchText = Guid.NewGuid().ToString() }, false); }); - AddAssert("selection lost", () => carousel.SelectedBeatmap == null); + AddAssert("selection lost", () => carousel.SelectedBeatmapInfo == null); AddStep("Restore different ruleset filter", () => { @@ -661,7 +661,7 @@ namespace osu.Game.Tests.Visual.SongSelect eagerSelectedIDs.Add(carousel.SelectedBeatmapSet.ID); }); - AddAssert("selection changed", () => carousel.SelectedBeatmap != manySets.First().Beatmaps.First()); + AddAssert("selection changed", () => carousel.SelectedBeatmapInfo != manySets.First().Beatmaps.First()); } AddAssert("Selection was random", () => eagerSelectedIDs.Count > 2); @@ -763,9 +763,9 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep($"selected is set{set}{(diff.HasValue ? $" diff{diff.Value}" : "")}", () => { if (diff != null) - return carousel.SelectedBeatmap == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff.Value - 1).First(); + return carousel.SelectedBeatmapInfo == carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Skip(diff.Value - 1).First(); - return carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Contains(carousel.SelectedBeatmap); + return carousel.BeatmapSets.Skip(set - 1).First().Beatmaps.Contains(carousel.SelectedBeatmapInfo); }); private void setSelected(int set, int diff) => @@ -800,7 +800,7 @@ namespace osu.Game.Tests.Visual.SongSelect { carousel.RandomAlgorithm.Value = RandomSelectAlgorithm.RandomPermutation; - if (!selectedSets.Any() && carousel.SelectedBeatmap != null) + if (!selectedSets.Any() && carousel.SelectedBeatmapInfo != null) selectedSets.Push(carousel.SelectedBeatmapSet); carousel.SelectNextRandom(); diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 102e5ee425..f9e81d3da6 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select next and enter", () => { InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType() - .First(b => ((CarouselBeatmap)b.Item).Beatmap != songSelect.Carousel.SelectedBeatmap)); + .First(b => ((CarouselBeatmap)b.Item).BeatmapInfo != songSelect.Carousel.SelectedBeatmapInfo)); InputManager.Click(MouseButton.Left); @@ -172,7 +172,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("select next and enter", () => { InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType() - .First(b => ((CarouselBeatmap)b.Item).Beatmap != songSelect.Carousel.SelectedBeatmap)); + .First(b => ((CarouselBeatmap)b.Item).BeatmapInfo != songSelect.Carousel.SelectedBeatmapInfo)); InputManager.PressButton(MouseButton.Left); @@ -312,7 +312,7 @@ namespace osu.Game.Tests.Visual.SongSelect { createSongSelect(); addRulesetImportStep(2); - AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null); + AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); } [Test] @@ -322,13 +322,13 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(2); addRulesetImportStep(2); addRulesetImportStep(1); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 2); changeRuleset(1); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 1); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 1); changeRuleset(0); - AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmap == null); + AddUntilStep("no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); } [Test] @@ -338,7 +338,7 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(2); addRulesetImportStep(2); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 2); addRulesetImportStep(0); addRulesetImportStep(0); @@ -355,7 +355,7 @@ namespace osu.Game.Tests.Visual.SongSelect Beatmap.Value = manager.GetWorkingBeatmap(target); }); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target)); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(target)); // this is an important check, to make sure updateComponentFromBeatmap() was actually run AddUntilStep("selection shown on wedge", () => songSelect.CurrentBeatmapDetailsBeatmap.BeatmapInfo.Equals(target)); @@ -368,7 +368,7 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(2); addRulesetImportStep(2); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.RulesetID == 2); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.RulesetID == 2); addRulesetImportStep(0); addRulesetImportStep(0); @@ -385,7 +385,7 @@ namespace osu.Game.Tests.Visual.SongSelect Ruleset.Value = rulesets.AvailableRulesets.First(r => r.ID == 0); }); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap.Equals(target)); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo.Equals(target)); AddUntilStep("has correct ruleset", () => Ruleset.Value.ID == 0); @@ -444,7 +444,7 @@ namespace osu.Game.Tests.Visual.SongSelect { createSongSelect(); addManyTestMaps(); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); bool startRequested = false; @@ -473,13 +473,13 @@ namespace osu.Game.Tests.Visual.SongSelect // used for filter check below AddStep("allow convert display", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, true)); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = "nonono"); AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap); - AddUntilStep("has no selection", () => songSelect.Carousel.SelectedBeatmap == null); + AddUntilStep("has no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); BeatmapInfo target = null; @@ -494,7 +494,7 @@ namespace osu.Game.Tests.Visual.SongSelect Beatmap.Value = manager.GetWorkingBeatmap(target); }); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); AddAssert("selected only shows expected ruleset (plus converts)", () => { @@ -502,16 +502,16 @@ namespace osu.Game.Tests.Visual.SongSelect // special case for converts checked here. return selectedPanel.ChildrenOfType().All(i => - i.IsFiltered || i.Item.Beatmap.Ruleset.ID == targetRuleset || i.Item.Beatmap.Ruleset.ID == 0); + i.IsFiltered || i.Item.BeatmapInfo.Ruleset.ID == targetRuleset || i.Item.BeatmapInfo.Ruleset.ID == 0); }); - AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmap?.OnlineBeatmapID == target.OnlineBeatmapID); + AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineBeatmapID == target.OnlineBeatmapID); AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID); 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.SelectedBeatmap.OnlineBeatmapID == target.OnlineBeatmapID); + AddAssert("carousel still correct", () => songSelect.Carousel.SelectedBeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID); } [Test] @@ -522,13 +522,13 @@ namespace osu.Game.Tests.Visual.SongSelect changeRuleset(0); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = "nonono"); AddUntilStep("dummy selected", () => Beatmap.Value is DummyWorkingBeatmap); - AddUntilStep("has no selection", () => songSelect.Carousel.SelectedBeatmap == null); + AddUntilStep("has no selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); BeatmapInfo target = null; @@ -540,15 +540,15 @@ namespace osu.Game.Tests.Visual.SongSelect Beatmap.Value = manager.GetWorkingBeatmap(target); }); - AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmap != null); + AddUntilStep("has selection", () => songSelect.Carousel.SelectedBeatmapInfo != null); - AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmap?.OnlineBeatmapID == target.OnlineBeatmapID); + AddUntilStep("carousel has correct", () => songSelect.Carousel.SelectedBeatmapInfo?.OnlineBeatmapID == target.OnlineBeatmapID); AddUntilStep("game has correct", () => Beatmap.Value.BeatmapInfo.OnlineBeatmapID == target.OnlineBeatmapID); AddStep("set filter text", () => songSelect.FilterControl.ChildrenOfType().First().Text = "nononoo"); AddUntilStep("game lost selection", () => Beatmap.Value is DummyWorkingBeatmap); - AddAssert("carousel lost selection", () => songSelect.Carousel.SelectedBeatmap == null); + AddAssert("carousel lost selection", () => songSelect.Carousel.SelectedBeatmapInfo == null); } [Test] @@ -581,9 +581,9 @@ namespace osu.Game.Tests.Visual.SongSelect createSongSelect(); addRulesetImportStep(0); AddStep("Move to last difficulty", () => songSelect.Carousel.SelectBeatmap(songSelect.Carousel.BeatmapSets.First().Beatmaps.Last())); - AddStep("Store current ID", () => previousID = songSelect.Carousel.SelectedBeatmap.ID); + AddStep("Store current ID", () => previousID = songSelect.Carousel.SelectedBeatmapInfo.ID); AddStep("Hide first beatmap", () => manager.Hide(songSelect.Carousel.SelectedBeatmapSet.Beatmaps.First())); - AddAssert("Selected beatmap has not changed", () => songSelect.Carousel.SelectedBeatmap.ID == previousID); + AddAssert("Selected beatmap has not changed", () => songSelect.Carousel.SelectedBeatmapInfo.ID == previousID); } [Test] @@ -641,7 +641,7 @@ namespace osu.Game.Tests.Visual.SongSelect InputManager.Click(MouseButton.Left); }); - AddAssert("Selected beatmap correct", () => songSelect.Carousel.SelectedBeatmap == filteredBeatmap); + AddAssert("Selected beatmap correct", () => songSelect.Carousel.SelectedBeatmapInfo == filteredBeatmap); } [Test] @@ -717,7 +717,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Find an icon for different ruleset", () => { difficultyIcon = set.ChildrenOfType() - .First(icon => icon.Item.Beatmap.Ruleset.ID == 3); + .First(icon => icon.Item.BeatmapInfo.Ruleset.ID == 3); }); AddAssert("Check ruleset is osu!", () => Ruleset.Value.ID == 0); @@ -735,7 +735,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3); - AddAssert("Selected beatmap still same set", () => songSelect.Carousel.SelectedBeatmap.BeatmapSet.ID == previousSetID); + AddAssert("Selected beatmap still same set", () => songSelect.Carousel.SelectedBeatmapInfo.BeatmapSet.ID == previousSetID); AddAssert("Selected beatmap is mania", () => Beatmap.Value.BeatmapInfo.Ruleset.ID == 3); } @@ -767,7 +767,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddStep("Find group icon for different ruleset", () => { groupIcon = set.ChildrenOfType() - .First(icon => icon.Items.First().Beatmap.Ruleset.ID == 3); + .First(icon => icon.Items.First().BeatmapInfo.Ruleset.ID == 3); }); AddAssert("Check ruleset is osu!", () => Ruleset.Value.ID == 0); @@ -781,7 +781,7 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3); - AddAssert("Check first item in group selected", () => Beatmap.Value.BeatmapInfo.Equals(groupIcon.Items.First().Beatmap)); + AddAssert("Check first item in group selected", () => Beatmap.Value.BeatmapInfo.Equals(groupIcon.Items.First().BeatmapInfo)); } [Test] @@ -856,7 +856,7 @@ namespace osu.Game.Tests.Visual.SongSelect private int getBeatmapIndex(BeatmapSetInfo set, BeatmapInfo info) => set.Beatmaps.FindIndex(b => b == info); - private int getCurrentBeatmapIndex() => getBeatmapIndex(songSelect.Carousel.SelectedBeatmapSet, songSelect.Carousel.SelectedBeatmap); + private int getCurrentBeatmapIndex() => getBeatmapIndex(songSelect.Carousel.SelectedBeatmapSet, songSelect.Carousel.SelectedBeatmapInfo); private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, FilterableDifficultyIcon icon) { diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index 6080f7b636..357c82df61 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -21,22 +21,22 @@ namespace osu.Game.Tournament.Components { public class SongBar : CompositeDrawable { - private BeatmapInfo beatmap; + private BeatmapInfo beatmapInfo; public const float HEIGHT = 145 / 2f; [Resolved] private IBindable ruleset { get; set; } - public BeatmapInfo Beatmap + public BeatmapInfo BeatmapInfo { - get => beatmap; + get => beatmapInfo; set { - if (beatmap == value) + if (beatmapInfo == value) return; - beatmap = value; + beatmapInfo = value; update(); } } @@ -95,18 +95,18 @@ namespace osu.Game.Tournament.Components private void update() { - if (beatmap == null) + if (beatmapInfo == null) { flow.Clear(); return; } - var bpm = beatmap.BeatmapSet.OnlineInfo.BPM; - var length = beatmap.Length; + var bpm = beatmapInfo.BeatmapSet.OnlineInfo.BPM; + var length = beatmapInfo.Length; string hardRockExtra = ""; string srExtra = ""; - var ar = beatmap.BaseDifficulty.ApproachRate; + var ar = beatmapInfo.BaseDifficulty.ApproachRate; if ((mods & LegacyMods.HardRock) > 0) { @@ -132,9 +132,9 @@ namespace osu.Game.Tournament.Components default: stats = new (string heading, string content)[] { - ("CS", $"{beatmap.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"), + ("CS", $"{beatmapInfo.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"), ("AR", $"{ar:0.#}{hardRockExtra}"), - ("OD", $"{beatmap.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"), + ("OD", $"{beatmapInfo.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"), }; break; @@ -142,15 +142,15 @@ namespace osu.Game.Tournament.Components case 3: stats = new (string heading, string content)[] { - ("OD", $"{beatmap.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"), - ("HP", $"{beatmap.BaseDifficulty.DrainRate:0.#}{hardRockExtra}") + ("OD", $"{beatmapInfo.BaseDifficulty.OverallDifficulty:0.#}{hardRockExtra}"), + ("HP", $"{beatmapInfo.BaseDifficulty.DrainRate:0.#}{hardRockExtra}") }; break; case 2: stats = new (string heading, string content)[] { - ("CS", $"{beatmap.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"), + ("CS", $"{beatmapInfo.BaseDifficulty.CircleSize:0.#}{hardRockExtra}"), ("AR", $"{ar:0.#}"), }; break; @@ -186,7 +186,7 @@ namespace osu.Game.Tournament.Components Children = new Drawable[] { new DiffPiece(stats), - new DiffPiece(("Star Rating", $"{beatmap.StarDifficulty:0.#}{srExtra}")) + new DiffPiece(("Star Rating", $"{beatmapInfo.StarDifficulty:0.#}{srExtra}")) } }, new FillFlowContainer @@ -229,7 +229,7 @@ namespace osu.Game.Tournament.Components } } }, - new TournamentBeatmapPanel(beatmap) + new TournamentBeatmapPanel(beatmapInfo) { RelativeSizeAxes = Axes.X, Width = 0.5f, diff --git a/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs b/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs index 50498304ca..b94b164116 100644 --- a/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs +++ b/osu.Game.Tournament/Screens/BeatmapInfoScreen.cs @@ -40,7 +40,7 @@ namespace osu.Game.Tournament.Screens private void beatmapChanged(ValueChangedEvent beatmap) { SongBar.FadeInFromZero(300, Easing.OutQuint); - SongBar.Beatmap = beatmap.NewValue; + SongBar.BeatmapInfo = beatmap.NewValue; } } } diff --git a/osu.Game/Overlays/BeatmapSet/BasicStats.cs b/osu.Game/Overlays/BeatmapSet/BasicStats.cs index 5a6cde8229..683f4f0c49 100644 --- a/osu.Game/Overlays/BeatmapSet/BasicStats.cs +++ b/osu.Game/Overlays/BeatmapSet/BasicStats.cs @@ -38,16 +38,16 @@ namespace osu.Game.Overlays.BeatmapSet } } - private BeatmapInfo beatmap; + private BeatmapInfo beatmapInfo; - public BeatmapInfo Beatmap + public BeatmapInfo BeatmapInfo { - get => beatmap; + get => beatmapInfo; set { - if (value == beatmap) return; + if (value == beatmapInfo) return; - beatmap = value; + beatmapInfo = value; updateDisplay(); } @@ -57,7 +57,7 @@ namespace osu.Game.Overlays.BeatmapSet { bpm.Value = BeatmapSet?.OnlineInfo?.BPM.ToLocalisableString(@"0.##") ?? (LocalisableString)"-"; - if (beatmap == null) + if (beatmapInfo == null) { length.Value = string.Empty; circleCount.Value = string.Empty; @@ -65,11 +65,11 @@ namespace osu.Game.Overlays.BeatmapSet } else { - length.TooltipText = BeatmapsetsStrings.ShowStatsTotalLength(TimeSpan.FromMilliseconds(beatmap.Length).ToFormattedDuration()); - length.Value = TimeSpan.FromMilliseconds(beatmap.Length).ToFormattedDuration(); + length.TooltipText = BeatmapsetsStrings.ShowStatsTotalLength(TimeSpan.FromMilliseconds(beatmapInfo.Length).ToFormattedDuration()); + length.Value = TimeSpan.FromMilliseconds(beatmapInfo.Length).ToFormattedDuration(); - circleCount.Value = beatmap.OnlineInfo.CircleCount.ToLocalisableString(@"N0"); - sliderCount.Value = beatmap.OnlineInfo.SliderCount.ToLocalisableString(@"N0"); + circleCount.Value = beatmapInfo.OnlineInfo.CircleCount.ToLocalisableString(@"N0"); + sliderCount.Value = beatmapInfo.OnlineInfo.SliderCount.ToLocalisableString(@"N0"); } } diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs index c3b6444a24..dcf06ac7fb 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapSetHeaderContent.cs @@ -211,7 +211,7 @@ namespace osu.Game.Overlays.BeatmapSet Picker.Beatmap.ValueChanged += b => { - Details.Beatmap = b.NewValue; + Details.BeatmapInfo = b.NewValue; externalLink.Link = $@"{api.WebsiteRootUrl}/beatmapsets/{BeatmapSet.Value?.OnlineBeatmapSetID}#{b.NewValue?.Ruleset.ShortName}/{b.NewValue?.OnlineBeatmapID}"; }; } diff --git a/osu.Game/Overlays/BeatmapSet/Details.cs b/osu.Game/Overlays/BeatmapSet/Details.cs index 680487ffbb..92361ae4f8 100644 --- a/osu.Game/Overlays/BeatmapSet/Details.cs +++ b/osu.Game/Overlays/BeatmapSet/Details.cs @@ -37,16 +37,16 @@ namespace osu.Game.Overlays.BeatmapSet } } - private BeatmapInfo beatmap; + private BeatmapInfo beatmapInfo; - public BeatmapInfo Beatmap + public BeatmapInfo BeatmapInfo { - get => beatmap; + get => beatmapInfo; set { - if (value == beatmap) return; + if (value == beatmapInfo) return; - basic.Beatmap = advanced.Beatmap = beatmap = value; + basic.BeatmapInfo = advanced.BeatmapInfo = beatmapInfo = value; } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5eceae3c6e..f424587e22 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Select /// /// The currently selected beatmap. /// - public BeatmapInfo SelectedBeatmap => selectedBeatmap?.Beatmap; + public BeatmapInfo SelectedBeatmapInfo => selectedBeatmap?.BeatmapInfo; private CarouselBeatmap selectedBeatmap => selectedBeatmapSet?.Beatmaps.FirstOrDefault(s => s.State.Value == CarouselItemState.Selected); @@ -65,7 +65,7 @@ namespace osu.Game.Screens.Select private CarouselBeatmapSet selectedBeatmapSet; /// - /// Raised when the is changed. + /// Raised when the is changed. /// public Action SelectionChanged; @@ -212,7 +212,7 @@ namespace osu.Game.Screens.Select // If the selected beatmap is about to be removed, store its ID so it can be re-selected if required if (existingSet?.State?.Value == CarouselItemState.Selected) - previouslySelectedID = selectedBeatmap?.Beatmap.ID; + previouslySelectedID = selectedBeatmap?.BeatmapInfo.ID; var newSet = createCarouselSet(beatmapSet); @@ -233,7 +233,7 @@ namespace osu.Game.Screens.Select // check if we can/need to maintain our current selection. if (previouslySelectedID != null) - select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.Beatmap.ID == previouslySelectedID) ?? newSet); + select((CarouselItem)newSet.Beatmaps.FirstOrDefault(b => b.BeatmapInfo.ID == previouslySelectedID) ?? newSet); itemsCache.Invalidate(); Schedule(() => BeatmapSetsChanged?.Invoke()); @@ -258,7 +258,7 @@ namespace osu.Game.Screens.Select if (!bypassFilters && set.Filtered.Value) continue; - var item = set.Beatmaps.FirstOrDefault(p => p.Beatmap.Equals(beatmap)); + var item = set.Beatmaps.FirstOrDefault(p => p.BeatmapInfo.Equals(beatmap)); if (item == null) // The beatmap that needs to be selected doesn't exist in this set @@ -472,7 +472,7 @@ namespace osu.Game.Screens.Select private float? scrollTarget; /// - /// Scroll to the current . + /// Scroll to the current . /// /// /// Whether the scroll position should immediately be shifted to the target, delegating animation to visible panels. @@ -720,7 +720,7 @@ namespace osu.Game.Screens.Select if (state.NewValue == CarouselItemState.Selected) { selectedBeatmapSet = set; - SelectionChanged?.Invoke(c.Beatmap); + SelectionChanged?.Invoke(c.BeatmapInfo); itemsCache.Invalidate(); ScrollToSelected(); diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index 973f54c038..d59d76300a 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -170,7 +170,7 @@ namespace osu.Game.Screens.Select private void updateStatistics() { - advanced.Beatmap = Beatmap; + advanced.BeatmapInfo = Beatmap; description.Text = Beatmap?.Version; source.Text = Beatmap?.Metadata?.Source; tags.Text = Beatmap?.Metadata?.Tags; diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index f95ddfee41..3f729d9477 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -12,11 +12,11 @@ namespace osu.Game.Screens.Select.Carousel { public override float TotalHeight => DrawableCarouselBeatmap.HEIGHT; - public readonly BeatmapInfo Beatmap; + public readonly BeatmapInfo BeatmapInfo; - public CarouselBeatmap(BeatmapInfo beatmap) + public CarouselBeatmap(BeatmapInfo beatmapInfo) { - Beatmap = beatmap; + BeatmapInfo = beatmapInfo; State.Value = CarouselItemState.Collapsed; } @@ -28,36 +28,36 @@ namespace osu.Game.Screens.Select.Carousel bool match = criteria.Ruleset == null || - Beatmap.RulesetID == criteria.Ruleset.ID || - (Beatmap.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps); + BeatmapInfo.RulesetID == criteria.Ruleset.ID || + (BeatmapInfo.RulesetID == 0 && criteria.Ruleset.ID > 0 && criteria.AllowConvertedBeatmaps); - if (Beatmap.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true) + if (BeatmapInfo.BeatmapSet?.Equals(criteria.SelectedBeatmapSet) == true) { // only check ruleset equality or convertability for selected beatmap Filtered.Value = !match; return; } - match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(Beatmap.StarDifficulty); - match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(Beatmap.BaseDifficulty.ApproachRate); - match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(Beatmap.BaseDifficulty.DrainRate); - match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(Beatmap.BaseDifficulty.CircleSize); - match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(Beatmap.BaseDifficulty.OverallDifficulty); - match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(Beatmap.Length); - match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(Beatmap.BPM); + match &= !criteria.StarDifficulty.HasFilter || criteria.StarDifficulty.IsInRange(BeatmapInfo.StarDifficulty); + match &= !criteria.ApproachRate.HasFilter || criteria.ApproachRate.IsInRange(BeatmapInfo.BaseDifficulty.ApproachRate); + match &= !criteria.DrainRate.HasFilter || criteria.DrainRate.IsInRange(BeatmapInfo.BaseDifficulty.DrainRate); + match &= !criteria.CircleSize.HasFilter || criteria.CircleSize.IsInRange(BeatmapInfo.BaseDifficulty.CircleSize); + match &= !criteria.OverallDifficulty.HasFilter || criteria.OverallDifficulty.IsInRange(BeatmapInfo.BaseDifficulty.OverallDifficulty); + match &= !criteria.Length.HasFilter || criteria.Length.IsInRange(BeatmapInfo.Length); + match &= !criteria.BPM.HasFilter || criteria.BPM.IsInRange(BeatmapInfo.BPM); - match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(Beatmap.BeatDivisor); - match &= !criteria.OnlineStatus.HasFilter || criteria.OnlineStatus.IsInRange(Beatmap.Status); + match &= !criteria.BeatDivisor.HasFilter || criteria.BeatDivisor.IsInRange(BeatmapInfo.BeatDivisor); + match &= !criteria.OnlineStatus.HasFilter || criteria.OnlineStatus.IsInRange(BeatmapInfo.Status); - match &= !criteria.Creator.HasFilter || criteria.Creator.Matches(Beatmap.Metadata.AuthorString); - match &= !criteria.Artist.HasFilter || criteria.Artist.Matches(Beatmap.Metadata.Artist) || - criteria.Artist.Matches(Beatmap.Metadata.ArtistUnicode); + match &= !criteria.Creator.HasFilter || criteria.Creator.Matches(BeatmapInfo.Metadata.AuthorString); + match &= !criteria.Artist.HasFilter || criteria.Artist.Matches(BeatmapInfo.Metadata.Artist) || + criteria.Artist.Matches(BeatmapInfo.Metadata.ArtistUnicode); - match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(Beatmap.StarDifficulty); + match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarDifficulty); if (match) { - var terms = Beatmap.SearchableTerms; + var terms = BeatmapInfo.SearchableTerms; foreach (var criteriaTerm in criteria.SearchTerms) match &= terms.Any(term => term.Contains(criteriaTerm, StringComparison.InvariantCultureIgnoreCase)); @@ -66,16 +66,16 @@ 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 = (Beatmap.OnlineBeatmapID == criteria.SearchNumber.Value) || - (Beatmap.BeatmapSet?.OnlineBeatmapSetID == criteria.SearchNumber.Value); + match = (BeatmapInfo.OnlineBeatmapID == criteria.SearchNumber.Value) || + (BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID == criteria.SearchNumber.Value); } } if (match) - match &= criteria.Collection?.Beatmaps.Contains(Beatmap) ?? true; + match &= criteria.Collection?.Beatmaps.Contains(BeatmapInfo) ?? true; if (match && criteria.RulesetCriteria != null) - match &= criteria.RulesetCriteria.Matches(Beatmap); + match &= criteria.RulesetCriteria.Matches(BeatmapInfo); Filtered.Value = !match; } @@ -89,13 +89,13 @@ namespace osu.Game.Screens.Select.Carousel { default: case SortMode.Difficulty: - var ruleset = Beatmap.RulesetID.CompareTo(otherBeatmap.Beatmap.RulesetID); + var ruleset = BeatmapInfo.RulesetID.CompareTo(otherBeatmap.BeatmapInfo.RulesetID); if (ruleset != 0) return ruleset; - return Beatmap.StarDifficulty.CompareTo(otherBeatmap.Beatmap.StarDifficulty); + return BeatmapInfo.StarDifficulty.CompareTo(otherBeatmap.BeatmapInfo.StarDifficulty); } } - public override string ToString() => Beatmap.ToString(); + public override string ToString() => BeatmapInfo.ToString(); } } diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs index 00c2c2cb4a..0d7882bf17 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmapSet.cs @@ -47,8 +47,8 @@ namespace osu.Game.Screens.Select.Carousel { if (LastSelected == null || LastSelected.Filtered.Value) { - if (GetRecommendedBeatmap?.Invoke(Children.OfType().Where(b => !b.Filtered.Value).Select(b => b.Beatmap)) is BeatmapInfo recommended) - return Children.OfType().First(b => b.Beatmap == recommended); + if (GetRecommendedBeatmap?.Invoke(Children.OfType().Where(b => !b.Filtered.Value).Select(b => b.BeatmapInfo)) is BeatmapInfo recommended) + return Children.OfType().First(b => b.BeatmapInfo == recommended); } return base.GetNextToSelect(); @@ -91,7 +91,7 @@ namespace osu.Game.Screens.Select.Carousel /// /// All beatmaps which are not filtered and valid for display. /// - protected IEnumerable ValidBeatmaps => Beatmaps.Where(b => !b.Filtered.Value || b.State.Value == CarouselItemState.Selected).Select(b => b.Beatmap); + protected IEnumerable ValidBeatmaps => Beatmaps.Where(b => !b.Filtered.Value || b.State.Value == CarouselItemState.Selected).Select(b => b.BeatmapInfo); private int compareUsingAggregateMax(CarouselBeatmapSet other, Func func) { diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 633ef9297e..2fe7ff4562 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Select.Carousel public DrawableCarouselBeatmap(CarouselBeatmap panel) { - beatmap = panel.Beatmap; + beatmap = panel.BeatmapInfo; Item = panel; } diff --git a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs index 51fe7796c7..ce0cec837b 100644 --- a/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs +++ b/osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Select.Carousel public readonly CarouselBeatmap Item; public FilterableDifficultyIcon(CarouselBeatmap item) - : base(item.Beatmap, performBackgroundDifficultyLookup: false) + : base(item.BeatmapInfo, performBackgroundDifficultyLookup: false) { filtered.BindTo(item.Filtered); filtered.ValueChanged += isFiltered => Schedule(() => this.FadeTo(isFiltered.NewValue ? 0.1f : 1, 100)); diff --git a/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs b/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs index d2f9ed3a6a..acffdd9f64 100644 --- a/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs +++ b/osu.Game/Screens/Select/Carousel/FilterableGroupedDifficultyIcon.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Select.Carousel public readonly List Items; public FilterableGroupedDifficultyIcon(List items, RulesetInfo ruleset) - : base(items.Select(i => i.Beatmap).ToList(), ruleset, Color4.White) + : base(items.Select(i => i.BeatmapInfo).ToList(), ruleset, Color4.White) { Items = items; diff --git a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs index 23a02547b2..9fb640ba1a 100644 --- a/osu.Game/Screens/Select/Carousel/SetPanelContent.cs +++ b/osu.Game/Screens/Select/Carousel/SetPanelContent.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Select.Carousel var beatmaps = carouselSet.Beatmaps.ToList(); return beatmaps.Count > maximum_difficulty_icons - ? (IEnumerable)beatmaps.GroupBy(b => b.Beatmap.Ruleset).Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Key)) + ? (IEnumerable)beatmaps.GroupBy(b => b.BeatmapInfo.Ruleset).Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Key)) : beatmaps.Select(b => new FilterableDifficultyIcon(b)); } } diff --git a/osu.Game/Screens/Select/Details/AdvancedStats.cs b/osu.Game/Screens/Select/Details/AdvancedStats.cs index 53e30fd9ca..8c978e25ae 100644 --- a/osu.Game/Screens/Select/Details/AdvancedStats.cs +++ b/osu.Game/Screens/Select/Details/AdvancedStats.cs @@ -38,16 +38,16 @@ namespace osu.Game.Screens.Select.Details protected readonly StatisticRow FirstValue, HpDrain, Accuracy, ApproachRate; private readonly StatisticRow starDifficulty; - private BeatmapInfo beatmap; + private BeatmapInfo beatmapInfo; - public BeatmapInfo Beatmap + public BeatmapInfo BeatmapInfo { - get => beatmap; + get => beatmapInfo; set { - if (value == beatmap) return; + if (value == beatmapInfo) return; - beatmap = value; + beatmapInfo = value; updateStatistics(); } @@ -106,7 +106,7 @@ namespace osu.Game.Screens.Select.Details private void updateStatistics() { - BeatmapDifficulty baseDifficulty = Beatmap?.BaseDifficulty; + BeatmapDifficulty baseDifficulty = BeatmapInfo?.BaseDifficulty; BeatmapDifficulty adjustedDifficulty = null; if (baseDifficulty != null && mods.Value.Any(m => m is IApplicableToDifficulty)) @@ -117,7 +117,7 @@ namespace osu.Game.Screens.Select.Details mod.ApplyToDifficulty(adjustedDifficulty); } - switch (Beatmap?.Ruleset?.ID ?? 0) + switch (BeatmapInfo?.Ruleset?.ID ?? 0) { case 3: // Account for mania differences locally for now @@ -145,13 +145,13 @@ namespace osu.Game.Screens.Select.Details { starDifficultyCancellationSource?.Cancel(); - if (Beatmap == null) + if (BeatmapInfo == null) return; starDifficultyCancellationSource = new CancellationTokenSource(); - var normalStarDifficulty = difficultyCache.GetDifficultyAsync(Beatmap, ruleset.Value, null, starDifficultyCancellationSource.Token); - var moddedStarDifficulty = difficultyCache.GetDifficultyAsync(Beatmap, ruleset.Value, mods.Value, starDifficultyCancellationSource.Token); + var normalStarDifficulty = difficultyCache.GetDifficultyAsync(BeatmapInfo, ruleset.Value, null, starDifficultyCancellationSource.Token); + var moddedStarDifficulty = difficultyCache.GetDifficultyAsync(BeatmapInfo, ruleset.Value, mods.Value, starDifficultyCancellationSource.Token); Task.WhenAll(normalStarDifficulty, moddedStarDifficulty).ContinueWith(_ => Schedule(() => { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 9801098952..e4ab360765 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -377,7 +377,7 @@ namespace osu.Game.Screens.Select // avoid attempting to continue before a selection has been obtained. // this could happen via a user interaction while the carousel is still in a loading state. - if (Carousel.SelectedBeatmap == null) return; + if (Carousel.SelectedBeatmapInfo == null) return; if (beatmap != null) Carousel.SelectBeatmap(beatmap); From d55836c0b2bd529735efef5fd281e4da4e023baa Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sat, 2 Oct 2021 15:10:30 +0200 Subject: [PATCH 053/183] Make `ResetButton` no longer part of search filtering The button will now appear if and only if all the bindings in its section are visible (not filtered out by the search) --- .../Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index 0e8e10c086..806390c0ec 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -75,5 +75,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input Content.CornerRadius = 5; } + + public override IEnumerable FilterTerms => Enumerable.Empty(); } } From 6ec2223b5c231fc747b31e3d9f87ba810d2e376b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Oct 2021 23:01:44 +0900 Subject: [PATCH 054/183] Catch potential file access exceptions also in async flow --- osu.Game.Tests/Database/RealmTest.cs | 38 +++++++++++++--------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs index b7658d6408..219690db30 100644 --- a/osu.Game.Tests/Database/RealmTest.cs +++ b/osu.Game.Tests/Database/RealmTest.cs @@ -39,25 +39,9 @@ namespace osu.Game.Tests.Database realmFactory.Dispose(); - try - { - Logger.Log($"Final database size: {testStorage.GetStream(realmFactory.Filename)?.Length ?? 0}"); - } - catch - { - // windows runs may error due to file still being open. - } - + Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}"); realmFactory.Compact(); - - try - { - Logger.Log($"Final database size after compact: {testStorage.GetStream(realmFactory.Filename)?.Length ?? 0}"); - } - catch - { - // windows runs may error due to file still being open. - } + Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}"); } }); } @@ -71,16 +55,28 @@ namespace osu.Game.Tests.Database using (var realmFactory = new RealmContextFactory(testStorage, caller)) { Logger.Log($"Running test using realm file {testStorage.GetFullPath(realmFactory.Filename)}"); - await testAction(realmFactory, testStorage); realmFactory.Dispose(); - Logger.Log($"Final database size: {testStorage.GetStream(realmFactory.Filename)?.Length ?? 0}"); + Logger.Log($"Final database size: {getFileSize(testStorage, realmFactory)}"); realmFactory.Compact(); - Logger.Log($"Final database size after compact: {testStorage.GetStream(realmFactory.Filename)?.Length ?? 0}"); + Logger.Log($"Final database size after compact: {getFileSize(testStorage, realmFactory)}"); } }); } + + private static long getFileSize(Storage testStorage, RealmContextFactory realmFactory) + { + try + { + return testStorage.GetStream(realmFactory.Filename)?.Length ?? 0; + } + catch + { + // windows runs may error due to file still being open. + return 0; + } + } } } From ec61c3c5eeb2a72e74930902eccb78bc06abf1ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 Oct 2021 00:55:29 +0900 Subject: [PATCH 055/183] Rename all remaining cases --- osu.Desktop/DiscordRichPresence.cs | 4 +- .../Beatmaps/ManiaBeatmapConverter.cs | 4 +- .../ManiaFilterCriteria.cs | 4 +- .../Beatmaps/IO/ImportBeatmapTest.cs | 6 +- .../NonVisual/Filtering/FilterMatchingTest.cs | 2 +- .../Filtering/FilterQueryParserTest.cs | 2 +- .../Online/TestSceneBeatmapSetOverlay.cs | 2 +- .../TestSceneBeatmapSetOverlaySuccessRate.cs | 8 +-- .../SongSelect/TestSceneBeatmapDetails.cs | 14 ++-- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 26 ++++---- .../TestSceneDeleteLocalScore.cs | 13 ++-- .../TestSceneTournamentBeatmapPanel.cs | 2 +- .../TestSceneTournamentModDisplay.cs | 6 +- .../Components/TournamentBeatmapPanel.cs | 20 +++--- osu.Game.Tournament/IPC/FileBasedIPC.cs | 2 +- .../Screens/Editors/RoundEditorScreen.cs | 2 +- .../Screens/Editors/SeedingEditorScreen.cs | 2 +- .../Screens/MapPool/MapPoolScreen.cs | 6 +- osu.Game.Tournament/TournamentGameBase.cs | 4 +- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 24 +++---- osu.Game/Beatmaps/BeatmapManager.cs | 16 ++--- osu.Game/Beatmaps/BeatmapModelManager.cs | 18 ++--- osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs | 66 +++++++++---------- osu.Game/Beatmaps/BeatmapStore.cs | 24 +++---- osu.Game/Beatmaps/DifficultyRecommender.cs | 6 +- osu.Game/Beatmaps/Drawables/DifficultyIcon.cs | 36 +++++----- .../Drawables/DifficultyIconTooltip.cs | 8 +-- .../Online/API/Requests/GetBeatmapRequest.cs | 8 +-- .../Online/API/Requests/GetScoresRequest.cs | 14 ++-- .../API/Requests/Responses/APIBeatmap.cs | 2 +- .../API/Requests/Responses/APIBeatmapSet.cs | 2 +- .../Requests/Responses/APILegacyScoreInfo.cs | 8 +-- .../Responses/APIUserMostPlayedBeatmap.cs | 8 +-- osu.Game/Online/Chat/NowPlayingCommand.cs | 10 +-- osu.Game/Online/Rooms/APIPlaylistBeatmap.cs | 4 +- osu.Game/Online/Rooms/PlaylistItem.cs | 2 +- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 20 +++--- osu.Game/Overlays/BeatmapSet/Info.cs | 6 +- osu.Game/Overlays/BeatmapSet/SuccessRate.cs | 16 ++--- osu.Game/Overlays/BeatmapSetOverlay.cs | 2 +- .../Sections/BeatmapMetadataContainer.cs | 18 ++--- .../Historical/DrawableMostPlayedBeatmap.cs | 24 +++---- .../Sections/Ranks/DrawableProfileScore.cs | 12 ++-- .../Rulesets/Filter/IRulesetFilterCriteria.cs | 6 +- .../Components/Menus/DifficultyMenuItem.cs | 4 +- osu.Game/Screens/Select/BeatmapCarousel.cs | 8 +-- .../Select/BeatmapClearScoresDialog.cs | 6 +- osu.Game/Screens/Select/BeatmapDetailArea.cs | 2 +- osu.Game/Screens/Select/BeatmapDetails.cs | 38 +++++------ .../Carousel/DrawableCarouselBeatmap.cs | 32 ++++----- .../Screens/Select/Carousel/TopLocalRank.cs | 12 ++-- .../Select/Leaderboards/BeatmapLeaderboard.cs | 20 +++--- .../Screens/Select/LocalScoreDeleteDialog.cs | 4 +- .../Screens/Select/PlayBeatmapDetailArea.cs | 2 +- osu.Game/Screens/Select/SongSelect.cs | 32 ++++----- osu.Game/Skinning/LegacyBeatmapSkin.cs | 8 +-- .../Beatmaps/LegacyBeatmapSkinColourTest.cs | 4 +- osu.Game/Users/UserActivity.cs | 22 +++---- 58 files changed, 342 insertions(+), 341 deletions(-) diff --git a/osu.Desktop/DiscordRichPresence.cs b/osu.Desktop/DiscordRichPresence.cs index dcb88efeb6..e2b40e9dc6 100644 --- a/osu.Desktop/DiscordRichPresence.cs +++ b/osu.Desktop/DiscordRichPresence.cs @@ -140,10 +140,10 @@ namespace osu.Desktop switch (activity) { case UserActivity.InGame game: - return game.Beatmap.ToString(); + return game.BeatmapInfo.ToString(); case UserActivity.Editing edit: - return edit.Beatmap.ToString(); + return edit.BeatmapInfo.ToString(); case UserActivity.InLobby lobby: return privacyMode.Value == DiscordRichPresenceMode.Limited ? string.Empty : lobby.Room.Name.Value; diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 26393c8edb..0321a5325b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -71,9 +71,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps originalTargetColumns = TargetColumns; } - public static int GetColumnCountForNonConvert(BeatmapInfo beatmap) + public static int GetColumnCountForNonConvert(BeatmapInfo beatmapInfo) { - var roundedCircleSize = Math.Round(beatmap.BaseDifficulty.CircleSize); + var roundedCircleSize = Math.Round(beatmapInfo.BaseDifficulty.CircleSize); return (int)Math.Max(1, roundedCircleSize); } diff --git a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs index d9a278ef29..0290230490 100644 --- a/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs +++ b/osu.Game.Rulesets.Mania/ManiaFilterCriteria.cs @@ -13,9 +13,9 @@ namespace osu.Game.Rulesets.Mania { private FilterCriteria.OptionalRange keys; - public bool Matches(BeatmapInfo beatmap) + public bool Matches(BeatmapInfo beatmapInfo) { - return !keys.HasFilter || (beatmap.RulesetID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmap))); + return !keys.HasFilter || (beatmapInfo.RulesetID == new ManiaRuleset().LegacyID && keys.IsInRange(ManiaBeatmapConverter.GetColumnCountForNonConvert(beatmapInfo))); } public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index cba7f34ede..b536fc61b7 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -945,13 +945,13 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(manager.QueryBeatmapSets(_ => true).First().DeletePending); } - private static Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmap) + private static Task createScoreForBeatmap(OsuGameBase osu, BeatmapInfo beatmapInfo) { return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2, - Beatmap = beatmap, - BeatmapInfoID = beatmap.ID + Beatmap = beatmapInfo, + BeatmapInfoID = beatmapInfo.ID }, new ImportScoreTest.TestArchiveReader()); } diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs index 8ff2743b6a..ed86daf8b6 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterMatchingTest.cs @@ -239,7 +239,7 @@ namespace osu.Game.Tests.NonVisual.Filtering match = shouldMatch; } - public bool Matches(BeatmapInfo beatmap) => match; + public bool Matches(BeatmapInfo beatmapInfo) => match; public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) => false; } } diff --git a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs index a55bdd2df8..df42c70c87 100644 --- a/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs +++ b/osu.Game.Tests/NonVisual/Filtering/FilterQueryParserTest.cs @@ -256,7 +256,7 @@ namespace osu.Game.Tests.NonVisual.Filtering { public string CustomValue { get; set; } - public bool Matches(BeatmapInfo beatmap) => true; + public bool Matches(BeatmapInfo beatmapInfo) => true; public bool TryParseCustomKeywordCriteria(string key, Operator op, string value) { diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index f420ad976b..453e26ef96 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -233,7 +233,7 @@ namespace osu.Game.Tests.Visual.Online }); }); - AddAssert("shown beatmaps of current ruleset", () => overlay.Header.HeaderContent.Picker.Difficulties.All(b => b.Beatmap.Ruleset.Equals(overlay.Header.RulesetSelector.Current.Value))); + AddAssert("shown beatmaps of current ruleset", () => overlay.Header.HeaderContent.Picker.Difficulties.All(b => b.BeatmapInfo.Ruleset.Equals(overlay.Header.RulesetSelector.Current.Value))); AddAssert("left-most beatmap selected", () => overlay.Header.HeaderContent.Picker.Difficulties.First().State == BeatmapPicker.DifficultySelectorState.Selected); } diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs index fd5c188b94..fe8e33f783 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlaySuccessRate.cs @@ -58,10 +58,10 @@ namespace osu.Game.Tests.Visual.Online var firstBeatmap = createBeatmap(); var secondBeatmap = createBeatmap(); - AddStep("set first set", () => successRate.Beatmap = firstBeatmap); + AddStep("set first set", () => successRate.BeatmapInfo = firstBeatmap); AddAssert("ratings set", () => successRate.Graph.Metrics == firstBeatmap.Metrics); - AddStep("set second set", () => successRate.Beatmap = secondBeatmap); + AddStep("set second set", () => successRate.BeatmapInfo = secondBeatmap); AddAssert("ratings set", () => successRate.Graph.Metrics == secondBeatmap.Metrics); static BeatmapInfo createBeatmap() => new BeatmapInfo @@ -77,7 +77,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestOnlyFailMetrics() { - AddStep("set beatmap", () => successRate.Beatmap = new BeatmapInfo + AddStep("set beatmap", () => successRate.BeatmapInfo = new BeatmapInfo { Metrics = new BeatmapMetrics { @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestEmptyMetrics() { - AddStep("set beatmap", () => successRate.Beatmap = new BeatmapInfo + AddStep("set beatmap", () => successRate.BeatmapInfo = new BeatmapInfo { Metrics = new BeatmapMetrics() }); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs index b4544fbc85..d5b4fb9a80 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapDetails.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestAllMetrics() { - AddStep("all metrics", () => details.Beatmap = new BeatmapInfo + AddStep("all metrics", () => details.BeatmapInfo = new BeatmapInfo { BeatmapSet = new BeatmapSetInfo { @@ -61,7 +61,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestAllMetricsExceptSource() { - AddStep("all except source", () => details.Beatmap = new BeatmapInfo + AddStep("all except source", () => details.BeatmapInfo = new BeatmapInfo { BeatmapSet = new BeatmapSetInfo { @@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestOnlyRatings() { - AddStep("ratings", () => details.Beatmap = new BeatmapInfo + AddStep("ratings", () => details.BeatmapInfo = new BeatmapInfo { BeatmapSet = new BeatmapSetInfo { @@ -117,7 +117,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestOnlyFailsAndRetries() { - AddStep("fails retries", () => details.Beatmap = new BeatmapInfo + AddStep("fails retries", () => details.BeatmapInfo = new BeatmapInfo { Version = "Only Retries and Fails", Metadata = new BeatmapMetadata @@ -144,7 +144,7 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestNoMetrics() { - AddStep("no metrics", () => details.Beatmap = new BeatmapInfo + AddStep("no metrics", () => details.BeatmapInfo = new BeatmapInfo { Version = "No Metrics", Metadata = new BeatmapMetadata @@ -166,13 +166,13 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestNullBeatmap() { - AddStep("null beatmap", () => details.Beatmap = null); + AddStep("null beatmap", () => details.BeatmapInfo = null); } [Test] public void TestOnlineMetrics() { - AddStep("online ratings/retries/fails", () => details.Beatmap = new BeatmapInfo + AddStep("online ratings/retries/fails", () => details.BeatmapInfo = new BeatmapInfo { OnlineBeatmapID = 162, }); diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 29815ce9ff..95cf6a9903 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -76,7 +76,7 @@ namespace osu.Game.Tests.Visual.SongSelect beatmapManager.Import(TestResources.GetQuickTestBeatmapForImport()).Wait(); beatmapInfo = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); - leaderboard.Beatmap = beatmapInfo; + leaderboard.BeatmapInfo = beatmapInfo; }); clearScores(); @@ -186,7 +186,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void checkCount(int expected) => AddUntilStep("Correct count displayed", () => leaderboard.ChildrenOfType().Count() == expected); - private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmap) + private static ScoreInfo[] generateSampleScores(BeatmapInfo beatmapInfo) { return new[] { @@ -197,7 +197,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmap, + Beatmap = beatmapInfo, User = new User { Id = 6602580, @@ -216,7 +216,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmap, + Beatmap = beatmapInfo, User = new User { Id = 4608074, @@ -235,7 +235,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmap, + Beatmap = beatmapInfo, User = new User { Id = 1014222, @@ -254,7 +254,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmap, + Beatmap = beatmapInfo, User = new User { Id = 1541390, @@ -273,7 +273,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmap, + Beatmap = beatmapInfo, User = new User { Id = 2243452, @@ -292,7 +292,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmap, + Beatmap = beatmapInfo, User = new User { Id = 2705430, @@ -311,7 +311,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmap, + Beatmap = beatmapInfo, User = new User { Id = 7151382, @@ -330,7 +330,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmap, + Beatmap = beatmapInfo, User = new User { Id = 2051389, @@ -349,7 +349,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmap, + Beatmap = beatmapInfo, User = new User { Id = 6169483, @@ -368,7 +368,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmap, + Beatmap = beatmapInfo, User = new User { Id = 6702666, @@ -385,7 +385,7 @@ namespace osu.Game.Tests.Visual.SongSelect private void showBeatmapWithStatus(BeatmapSetOnlineStatus status) { - leaderboard.Beatmap = new BeatmapInfo + leaderboard.BeatmapInfo = new BeatmapInfo { OnlineBeatmapID = 1113057, Status = status, diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index 2e30ed9827..f58dbef145 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -37,7 +37,8 @@ namespace osu.Game.Tests.Visual.UserInterface private ScoreManager scoreManager; private readonly List importedScores = new List(); - private BeatmapInfo beatmap; + + private BeatmapInfo beatmapInfo; [Cached] private readonly DialogOverlay dialogOverlay; @@ -55,7 +56,7 @@ namespace osu.Game.Tests.Visual.UserInterface Anchor = Anchor.Centre, Size = new Vector2(550f, 450f), Scope = BeatmapLeaderboardScope.Local, - Beatmap = new BeatmapInfo + BeatmapInfo = new BeatmapInfo { ID = 1, Metadata = new BeatmapMetadata @@ -84,15 +85,15 @@ namespace osu.Game.Tests.Visual.UserInterface dependencies.Cache(beatmapManager = new BeatmapManager(LocalStorage, ContextFactory, rulesetStore, null, dependencies.Get(), Resources, dependencies.Get(), Beatmap.Default)); dependencies.Cache(scoreManager = new ScoreManager(rulesetStore, () => beatmapManager, LocalStorage, null, ContextFactory, Scheduler)); - beatmap = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Beatmaps[0]; + beatmapInfo = beatmapManager.Import(new ImportTask(TestResources.GetQuickTestBeatmapForImport())).Result.Beatmaps[0]; for (int i = 0; i < 50; i++) { var score = new ScoreInfo { OnlineScoreID = i, - Beatmap = beatmap, - BeatmapInfoID = beatmap.ID, + Beatmap = beatmapInfo, + BeatmapInfoID = beatmapInfo.ID, Accuracy = RNG.NextDouble(), TotalScore = RNG.Next(1, 1000000), MaxCombo = RNG.Next(1, 1000), @@ -115,7 +116,7 @@ namespace osu.Game.Tests.Visual.UserInterface leaderboard.Scores = null; leaderboard.FinishTransforms(true); // After setting scores, we may be waiting for transforms to expire drawables - leaderboard.Beatmap = beatmap; + leaderboard.BeatmapInfo = beatmapInfo; leaderboard.RefreshScores(); // Required in the case that the beatmap hasn't changed }); diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs index bc32a12ab7..f9c553cb3f 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tournament.Tests.Components private void success(APIBeatmap apiBeatmap) { - var beatmap = apiBeatmap.ToBeatmap(rulesets); + var beatmap = apiBeatmap.ToBeatmapInfo(rulesets); Add(new TournamentBeatmapPanel(beatmap) { Anchor = Anchor.Centre, diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs index 47e7ed9b61..27eb55a9fb 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentModDisplay.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tournament.Tests.Components private FillFlowContainer fillFlow; - private BeatmapInfo beatmap; + private BeatmapInfo beatmapInfo; [BackgroundDependencyLoader] private void load() @@ -44,12 +44,12 @@ namespace osu.Game.Tournament.Tests.Components private void success(APIBeatmap apiBeatmap) { - beatmap = apiBeatmap.ToBeatmap(rulesets); + beatmapInfo = apiBeatmap.ToBeatmapInfo(rulesets); var mods = rulesets.GetRuleset(Ladder.Ruleset.Value.ID ?? 0).CreateInstance().AllMods; foreach (var mod in mods) { - fillFlow.Add(new TournamentBeatmapPanel(beatmap, mod.Acronym) + fillFlow.Add(new TournamentBeatmapPanel(beatmapInfo, mod.Acronym) { Anchor = Anchor.Centre, Origin = Anchor.Centre diff --git a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs index e6d73c6e83..0e5a66e7fe 100644 --- a/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs +++ b/osu.Game.Tournament/Components/TournamentBeatmapPanel.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tournament.Components { public class TournamentBeatmapPanel : CompositeDrawable { - public readonly BeatmapInfo Beatmap; + public readonly BeatmapInfo BeatmapInfo; private readonly string mod; private const float horizontal_padding = 10; @@ -32,11 +32,11 @@ namespace osu.Game.Tournament.Components private readonly Bindable currentMatch = new Bindable(); private Box flash; - public TournamentBeatmapPanel(BeatmapInfo beatmap, string mod = null) + public TournamentBeatmapPanel(BeatmapInfo beatmapInfo, string mod = null) { - if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); + if (beatmapInfo == null) throw new ArgumentNullException(nameof(beatmapInfo)); - Beatmap = beatmap; + BeatmapInfo = beatmapInfo; this.mod = mod; Width = 400; Height = HEIGHT; @@ -61,7 +61,7 @@ namespace osu.Game.Tournament.Components { RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(0.5f), - BeatmapSet = Beatmap.BeatmapSet, + BeatmapSet = BeatmapInfo.BeatmapSet, }, new FillFlowContainer { @@ -75,8 +75,8 @@ namespace osu.Game.Tournament.Components new TournamentSpriteText { Text = new RomanisableString( - $"{Beatmap.Metadata.ArtistUnicode ?? Beatmap.Metadata.Artist} - {Beatmap.Metadata.TitleUnicode ?? Beatmap.Metadata.Title}", - $"{Beatmap.Metadata.Artist} - {Beatmap.Metadata.Title}"), + $"{BeatmapInfo.Metadata.ArtistUnicode ?? BeatmapInfo.Metadata.Artist} - {BeatmapInfo.Metadata.TitleUnicode ?? BeatmapInfo.Metadata.Title}", + $"{BeatmapInfo.Metadata.Artist} - {BeatmapInfo.Metadata.Title}"), Font = OsuFont.Torus.With(weight: FontWeight.Bold), }, new FillFlowContainer @@ -93,7 +93,7 @@ namespace osu.Game.Tournament.Components }, new TournamentSpriteText { - Text = Beatmap.Metadata.AuthorString, + Text = BeatmapInfo.Metadata.AuthorString, Padding = new MarginPadding { Right = 20 }, Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 14) }, @@ -105,7 +105,7 @@ namespace osu.Game.Tournament.Components }, new TournamentSpriteText { - Text = Beatmap.Version, + Text = BeatmapInfo.Version, Font = OsuFont.Torus.With(weight: FontWeight.Bold, size: 14) }, } @@ -149,7 +149,7 @@ namespace osu.Game.Tournament.Components private void updateState() { - var found = currentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == Beatmap.OnlineBeatmapID); + var found = currentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == BeatmapInfo.OnlineBeatmapID); bool doFlash = found != choice; choice = found; diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index f538d4a7d9..7010a30eb7 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tournament.IPC else { beatmapLookupRequest = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = beatmapId }); - beatmapLookupRequest.Success += b => Beatmap.Value = b.ToBeatmap(Rulesets); + beatmapLookupRequest.Success += b => Beatmap.Value = b.ToBeatmapInfo(Rulesets); API.Queue(beatmapLookupRequest); } } diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs index 27ad6650d1..6e4fc8fe1a 100644 --- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs @@ -238,7 +238,7 @@ namespace osu.Game.Tournament.Screens.Editors req.Success += res => { - Model.BeatmapInfo = res.ToBeatmap(rulesets); + Model.BeatmapInfo = res.ToBeatmapInfo(rulesets); updatePanel(); }; diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs index 6418bf97da..b64a3993e6 100644 --- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs @@ -246,7 +246,7 @@ namespace osu.Game.Tournament.Screens.Editors req.Success += res => { - Model.BeatmapInfo = res.ToBeatmap(rulesets); + Model.BeatmapInfo = res.ToBeatmapInfo(rulesets); updatePanel(); }; diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index d4292c5492..1e3c550323 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -147,11 +147,11 @@ namespace osu.Game.Tournament.Screens.MapPool if (map != null) { - if (e.Button == MouseButton.Left && map.Beatmap.OnlineBeatmapID != null) - addForBeatmap(map.Beatmap.OnlineBeatmapID.Value); + if (e.Button == MouseButton.Left && map.BeatmapInfo.OnlineBeatmapID != null) + addForBeatmap(map.BeatmapInfo.OnlineBeatmapID.Value); else { - var existing = CurrentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == map.Beatmap.OnlineBeatmapID); + var existing = CurrentMatch.Value.PicksBans.FirstOrDefault(p => p.BeatmapID == map.BeatmapInfo.OnlineBeatmapID); if (existing != null) { diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 531da00faf..2e4ed9d5b1 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -182,7 +182,7 @@ namespace osu.Game.Tournament { var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID }); API.Perform(req); - b.BeatmapInfo = req.Result?.ToBeatmap(RulesetStore); + b.BeatmapInfo = req.Result?.ToBeatmapInfo(RulesetStore); addedInfo = true; } @@ -203,7 +203,7 @@ namespace osu.Game.Tournament { var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID }); req.Perform(API); - b.BeatmapInfo = req.Result?.ToBeatmap(RulesetStore); + b.BeatmapInfo = req.Result?.ToBeatmapInfo(RulesetStore); addedInfo = true; } diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index 0aa6a6dd0b..c46ab93ece 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -242,7 +242,7 @@ namespace osu.Game.Beatmaps { // GetDifficultyAsync will fall back to existing data from BeatmapInfo if not locally available // (contrary to GetAsync) - GetDifficultyAsync(bindable.Beatmap, rulesetInfo, mods, cancellationToken) + GetDifficultyAsync(bindable.BeatmapInfo, rulesetInfo, mods, cancellationToken) .ContinueWith(t => { // We're on a threadpool thread, but we should exit back to the update thread so consumers can safely handle value-changed events. @@ -262,7 +262,7 @@ namespace osu.Game.Beatmaps private StarDifficulty computeDifficulty(in DifficultyCacheLookup key) { // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. - var beatmapInfo = key.Beatmap; + var beatmapInfo = key.BeatmapInfo; var rulesetInfo = key.Ruleset; try @@ -270,7 +270,7 @@ namespace osu.Game.Beatmaps var ruleset = rulesetInfo.CreateInstance(); Debug.Assert(ruleset != null); - var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(key.Beatmap)); + var calculator = ruleset.CreateDifficultyCalculator(beatmapManager.GetWorkingBeatmap(key.BeatmapInfo)); var attributes = calculator.Calculate(key.OrderedMods); return new StarDifficulty(attributes); @@ -300,21 +300,21 @@ namespace osu.Game.Beatmaps public readonly struct DifficultyCacheLookup : IEquatable { - public readonly BeatmapInfo Beatmap; + public readonly BeatmapInfo BeatmapInfo; public readonly RulesetInfo Ruleset; public readonly Mod[] OrderedMods; - public DifficultyCacheLookup([NotNull] BeatmapInfo beatmap, [CanBeNull] RulesetInfo ruleset, IEnumerable mods) + public DifficultyCacheLookup([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo ruleset, IEnumerable mods) { - Beatmap = beatmap; + BeatmapInfo = beatmapInfo; // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. - Ruleset = ruleset ?? Beatmap.Ruleset; + Ruleset = ruleset ?? BeatmapInfo.Ruleset; OrderedMods = mods?.OrderBy(m => m.Acronym).Select(mod => mod.DeepClone()).ToArray() ?? Array.Empty(); } public bool Equals(DifficultyCacheLookup other) - => Beatmap.ID == other.Beatmap.ID + => BeatmapInfo.ID == other.BeatmapInfo.ID && Ruleset.ID == other.Ruleset.ID && OrderedMods.SequenceEqual(other.OrderedMods); @@ -322,7 +322,7 @@ namespace osu.Game.Beatmaps { var hashCode = new HashCode(); - hashCode.Add(Beatmap.ID); + hashCode.Add(BeatmapInfo.ID); hashCode.Add(Ruleset.ID); foreach (var mod in OrderedMods) @@ -334,12 +334,12 @@ namespace osu.Game.Beatmaps private class BindableStarDifficulty : Bindable { - public readonly BeatmapInfo Beatmap; + public readonly BeatmapInfo BeatmapInfo; public readonly CancellationToken CancellationToken; - public BindableStarDifficulty(BeatmapInfo beatmap, CancellationToken cancellationToken) + public BindableStarDifficulty(BeatmapInfo beatmapInfo, CancellationToken cancellationToken) { - Beatmap = beatmap; + BeatmapInfo = beatmapInfo; CancellationToken = cancellationToken; } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 2f80633279..a3081cc462 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps private readonly BeatmapModelDownloader beatmapModelDownloader; private readonly WorkingBeatmapCache workingBeatmapCache; - private readonly BeatmapOnlineLookupQueue onlineBetamapLookupQueue; + private readonly BeatmapOnlineLookupQueue onlineBeatmapLookupQueue; public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, [NotNull] AudioManager audioManager, IResourceStore resources, GameHost host = null, WorkingBeatmap defaultBeatmap = null, bool performOnlineLookups = false) @@ -48,8 +48,8 @@ namespace osu.Game.Beatmaps if (performOnlineLookups) { - onlineBetamapLookupQueue = new BeatmapOnlineLookupQueue(api, storage); - beatmapModelManager.OnlineLookupQueue = onlineBetamapLookupQueue; + onlineBeatmapLookupQueue = new BeatmapOnlineLookupQueue(api, storage); + beatmapModelManager.OnlineLookupQueue = onlineBeatmapLookupQueue; } } @@ -182,14 +182,14 @@ namespace osu.Game.Beatmaps /// /// Delete a beatmap difficulty. /// - /// The beatmap difficulty to hide. - public void Hide(BeatmapInfo beatmap) => beatmapModelManager.Hide(beatmap); + /// The beatmap difficulty to hide. + public void Hide(BeatmapInfo beatmapInfo) => beatmapModelManager.Hide(beatmapInfo); /// /// Restore a beatmap difficulty. /// - /// The beatmap difficulty to restore. - public void Restore(BeatmapInfo beatmap) => beatmapModelManager.Restore(beatmap); + /// The beatmap difficulty to restore. + public void Restore(BeatmapInfo beatmapInfo) => beatmapModelManager.Restore(beatmapInfo); #endregion @@ -329,7 +329,7 @@ namespace osu.Game.Beatmaps public void Dispose() { - onlineBetamapLookupQueue?.Dispose(); + onlineBeatmapLookupQueue?.Dispose(); } #endregion diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 0beddc1e9b..aa14f95863 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -173,24 +173,24 @@ namespace osu.Game.Beatmaps /// /// Delete a beatmap difficulty. /// - /// The beatmap difficulty to hide. - public void Hide(BeatmapInfo beatmap) => beatmaps.Hide(beatmap); + /// The beatmap difficulty to hide. + public void Hide(BeatmapInfo beatmapInfo) => beatmaps.Hide(beatmapInfo); /// /// Restore a beatmap difficulty. /// - /// The beatmap difficulty to restore. - public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap); + /// The beatmap difficulty to restore. + public void Restore(BeatmapInfo beatmapInfo) => beatmaps.Restore(beatmapInfo); /// /// Saves an file against a given . /// - /// The to save the content against. The file referenced by will be replaced. + /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. - public virtual void Save(BeatmapInfo info, IBeatmap beatmapContent, ISkin beatmapSkin = null) + public virtual void Save(BeatmapInfo baetmapInfo, IBeatmap beatmapContent, ISkin beatmapSkin = null) { - var setInfo = info.BeatmapSet; + var setInfo = baetmapInfo.BeatmapSet; using (var stream = new MemoryStream()) { @@ -201,7 +201,7 @@ namespace osu.Game.Beatmaps using (ContextFactory.GetForWrite()) { - var beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == info.ID); + var beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == baetmapInfo.ID); var metadata = beatmapInfo.Metadata ?? setInfo.Metadata; // grab the original file (or create a new one if not found). @@ -219,7 +219,7 @@ namespace osu.Game.Beatmaps } } - WorkingBeatmapCache?.Invalidate(info); + WorkingBeatmapCache?.Invalidate(baetmapInfo); } /// diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs index 55164e2442..e1faf6005b 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs @@ -58,18 +58,18 @@ namespace osu.Game.Beatmaps } // todo: expose this when we need to do individual difficulty lookups. - protected Task UpdateAsync(BeatmapSetInfo beatmapSet, BeatmapInfo beatmap, CancellationToken cancellationToken) - => Task.Factory.StartNew(() => lookup(beatmapSet, beatmap), cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); + protected Task UpdateAsync(BeatmapSetInfo beatmapSet, BeatmapInfo beatmapInfo, CancellationToken cancellationToken) + => Task.Factory.StartNew(() => lookup(beatmapSet, beatmapInfo), cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); - private void lookup(BeatmapSetInfo set, BeatmapInfo beatmap) + private void lookup(BeatmapSetInfo set, BeatmapInfo beatmapInfo) { - if (checkLocalCache(set, beatmap)) + if (checkLocalCache(set, beatmapInfo)) return; if (api?.State.Value != APIState.Online) return; - var req = new GetBeatmapRequest(beatmap); + var req = new GetBeatmapRequest(beatmapInfo); req.Failure += fail; @@ -82,18 +82,18 @@ namespace osu.Game.Beatmaps if (res != null) { - beatmap.Status = res.Status; - beatmap.BeatmapSet.Status = res.BeatmapSet.Status; - beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; - beatmap.OnlineBeatmapID = res.OnlineBeatmapID; + beatmapInfo.Status = res.Status; + beatmapInfo.BeatmapSet.Status = res.BeatmapSet.Status; + beatmapInfo.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID; + beatmapInfo.OnlineBeatmapID = res.OnlineBeatmapID; - if (beatmap.Metadata != null) - beatmap.Metadata.AuthorID = res.AuthorID; + if (beatmapInfo.Metadata != null) + beatmapInfo.Metadata.AuthorID = res.AuthorID; - if (beatmap.BeatmapSet.Metadata != null) - beatmap.BeatmapSet.Metadata.AuthorID = res.AuthorID; + if (beatmapInfo.BeatmapSet.Metadata != null) + beatmapInfo.BeatmapSet.Metadata.AuthorID = res.AuthorID; - logForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}."); + logForModel(set, $"Online retrieval mapped {beatmapInfo} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}."); } } catch (Exception e) @@ -103,8 +103,8 @@ namespace osu.Game.Beatmaps void fail(Exception e) { - beatmap.OnlineBeatmapID = null; - logForModel(set, $"Online retrieval failed for {beatmap} ({e.Message})"); + beatmapInfo.OnlineBeatmapID = null; + logForModel(set, $"Online retrieval failed for {beatmapInfo} ({e.Message})"); } } @@ -149,7 +149,7 @@ namespace osu.Game.Beatmaps cacheDownloadRequest.PerformAsync(); } - private bool checkLocalCache(BeatmapSetInfo set, BeatmapInfo beatmap) + private bool checkLocalCache(BeatmapSetInfo set, BeatmapInfo beatmapInfo) { // download is in progress (or was, and failed). if (cacheDownloadRequest != null) @@ -159,9 +159,9 @@ namespace osu.Game.Beatmaps if (!storage.Exists(cache_database_name)) return false; - if (string.IsNullOrEmpty(beatmap.MD5Hash) - && string.IsNullOrEmpty(beatmap.Path) - && beatmap.OnlineBeatmapID == null) + if (string.IsNullOrEmpty(beatmapInfo.MD5Hash) + && string.IsNullOrEmpty(beatmapInfo.Path) + && beatmapInfo.OnlineBeatmapID == null) return false; try @@ -174,9 +174,9 @@ namespace osu.Game.Beatmaps { cmd.CommandText = "SELECT beatmapset_id, beatmap_id, approved, user_id FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineBeatmapID OR filename = @Path"; - cmd.Parameters.Add(new SqliteParameter("@MD5Hash", beatmap.MD5Hash)); - cmd.Parameters.Add(new SqliteParameter("@OnlineBeatmapID", beatmap.OnlineBeatmapID ?? (object)DBNull.Value)); - cmd.Parameters.Add(new SqliteParameter("@Path", beatmap.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("@Path", beatmapInfo.Path)); using (var reader = cmd.ExecuteReader()) { @@ -184,18 +184,18 @@ namespace osu.Game.Beatmaps { var status = (BeatmapSetOnlineStatus)reader.GetByte(2); - beatmap.Status = status; - beatmap.BeatmapSet.Status = status; - beatmap.BeatmapSet.OnlineBeatmapSetID = reader.GetInt32(0); - beatmap.OnlineBeatmapID = reader.GetInt32(1); + beatmapInfo.Status = status; + beatmapInfo.BeatmapSet.Status = status; + beatmapInfo.BeatmapSet.OnlineBeatmapSetID = reader.GetInt32(0); + beatmapInfo.OnlineBeatmapID = reader.GetInt32(1); - if (beatmap.Metadata != null) - beatmap.Metadata.AuthorID = reader.GetInt32(3); + if (beatmapInfo.Metadata != null) + beatmapInfo.Metadata.AuthorID = reader.GetInt32(3); - if (beatmap.BeatmapSet.Metadata != null) - beatmap.BeatmapSet.Metadata.AuthorID = reader.GetInt32(3); + if (beatmapInfo.BeatmapSet.Metadata != null) + beatmapInfo.BeatmapSet.Metadata.AuthorID = reader.GetInt32(3); - logForModel(set, $"Cached local retrieval for {beatmap}."); + logForModel(set, $"Cached local retrieval for {beatmapInfo}."); return true; } } @@ -204,7 +204,7 @@ namespace osu.Game.Beatmaps } catch (Exception ex) { - logForModel(set, $"Cached local retrieval for {beatmap} failed with {ex}."); + logForModel(set, $"Cached local retrieval for {beatmapInfo} failed with {ex}."); } return false; diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index e3214b7c03..197581db88 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -25,40 +25,40 @@ namespace osu.Game.Beatmaps /// /// Hide a in the database. /// - /// The beatmap to hide. + /// The beatmap to hide. /// Whether the beatmap's was changed. - public bool Hide(BeatmapInfo beatmap) + public bool Hide(BeatmapInfo beatmapInfo) { using (ContextFactory.GetForWrite()) { - Refresh(ref beatmap, Beatmaps); + Refresh(ref beatmapInfo, Beatmaps); - if (beatmap.Hidden) return false; + if (beatmapInfo.Hidden) return false; - beatmap.Hidden = true; + beatmapInfo.Hidden = true; } - BeatmapHidden?.Invoke(beatmap); + BeatmapHidden?.Invoke(beatmapInfo); return true; } /// /// Restore a previously hidden . /// - /// The beatmap to restore. + /// The beatmap to restore. /// Whether the beatmap's was changed. - public bool Restore(BeatmapInfo beatmap) + public bool Restore(BeatmapInfo beatmapInfo) { using (ContextFactory.GetForWrite()) { - Refresh(ref beatmap, Beatmaps); + Refresh(ref beatmapInfo, Beatmaps); - if (!beatmap.Hidden) return false; + if (!beatmapInfo.Hidden) return false; - beatmap.Hidden = false; + beatmapInfo.Hidden = false; } - BeatmapRestored?.Invoke(beatmap); + BeatmapRestored?.Invoke(beatmapInfo); return true; } diff --git a/osu.Game/Beatmaps/DifficultyRecommender.cs b/osu.Game/Beatmaps/DifficultyRecommender.cs index ca910e70b8..b1b1e58ab7 100644 --- a/osu.Game/Beatmaps/DifficultyRecommender.cs +++ b/osu.Game/Beatmaps/DifficultyRecommender.cs @@ -62,14 +62,14 @@ namespace osu.Game.Beatmaps if (!recommendedDifficultyMapping.TryGetValue(r, out var recommendation)) continue; - BeatmapInfo beatmap = beatmaps.Where(b => b.Ruleset.Equals(r)).OrderBy(b => + BeatmapInfo beatmapInfo = beatmaps.Where(b => b.Ruleset.Equals(r)).OrderBy(b => { var difference = b.StarDifficulty - recommendation; return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder }).FirstOrDefault(); - if (beatmap != null) - return beatmap; + if (beatmapInfo != null) + return beatmapInfo; } return null; diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs index 0751a777d8..880d70aec2 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIcon.cs @@ -37,7 +37,7 @@ namespace osu.Game.Beatmaps.Drawables } [NotNull] - private readonly BeatmapInfo beatmap; + private readonly BeatmapInfo beatmapInfo; [CanBeNull] private readonly RulesetInfo ruleset; @@ -56,26 +56,26 @@ namespace osu.Game.Beatmaps.Drawables /// /// Creates a new with a given and combination. /// - /// The beatmap to show the difficulty of. + /// The beatmap to show the difficulty of. /// The ruleset to show the difficulty with. /// The mods to show the difficulty with. /// Whether to display a tooltip when hovered. - public DifficultyIcon([NotNull] BeatmapInfo beatmap, [CanBeNull] RulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true) - : this(beatmap, shouldShowTooltip) + public DifficultyIcon([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo ruleset, [CanBeNull] IReadOnlyList mods, bool shouldShowTooltip = true) + : this(beatmapInfo, shouldShowTooltip) { - this.ruleset = ruleset ?? beatmap.Ruleset; + this.ruleset = ruleset ?? beatmapInfo.Ruleset; this.mods = mods ?? Array.Empty(); } /// /// Creates a new that follows the currently-selected ruleset and mods. /// - /// The beatmap to show the difficulty of. + /// The beatmap to show the difficulty of. /// Whether to display a tooltip when hovered. /// Whether to perform difficulty lookup (including calculation if necessary). - public DifficultyIcon([NotNull] BeatmapInfo beatmap, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true) + public DifficultyIcon([NotNull] BeatmapInfo beatmapInfo, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true) { - this.beatmap = beatmap ?? throw new ArgumentNullException(nameof(beatmap)); + this.beatmapInfo = beatmapInfo ?? throw new ArgumentNullException(nameof(beatmapInfo)); this.shouldShowTooltip = shouldShowTooltip; this.performBackgroundDifficultyLookup = performBackgroundDifficultyLookup; @@ -105,7 +105,7 @@ namespace osu.Game.Beatmaps.Drawables Child = background = new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.ForStarDifficulty(beatmap.StarDifficulty) // Default value that will be re-populated once difficulty calculation completes + Colour = colours.ForStarDifficulty(beatmapInfo.StarDifficulty) // Default value that will be re-populated once difficulty calculation completes }, }, new ConstrainedIconContainer @@ -114,27 +114,27 @@ namespace osu.Game.Beatmaps.Drawables Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) - Icon = (ruleset ?? beatmap.Ruleset)?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } + Icon = (ruleset ?? beatmapInfo.Ruleset)?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle } }, }; if (performBackgroundDifficultyLookup) - iconContainer.Add(new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmap, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0)); + iconContainer.Add(new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmapInfo, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0)); else - difficultyBindable.Value = new StarDifficulty(beatmap.StarDifficulty, 0); + difficultyBindable.Value = new StarDifficulty(beatmapInfo.StarDifficulty, 0); difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForStarDifficulty(difficulty.NewValue.Stars)); } ITooltip IHasCustomTooltip.GetCustomTooltip() => new DifficultyIconTooltip(); - DifficultyIconTooltipContent IHasCustomTooltip.TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmap, difficultyBindable) : null; + DifficultyIconTooltipContent IHasCustomTooltip.TooltipContent => shouldShowTooltip ? new DifficultyIconTooltipContent(beatmapInfo, difficultyBindable) : null; private class DifficultyRetriever : Component { public readonly Bindable StarDifficulty = new Bindable(); - private readonly BeatmapInfo beatmap; + private readonly BeatmapInfo beatmapInfo; private readonly RulesetInfo ruleset; private readonly IReadOnlyList mods; @@ -143,9 +143,9 @@ namespace osu.Game.Beatmaps.Drawables [Resolved] private BeatmapDifficultyCache difficultyCache { get; set; } - public DifficultyRetriever(BeatmapInfo beatmap, RulesetInfo ruleset, IReadOnlyList mods) + public DifficultyRetriever(BeatmapInfo beatmapInfo, RulesetInfo ruleset, IReadOnlyList mods) { - this.beatmap = beatmap; + this.beatmapInfo = beatmapInfo; this.ruleset = ruleset; this.mods = mods; } @@ -157,8 +157,8 @@ namespace osu.Game.Beatmaps.Drawables { difficultyCancellation = new CancellationTokenSource(); localStarDifficulty = ruleset != null - ? difficultyCache.GetBindableDifficulty(beatmap, ruleset, mods, difficultyCancellation.Token) - : difficultyCache.GetBindableDifficulty(beatmap, difficultyCancellation.Token); + ? difficultyCache.GetBindableDifficulty(beatmapInfo, ruleset, mods, difficultyCancellation.Token) + : difficultyCache.GetBindableDifficulty(beatmapInfo, difficultyCancellation.Token); localStarDifficulty.BindValueChanged(d => { if (d.NewValue is StarDifficulty diff) diff --git a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs index 0329e935bc..d4c9f83a0a 100644 --- a/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs +++ b/osu.Game/Beatmaps/Drawables/DifficultyIconTooltip.cs @@ -89,7 +89,7 @@ namespace osu.Game.Beatmaps.Drawables public void SetContent(DifficultyIconTooltipContent content) { - difficultyName.Text = content.Beatmap.Version; + difficultyName.Text = content.BeatmapInfo.Version; starDifficulty.UnbindAll(); starDifficulty.BindTo(content.Difficulty); @@ -109,12 +109,12 @@ namespace osu.Game.Beatmaps.Drawables internal class DifficultyIconTooltipContent { - public readonly BeatmapInfo Beatmap; + public readonly BeatmapInfo BeatmapInfo; public readonly IBindable Difficulty; - public DifficultyIconTooltipContent(BeatmapInfo beatmap, IBindable difficulty) + public DifficultyIconTooltipContent(BeatmapInfo beatmapInfo, IBindable difficulty) { - Beatmap = beatmap; + BeatmapInfo = beatmapInfo; Difficulty = difficulty; } } diff --git a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs index 87925b94c6..901f7365b8 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs @@ -8,13 +8,13 @@ namespace osu.Game.Online.API.Requests { public class GetBeatmapRequest : APIRequest { - private readonly BeatmapInfo beatmap; + private readonly BeatmapInfo beatmapInfo; - public GetBeatmapRequest(BeatmapInfo beatmap) + public GetBeatmapRequest(BeatmapInfo beatmapInfo) { - this.beatmap = beatmap; + this.beatmapInfo = beatmapInfo; } - protected override string Target => $@"beatmaps/lookup?id={beatmap.OnlineBeatmapID}&checksum={beatmap.MD5Hash}&filename={System.Uri.EscapeUriString(beatmap.Path ?? string.Empty)}"; + protected override string Target => $@"beatmaps/lookup?id={beatmapInfo.OnlineBeatmapID}&checksum={beatmapInfo.MD5Hash}&filename={System.Uri.EscapeUriString(beatmapInfo.Path ?? string.Empty)}"; } } diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index b4e0e44b2c..f3bf690ed5 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -15,20 +15,20 @@ namespace osu.Game.Online.API.Requests { public class GetScoresRequest : APIRequest { - private readonly BeatmapInfo beatmap; + private readonly BeatmapInfo beatmapInfo; private readonly BeatmapLeaderboardScope scope; private readonly RulesetInfo ruleset; private readonly IEnumerable mods; - public GetScoresRequest(BeatmapInfo beatmap, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null) + public GetScoresRequest(BeatmapInfo beatmapInfo, RulesetInfo ruleset, BeatmapLeaderboardScope scope = BeatmapLeaderboardScope.Global, IEnumerable mods = null) { - if (!beatmap.OnlineBeatmapID.HasValue) + if (!beatmapInfo.OnlineBeatmapID.HasValue) throw new InvalidOperationException($"Cannot lookup a beatmap's scores without having a populated {nameof(BeatmapInfo.OnlineBeatmapID)}."); if (scope == BeatmapLeaderboardScope.Local) throw new InvalidOperationException("Should not attempt to request online scores for a local scoped leaderboard"); - this.beatmap = beatmap; + this.beatmapInfo = beatmapInfo; this.scope = scope; this.ruleset = ruleset ?? throw new ArgumentNullException(nameof(ruleset)); this.mods = mods ?? Array.Empty(); @@ -42,7 +42,7 @@ namespace osu.Game.Online.API.Requests foreach (APILegacyScoreInfo score in r.Scores) { - score.Beatmap = beatmap; + score.BeatmapInfo = beatmapInfo; score.OnlineRulesetID = ruleset.ID.Value; } @@ -50,12 +50,12 @@ namespace osu.Game.Online.API.Requests if (userScore != null) { - userScore.Score.Beatmap = beatmap; + userScore.Score.BeatmapInfo = beatmapInfo; userScore.Score.OnlineRulesetID = ruleset.ID.Value; } } - protected override string Target => $@"beatmaps/{beatmap.OnlineBeatmapID}/scores{createQueryParameters()}"; + protected override string Target => $@"beatmaps/{beatmapInfo.OnlineBeatmapID}/scores{createQueryParameters()}"; private string createQueryParameters() { diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index 7343870dbc..c2a68c8ca1 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs @@ -64,7 +64,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty(@"max_combo")] private int? maxCombo { get; set; } - public virtual BeatmapInfo ToBeatmap(RulesetStore rulesets) + public virtual BeatmapInfo ToBeatmapInfo(RulesetStore rulesets) { var set = BeatmapSet?.ToBeatmapSet(rulesets); diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs index f653a654ca..35963792d0 100644 --- a/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs +++ b/osu.Game/Online/API/Requests/Responses/APIBeatmapSet.cs @@ -116,7 +116,7 @@ namespace osu.Game.Online.API.Requests.Responses beatmapSet.Beatmaps = beatmaps?.Select(b => { - var beatmap = b.ToBeatmap(rulesets); + var beatmap = b.ToBeatmapInfo(rulesets); beatmap.BeatmapSet = beatmapSet; beatmap.Metadata = beatmapSet.Metadata; return beatmap; diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index 567df524b1..18a0db3928 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -37,7 +37,7 @@ namespace osu.Game.Online.API.Requests.Responses OnlineScoreID = OnlineScoreID, Date = Date, PP = PP, - Beatmap = Beatmap, + Beatmap = BeatmapInfo, RulesetID = OnlineRulesetID, Hash = Replay ? "online" : string.Empty, // todo: temporary? Rank = Rank, @@ -100,7 +100,7 @@ namespace osu.Game.Online.API.Requests.Responses public DateTimeOffset Date { get; set; } [JsonProperty(@"beatmap")] - public BeatmapInfo Beatmap { get; set; } + public BeatmapInfo BeatmapInfo { get; set; } [JsonProperty("accuracy")] public double Accuracy { get; set; } @@ -114,10 +114,10 @@ namespace osu.Game.Online.API.Requests.Responses set { // extract the set ID to its correct place. - Beatmap.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = value.ID }; + BeatmapInfo.BeatmapSet = new BeatmapSetInfo { OnlineBeatmapSetID = value.ID }; value.ID = 0; - Beatmap.Metadata = value; + BeatmapInfo.Metadata = value; } } diff --git a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs index 4614fe29b7..15f67eda47 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs @@ -16,7 +16,7 @@ namespace osu.Game.Online.API.Requests.Responses public int PlayCount { get; set; } [JsonProperty] - private BeatmapInfo beatmap { get; set; } + private BeatmapInfo beatmapInfo { get; set; } [JsonProperty] private APIBeatmapSet beatmapSet { get; set; } @@ -24,9 +24,9 @@ namespace osu.Game.Online.API.Requests.Responses public BeatmapInfo GetBeatmapInfo(RulesetStore rulesets) { BeatmapSetInfo setInfo = beatmapSet.ToBeatmapSet(rulesets); - beatmap.BeatmapSet = setInfo; - beatmap.Metadata = setInfo.Metadata; - return beatmap; + beatmapInfo.BeatmapSet = setInfo; + beatmapInfo.Metadata = setInfo.Metadata; + return beatmapInfo; } } } diff --git a/osu.Game/Online/Chat/NowPlayingCommand.cs b/osu.Game/Online/Chat/NowPlayingCommand.cs index 97a2fbdd5c..89eb00a45a 100644 --- a/osu.Game/Online/Chat/NowPlayingCommand.cs +++ b/osu.Game/Online/Chat/NowPlayingCommand.cs @@ -37,27 +37,27 @@ namespace osu.Game.Online.Chat base.LoadComplete(); string verb; - BeatmapInfo beatmap; + BeatmapInfo beatmapInfo; switch (api.Activity.Value) { case UserActivity.InGame game: verb = "playing"; - beatmap = game.Beatmap; + beatmapInfo = game.BeatmapInfo; break; case UserActivity.Editing edit: verb = "editing"; - beatmap = edit.Beatmap; + beatmapInfo = edit.BeatmapInfo; break; default: verb = "listening to"; - beatmap = currentBeatmap.Value.BeatmapInfo; + beatmapInfo = currentBeatmap.Value.BeatmapInfo; break; } - var beatmapString = beatmap.OnlineBeatmapID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmap.OnlineBeatmapID} {beatmap}]" : beatmap.ToString(); + var beatmapString = beatmapInfo.OnlineBeatmapID.HasValue ? $"[{api.WebsiteRootUrl}/b/{beatmapInfo.OnlineBeatmapID} {beatmapInfo}]" : beatmapInfo.ToString(); channelManager.PostMessage($"is {verb} {beatmapString}", true, target); Expire(); diff --git a/osu.Game/Online/Rooms/APIPlaylistBeatmap.cs b/osu.Game/Online/Rooms/APIPlaylistBeatmap.cs index 973dccd528..00623282d3 100644 --- a/osu.Game/Online/Rooms/APIPlaylistBeatmap.cs +++ b/osu.Game/Online/Rooms/APIPlaylistBeatmap.cs @@ -13,9 +13,9 @@ namespace osu.Game.Online.Rooms [JsonProperty("checksum")] public string Checksum { get; set; } - public override BeatmapInfo ToBeatmap(RulesetStore rulesets) + public override BeatmapInfo ToBeatmapInfo(RulesetStore rulesets) { - var b = base.ToBeatmap(rulesets); + var b = base.ToBeatmapInfo(rulesets); b.MD5Hash = Checksum; return b; } diff --git a/osu.Game/Online/Rooms/PlaylistItem.cs b/osu.Game/Online/Rooms/PlaylistItem.cs index 1d409d4b56..48f1347fa1 100644 --- a/osu.Game/Online/Rooms/PlaylistItem.cs +++ b/osu.Game/Online/Rooms/PlaylistItem.cs @@ -70,7 +70,7 @@ namespace osu.Game.Online.Rooms public void MapObjects(BeatmapManager beatmaps, RulesetStore rulesets) { - Beatmap.Value ??= apiBeatmap.ToBeatmap(rulesets); + Beatmap.Value ??= apiBeatmap.ToBeatmapInfo(rulesets); Ruleset.Value ??= rulesets.GetRuleset(RulesetID); Ruleset rulesetInstance = Ruleset.Value.CreateInstance(); diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 60e341d2ac..3df275c6d3 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -178,21 +178,21 @@ namespace osu.Game.Overlays.BeatmapSet } starRatingContainer.FadeOut(100); - Beatmap.Value = Difficulties.FirstOrDefault()?.Beatmap; + Beatmap.Value = Difficulties.FirstOrDefault()?.BeatmapInfo; plays.Value = BeatmapSet?.OnlineInfo.PlayCount ?? 0; favourites.Value = BeatmapSet?.OnlineInfo.FavouriteCount ?? 0; updateDifficultyButtons(); } - private void showBeatmap(BeatmapInfo beatmap) + private void showBeatmap(BeatmapInfo beatmapInfo) { - version.Text = beatmap?.Version; + version.Text = beatmapInfo?.Version; } private void updateDifficultyButtons() { - Difficulties.Children.ToList().ForEach(diff => diff.State = diff.Beatmap == Beatmap.Value ? DifficultySelectorState.Selected : DifficultySelectorState.NotSelected); + Difficulties.Children.ToList().ForEach(diff => diff.State = diff.BeatmapInfo == Beatmap.Value ? DifficultySelectorState.Selected : DifficultySelectorState.NotSelected); } public class DifficultiesContainer : FillFlowContainer @@ -216,7 +216,7 @@ namespace osu.Game.Overlays.BeatmapSet private readonly Box backgroundBox; private readonly DifficultyIcon icon; - public readonly BeatmapInfo Beatmap; + public readonly BeatmapInfo BeatmapInfo; public Action OnHovered; public Action OnClicked; @@ -241,9 +241,9 @@ namespace osu.Game.Overlays.BeatmapSet } } - public DifficultySelectorButton(BeatmapInfo beatmap) + public DifficultySelectorButton(BeatmapInfo beatmapInfo) { - Beatmap = beatmap; + BeatmapInfo = beatmapInfo; Size = new Vector2(size); Margin = new MarginPadding { Horizontal = tile_spacing / 2 }; @@ -260,7 +260,7 @@ namespace osu.Game.Overlays.BeatmapSet Alpha = 0.5f } }, - icon = new DifficultyIcon(beatmap, shouldShowTooltip: false) + icon = new DifficultyIcon(beatmapInfo, shouldShowTooltip: false) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -273,7 +273,7 @@ namespace osu.Game.Overlays.BeatmapSet protected override bool OnHover(HoverEvent e) { fadeIn(); - OnHovered?.Invoke(Beatmap); + OnHovered?.Invoke(BeatmapInfo); return base.OnHover(e); } @@ -286,7 +286,7 @@ namespace osu.Game.Overlays.BeatmapSet protected override bool OnClick(ClickEvent e) { - OnClicked?.Invoke(Beatmap); + OnClicked?.Invoke(BeatmapInfo); return base.OnClick(e); } diff --git a/osu.Game/Overlays/BeatmapSet/Info.cs b/osu.Game/Overlays/BeatmapSet/Info.cs index f9b8de9dba..61c660cbaa 100644 --- a/osu.Game/Overlays/BeatmapSet/Info.cs +++ b/osu.Game/Overlays/BeatmapSet/Info.cs @@ -24,10 +24,10 @@ namespace osu.Game.Overlays.BeatmapSet public readonly Bindable BeatmapSet = new Bindable(); - public BeatmapInfo Beatmap + public BeatmapInfo BeatmapInfo { - get => successRate.Beatmap; - set => successRate.Beatmap = value; + get => successRate.BeatmapInfo; + set => successRate.BeatmapInfo = value; } public Info() diff --git a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs index cde4589c98..4a9b8244a5 100644 --- a/osu.Game/Overlays/BeatmapSet/SuccessRate.cs +++ b/osu.Game/Overlays/BeatmapSet/SuccessRate.cs @@ -23,16 +23,16 @@ namespace osu.Game.Overlays.BeatmapSet private readonly Bar successRate; private readonly Container percentContainer; - private BeatmapInfo beatmap; + private BeatmapInfo beatmapInfo; - public BeatmapInfo Beatmap + public BeatmapInfo BeatmapInfo { - get => beatmap; + get => beatmapInfo; set { - if (value == beatmap) return; + if (value == beatmapInfo) return; - beatmap = value; + beatmapInfo = value; updateDisplay(); } @@ -40,15 +40,15 @@ namespace osu.Game.Overlays.BeatmapSet private void updateDisplay() { - int passCount = beatmap?.OnlineInfo?.PassCount ?? 0; - int playCount = beatmap?.OnlineInfo?.PlayCount ?? 0; + int passCount = beatmapInfo?.OnlineInfo?.PassCount ?? 0; + int playCount = beatmapInfo?.OnlineInfo?.PlayCount ?? 0; var rate = playCount != 0 ? (float)passCount / playCount : 0; successPercent.Text = rate.ToLocalisableString(@"0.#%"); successRate.Length = rate; percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic); - Graph.Metrics = beatmap?.Metrics; + Graph.Metrics = beatmapInfo?.Metrics; } public SuccessRate() diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index bdb3715e73..f987b57d6e 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -61,7 +61,7 @@ namespace osu.Game.Overlays Header.HeaderContent.Picker.Beatmap.ValueChanged += b => { - info.Beatmap = b.NewValue; + info.BeatmapInfo = b.NewValue; ScrollFlow.ScrollToStart(); }; } diff --git a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs index a8a4cfc365..7812a81f30 100644 --- a/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs +++ b/osu.Game/Overlays/Profile/Sections/BeatmapMetadataContainer.cs @@ -15,12 +15,12 @@ namespace osu.Game.Overlays.Profile.Sections /// public abstract class BeatmapMetadataContainer : OsuHoverContainer { - private readonly BeatmapInfo beatmap; + private readonly BeatmapInfo beatmapInfo; - protected BeatmapMetadataContainer(BeatmapInfo beatmap) + protected BeatmapMetadataContainer(BeatmapInfo beatmapInfo) : base(HoverSampleSet.Submit) { - this.beatmap = beatmap; + this.beatmapInfo = beatmapInfo; AutoSizeAxes = Axes.Both; } @@ -30,19 +30,19 @@ namespace osu.Game.Overlays.Profile.Sections { Action = () => { - if (beatmap.OnlineBeatmapID != null) - beatmapSetOverlay?.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value); - else if (beatmap.BeatmapSet?.OnlineBeatmapSetID != null) - beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmap.BeatmapSet.OnlineBeatmapSetID.Value); + if (beatmapInfo.OnlineBeatmapID != null) + beatmapSetOverlay?.FetchAndShowBeatmap(beatmapInfo.OnlineBeatmapID.Value); + else if (beatmapInfo.BeatmapSet?.OnlineBeatmapSetID != null) + beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmapInfo.BeatmapSet.OnlineBeatmapSetID.Value); }; Child = new FillFlowContainer { AutoSizeAxes = Axes.Both, - Children = CreateText(beatmap), + Children = CreateText(beatmapInfo), }; } - protected abstract Drawable[] CreateText(BeatmapInfo beatmap); + protected abstract Drawable[] CreateText(BeatmapInfo beatmapInfo); } } diff --git a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs index a419bef233..2c6fa76ca4 100644 --- a/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs +++ b/osu.Game/Overlays/Profile/Sections/Historical/DrawableMostPlayedBeatmap.cs @@ -22,12 +22,12 @@ namespace osu.Game.Overlays.Profile.Sections.Historical private const int cover_width = 100; private const int corner_radius = 6; - private readonly BeatmapInfo beatmap; + private readonly BeatmapInfo beatmapInfo; private readonly int playCount; - public DrawableMostPlayedBeatmap(BeatmapInfo beatmap, int playCount) + public DrawableMostPlayedBeatmap(BeatmapInfo beatmapInfo, int playCount) { - this.beatmap = beatmap; + this.beatmapInfo = beatmapInfo; this.playCount = playCount; RelativeSizeAxes = Axes.X; @@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical { RelativeSizeAxes = Axes.Y, Width = cover_width, - BeatmapSet = beatmap.BeatmapSet, + BeatmapSet = beatmapInfo.BeatmapSet, }, new Container { @@ -77,7 +77,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical Direction = FillDirection.Vertical, Children = new Drawable[] { - new MostPlayedBeatmapMetadataContainer(beatmap), + new MostPlayedBeatmapMetadataContainer(beatmapInfo), new LinkFlowContainer(t => { t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular); @@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Profile.Sections.Historical }.With(d => { d.AddText("mapped by "); - d.AddUserLink(beatmap.Metadata.Author); + d.AddUserLink(beatmapInfo.Metadata.Author); }), } }, @@ -120,23 +120,23 @@ namespace osu.Game.Overlays.Profile.Sections.Historical private class MostPlayedBeatmapMetadataContainer : BeatmapMetadataContainer { - public MostPlayedBeatmapMetadataContainer(BeatmapInfo beatmap) - : base(beatmap) + public MostPlayedBeatmapMetadataContainer(BeatmapInfo beatmapInfo) + : base(beatmapInfo) { } - protected override Drawable[] CreateText(BeatmapInfo beatmap) => new Drawable[] + protected override Drawable[] CreateText(BeatmapInfo beatmapInfo) => new Drawable[] { new OsuSpriteText { Text = new RomanisableString( - $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} [{beatmap.Version}] ", - $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} [{beatmap.Version}] "), + $"{beatmapInfo.Metadata.TitleUnicode ?? beatmapInfo.Metadata.Title} [{beatmapInfo.Version}] ", + $"{beatmapInfo.Metadata.Title ?? beatmapInfo.Metadata.TitleUnicode} [{beatmapInfo.Version}] "), Font = OsuFont.GetFont(weight: FontWeight.Bold) }, new OsuSpriteText { - Text = "by " + new RomanisableString(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist), + Text = "by " + new RomanisableString(beatmapInfo.Metadata.ArtistUnicode, beatmapInfo.Metadata.Artist), Font = OsuFont.GetFont(weight: FontWeight.Regular) }, }; diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index 713303285a..c221f070df 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -245,27 +245,27 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks private class ScoreBeatmapMetadataContainer : BeatmapMetadataContainer { - public ScoreBeatmapMetadataContainer(BeatmapInfo beatmap) - : base(beatmap) + public ScoreBeatmapMetadataContainer(BeatmapInfo beatmapInfo) + : base(beatmapInfo) { } - protected override Drawable[] CreateText(BeatmapInfo beatmap) => new Drawable[] + protected override Drawable[] CreateText(BeatmapInfo beatmapInfo) => new Drawable[] { new OsuSpriteText { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Text = new RomanisableString( - $"{beatmap.Metadata.TitleUnicode ?? beatmap.Metadata.Title} ", - $"{beatmap.Metadata.Title ?? beatmap.Metadata.TitleUnicode} "), + $"{beatmapInfo.Metadata.TitleUnicode ?? beatmapInfo.Metadata.Title} ", + $"{beatmapInfo.Metadata.Title ?? beatmapInfo.Metadata.TitleUnicode} "), Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold, italics: true) }, new OsuSpriteText { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Text = "by " + new RomanisableString(beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist), + Text = "by " + new RomanisableString(beatmapInfo.Metadata.ArtistUnicode, beatmapInfo.Metadata.Artist), Font = OsuFont.GetFont(size: 12, italics: true) }, }; diff --git a/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs b/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs index 13cc41f8e0..dd2ad2cbfa 100644 --- a/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs +++ b/osu.Game/Rulesets/Filter/IRulesetFilterCriteria.cs @@ -14,15 +14,15 @@ namespace osu.Game.Rulesets.Filter public interface IRulesetFilterCriteria { /// - /// Checks whether the supplied satisfies ruleset-specific custom criteria, + /// Checks whether the supplied satisfies ruleset-specific custom criteria, /// in addition to the ones mandated by song select. /// - /// The beatmap to test the criteria against. + /// The beatmap to test the criteria against. /// /// true if the beatmap matches the ruleset-specific custom filtering criteria, /// false otherwise. /// - bool Matches(BeatmapInfo beatmap); + bool Matches(BeatmapInfo beatmapInfo); /// /// Attempts to parse a single custom keyword criterion, given by the user via the song select search box. diff --git a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs index 5f9b72447b..c458b65607 100644 --- a/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs +++ b/osu.Game/Screens/Edit/Components/Menus/DifficultyMenuItem.cs @@ -10,12 +10,12 @@ namespace osu.Game.Screens.Edit.Components.Menus { public class DifficultyMenuItem : StatefulMenuItem { - public BeatmapInfo Beatmap { get; } + public BeatmapInfo BeatmapInfo { get; } public DifficultyMenuItem(BeatmapInfo beatmapInfo, bool selected, Action difficultyChangeFunc) : base(beatmapInfo.Version ?? "(unnamed)", null) { - Beatmap = beatmapInfo; + BeatmapInfo = beatmapInfo; State.Value = selected; if (!selected) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f424587e22..e5e28d2fde 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -242,15 +242,15 @@ namespace osu.Game.Screens.Select /// /// Selects a given beatmap on the carousel. /// - /// The beatmap to select. + /// The beatmap to select. /// Whether to select the beatmap even if it is filtered (i.e., not visible on carousel). /// True if a selection was made, False if it wasn't. - public bool SelectBeatmap(BeatmapInfo beatmap, bool bypassFilters = true) + public bool SelectBeatmap(BeatmapInfo beatmapInfo, bool bypassFilters = true) { // ensure that any pending events from BeatmapManager have been run before attempting a selection. Scheduler.Update(); - if (beatmap?.Hidden != false) + if (beatmapInfo?.Hidden != false) return false; foreach (CarouselBeatmapSet set in beatmapSets) @@ -258,7 +258,7 @@ namespace osu.Game.Screens.Select if (!bypassFilters && set.Filtered.Value) continue; - var item = set.Beatmaps.FirstOrDefault(p => p.BeatmapInfo.Equals(beatmap)); + var item = set.Beatmaps.FirstOrDefault(p => p.BeatmapInfo.Equals(beatmapInfo)); if (item == null) // The beatmap that needs to be selected doesn't exist in this set diff --git a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs index b32416b361..8c33b1ea0b 100644 --- a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs +++ b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs @@ -17,9 +17,9 @@ namespace osu.Game.Screens.Select [Resolved] private ScoreManager scoreManager { get; set; } - public BeatmapClearScoresDialog(BeatmapInfo beatmap, Action onCompletion) + public BeatmapClearScoresDialog(BeatmapInfo beatmapInfo, Action onCompletion) { - BodyText = $@"{beatmap.Metadata?.Artist} - {beatmap.Metadata?.Title}"; + BodyText = $@"{beatmapInfo.Metadata?.Artist} - {beatmapInfo.Metadata?.Title}"; Icon = FontAwesome.Solid.Eraser; HeaderText = @"Clearing all local scores. Are you sure?"; Buttons = new PopupDialogButton[] @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select Text = @"Yes. Please.", Action = () => { - Task.Run(() => scoreManager.Delete(scoreManager.QueryScores(s => !s.DeletePending && s.Beatmap.ID == beatmap.ID).ToList())) + Task.Run(() => scoreManager.Delete(scoreManager.QueryScores(s => !s.DeletePending && s.Beatmap.ID == beatmapInfo.ID).ToList())) .ContinueWith(_ => onCompletion); } }, diff --git a/osu.Game/Screens/Select/BeatmapDetailArea.cs b/osu.Game/Screens/Select/BeatmapDetailArea.cs index 89ae92ec91..72c2ba708b 100644 --- a/osu.Game/Screens/Select/BeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/BeatmapDetailArea.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Select { beatmap = value; - Details.Beatmap = value?.BeatmapInfo; + Details.BeatmapInfo = value?.BeatmapInfo; } } diff --git a/osu.Game/Screens/Select/BeatmapDetails.cs b/osu.Game/Screens/Select/BeatmapDetails.cs index d59d76300a..6ace92370c 100644 --- a/osu.Game/Screens/Select/BeatmapDetails.cs +++ b/osu.Game/Screens/Select/BeatmapDetails.cs @@ -41,16 +41,16 @@ namespace osu.Game.Screens.Select [Resolved] private RulesetStore rulesets { get; set; } - private BeatmapInfo beatmap; + private BeatmapInfo beatmapInfo; - public BeatmapInfo Beatmap + public BeatmapInfo BeatmapInfo { - get => beatmap; + get => beatmapInfo; set { - if (value == beatmap) return; + if (value == beatmapInfo) return; - beatmap = value; + beatmapInfo = value; Scheduler.AddOnce(updateStatistics); } @@ -170,26 +170,26 @@ namespace osu.Game.Screens.Select private void updateStatistics() { - advanced.BeatmapInfo = Beatmap; - description.Text = Beatmap?.Version; - source.Text = Beatmap?.Metadata?.Source; - tags.Text = Beatmap?.Metadata?.Tags; + advanced.BeatmapInfo = BeatmapInfo; + description.Text = BeatmapInfo?.Version; + source.Text = BeatmapInfo?.Metadata?.Source; + tags.Text = BeatmapInfo?.Metadata?.Tags; // metrics may have been previously fetched - if (Beatmap?.BeatmapSet?.Metrics != null && Beatmap?.Metrics != null) + if (BeatmapInfo?.BeatmapSet?.Metrics != null && BeatmapInfo?.Metrics != null) { updateMetrics(); return; } // for now, let's early abort if an OnlineBeatmapID is not present (should have been populated at import time). - if (Beatmap?.OnlineBeatmapID == null || api.State.Value == APIState.Offline) + if (BeatmapInfo?.OnlineBeatmapID == null || api.State.Value == APIState.Offline) { updateMetrics(); return; } - var requestedBeatmap = Beatmap; + var requestedBeatmap = BeatmapInfo; var lookup = new GetBeatmapRequest(requestedBeatmap); @@ -197,11 +197,11 @@ namespace osu.Game.Screens.Select { Schedule(() => { - if (beatmap != requestedBeatmap) + if (beatmapInfo != requestedBeatmap) // the beatmap has been changed since we started the lookup. return; - var b = res.ToBeatmap(rulesets); + var b = res.ToBeatmapInfo(rulesets); if (requestedBeatmap.BeatmapSet == null) requestedBeatmap.BeatmapSet = b.BeatmapSet; @@ -218,7 +218,7 @@ namespace osu.Game.Screens.Select { Schedule(() => { - if (beatmap != requestedBeatmap) + if (beatmapInfo != requestedBeatmap) // the beatmap has been changed since we started the lookup. return; @@ -232,12 +232,12 @@ namespace osu.Game.Screens.Select private void updateMetrics() { - var hasRatings = beatmap?.BeatmapSet?.Metrics?.Ratings?.Any() ?? false; - var hasRetriesFails = (beatmap?.Metrics?.Retries?.Any() ?? false) || (beatmap?.Metrics?.Fails?.Any() ?? false); + var hasRatings = beatmapInfo?.BeatmapSet?.Metrics?.Ratings?.Any() ?? false; + var hasRetriesFails = (beatmapInfo?.Metrics?.Retries?.Any() ?? false) || (beatmapInfo?.Metrics?.Fails?.Any() ?? false); if (hasRatings) { - ratings.Metrics = beatmap.BeatmapSet.Metrics; + ratings.Metrics = beatmapInfo.BeatmapSet.Metrics; ratings.FadeIn(transition_duration); } else @@ -249,7 +249,7 @@ namespace osu.Game.Screens.Select if (hasRetriesFails) { - failRetryGraph.Metrics = beatmap.Metrics; + failRetryGraph.Metrics = beatmapInfo.Metrics; failRetryContainer.FadeIn(transition_duration); } else diff --git a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs index 2fe7ff4562..5940911d4a 100644 --- a/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/DrawableCarouselBeatmap.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Select.Carousel private const float height = MAX_HEIGHT * 0.6f; - private readonly BeatmapInfo beatmap; + private readonly BeatmapInfo beatmapInfo; private Sprite background; @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Select.Carousel public DrawableCarouselBeatmap(CarouselBeatmap panel) { - beatmap = panel.BeatmapInfo; + beatmapInfo = panel.BeatmapInfo; Item = panel; } @@ -109,7 +109,7 @@ namespace osu.Game.Screens.Select.Carousel Origin = Anchor.CentreLeft, Children = new Drawable[] { - new DifficultyIcon(beatmap, shouldShowTooltip: false) + new DifficultyIcon(beatmapInfo, shouldShowTooltip: false) { Scale = new Vector2(1.8f), }, @@ -129,7 +129,7 @@ namespace osu.Game.Screens.Select.Carousel { new OsuSpriteText { - Text = beatmap.Version, + Text = beatmapInfo.Version, Font = OsuFont.GetFont(size: 20), Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft @@ -142,7 +142,7 @@ namespace osu.Game.Screens.Select.Carousel }, new OsuSpriteText { - Text = $"{(beatmap.Metadata ?? beatmap.BeatmapSet.Metadata).Author.Username}", + Text = $"{(beatmapInfo.Metadata ?? beatmapInfo.BeatmapSet.Metadata).Author.Username}", Font = OsuFont.GetFont(italics: true), Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft @@ -156,7 +156,7 @@ namespace osu.Game.Screens.Select.Carousel AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new TopLocalRank(beatmap) + new TopLocalRank(beatmapInfo) { Scale = new Vector2(0.8f), Size = new Vector2(40, 20) @@ -200,7 +200,7 @@ namespace osu.Game.Screens.Select.Carousel protected override bool OnClick(ClickEvent e) { if (Item.State.Value == CarouselItemState.Selected) - startRequested?.Invoke(beatmap); + startRequested?.Invoke(beatmapInfo); return base.OnClick(e); } @@ -216,7 +216,7 @@ namespace osu.Game.Screens.Select.Carousel if (Item.State.Value != CarouselItemState.Collapsed) { // We've potentially cancelled the computation above so a new bindable is required. - starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmap, (starDifficultyCancellationSource = new CancellationTokenSource()).Token); + starDifficultyBindable = difficultyCache.GetBindableDifficulty(beatmapInfo, (starDifficultyCancellationSource = new CancellationTokenSource()).Token); starDifficultyBindable.BindValueChanged(d => { starCounter.Current = (float)(d.NewValue?.Stars ?? 0); @@ -233,13 +233,13 @@ namespace osu.Game.Screens.Select.Carousel List items = new List(); if (startRequested != null) - items.Add(new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested(beatmap))); + items.Add(new OsuMenuItem("Play", MenuItemType.Highlighted, () => startRequested(beatmapInfo))); if (editRequested != null) - items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested(beatmap))); + items.Add(new OsuMenuItem("Edit", MenuItemType.Standard, () => editRequested(beatmapInfo))); - if (beatmap.OnlineBeatmapID.HasValue && beatmapOverlay != null) - items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmap.OnlineBeatmapID.Value))); + if (beatmapInfo.OnlineBeatmapID.HasValue && beatmapOverlay != null) + items.Add(new OsuMenuItem("Details...", MenuItemType.Standard, () => beatmapOverlay.FetchAndShowBeatmap(beatmapInfo.OnlineBeatmapID.Value))); if (collectionManager != null) { @@ -251,7 +251,7 @@ namespace osu.Game.Screens.Select.Carousel } if (hideRequested != null) - items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested(beatmap))); + items.Add(new OsuMenuItem("Hide", MenuItemType.Destructive, () => hideRequested(beatmapInfo))); return items.ToArray(); } @@ -262,12 +262,12 @@ namespace osu.Game.Screens.Select.Carousel return new ToggleMenuItem(collection.Name.Value, MenuItemType.Standard, s => { if (s) - collection.Beatmaps.Add(beatmap); + collection.Beatmaps.Add(beatmapInfo); else - collection.Beatmaps.Remove(beatmap); + collection.Beatmaps.Remove(beatmapInfo); }) { - State = { Value = collection.Beatmaps.Contains(beatmap) } + State = { Value = collection.Beatmaps.Contains(beatmapInfo) } }; } diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index 3ad57c1cb0..f2485587d8 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Select.Carousel { public class TopLocalRank : UpdateableRank { - private readonly BeatmapInfo beatmap; + private readonly BeatmapInfo beatmapInfo; [Resolved] private ScoreManager scores { get; set; } @@ -31,10 +31,10 @@ namespace osu.Game.Screens.Select.Carousel private IBindable> itemUpdated; private IBindable> itemRemoved; - public TopLocalRank(BeatmapInfo beatmap) + public TopLocalRank(BeatmapInfo beatmapInfo) : base(null) { - this.beatmap = beatmap; + this.beatmapInfo = beatmapInfo; } [BackgroundDependencyLoader] @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Select.Carousel { if (weakScore.NewValue.TryGetTarget(out var score)) { - if (score.BeatmapInfoID == beatmap.ID) + if (score.BeatmapInfoID == beatmapInfo.ID) fetchAndLoadTopScore(); } } @@ -79,10 +79,10 @@ namespace osu.Game.Screens.Select.Carousel private ScoreInfo fetchTopScore() { - if (scores == null || beatmap == null || ruleset?.Value == null || api?.LocalUser.Value == null) + if (scores == null || beatmapInfo == null || ruleset?.Value == null || api?.LocalUser.Value == null) return null; - return scores.QueryScores(s => s.UserID == api.LocalUser.Value.Id && s.BeatmapInfoID == beatmap.ID && s.RulesetID == ruleset.Value.ID && !s.DeletePending) + return scores.QueryScores(s => s.UserID == api.LocalUser.Value.Id && s.BeatmapInfoID == beatmapInfo.ID && s.RulesetID == ruleset.Value.ID && !s.DeletePending) .OrderByDescending(s => s.TotalScore) .FirstOrDefault(); } diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 7820264505..2fdb41a1a1 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -25,17 +25,17 @@ namespace osu.Game.Screens.Select.Leaderboards [Resolved] private RulesetStore rulesets { get; set; } - private BeatmapInfo beatmap; + private BeatmapInfo beatmapInfo; - public BeatmapInfo Beatmap + public BeatmapInfo BeatmapInfo { - get => beatmap; + get => beatmapInfo; set { - if (beatmap == value) + if (beatmapInfo == value) return; - beatmap = value; + beatmapInfo = value; Scores = null; UpdateScores(); @@ -116,7 +116,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (score.NewValue.TryGetTarget(out var scoreInfo)) { - if (Beatmap?.ID != scoreInfo.BeatmapInfoID) + if (BeatmapInfo?.ID != scoreInfo.BeatmapInfoID) return; } @@ -132,7 +132,7 @@ namespace osu.Game.Screens.Select.Leaderboards loadCancellationSource?.Cancel(); loadCancellationSource = new CancellationTokenSource(); - if (Beatmap == null) + if (BeatmapInfo == null) { PlaceholderState = PlaceholderState.NoneSelected; return null; @@ -141,7 +141,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { var scores = scoreManager - .QueryScores(s => !s.DeletePending && s.Beatmap.ID == Beatmap.ID && s.Ruleset.ID == ruleset.Value.ID); + .QueryScores(s => !s.DeletePending && s.Beatmap.ID == BeatmapInfo.ID && s.Ruleset.ID == ruleset.Value.ID); if (filterMods && !mods.Value.Any()) { @@ -168,7 +168,7 @@ namespace osu.Game.Screens.Select.Leaderboards return null; } - if (Beatmap.OnlineBeatmapID == null || Beatmap?.Status <= BeatmapSetOnlineStatus.Pending) + if (BeatmapInfo.OnlineBeatmapID == null || BeatmapInfo?.Status <= BeatmapSetOnlineStatus.Pending) { PlaceholderState = PlaceholderState.Unavailable; return null; @@ -188,7 +188,7 @@ namespace osu.Game.Screens.Select.Leaderboards else if (filterMods) requestMods = mods.Value; - var req = new GetScoresRequest(Beatmap, ruleset.Value ?? Beatmap.Ruleset, Scope, requestMods); + var req = new GetScoresRequest(BeatmapInfo, ruleset.Value ?? BeatmapInfo.Ruleset, Scope, requestMods); req.Success += r => { diff --git a/osu.Game/Screens/Select/LocalScoreDeleteDialog.cs b/osu.Game/Screens/Select/LocalScoreDeleteDialog.cs index 085ea372c0..1ae244281b 100644 --- a/osu.Game/Screens/Select/LocalScoreDeleteDialog.cs +++ b/osu.Game/Screens/Select/LocalScoreDeleteDialog.cs @@ -29,8 +29,8 @@ namespace osu.Game.Screens.Select [BackgroundDependencyLoader] private void load() { - BeatmapInfo beatmap = beatmapManager.QueryBeatmap(b => b.ID == score.BeatmapInfoID); - Debug.Assert(beatmap != null); + BeatmapInfo beatmapInfo = beatmapManager.QueryBeatmap(b => b.ID == score.BeatmapInfoID); + Debug.Assert(beatmapInfo != null); BodyText = $"{score.User} ({score.DisplayAccuracy}, {score.Rank})"; diff --git a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs index c87a4bbc54..b8b8e3e4bc 100644 --- a/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs +++ b/osu.Game/Screens/Select/PlayBeatmapDetailArea.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Select { base.Beatmap = value; - Leaderboard.Beatmap = value is DummyWorkingBeatmap ? null : value?.BeatmapInfo; + Leaderboard.BeatmapInfo = value is DummyWorkingBeatmap ? null : value?.BeatmapInfo; } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index e4ab360765..6cafcb9d16 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -345,22 +345,22 @@ namespace osu.Game.Screens.Select /// protected abstract BeatmapDetailArea CreateBeatmapDetailArea(); - public void Edit(BeatmapInfo beatmap = null) + public void Edit(BeatmapInfo beatmapInfo = null) { if (!AllowEditing) throw new InvalidOperationException($"Attempted to edit when {nameof(AllowEditing)} is disabled"); - Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap ?? beatmapNoDebounce); + Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo ?? beatmapInfoNoDebounce); this.Push(new EditorLoader()); } /// /// Call to make a selection and perform the default action for this SongSelect. /// - /// An optional beatmap to override the current carousel selection. + /// An optional beatmap to override the current carousel selection. /// An optional ruleset to override the current carousel selection. /// An optional custom action to perform instead of . - public void FinaliseSelection(BeatmapInfo beatmap = null, RulesetInfo ruleset = null, Action customStartAction = null) + public void FinaliseSelection(BeatmapInfo beatmapInfo = null, RulesetInfo ruleset = null, Action customStartAction = null) { // This is very important as we have not yet bound to screen-level bindables before the carousel load is completed. if (!Carousel.BeatmapSetsLoaded) @@ -379,8 +379,8 @@ namespace osu.Game.Screens.Select // this could happen via a user interaction while the carousel is still in a loading state. if (Carousel.SelectedBeatmapInfo == null) return; - if (beatmap != null) - Carousel.SelectBeatmap(beatmap); + if (beatmapInfo != null) + Carousel.SelectBeatmap(beatmapInfo); if (selectionChangedDebounce?.Completed == false) { @@ -435,18 +435,18 @@ 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 beatmapNoDebounce; + private BeatmapInfo beatmapInfoNoDebounce; private RulesetInfo rulesetNoDebounce; - private void updateSelectedBeatmap(BeatmapInfo beatmap) + private void updateSelectedBeatmap(BeatmapInfo beatmapInfo) { - if (beatmap == null && beatmapNoDebounce == null) + if (beatmapInfo == null && beatmapInfoNoDebounce == null) return; - if (beatmap?.Equals(beatmapNoDebounce) == true) + if (beatmapInfo?.Equals(beatmapInfoNoDebounce) == true) return; - beatmapNoDebounce = beatmap; + beatmapInfoNoDebounce = beatmapInfo; performUpdateSelected(); } @@ -467,12 +467,12 @@ namespace osu.Game.Screens.Select /// private void performUpdateSelected() { - var beatmap = beatmapNoDebounce; + var beatmap = beatmapInfoNoDebounce; var ruleset = rulesetNoDebounce; selectionChangedDebounce?.Cancel(); - if (beatmapNoDebounce == null) + if (beatmapInfoNoDebounce == null) run(); else selectionChangedDebounce = Scheduler.AddDelayed(run, 200); @@ -803,11 +803,11 @@ namespace osu.Game.Screens.Select dialogOverlay?.Push(new BeatmapDeleteDialog(beatmap)); } - private void clearScores(BeatmapInfo beatmap) + private void clearScores(BeatmapInfo beatmapInfo) { - if (beatmap == null || beatmap.ID <= 0) return; + if (beatmapInfo == null || beatmapInfo.ID <= 0) return; - dialogOverlay?.Push(new BeatmapClearScoresDialog(beatmap, () => + dialogOverlay?.Push(new BeatmapClearScoresDialog(beatmapInfo, () => // schedule done here rather than inside the dialog as the dialog may fade out and never callback. Schedule(() => BeatmapDetails.Refresh()))); } diff --git a/osu.Game/Skinning/LegacyBeatmapSkin.cs b/osu.Game/Skinning/LegacyBeatmapSkin.cs index e6ddeba316..2093182dcc 100644 --- a/osu.Game/Skinning/LegacyBeatmapSkin.cs +++ b/osu.Game/Skinning/LegacyBeatmapSkin.cs @@ -20,8 +20,8 @@ namespace osu.Game.Skinning protected override bool AllowManiaSkin => false; protected override bool UseCustomSampleBanks => true; - public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore storage, IStorageResourceProvider resources) - : base(createSkinInfo(beatmap), new LegacySkinResourceStore(beatmap.BeatmapSet, storage), resources, beatmap.Path) + public LegacyBeatmapSkin(BeatmapInfo beatmapInfo, IResourceStore storage, IStorageResourceProvider resources) + : base(createSkinInfo(beatmapInfo), new LegacySkinResourceStore(beatmapInfo.BeatmapSet, storage), resources, beatmapInfo.Path) { // Disallow default colours fallback on beatmap skins to allow using parent skin combo colours. (via SkinProvidingContainer) Configuration.AllowDefaultComboColoursFallback = false; @@ -76,7 +76,7 @@ namespace osu.Game.Skinning return base.GetSample(sampleInfo); } - private static SkinInfo createSkinInfo(BeatmapInfo beatmap) => - new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata?.AuthorString }; + private static SkinInfo createSkinInfo(BeatmapInfo beatmapInfo) => + new SkinInfo { Name = beatmapInfo.ToString(), Creator = beatmapInfo.Metadata?.AuthorString }; } } diff --git a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs index 27162b1d66..5c522058d9 100644 --- a/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs +++ b/osu.Game/Tests/Beatmaps/LegacyBeatmapSkinColourTest.cs @@ -111,8 +111,8 @@ namespace osu.Game.Tests.Beatmaps public static readonly Color4 HYPER_DASH_FRUIT_COLOUR = Color4.DarkGoldenrod; - public TestBeatmapSkin(BeatmapInfo beatmap, bool hasColours) - : base(beatmap, new ResourceStore(), null) + public TestBeatmapSkin(BeatmapInfo beatmapInfo, bool hasColours) + : base(beatmapInfo, new ResourceStore(), null) { if (hasColours) { diff --git a/osu.Game/Users/UserActivity.cs b/osu.Game/Users/UserActivity.cs index 75aa4866ff..91bcb37fcc 100644 --- a/osu.Game/Users/UserActivity.cs +++ b/osu.Game/Users/UserActivity.cs @@ -27,13 +27,13 @@ namespace osu.Game.Users public abstract class InGame : UserActivity { - public BeatmapInfo Beatmap { get; } + public BeatmapInfo BeatmapInfo { get; } public RulesetInfo Ruleset { get; } - protected InGame(BeatmapInfo info, RulesetInfo ruleset) + protected InGame(BeatmapInfo beatmapInfo, RulesetInfo ruleset) { - Beatmap = info; + BeatmapInfo = beatmapInfo; Ruleset = ruleset; } @@ -42,8 +42,8 @@ namespace osu.Game.Users public class InMultiplayerGame : InGame { - public InMultiplayerGame(BeatmapInfo beatmap, RulesetInfo ruleset) - : base(beatmap, ruleset) + public InMultiplayerGame(BeatmapInfo beatmapInfo, RulesetInfo ruleset) + : base(beatmapInfo, ruleset) { } @@ -52,27 +52,27 @@ namespace osu.Game.Users public class InPlaylistGame : InGame { - public InPlaylistGame(BeatmapInfo beatmap, RulesetInfo ruleset) - : base(beatmap, ruleset) + public InPlaylistGame(BeatmapInfo beatmapInfo, RulesetInfo ruleset) + : base(beatmapInfo, ruleset) { } } public class InSoloGame : InGame { - public InSoloGame(BeatmapInfo info, RulesetInfo ruleset) - : base(info, ruleset) + public InSoloGame(BeatmapInfo beatmapInfo, RulesetInfo ruleset) + : base(beatmapInfo, ruleset) { } } public class Editing : UserActivity { - public BeatmapInfo Beatmap { get; } + public BeatmapInfo BeatmapInfo { get; } public Editing(BeatmapInfo info) { - Beatmap = info; + BeatmapInfo = info; } public override string Status => @"Editing a beatmap"; From 281a3a0cea270b1531cc13ee1a9ae6cb559788c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 2 Oct 2021 18:40:41 +0200 Subject: [PATCH 056/183] Add test case for legacy loop count behaviour --- .../Formats/LegacyStoryboardDecoderTest.cs | 27 +++++++++++++++++++ osu.Game.Tests/Resources/loop-count.osb | 15 +++++++++++ 2 files changed, 42 insertions(+) create mode 100644 osu.Game.Tests/Resources/loop-count.osb diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs index bcde899789..560e2ef894 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyStoryboardDecoderTest.cs @@ -149,5 +149,32 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(AnimationLoopType.LoopForever, ((StoryboardAnimation)foreground.Elements[5]).LoopType); } } + + [Test] + public void TestDecodeLoopCount() + { + // all loop sequences in loop-count.osb have a total duration of 2000ms (fade in 0->1000ms, fade out 1000->2000ms). + const double loop_duration = 2000; + + var decoder = new LegacyStoryboardDecoder(); + + using (var resStream = TestResources.OpenResource("loop-count.osb")) + using (var stream = new LineBufferedReader(resStream)) + { + var storyboard = decoder.Decode(stream); + + StoryboardLayer background = storyboard.Layers.Single(l => l.Depth == 3); + + // stable ensures that any loop command executes at least once, even if the loop count specified in the .osb is zero or negative. + StoryboardSprite zeroTimes = background.Elements.OfType().Single(s => s.Path == "zero-times.png"); + Assert.That(zeroTimes.EndTime, Is.EqualTo(1000 + loop_duration)); + + StoryboardSprite oneTime = background.Elements.OfType().Single(s => s.Path == "one-time.png"); + Assert.That(oneTime.EndTime, Is.EqualTo(4000 + loop_duration)); + + StoryboardSprite manyTimes = background.Elements.OfType().Single(s => s.Path == "many-times.png"); + Assert.That(manyTimes.EndTime, Is.EqualTo(9000 + 40 * loop_duration)); + } + } } } diff --git a/osu.Game.Tests/Resources/loop-count.osb b/osu.Game.Tests/Resources/loop-count.osb new file mode 100644 index 0000000000..ec75e85ef1 --- /dev/null +++ b/osu.Game.Tests/Resources/loop-count.osb @@ -0,0 +1,15 @@ +osu file format v14 + +[Events] +Sprite,Background,TopCentre,"zero-times.png",320,240 + L,1000,0 + F,0,0,1000,0,1 + F,0,1000,2000,1,0 +Sprite,Background,TopCentre,"one-time.png",320,240 + L,4000,1 + F,0,0,1000,0,1 + F,0,1000,2000,1,0 +Sprite,Background,TopCentre,"many-times.png",320,240 + L,9000,40 + F,0,0,1000,0,1 + F,0,1000,2000,1,0 From 3e403cfe031604792798898218927691d3c2fe21 Mon Sep 17 00:00:00 2001 From: Susko3 <16479013+Susko3@users.noreply.github.com> Date: Sat, 2 Oct 2021 19:16:46 +0200 Subject: [PATCH 057/183] Add comment explaining the purpose of the empty `FilterTerms` --- .../Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs index 806390c0ec..2cc2857e9b 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/KeyBindingsSubsection.cs @@ -76,6 +76,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input Content.CornerRadius = 5; } + // Empty FilterTerms so that the ResetButton is visible only when the whole subsection is visible. public override IEnumerable FilterTerms => Enumerable.Empty(); } } From bc1ff019da91aebf64f730b424ba4f442096ef71 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Sun, 3 Oct 2021 11:27:17 +0100 Subject: [PATCH 058/183] Implement relax checks --- .../Difficulty/OsuPerformanceCalculator.cs | 15 +++++++++++++++ osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index bf4d92652c..a8e43ce09b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -90,6 +90,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= lengthBonus; + if (mods.Any(h => h is OsuModRelax)) + { + aimValue *= 0.75; + countMiss += countOk + countMeh; + } + // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (countMiss > 0) aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), countMiss); @@ -126,6 +132,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty { double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0; + if (mods.Any(h => h is OsuModRelax)) + { + speedValue *= 0.75; + countMiss += countOk + countMeh; + } + // Longer maps are worth more. double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); @@ -160,6 +172,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double computeAccuracyValue() { + if (mods.Any(h => h is OsuModRelax)) + return 0.0; + // This percentage only considers HitCircles of any value - in this part of the calculation we focus on hitting the timing hit window. double betterAccuracyPercentage; int amountHitObjectsWithAccuracy = Attributes.HitCircleCount; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index 9364b11048..e8169ba660 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -7,6 +7,8 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; using osu.Framework.Utils; +using System.Linq; +using osu.Game.Rulesets.Osu.Mods; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { @@ -79,6 +81,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } } + if (Mods.Any(m => m is OsuModRelax)) + speedBonus = 0.0; + return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) From 03f0a3658947d0b99e0d91c7e06f0700e728f08f Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Sun, 3 Oct 2021 11:53:14 +0100 Subject: [PATCH 059/183] additional balancing --- .../Difficulty/OsuPerformanceCalculator.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index a8e43ce09b..62e0dea4fa 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -40,6 +40,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss); + if (mods.Any(h => h is OsuModRelax)) + { + countMiss += countOk + countMeh; + } + // Custom multipliers for NoFail and SpunOut. double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. @@ -92,8 +97,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (mods.Any(h => h is OsuModRelax)) { - aimValue *= 0.75; - countMiss += countOk + countMeh; + aimValue *= 0.6; } // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. @@ -134,8 +138,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (mods.Any(h => h is OsuModRelax)) { - speedValue *= 0.75; - countMiss += countOk + countMeh; + speedValue *= 0.6; } // Longer maps are worth more. @@ -219,6 +222,11 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (mods.Any(h => h is OsuModHidden)) flashlightValue *= 1.3; + if (mods.Any(h => h is OsuModRelax)) + { + flashlightValue *= 0.6; + } + // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (countMiss > 0) flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), Math.Pow(countMiss, .875)); From f05cb6bb5b677255517212369ed4292d1d4c48e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 3 Oct 2021 13:53:26 +0200 Subject: [PATCH 060/183] Add test case covering reset section button hiding --- .../Visual/Settings/TestSceneKeyBindingPanel.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 168d9fafcf..1effe52608 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Testing; using osu.Framework.Threading; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; using osu.Game.Overlays.Settings.Sections.Input; using osuTK.Input; @@ -230,6 +231,22 @@ namespace osu.Game.Tests.Visual.Settings AddAssert("first binding selected", () => multiBindingRow.ChildrenOfType().First().IsBinding); } + [Test] + public void TestFilteringHidesResetSectionButtons() + { + SearchTextBox searchTextBox = null; + + AddStep("add any search term", () => + { + searchTextBox = panel.ChildrenOfType().Single(); + searchTextBox.Current.Value = "chat"; + }); + AddUntilStep("all reset section bindings buttons hidden", () => panel.ChildrenOfType().All(button => button.Alpha == 0)); + + AddStep("clear search term", () => searchTextBox.Current.Value = string.Empty); + AddUntilStep("all reset section bindings buttons shown", () => panel.ChildrenOfType().All(button => button.Alpha == 1)); + } + private void checkBinding(string name, string keyName) { AddAssert($"Check {name} is bound to {keyName}", () => From 4f00a9e165af5d7b8a321e2bf72fe81144331482 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 Oct 2021 22:32:46 +0900 Subject: [PATCH 061/183] Adjust max runtime for diffcalc runs --- .github/workflows/diffcalc.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/diffcalc.yml b/.github/workflows/diffcalc.yml index bc2626d3d6..9e11ab6663 100644 --- a/.github/workflows/diffcalc.yml +++ b/.github/workflows/diffcalc.yml @@ -53,6 +53,7 @@ jobs: diffcalc: name: Run runs-on: self-hosted + timeout-minutes: 1440 if: needs.metadata.outputs.continue == 'yes' needs: metadata strategy: From 07c11953cddbf66b5b84c996449f6af82821b1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 3 Oct 2021 15:39:54 +0200 Subject: [PATCH 062/183] Modify special test skin to visually cover regression --- .../special-skin/hitcircleoverlay@2x.png | Bin 247101 -> 26595 bytes .../Resources/special-skin/skin.ini | 3 +++ 2 files changed, 3 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/Resources/special-skin/skin.ini diff --git a/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png b/osu.Game.Rulesets.Osu.Tests/Resources/special-skin/hitcircleoverlay@2x.png index a9b2d95d882b99a53aeba501919b5e5f1600bf26..8e50cd033596fb3daacd173247d6779168cc8f99 100755 GIT binary patch literal 26595 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

yftJ!Y0GsdcN%;MEM+5u>2KjHnOqx_AeVsl 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 literal 247101 zcmb^4Ys|j+b{F(1^-wC*dWfKvbR3L8;MsGX4`VlQ9ZMT_*^~k;V&FcuWA~mJW@fj$ z4W><05G4>WM0`Pz7>yO>of6WRLIfmYh@ymGOfbIjg$a!!ZzLW*>wcd9Zee~G+|Tn| zo3i&D{?~P1ztdXZ^mw|@S2{F?c@-+Jro{N>lZ^$q`! z|M{&S|HoH<)4o4VUmVUK9xrcy_qUI4|I)ngyI=O!Tfg)V{Gs3fQ{VYtees|C-9Pm$ z%b)vWzv&zP*Z<|CU-Ij}_@%${XMXQ@{7e7V+u!@gr$6|geaG~Nj{o=vf8xjf?{EHn zAN-=<`al1xKj)Wx`=1&9kDvQHzy8nt#J@KE>p$-w{I~!5|Mf@ydjHSq#KL4%1xBt^W@8jS0 zb-(r({mVc2eLwv@fAK%LeBWRG8~?)a&;R~^_}}||f2sUa-}<|M&v*V4 zf91db^}paR{PfrV_WWDF)c&GB^_%|l|Mb87TR-;ye*Pc(tAF$I!5{thZ}`=};`6`Y z`-ZRnvCsY3Z~TS-;=l91AOGu*{^@V}>BEnH&-o|YkN)J>{Ud*J`7eIUzwv+mQ{VPY zKlaw2{`0@?|M{E0;aB|yGx>8r`ZeG8$y;xI)1Qt1|8;-pmw(f@y!G`z`a9nLp6~ti zd%y5&-#dNy<w8W=`h%zb{LZiXm2ZFBSKsr3FJC@Ay#3WL zf9Zoy-uvq3-?_c_J%1hl_}zEjzWtF;fAI6~JTB<>e&LI6Pal4KdAt5x^KiL>yd7^m96$WZr=Nevmp%^R%O8F1wI6))G)*S#-LD=#dN==E7DKxI!Sm^Bf9|8N zeEg-`kDpKPUcPks^5uh1Kk;Ytul?DNe&EATKm6neKHPZq*Z%e2@dKvz3t#x!ZzR9v z%U}N5Pki#}@lbXk7LXa3?hqxt01@4o!ZeBSZ6 zKmN6cKYjnY@ofB?zhh9BufD(WN8{go?T>!+-t_V1@acyif6s>>e(BBL`Qn>de0!SS z{-wY9{SVF`{_rQiEJpD3$1n5Y)63`IiEZ?dwGT!1J$d$CR=-y@zdS47%d*Em`h^eA z?_Yo9Yk#zlKU(^u{d-yeUXlHB|NXT;Y7d_epC10cfB1ZQ@A~1#Up`oY_rHAj{>!@` zeenJ7y#2LxeQ)~kOCNshhkaf7v;X;buyh}Pczyp%{>9&I@or!Hg)hAK9Upx1>EVOZ zNClp)by-roLRSP5C%l&(9y8z7kt9 zo_{=@=MPWb@iSl7wN=@k^8BI8y5qy8sM?3)p{X9KLwRbi*{RKo^P8{vbsxXaKpejG zv)>wDb&jt(cX?Mfmxoi|W)Ih_>K?kbygW2{-(IhIpI@@mJ8#FHc+YEm(jiXa=ikYn z{^9q=-u%*69{*wAWd8GR9{>IH@4xTw^Y4G=U;oAr-nSIRJ8#FXdYt{=z`&X1rmoBS z>7Re`{jXlW^u6=@X6KXJhZJ@5v;Woi{aycx4f>f$douxprTN58{p86)jfd}l`l~O! z|0}bs$cwx$%dTyQVra{Cn3t@n#zdD1wk-YFP4zIf zc|OjmCzhe9iaH;1;~B;xYqPOf%YGc@uI!4$6GdKT<&-%b5>%!~WM zA)ia%xh8&N=*qgvhM3W&>5IDVy`WyEX&Kvm>Jv|lPNSDwjB2fxX~~wd&7F{bs;hcf zQcq0zu=L*CEagqMBHS!a?fud93oR8S~Wa`l6~UvbmTG=Y3lH*^bJqzRcQkDN?(A zUB)n0^*rU(z}iiHQPwQw)D%tGB}UE%EOW_b)&8_EtIMpZ=PGa4`0*uiKJv_N&zi=$ z7}h467`t|==ej7nd2y~jyGwFbdY;yzs@lG8hmu7s%VntAa%l6cO?_29jmuK-a&y%$ zE&EuH*}6KA{z;!F>{VVg?NZnN^i)rC*M?KDSH{*4GQX*D6x}klQ&x@B%7}Jeo7Z#J zteuh7bz(j8g6%2FWv$1$XAg^cwxzn!?{u>RlNfnf5AD#-w(exmc6zp~%`jJtqT?~9 zo*2WŜ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

>Yvp|~X6yDxUT?flxj)t$a%;QQ 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�R>aQjB9oI!EpJo00!>LKYVJ-AR(0vZg zg(M;+D_&!VKQ61dJ8`r%>#-JP%tglYrkj=v#g(@o}~;adv+E4 zg)sKLsQNd>h3)K6oM)_4gawM6N87$mj529pb`4~XgfNnj8H@FkH^s{wx`kZ6iK$?O zs>qL(-!S Date: Sun, 3 Oct 2021 15:40:32 +0200 Subject: [PATCH 063/183] Proxy slider head circle number along with overlay --- .../Skinning/Legacy/LegacyMainCirclePiece.cs | 22 ++++++++++++------- .../Legacy/LegacySliderHeadHitCircle.cs | 8 +++---- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index 3afd814174..8b45513a2e 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -35,8 +35,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private Drawable hitCircleSprite; - protected Drawable HitCircleOverlay { get; private set; } + protected Container OverlayLayer; + private Drawable hitCircleOverlay; private SkinnableSpriteText hitCircleText; private readonly Bindable accentColour = new Bindable(); @@ -78,17 +79,22 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy Anchor = Anchor.Centre, Origin = Anchor.Centre, }, - HitCircleOverlay = new KiaiFlashingSprite + OverlayLayer = new Container { - Texture = overlayTexture, Anchor = Anchor.Centre, Origin = Anchor.Centre, - }, + Child = hitCircleOverlay = new KiaiFlashingSprite + { + Texture = overlayTexture, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + } }; if (hasNumber) { - AddInternal(hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText + OverlayLayer.Add(hitCircleText = new SkinnableSpriteText(new OsuSkinComponent(OsuSkinComponents.HitCircleText), _ => new OsuSpriteText { Font = OsuFont.Numeric.With(size: 40), UseFullGlyphHeight = false, @@ -102,7 +108,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy bool overlayAboveNumber = skin.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber)?.Value ?? true; if (overlayAboveNumber) - ChangeInternalChildDepth(HitCircleOverlay, float.MinValue); + OverlayLayer.ChangeChildDepth(hitCircleOverlay, float.MinValue); accentColour.BindTo(drawableObject.AccentColour); indexInCurrentCombo.BindTo(drawableOsuObject.IndexInCurrentComboBindable); @@ -147,8 +153,8 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy hitCircleSprite.FadeOut(legacy_fade_duration, Easing.Out); hitCircleSprite.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); - HitCircleOverlay.FadeOut(legacy_fade_duration, Easing.Out); - HitCircleOverlay.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); + hitCircleOverlay.FadeOut(legacy_fade_duration, Easing.Out); + hitCircleOverlay.ScaleTo(1.4f, legacy_fade_duration, Easing.Out); if (hasNumber) { diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderHeadHitCircle.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderHeadHitCircle.cs index 13ba42ba50..7de2b8c7fa 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderHeadHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacySliderHeadHitCircle.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy [Resolved(canBeNull: true)] private DrawableHitObject drawableHitObject { get; set; } - private Drawable proxiedHitCircleOverlay; + private Drawable proxiedOverlayLayer; public LegacySliderHeadHitCircle() : base("sliderstartcircle") @@ -24,7 +24,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy protected override void LoadComplete() { base.LoadComplete(); - proxiedHitCircleOverlay = HitCircleOverlay.CreateProxy(); + proxiedOverlayLayer = OverlayLayer.CreateProxy(); if (drawableHitObject != null) { @@ -35,11 +35,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private void onHitObjectApplied(DrawableHitObject drawableObject) { - Debug.Assert(proxiedHitCircleOverlay.Parent == null); + Debug.Assert(proxiedOverlayLayer.Parent == null); // see logic in LegacyReverseArrow. (drawableObject as DrawableSliderHead)?.DrawableSlider - .OverlayElementContainer.Add(proxiedHitCircleOverlay.With(d => d.Depth = float.MinValue)); + .OverlayElementContainer.Add(proxiedOverlayLayer.With(d => d.Depth = float.MinValue)); } protected override void Dispose(bool isDisposing) From 5e5cdaab5ef1a931541fe76941a4770624b0eed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 3 Oct 2021 19:14:01 +0200 Subject: [PATCH 064/183] Privatise setter Co-authored-by: Dean Herbert --- osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs index 8b45513a2e..d1c9b1bf92 100644 --- a/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs +++ b/osu.Game.Rulesets.Osu/Skinning/Legacy/LegacyMainCirclePiece.cs @@ -35,7 +35,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy private Drawable hitCircleSprite; - protected Container OverlayLayer; + protected Container OverlayLayer { get; private set; } private Drawable hitCircleOverlay; private SkinnableSpriteText hitCircleText; From 86240cc8ecf570e9c9eba1760db1d651d67c2c9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 3 Oct 2021 23:36:39 +0200 Subject: [PATCH 065/183] Add alternate Torus font --- osu.Game/Graphics/OsuFont.cs | 6 ++++++ osu.Game/OsuGameBase.cs | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/osu.Game/Graphics/OsuFont.cs b/osu.Game/Graphics/OsuFont.cs index b6090d0e1a..edb484021c 100644 --- a/osu.Game/Graphics/OsuFont.cs +++ b/osu.Game/Graphics/OsuFont.cs @@ -21,6 +21,8 @@ namespace osu.Game.Graphics public static FontUsage Torus => GetFont(Typeface.Torus, weight: FontWeight.Regular); + public static FontUsage TorusAlternate => GetFont(Typeface.TorusAlternate, weight: FontWeight.Regular); + public static FontUsage Inter => GetFont(Typeface.Inter, weight: FontWeight.Regular); ///

@@ -57,6 +59,9 @@ namespace osu.Game.Graphics case Typeface.Torus: return "Torus"; + case Typeface.TorusAlternate: + return "Torus-Alternate"; + case Typeface.Inter: return "Inter"; } @@ -113,6 +118,7 @@ namespace osu.Game.Graphics { Venera, Torus, + TorusAlternate, Inter, } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index adb819bf20..02de92e805 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -347,6 +347,11 @@ namespace osu.Game AddFont(Resources, @"Fonts/Torus/Torus-SemiBold"); AddFont(Resources, @"Fonts/Torus/Torus-Bold"); + AddFont(Resources, @"Fonts/Torus-Alternate/Torus-Alternate-Regular"); + AddFont(Resources, @"Fonts/Torus-Alternate/Torus-Alternate-Light"); + AddFont(Resources, @"Fonts/Torus-Alternate/Torus-Alternate-SemiBold"); + AddFont(Resources, @"Fonts/Torus-Alternate/Torus-Alternate-Bold"); + AddFont(Resources, @"Fonts/Inter/Inter-Regular"); AddFont(Resources, @"Fonts/Inter/Inter-RegularItalic"); AddFont(Resources, @"Fonts/Inter/Inter-Light"); From 67d08a3eeee036fc94fca611e4986efa3002c372 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Oct 2021 00:20:16 +0200 Subject: [PATCH 066/183] Add test scene for previewing Torus alternates --- .../Visual/UserInterface/TestSceneOsuFont.cs | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneOsuFont.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuFont.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuFont.cs new file mode 100644 index 0000000000..eedafce271 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuFont.cs @@ -0,0 +1,77 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneOsuFont : OsuTestScene + { + private OsuSpriteText spriteText; + + private readonly BindableBool useAlternates = new BindableBool(); + private readonly Bindable weight = new Bindable(FontWeight.Regular); + + [BackgroundDependencyLoader] + private void load() + { + Child = spriteText = new OsuSpriteText + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.X, + AllowMultiline = true, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + useAlternates.BindValueChanged(_ => updateFont()); + weight.BindValueChanged(_ => updateFont(), true); + } + + private void updateFont() + { + FontUsage usage = useAlternates.Value ? OsuFont.TorusAlternate : OsuFont.Torus; + spriteText.Font = usage.With(size: 40, weight: weight.Value); + } + + [Test] + public void TestTorusAlternates() + { + AddStep("set all ASCII letters", () => spriteText.Text = @"ABCDEFGHIJKLMNOPQRSTUVWXYZ +abcdefghijklmnopqrstuvwxyz"); + AddStep("set all alternates", () => spriteText.Text = @"A Á Ă Â Ä À Ā Ą Å Ã +Æ B D Ð Ď Đ E É Ě Ê +Ë Ė È Ē Ę F G Ğ Ģ Ġ +H I Í Î Ï İ Ì Ī Į K +Ķ O Œ P Þ Q R Ŕ Ř Ŗ +T Ŧ Ť Ţ Ț V W Ẃ Ŵ Ẅ +Ẁ X Y Ý Ŷ Ÿ Ỳ a á ă +â ä à ā ą å ã æ b d +ď đ e é ě ê ë ė è ē +ę f g ğ ģ ġ k ķ m n +ń ň ņ ŋ ñ o œ p þ q +t ŧ ť ţ ț u ú û ü ù +ű ū ų ů w ẃ ŵ ẅ ẁ x +y ý ŷ ÿ ỳ"); + + AddToggleStep("toggle alternates", alternates => useAlternates.Value = alternates); + + addSetWeightStep(FontWeight.Light); + addSetWeightStep(FontWeight.Regular); + addSetWeightStep(FontWeight.SemiBold); + addSetWeightStep(FontWeight.Bold); + + void addSetWeightStep(FontWeight newWeight) => AddStep($"set weight {newWeight}", () => weight.Value = newWeight); + } + } +} From 017756cbcae754236a4e6cdb3b37f0301121b6be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Oct 2021 00:21:36 +0200 Subject: [PATCH 067/183] Use Torus alternates on online play screens as per design --- osu.Game/Screens/OnlinePlay/Header.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/OnlinePlay/Header.cs b/osu.Game/Screens/OnlinePlay/Header.cs index b0db9256f5..2d4b5cc527 100644 --- a/osu.Game/Screens/OnlinePlay/Header.cs +++ b/osu.Game/Screens/OnlinePlay/Header.cs @@ -72,21 +72,21 @@ namespace osu.Game.Screens.OnlinePlay { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: 24), + Font = OsuFont.TorusAlternate.With(size: 24), Text = mainTitle }, dot = new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: 24), + Font = OsuFont.TorusAlternate.With(size: 24), Text = "·" }, pageTitle = new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: 24), + Font = OsuFont.TorusAlternate.With(size: 24), Text = "Lounge" } } From 11e9c16b92eec768d23c389329600ca89cf0e2aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 11:13:46 +0900 Subject: [PATCH 068/183] Update resources --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index b84f1730ac..eeca40e73d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -51,7 +51,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c162025f1f..33d4e5a6c8 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 8597a06c03..e30722c334 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 537b29654e60e738128fa123345a01ab39074b22 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 14:30:22 +0900 Subject: [PATCH 069/183] Fix stream being held open causing windows CI failures --- osu.Game.Tests/Database/RealmTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Database/RealmTest.cs b/osu.Game.Tests/Database/RealmTest.cs index 219690db30..576f901c1a 100644 --- a/osu.Game.Tests/Database/RealmTest.cs +++ b/osu.Game.Tests/Database/RealmTest.cs @@ -70,7 +70,8 @@ namespace osu.Game.Tests.Database { try { - return testStorage.GetStream(realmFactory.Filename)?.Length ?? 0; + using (var stream = testStorage.GetStream(realmFactory.Filename)) + return stream?.Length ?? 0; } catch { From c6aba3e78b3a9f8cf89e60c5f4b069c2873532ec Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 14:44:16 +0900 Subject: [PATCH 070/183] Ensure a `DrawableChannel` is not attempted to be added after disposal --- osu.Game/Overlays/ChatOverlay.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 25c5154d4a..a61b80cc8e 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -284,6 +284,10 @@ namespace osu.Game.Overlays if (currentChannel.Value != e.NewValue) return; + // check once more to ensure the channel hasn't since been removed from the loaded channels like (may have been left by some automated means). + if (loadedChannels.Contains(loaded)) + return; + loading.Hide(); currentChannelContainer.Clear(false); @@ -426,7 +430,7 @@ namespace osu.Game.Overlays base.PopOut(); } - private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) + private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) => Schedule(() => { switch (args.Action) { @@ -444,10 +448,9 @@ namespace osu.Game.Overlays if (loaded != null) { - loadedChannels.Remove(loaded); - // Because the container is only cleared in the async load callback of a new channel, it is forcefully cleared // to ensure that the previous channel doesn't get updated after it's disposed + loadedChannels.Remove(loaded); currentChannelContainer.Remove(loaded); loaded.Dispose(); } @@ -455,7 +458,7 @@ namespace osu.Game.Overlays break; } - } + }); private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) { From c19c2335eccdd752a521db4b869abf7d96428c19 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 14:58:54 +0900 Subject: [PATCH 071/183] Remove added schedule due to changing flow --- osu.Game/Overlays/ChatOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index a61b80cc8e..7be9258248 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -430,7 +430,7 @@ namespace osu.Game.Overlays base.PopOut(); } - private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) => Schedule(() => + private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) { switch (args.Action) { @@ -458,7 +458,7 @@ namespace osu.Game.Overlays break; } - }); + } private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) { From bc984dff4f2b48d1a4975dfdb031eb378e4d2045 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 4 Oct 2021 15:35:28 +0900 Subject: [PATCH 072/183] Fix typo --- osu.Game/Overlays/ChatOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 7be9258248..20d637d957 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -284,7 +284,7 @@ namespace osu.Game.Overlays if (currentChannel.Value != e.NewValue) return; - // check once more to ensure the channel hasn't since been removed from the loaded channels like (may have been left by some automated means). + // check once more to ensure the channel hasn't since been removed from the loaded channels list (may have been left by some automated means). if (loadedChannels.Contains(loaded)) return; From 5aaafce597e370f9c6c900d3c9f9e992499e0361 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 15:40:00 +0900 Subject: [PATCH 073/183] Make `AuthenticateWithLogin` throw instead of return a `bool` success status --- .../ErrorTextFlowContainer.cs | 2 +- osu.Game/Online/API/OAuth.cs | 37 ++++++++++++++++--- 2 files changed, 32 insertions(+), 7 deletions(-) rename osu.Game/{Overlays/AccountCreation => Graphics}/ErrorTextFlowContainer.cs (95%) diff --git a/osu.Game/Overlays/AccountCreation/ErrorTextFlowContainer.cs b/osu.Game/Graphics/ErrorTextFlowContainer.cs similarity index 95% rename from osu.Game/Overlays/AccountCreation/ErrorTextFlowContainer.cs rename to osu.Game/Graphics/ErrorTextFlowContainer.cs index 87ff4dd398..f17a2a2c3d 100644 --- a/osu.Game/Overlays/AccountCreation/ErrorTextFlowContainer.cs +++ b/osu.Game/Graphics/ErrorTextFlowContainer.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics; using osu.Game.Graphics.Containers; using osuTK.Graphics; -namespace osu.Game.Overlays.AccountCreation +namespace osu.Game.Graphics { public class ErrorTextFlowContainer : OsuTextFlowContainer { diff --git a/osu.Game/Online/API/OAuth.cs b/osu.Game/Online/API/OAuth.cs index bdc47aab8d..693e7c5336 100644 --- a/osu.Game/Online/API/OAuth.cs +++ b/osu.Game/Online/API/OAuth.cs @@ -1,8 +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 System.Net.Http; +using Newtonsoft.Json; using osu.Framework.Bindables; namespace osu.Game.Online.API @@ -32,10 +34,10 @@ namespace osu.Game.Online.API this.endpoint = endpoint; } - internal bool AuthenticateWithLogin(string username, string password) + internal void AuthenticateWithLogin(string username, string password) { - if (string.IsNullOrEmpty(username)) return false; - if (string.IsNullOrEmpty(password)) return false; + if (string.IsNullOrEmpty(username)) throw new ArgumentException("Missing username."); + if (string.IsNullOrEmpty(password)) throw new ArgumentException("Missing password."); using (var req = new AccessTokenRequestPassword(username, password) { @@ -49,13 +51,27 @@ namespace osu.Game.Online.API { req.Perform(); } - catch + catch (Exception ex) { - return false; + Token.Value = null; + + var throwableException = ex; + + try + { + // attempt to decode a displayable error string. + var error = JsonConvert.DeserializeObject(req.GetResponseString() ?? string.Empty); + if (error != null) + throwableException = new APIException(error.Message, ex); + } + catch + { + } + + throw throwableException; } Token.Value = req.ResponseObject; - return true; } } @@ -182,5 +198,14 @@ namespace osu.Game.Online.API base.PrePerform(); } } + + private class OAuthError + { + [JsonProperty("error")] + public string ErrorType { get; set; } + + [JsonProperty("hint")] + public string Message { get; set; } + } } } From 266b4c7124fe4b578087afaaba46e18ec4ee0423 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 15:40:24 +0900 Subject: [PATCH 074/183] Expose login errors from `IAPIProvider` and show on the login form --- .../Visual/Menus/TestSceneLoginPanel.cs | 16 ++++++++++- osu.Game/Online/API/APIAccess.cs | 28 ++++++++++++------- osu.Game/Online/API/DummyAPIAccess.cs | 18 ++++++++++++ osu.Game/Online/API/IAPIProvider.cs | 6 ++++ osu.Game/Overlays/Login/LoginForm.cs | 14 +++++++++- 5 files changed, 70 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs b/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs index 5fdadfc2fb..4754a73f83 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneLoginPanel.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Testing; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; using osu.Game.Overlays.Login; namespace osu.Game.Tests.Visual.Menus @@ -30,12 +31,25 @@ namespace osu.Game.Tests.Visual.Menus } [Test] - public void TestBasicLogin() + public void TestLoginSuccess() { AddStep("logout", () => API.Logout()); AddStep("enter password", () => loginPanel.ChildrenOfType().First().Text = "password"); AddStep("submit", () => loginPanel.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick()); } + + [Test] + public void TestLoginFailure() + { + AddStep("logout", () => + { + API.Logout(); + ((DummyAPIAccess)API).FailNextLogin(); + }); + + AddStep("enter password", () => loginPanel.ChildrenOfType().First().Text = "password"); + AddStep("submit", () => loginPanel.ChildrenOfType().First(b => b.Text.ToString() == "Sign in").TriggerClick()); + } } } diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index af14cdc7b3..94508e3a81 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -35,9 +35,8 @@ namespace osu.Game.Online.API public string WebsiteRootUrl { get; } - /// - /// The username/email provided by the user when initiating a login. - /// + public Exception LastLoginError { get; private set; } + public string ProvidedUsername { get; private set; } private string password; @@ -136,14 +135,23 @@ namespace osu.Game.Online.API // save the username at this point, if the user requested for it to be. config.SetValue(OsuSetting.Username, config.Get(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty); - if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(ProvidedUsername, password)) + if (!authentication.HasValidAccessToken) { - //todo: this fails even on network-related issues. we should probably handle those differently. - //NotificationOverlay.ShowMessage("Login failed!"); - log.Add(@"Login failed!"); - password = null; - authentication.Clear(); - continue; + LastLoginError = null; + + try + { + authentication.AuthenticateWithLogin(ProvidedUsername, password); + } + catch (Exception e) + { + //todo: this fails even on network-related issues. we should probably handle those differently. + LastLoginError = e; + log.Add(@"Login failed!"); + password = null; + authentication.Clear(); + continue; + } } var userReq = new GetUserRequest(); diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 1ba31db9fa..8f91a4d198 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -32,6 +32,8 @@ namespace osu.Game.Online.API public string WebsiteRootUrl => "http://localhost"; + public Exception LastLoginError { get; private set; } + /// /// Provide handling logic for an arbitrary API request. /// Should return true is a request was handled. If null or false return, the request will be failed with a . @@ -40,6 +42,8 @@ namespace osu.Game.Online.API private readonly Bindable state = new Bindable(APIState.Online); + private bool shouldFailNextLogin; + /// /// The current connectivity state of the API. /// @@ -74,6 +78,18 @@ namespace osu.Game.Online.API public void Login(string username, string password) { + state.Value = APIState.Connecting; + + if (shouldFailNextLogin) + { + LastLoginError = new APIException("Not powerful enough to login.", new ArgumentException(nameof(shouldFailNextLogin))); + + state.Value = APIState.Offline; + shouldFailNextLogin = false; + return; + } + + LastLoginError = null; LocalUser.Value = new User { Username = username, @@ -102,5 +118,7 @@ namespace osu.Game.Online.API IBindable IAPIProvider.LocalUser => LocalUser; IBindableList IAPIProvider.Friends => Friends; IBindable IAPIProvider.Activity => Activity; + + public void FailNextLogin() => shouldFailNextLogin = true; } } diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs index 5ad5367924..72ca37bcf4 100644 --- a/osu.Game/Online/API/IAPIProvider.cs +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -3,6 +3,7 @@ #nullable enable +using System; using System.Threading.Tasks; using osu.Framework.Bindables; using osu.Game.Users; @@ -55,6 +56,11 @@ namespace osu.Game.Online.API /// string WebsiteRootUrl { get; } + /// + /// The last login error that occurred, if any. + /// + Exception? LastLoginError { get; } + /// /// The current connection state of the API. /// This is not thread-safe and should be scheduled locally if consumed from a drawable component. diff --git a/osu.Game/Overlays/Login/LoginForm.cs b/osu.Game/Overlays/Login/LoginForm.cs index e43b84d52a..f7842dcd30 100644 --- a/osu.Game/Overlays/Login/LoginForm.cs +++ b/osu.Game/Overlays/Login/LoginForm.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Configuration; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -42,6 +43,9 @@ namespace osu.Game.Overlays.Login Spacing = new Vector2(0, 5); AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; + + ErrorTextFlowContainer errorText; + Children = new Drawable[] { username = new OsuTextBox @@ -57,6 +61,11 @@ namespace osu.Game.Overlays.Login RelativeSizeAxes = Axes.X, TabbableContentContainer = this, }, + errorText = new ErrorTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, new SettingsCheckbox { LabelText = "Remember username", @@ -97,6 +106,9 @@ namespace osu.Game.Overlays.Login }; password.OnCommit += (sender, newText) => performLogin(); + + if (api?.LastLoginError?.Message is string error) + errorText.AddErrors(new[] { error }); } public override bool AcceptsFocus => true; @@ -108,4 +120,4 @@ namespace osu.Game.Overlays.Login Schedule(() => { GetContainingInputManager().ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); }); } } -} \ No newline at end of file +} From 4e1322effac966e1b3fec6dab2231d131648defc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 4 Oct 2021 16:02:45 +0900 Subject: [PATCH 075/183] Fix typo --- osu.Game/Beatmaps/BeatmapModelManager.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index aa14f95863..250d6653d5 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -185,12 +185,12 @@ namespace osu.Game.Beatmaps /// /// Saves an file against a given . /// - /// The to save the content against. The file referenced by will be replaced. + /// The to save the content against. The file referenced by will be replaced. /// The content to write. /// The beatmap content to write, null if to be omitted. - public virtual void Save(BeatmapInfo baetmapInfo, IBeatmap beatmapContent, ISkin beatmapSkin = null) + public virtual void Save(BeatmapInfo beatmapInfo, IBeatmap beatmapContent, ISkin beatmapSkin = null) { - var setInfo = baetmapInfo.BeatmapSet; + var setInfo = beatmapInfo.BeatmapSet; using (var stream = new MemoryStream()) { @@ -201,7 +201,7 @@ namespace osu.Game.Beatmaps using (ContextFactory.GetForWrite()) { - var beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == baetmapInfo.ID); + beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == beatmapInfo.ID); var metadata = beatmapInfo.Metadata ?? setInfo.Metadata; // grab the original file (or create a new one if not found). @@ -219,7 +219,7 @@ namespace osu.Game.Beatmaps } } - WorkingBeatmapCache?.Invalidate(baetmapInfo); + WorkingBeatmapCache?.Invalidate(beatmapInfo); } /// From 3a0b7ba8fffe61e85b8adceebf48a7eeffb10fd8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 16:18:55 +0900 Subject: [PATCH 076/183] Add fallback to use `Message` when `Hint` is not available --- osu.Game/Online/API/OAuth.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/API/OAuth.cs b/osu.Game/Online/API/OAuth.cs index 693e7c5336..d79fc58d1c 100644 --- a/osu.Game/Online/API/OAuth.cs +++ b/osu.Game/Online/API/OAuth.cs @@ -62,7 +62,7 @@ namespace osu.Game.Online.API // attempt to decode a displayable error string. var error = JsonConvert.DeserializeObject(req.GetResponseString() ?? string.Empty); if (error != null) - throwableException = new APIException(error.Message, ex); + throwableException = new APIException(error.UserDisplayableError, ex); } catch { @@ -201,10 +201,15 @@ namespace osu.Game.Online.API private class OAuthError { + public string UserDisplayableError => !string.IsNullOrEmpty(Hint) ? Hint : ErrorIdentifier; + [JsonProperty("error")] - public string ErrorType { get; set; } + public string ErrorIdentifier { get; set; } [JsonProperty("hint")] + public string Hint { get; set; } + + [JsonProperty("message")] public string Message { get; set; } } } From 3c15ef720f96e0cfd52cd4f7b90929a834ff2f6f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 16:26:28 +0900 Subject: [PATCH 077/183] Remove setter from `IHasGuidPrimaryKey` interface --- osu.Game/Database/IHasGuidPrimaryKey.cs | 2 +- osu.Game/Database/ILive.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/IHasGuidPrimaryKey.cs b/osu.Game/Database/IHasGuidPrimaryKey.cs index c9cd9b257a..f52dc5c8ef 100644 --- a/osu.Game/Database/IHasGuidPrimaryKey.cs +++ b/osu.Game/Database/IHasGuidPrimaryKey.cs @@ -11,6 +11,6 @@ namespace osu.Game.Database { [JsonIgnore] [PrimaryKey] - Guid ID { get; set; } + Guid ID { get; } } } diff --git a/osu.Game/Database/ILive.cs b/osu.Game/Database/ILive.cs index 29e5756dba..9359b09eaf 100644 --- a/osu.Game/Database/ILive.cs +++ b/osu.Game/Database/ILive.cs @@ -9,7 +9,7 @@ namespace osu.Game.Database /// A wrapper to provide access to database backed classes in a thread-safe manner. /// /// The databased type. - public interface ILive where T : class + public interface ILive where T : class // TODO: Add IHasGuidPrimaryKey once we don't need EF support any more. { Guid ID { get; } From 857000b756765a99337d4b22c5085d4a27fdabfd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 16:29:46 +0900 Subject: [PATCH 078/183] Mark `IPresentImports` as covariant --- osu.Game/Database/IPresentImports.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/IPresentImports.cs b/osu.Game/Database/IPresentImports.cs index 6aa29a5083..fb3aad7ee1 100644 --- a/osu.Game/Database/IPresentImports.cs +++ b/osu.Game/Database/IPresentImports.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; namespace osu.Game.Database { - public interface IPresentImports + public interface IPresentImports where TModel : class { /// From e631653f4b1b471ec3794e7845b34492da88e801 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 16:30:12 +0900 Subject: [PATCH 079/183] Remove incorrectly committed `FodyWeavers` file --- osu.Game/FodyWeavers.xml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 osu.Game/FodyWeavers.xml diff --git a/osu.Game/FodyWeavers.xml b/osu.Game/FodyWeavers.xml deleted file mode 100644 index cc07b89533..0000000000 --- a/osu.Game/FodyWeavers.xml +++ /dev/null @@ -1,3 +0,0 @@ - - - \ No newline at end of file From 63f0b0c93215cf1b0266b4891e6e82a160641951 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 16:35:55 +0900 Subject: [PATCH 080/183] Rename out of place interface name --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Database/ArchiveModelManager.cs | 8 ++++---- osu.Game/Database/{IPresentImports.cs => IPostImports.cs} | 4 ++-- osu.Game/OsuGame.cs | 2 +- osu.Game/Scoring/ScoreManager.cs | 6 +++--- 5 files changed, 11 insertions(+), 11 deletions(-) rename osu.Game/Database/{IPresentImports.cs => IPostImports.cs} (76%) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index b302df1516..f8181cd010 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -178,7 +178,7 @@ namespace osu.Game.Beatmaps /// /// Fired when the user requests to view the resulting import. /// - public Action>> PresentImport { set => beatmapModelManager.PresentImport = value; } + public Action>> PresentImport { set => beatmapModelManager.PostImport = value; } /// /// Delete a beatmap difficulty. diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 403bfdf621..9ad2dec12e 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -30,7 +30,7 @@ namespace osu.Game.Database /// /// The model type. /// The associated file join type. - public abstract class ArchiveModelManager : ICanAcceptFiles, IModelManager, IModelFileManager, IPresentImports + public abstract class ArchiveModelManager : ICanAcceptFiles, IModelManager, IModelFileManager, IPostImports where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete where TFileModel : class, INamedFileInfo, new() { @@ -200,12 +200,12 @@ namespace osu.Game.Database ? $"Imported {imported.First()}!" : $"Imported {imported.Count} {HumanisedModelName}s!"; - if (imported.Count > 0 && PresentImport != null) + if (imported.Count > 0 && PostImport != null) { notification.CompletionText += " Click to view."; notification.CompletionClickAction = () => { - PresentImport?.Invoke(imported); + PostImport?.Invoke(imported); return true; }; } @@ -249,7 +249,7 @@ namespace osu.Game.Database return import; } - public Action>> PresentImport { protected get; set; } + public Action>> PostImport { protected get; set; } /// /// Silently import an item from an . diff --git a/osu.Game/Database/IPresentImports.cs b/osu.Game/Database/IPostImports.cs similarity index 76% rename from osu.Game/Database/IPresentImports.cs rename to osu.Game/Database/IPostImports.cs index fb3aad7ee1..f09285089a 100644 --- a/osu.Game/Database/IPresentImports.cs +++ b/osu.Game/Database/IPostImports.cs @@ -6,12 +6,12 @@ using System.Collections.Generic; namespace osu.Game.Database { - public interface IPresentImports + public interface IPostImports where TModel : class { /// /// Fired when the user requests to view the resulting import. /// - public Action>> PresentImport { set; } + public Action>> PostImport { set; } } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 35ec213755..64c77c370e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -627,7 +627,7 @@ namespace osu.Game BeatmapManager.PresentImport = items => PresentBeatmap(items.First().Value); ScoreManager.PostNotification = n => Notifications.Post(n); - ScoreManager.PresentImport = items => PresentScore(items.First().Value); + ScoreManager.PostImport = items => PresentScore(items.First().Value); // make config aware of how to lookup skins for on-screen display purposes. // if this becomes a more common thing, tracked settings should be reconsidered to allow local DI. diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index aa0ee4bbbb..922b4f0a38 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -25,7 +25,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Scoring { - public class ScoreManager : IModelManager, IModelFileManager, IModelDownloader, ICanAcceptFiles, IPresentImports + public class ScoreManager : IModelManager, IModelFileManager, IModelDownloader, ICanAcceptFiles, IPostImports { private readonly Scheduler scheduler; private readonly Func difficulties; @@ -365,9 +365,9 @@ namespace osu.Game.Scoring #region Implementation of IPresentImports - public Action>> PresentImport + public Action>> PostImport { - set => scoreModelManager.PresentImport = value; + set => scoreModelManager.PostImport = value; } #endregion From 8bfdfe3672997fcacfed8a571629c17403348d9e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 16:54:00 +0900 Subject: [PATCH 081/183] Add literal string marker MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Beatmaps/IBeatmapSetInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/IBeatmapSetInfo.cs b/osu.Game/Beatmaps/IBeatmapSetInfo.cs index 548a48367c..d5aded7b53 100644 --- a/osu.Game/Beatmaps/IBeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapSetInfo.cs @@ -53,6 +53,6 @@ namespace osu.Game.Beatmaps /// /// The filename for the storyboard. /// - string StoryboardFile => Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename ?? string.Empty; + string StoryboardFile => Files.FirstOrDefault(f => f.Filename.EndsWith(@".osb", StringComparison.OrdinalIgnoreCase))?.Filename ?? string.Empty; } } From 4df5f931522f63553e4d2049b3147f5c7fbb650b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 16:50:29 +0900 Subject: [PATCH 082/183] Inline single usage of `StoryboardFile` to avoid interface default method woes --- osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 6 ++++-- osu.Game/Beatmaps/BeatmapSetInfo.cs | 4 ---- osu.Game/Beatmaps/IBeatmapSetInfo.cs | 6 ------ 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 45112ae74c..f9889474ac 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -107,12 +107,14 @@ namespace osu.Game.Beatmaps { var decoder = Decoder.GetDecoder(stream); + var storyboardFilename = BeatmapSetInfo?.Files.FirstOrDefault(f => f.Filename.EndsWith(".osb", StringComparison.OrdinalIgnoreCase))?.Filename; + // todo: support loading from both set-wide storyboard *and* beatmap specific. - if (BeatmapSetInfo?.StoryboardFile == null) + if (string.IsNullOrEmpty(storyboardFilename)) storyboard = decoder.Decode(stream); else { - using (var secondaryStream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(BeatmapSetInfo.StoryboardFile)))) + using (var secondaryStream = new LineBufferedReader(GetStream(BeatmapSetInfo.GetPathForFile(storyboardFilename)))) storyboard = decoder.Decode(stream, secondaryStream); } } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 9f5a07ec43..8b01831b3c 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using JetBrains.Annotations; -using Newtonsoft.Json; using osu.Framework.Testing; using osu.Game.Database; @@ -62,9 +61,6 @@ namespace osu.Game.Beatmaps public string Hash { get; set; } - [JsonIgnore] - public string StoryboardFile => ((IBeatmapSetInfo)this).StoryboardFile; - /// /// Returns the storage path for the file in this beatmapset with the given filename, if any exists, otherwise null. /// The path returned is relative to the user file storage. diff --git a/osu.Game/Beatmaps/IBeatmapSetInfo.cs b/osu.Game/Beatmaps/IBeatmapSetInfo.cs index d5aded7b53..0cfb0c4242 100644 --- a/osu.Game/Beatmaps/IBeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapSetInfo.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Game.Database; #nullable enable @@ -49,10 +48,5 @@ namespace osu.Game.Beatmaps /// The maximum BPM of all beatmaps in this set. /// double MaxBPM { get; } - - /// - /// The filename for the storyboard. - /// - string StoryboardFile => Files.FirstOrDefault(f => f.Filename.EndsWith(@".osb", StringComparison.OrdinalIgnoreCase))?.Filename ?? string.Empty; } } From fd6b10656c638753a15efcb004f8848e109852bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 16:55:16 +0900 Subject: [PATCH 083/183] Add TODO reminder about ruleset reference transfer quirk --- osu.Game/Rulesets/IRulesetInfo.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/IRulesetInfo.cs b/osu.Game/Rulesets/IRulesetInfo.cs index d4dec0de64..ded3ac4b58 100644 --- a/osu.Game/Rulesets/IRulesetInfo.cs +++ b/osu.Game/Rulesets/IRulesetInfo.cs @@ -38,6 +38,7 @@ namespace osu.Game.Rulesets var ruleset = Activator.CreateInstance(type) as Ruleset; // overwrite the pre-populated RulesetInfo with a potentially database attached copy. + // TODO: figure if we still want/need this after switching to realm. // ruleset.RulesetInfo = this; return ruleset; From 51b7dce16f2bfb031a135183cdabecbbf2a2359b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 16:55:51 +0900 Subject: [PATCH 084/183] Remove reference to `osu-web-10` --- osu.Game/Beatmaps/IBeatmapInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/IBeatmapInfo.cs b/osu.Game/Beatmaps/IBeatmapInfo.cs index 6153a0af08..d552a3f1e3 100644 --- a/osu.Game/Beatmaps/IBeatmapInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapInfo.cs @@ -50,7 +50,7 @@ namespace osu.Game.Beatmaps string Hash { get; } /// - /// MD5 is kept for legacy support (matching against replays, osu-web-10 etc.). + /// MD5 is kept for legacy support (matching against replays etc.). /// string MD5Hash { get; } From f293e008d953d334ebb291481502679519d2c88c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 17:00:22 +0900 Subject: [PATCH 085/183] Move `BeatmapInfo`'s `SearchableTerms` implementation to interface --- osu.Game/Beatmaps/BeatmapInfo.cs | 5 +---- osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 1 + osu.Game/Beatmaps/IBeatmapInfo.cs | 13 +++++++++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 09f237a5de..cd5f8fb9a1 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -152,10 +152,7 @@ namespace osu.Game.Beatmaps [JsonIgnore] public DifficultyRating DifficultyRating => BeatmapDifficultyCache.GetDifficultyRating(StarDifficulty); - public string[] SearchableTerms => new[] - { - Version - }.Concat(Metadata?.SearchableTerms ?? Enumerable.Empty()).Where(s => !string.IsNullOrEmpty(s)).ToArray(); + public IEnumerable SearchableTerms => ((IBeatmapInfo)this).SearchableTerms; public override string ToString() => ((IBeatmapInfo)this).DisplayTitle; diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index f9889474ac..5fec8f7a89 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics.CodeAnalysis; using System.IO; +using System.Linq; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Textures; using osu.Framework.Logging; diff --git a/osu.Game/Beatmaps/IBeatmapInfo.cs b/osu.Game/Beatmaps/IBeatmapInfo.cs index d552a3f1e3..fb30b0279c 100644 --- a/osu.Game/Beatmaps/IBeatmapInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapInfo.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Localisation; using osu.Game.Database; using osu.Game.Rulesets; @@ -22,7 +23,7 @@ namespace osu.Game.Beatmaps /// /// The metadata representing this beatmap. May be shared between multiple beatmaps. /// - IBeatmapMetadataInfo Metadata { get; } + IBeatmapMetadataInfo? Metadata { get; } /// /// The difficulty settings for this beatmap. @@ -76,12 +77,20 @@ namespace osu.Game.Beatmaps { get { - var metadata = Metadata.DisplayTitleRomanisable; + var metadata = closestMetadata.DisplayTitleRomanisable; return new RomanisableString($"{metadata.GetPreferred(true)} {versionString}".Trim(), $"{metadata.GetPreferred(false)} {versionString}".Trim()); } } + string[] SearchableTerms => new[] + { + DifficultyName + }.Concat(closestMetadata.SearchableTerms).Where(s => !string.IsNullOrEmpty(s)).ToArray(); + private string versionString => string.IsNullOrEmpty(DifficultyName) ? string.Empty : $"[{DifficultyName}]"; + + // temporary helper methods until we figure which metadata should be where. + private IBeatmapMetadataInfo closestMetadata => (Metadata ?? BeatmapSet.Metadata)!; } } From 95f1cc85d4b57a5eb710da9ace0cc00d87952197 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 17:12:40 +0900 Subject: [PATCH 086/183] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 8fad10d247..fb3e4b3bbd 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ba118c5240..e53b83100e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index 37931d0c38..4e7053d816 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -93,7 +93,7 @@ - + From 6ac0601d2cdba14f61f3bc589ab8198f5ecb63da Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 17:18:08 +0900 Subject: [PATCH 087/183] Fix incorrect csproj merge --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 36c5fd89bf..4877ddf725 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -35,7 +35,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 853cf6feaa165e833ecb7ca18c6cdffe8ca6e005 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Oct 2021 17:35:53 +0900 Subject: [PATCH 088/183] Rename last remaining `BeatmapInfo Beatmap` usage --- .../TestSceneAccuracyHeatmap.cs | 2 +- .../Beatmaps/IO/ImportBeatmapTest.cs | 2 +- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 4 ++-- .../Background/TestSceneUserDimBackgrounds.cs | 2 +- .../Gameplay/TestSceneReplayRecorder.cs | 2 +- .../Gameplay/TestSceneReplayRecording.cs | 2 +- .../Gameplay/TestSceneSpectatorPlayback.cs | 2 +- .../TestSceneMultiplayerResults.cs | 2 +- .../TestSceneMultiplayerTeamResults.cs | 2 +- .../Navigation/TestScenePresentScore.cs | 2 +- .../Online/TestSceneUserProfileScores.cs | 8 ++++---- .../Visual/Ranking/TestSceneAccuracyCircle.cs | 2 +- .../TestSceneExpandedPanelMiddleContent.cs | 8 ++++---- .../Visual/Ranking/TestSceneResultsScreen.cs | 4 ++-- .../SongSelect/TestSceneBeatmapLeaderboard.cs | 20 +++++++++---------- .../SongSelect/TestScenePlaySongSelect.cs | 4 ++-- .../TestSceneDeleteLocalScore.cs | 2 +- .../Requests/Responses/APILegacyScoreInfo.cs | 2 +- osu.Game/Online/Rooms/MultiplayerScore.cs | 2 +- osu.Game/Online/Spectator/SpectatorClient.cs | 2 +- osu.Game/OsuGame.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- .../Overlays/BeatmapSet/Scores/ScoreTable.cs | 2 +- .../BeatmapSet/Scores/ScoresContainer.cs | 2 +- .../Scores/TopScoreStatisticsSection.cs | 2 +- .../Sections/Ranks/DrawableProfileScore.cs | 4 ++-- osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs | 4 ++-- osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs | 4 ++-- osu.Game/Scoring/ScoreInfo.cs | 5 +++-- osu.Game/Scoring/ScoreManager.cs | 12 +++++------ osu.Game/Scoring/ScorePerformanceCache.cs | 2 +- osu.Game/Scoring/ScoreStore.cs | 6 +++--- .../Multiplayer/Spectate/PlayerArea.cs | 2 +- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Play/SoloPlayer.cs | 2 +- .../Expanded/ExpandedPanelMiddleContent.cs | 2 +- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 4 ++-- .../Ranking/Statistics/StatisticsPanel.cs | 2 +- .../Select/BeatmapClearScoresDialog.cs | 2 +- .../Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- osu.Game/Screens/Select/PlaySongSelect.cs | 2 +- osu.Game/Screens/Spectate/SpectatorScreen.cs | 2 +- osu.Game/Tests/TestScoreInfo.cs | 2 +- 43 files changed, 74 insertions(+), 73 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs index 10d9d7ffde..79150a1941 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Position = new Vector2(100, 300), }, - accuracyHeatmap = new TestAccuracyHeatmap(new ScoreInfo { Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }) + accuracyHeatmap = new TestAccuracyHeatmap(new ScoreInfo { BeatmapInfo = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index b536fc61b7..dce01448f4 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -950,7 +950,7 @@ namespace osu.Game.Tests.Beatmaps.IO return ImportScoreTest.LoadScoreIntoOsu(osu, new ScoreInfo { OnlineScoreID = 2, - Beatmap = beatmapInfo, + BeatmapInfo = beatmapInfo, BeatmapInfoID = beatmapInfo.ID }, new ImportScoreTest.TestArchiveReader()); } diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index cd7d744f53..2cd02329b7 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -141,7 +141,7 @@ namespace osu.Game.Tests.Scores.IO var beatmapManager = osu.Dependencies.Get(); var scoreManager = osu.Dependencies.Get(); - beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.Beatmap.ID))); + beatmapManager.Delete(beatmapManager.QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == imported.BeatmapInfo.ID))); Assert.That(scoreManager.Query(s => s.ID == imported.ID).DeletePending, Is.EqualTo(true)); var secondImport = await LoadScoreIntoOsu(osu, imported); @@ -181,7 +181,7 @@ namespace osu.Game.Tests.Scores.IO { var beatmapManager = osu.Dependencies.Get(); - score.Beatmap ??= beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); + score.BeatmapInfo ??= beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); score.Ruleset ??= new OsuRuleset().RulesetInfo; var scoreManager = osu.Dependencies.Get(); diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 12a85c3f26..693c66ccb0 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -232,7 +232,7 @@ namespace osu.Game.Tests.Visual.Background AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" }, - Beatmap = new TestBeatmap(Ruleset.Value).BeatmapInfo, + BeatmapInfo = new TestBeatmap(Ruleset.Value).BeatmapInfo, Ruleset = Ruleset.Value, }))); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs index d89fd322d1..c8040f42f0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecorder.cs @@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual.Gameplay Recorder = recorder = new TestReplayRecorder(new Score { Replay = replay, - ScoreInfo = { Beatmap = gameplayState.Beatmap.BeatmapInfo } + ScoreInfo = { BeatmapInfo = gameplayState.Beatmap.BeatmapInfo } }) { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos), diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs index 07514ad51a..3545fc96e8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayRecording.cs @@ -51,7 +51,7 @@ namespace osu.Game.Tests.Visual.Gameplay Recorder = new TestReplayRecorder(new Score { Replay = replay, - ScoreInfo = { Beatmap = gameplayState.Beatmap.BeatmapInfo } + ScoreInfo = { BeatmapInfo = gameplayState.Beatmap.BeatmapInfo } }) { ScreenSpaceToGamefield = pos => recordingManager.ToLocalSpace(pos) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 07ff35f77b..b4de060578 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -356,7 +356,7 @@ namespace osu.Game.Tests.Visual.Gameplay internal class TestReplayRecorder : ReplayRecorder { public TestReplayRecorder() - : base(new Score { ScoreInfo = { Beatmap = new BeatmapInfo() } }) + : base(new Score { ScoreInfo = { BeatmapInfo = new BeatmapInfo() } }) { } diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs index ff06d4d9c7..5032cdaec7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerResults.cs @@ -30,7 +30,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Accuracy = 0.8, MaxCombo = 500, Combo = 250, - Beatmap = beatmapInfo, + BeatmapInfo = beatmapInfo, User = new User { Username = "Test user" }, Date = DateTimeOffset.Now, OnlineScoreID = 12345, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs index 0a8bda7ec0..99d5fd46e9 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMultiplayerTeamResults.cs @@ -34,7 +34,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Accuracy = 0.8, MaxCombo = 500, Combo = 250, - Beatmap = beatmapInfo, + BeatmapInfo = beatmapInfo, User = new User { Username = "Test user" }, Date = DateTimeOffset.Now, OnlineScoreID = 12345, diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs index 52b577b402..ee84d775d2 100644 --- a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -130,7 +130,7 @@ namespace osu.Game.Tests.Visual.Navigation { Hash = Guid.NewGuid().ToString(), OnlineScoreID = i, - Beatmap = beatmap.Beatmaps.First(), + BeatmapInfo = beatmap.Beatmaps.First(), Ruleset = ruleset ?? new OsuRuleset().RulesetInfo }).Result; }); diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs index 5dca218531..513631a221 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfileScores.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Online { PP = 1047.21, Rank = ScoreRank.SH, - Beatmap = new BeatmapInfo + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Online { PP = 134.32, Rank = ScoreRank.A, - Beatmap = new BeatmapInfo + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.Online { PP = 96.83, Rank = ScoreRank.S, - Beatmap = new BeatmapInfo + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Visual.Online var noPPScore = new ScoreInfo { Rank = ScoreRank.B, - Beatmap = new BeatmapInfo + BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata { diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs index a5e2f02f31..df8500fab2 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyCircle.cs @@ -71,7 +71,7 @@ namespace osu.Game.Tests.Visual.Ranking Id = 2, Username = "peppy", }, - Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, + BeatmapInfo = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, TotalScore = 2845370, Accuracy = accuracy, diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index 5180854aba..899f351a2a 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo) { - Beatmap = createTestBeatmap(author) + BeatmapInfo = createTestBeatmap(author) })); } @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("show excess mods score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo, true) { - Beatmap = createTestBeatmap(author) + BeatmapInfo = createTestBeatmap(author) })); AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Current.Value == "mapper_name")); @@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Ranking { AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo) { - Beatmap = createTestBeatmap(null) + BeatmapInfo = createTestBeatmap(null) })); AddAssert("mapped by text not present", () => @@ -79,7 +79,7 @@ namespace osu.Game.Tests.Visual.Ranking showPanel(new TestScoreInfo(ruleset.RulesetInfo) { Mods = mods, - Beatmap = beatmap, + BeatmapInfo = beatmap, Date = default, }); }); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 631455b727..8d5d0ba8c7 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -337,8 +337,8 @@ namespace osu.Game.Tests.Visual.Ranking public UnrankedSoloResultsScreen(ScoreInfo score) : base(score, true) { - Score.Beatmap.OnlineBeatmapID = 0; - Score.Beatmap.Status = BeatmapSetOnlineStatus.Pending; + Score.BeatmapInfo.OnlineBeatmapID = 0; + Score.BeatmapInfo.Status = BeatmapSetOnlineStatus.Pending; } protected override void LoadComplete() diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs index 95cf6a9903..13b769c80a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapLeaderboard.cs @@ -197,7 +197,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmapInfo, + BeatmapInfo = beatmapInfo, User = new User { Id = 6602580, @@ -216,7 +216,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmapInfo, + BeatmapInfo = beatmapInfo, User = new User { Id = 4608074, @@ -235,7 +235,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmapInfo, + BeatmapInfo = beatmapInfo, User = new User { Id = 1014222, @@ -254,7 +254,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmapInfo, + BeatmapInfo = beatmapInfo, User = new User { Id = 1541390, @@ -273,7 +273,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmapInfo, + BeatmapInfo = beatmapInfo, User = new User { Id = 2243452, @@ -292,7 +292,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmapInfo, + BeatmapInfo = beatmapInfo, User = new User { Id = 2705430, @@ -311,7 +311,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmapInfo, + BeatmapInfo = beatmapInfo, User = new User { Id = 7151382, @@ -330,7 +330,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmapInfo, + BeatmapInfo = beatmapInfo, User = new User { Id = 2051389, @@ -349,7 +349,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmapInfo, + BeatmapInfo = beatmapInfo, User = new User { Id = 6169483, @@ -368,7 +368,7 @@ namespace osu.Game.Tests.Visual.SongSelect MaxCombo = 244, TotalScore = 1707827, //Mods = new Mod[] { new OsuModHidden(), new OsuModHardRock(), }, - Beatmap = beatmapInfo, + BeatmapInfo = beatmapInfo, User = new User { Id = 6702666, diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index f9e81d3da6..78040b3d6a 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -805,7 +805,7 @@ namespace osu.Game.Tests.Visual.SongSelect songSelect.PresentScore(new ScoreInfo { User = new User { Username = "woo" }, - Beatmap = getPresentBeatmap(), + BeatmapInfo = getPresentBeatmap(), Ruleset = getPresentBeatmap().Ruleset }); }); @@ -837,7 +837,7 @@ namespace osu.Game.Tests.Visual.SongSelect songSelect.PresentScore(new ScoreInfo { User = new User { Username = "woo" }, - Beatmap = getPresentBeatmap(), + BeatmapInfo = getPresentBeatmap(), Ruleset = getPresentBeatmap().Ruleset }); }); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index f58dbef145..c237fcaebf 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -92,7 +92,7 @@ namespace osu.Game.Tests.Visual.UserInterface var score = new ScoreInfo { OnlineScoreID = i, - Beatmap = beatmapInfo, + BeatmapInfo = beatmapInfo, BeatmapInfoID = beatmapInfo.ID, Accuracy = RNG.NextDouble(), TotalScore = RNG.Next(1, 1000000), diff --git a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs index 18a0db3928..aaf2dccc82 100644 --- a/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs +++ b/osu.Game/Online/API/Requests/Responses/APILegacyScoreInfo.cs @@ -37,7 +37,7 @@ namespace osu.Game.Online.API.Requests.Responses OnlineScoreID = OnlineScoreID, Date = Date, PP = PP, - Beatmap = BeatmapInfo, + BeatmapInfo = BeatmapInfo, RulesetID = OnlineRulesetID, Hash = Replay ? "online" : string.Empty, // todo: temporary? Rank = Rank, diff --git a/osu.Game/Online/Rooms/MultiplayerScore.cs b/osu.Game/Online/Rooms/MultiplayerScore.cs index 30c1d2f826..7ec34e70d5 100644 --- a/osu.Game/Online/Rooms/MultiplayerScore.cs +++ b/osu.Game/Online/Rooms/MultiplayerScore.cs @@ -70,7 +70,7 @@ namespace osu.Game.Online.Rooms OnlineScoreID = ID, TotalScore = TotalScore, MaxCombo = MaxCombo, - Beatmap = playlistItem.Beatmap.Value, + BeatmapInfo = playlistItem.Beatmap.Value, BeatmapInfoID = playlistItem.BeatmapID, Ruleset = playlistItem.Ruleset.Value, RulesetID = playlistItem.RulesetID, diff --git a/osu.Game/Online/Spectator/SpectatorClient.cs b/osu.Game/Online/Spectator/SpectatorClient.cs index d55ad45ff5..b597b2f214 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.Beatmap.OnlineBeatmapID; + currentState.BeatmapID = score.ScoreInfo.BeatmapInfo.OnlineBeatmapID; 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 99925bb1fb..9dd879fd7e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -482,7 +482,7 @@ namespace osu.Game return; } - var databasedBeatmap = BeatmapManager.QueryBeatmap(b => b.ID == databasedScoreInfo.Beatmap.ID); + var databasedBeatmap = BeatmapManager.QueryBeatmap(b => b.ID == databasedScoreInfo.BeatmapInfo.ID); if (databasedBeatmap == null) { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 02de92e805..aec06e18f6 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -250,7 +250,7 @@ namespace osu.Game List getBeatmapScores(BeatmapSetInfo set) { var beatmapIds = BeatmapManager.QueryBeatmaps(b => b.BeatmapSetInfoID == set.ID).Select(b => b.ID).ToList(); - return ScoreManager.QueryScores(s => beatmapIds.Contains(s.Beatmap.ID)).ToList(); + return ScoreManager.QueryScores(s => beatmapIds.Contains(s.BeatmapInfo.ID)).ToList(); } BeatmapManager.ItemRemoved.BindValueChanged(i => diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs index 8fe1d35b62..018faf2011 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoreTable.cs @@ -172,7 +172,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores { Text = score.MaxCombo.ToLocalisableString(@"0\x"), Font = OsuFont.GetFont(size: text_size), - Colour = score.MaxCombo == score.Beatmap?.MaxCombo ? highAccuracyColour : Color4.White + Colour = score.MaxCombo == score.BeatmapInfo?.MaxCombo ? highAccuracyColour : Color4.White } }; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs index fb1769fbe1..82657afc86 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/ScoresContainer.cs @@ -74,7 +74,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores var topScore = ordered.Result.First(); - scoreTable.DisplayScores(ordered.Result, topScore.Beatmap?.Status.GrantsPerformancePoints() == true); + scoreTable.DisplayScores(ordered.Result, topScore.BeatmapInfo?.Status.GrantsPerformancePoints() == true); scoreTable.Show(); var userScore = value.UserScore; diff --git a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs index 883e83ce6e..630aa8fe53 100644 --- a/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs +++ b/osu.Game/Overlays/BeatmapSet/Scores/TopScoreStatisticsSection.cs @@ -115,7 +115,7 @@ namespace osu.Game.Overlays.BeatmapSet.Scores accuracyColumn.Text = value.DisplayAccuracy; maxComboColumn.Text = value.MaxCombo.ToLocalisableString(@"0\x"); - ppColumn.Alpha = value.Beatmap?.Status.GrantsPerformancePoints() == true ? 1 : 0; + ppColumn.Alpha = value.BeatmapInfo?.Status.GrantsPerformancePoints() == true ? 1 : 0; ppColumn.Text = value.PP?.ToLocalisableString(@"N0"); statisticsColumns.ChildrenEnumerable = value.GetStatisticsForDisplay().Select(createStatisticsColumn); diff --git a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs index c221f070df..3561e9700e 100644 --- a/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs +++ b/osu.Game/Overlays/Profile/Sections/Ranks/DrawableProfileScore.cs @@ -78,7 +78,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks Spacing = new Vector2(0, 2), Children = new Drawable[] { - new ScoreBeatmapMetadataContainer(Score.Beatmap), + new ScoreBeatmapMetadataContainer(Score.BeatmapInfo), new FillFlowContainer { AutoSizeAxes = Axes.Both, @@ -88,7 +88,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks { new OsuSpriteText { - Text = $"{Score.Beatmap.Version}", + Text = $"{Score.BeatmapInfo.Version}", Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), Colour = colours.Yellow }, diff --git a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs index 2e1a29372d..a1658b4cf3 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreDecoder.cs @@ -70,7 +70,7 @@ namespace osu.Game.Scoring.Legacy scoreInfo.Mods = scoreInfo.Mods.Append(currentRuleset.CreateMod()).ToArray(); currentBeatmap = workingBeatmap.GetPlayableBeatmap(currentRuleset.RulesetInfo, scoreInfo.Mods); - scoreInfo.Beatmap = currentBeatmap.BeatmapInfo; + scoreInfo.BeatmapInfo = currentBeatmap.BeatmapInfo; /* score.HpGraphString = */ sr.ReadString(); @@ -119,7 +119,7 @@ namespace osu.Game.Scoring.Legacy // before returning for database import, we must restore the database-sourced BeatmapInfo. // if not, the clone operation in GetPlayableBeatmap will cause a dereference and subsequent database exception. - score.ScoreInfo.Beatmap = workingBeatmap.BeatmapInfo; + score.ScoreInfo.BeatmapInfo = workingBeatmap.BeatmapInfo; return score; } diff --git a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs index 288552879c..58e4192f77 100644 --- a/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs +++ b/osu.Game/Scoring/Legacy/LegacyScoreEncoder.cs @@ -34,7 +34,7 @@ namespace osu.Game.Scoring.Legacy this.score = score; this.beatmap = beatmap; - if (score.ScoreInfo.Beatmap.RulesetID < 0 || score.ScoreInfo.Beatmap.RulesetID > 3) + if (score.ScoreInfo.BeatmapInfo.RulesetID < 0 || score.ScoreInfo.BeatmapInfo.RulesetID > 3) throw new ArgumentException("Only scores in the osu, taiko, catch, or mania rulesets can be encoded to the legacy score format.", nameof(score)); } @@ -44,7 +44,7 @@ namespace osu.Game.Scoring.Legacy { sw.Write((byte)(score.ScoreInfo.Ruleset.ID ?? 0)); sw.Write(LATEST_VERSION); - sw.Write(score.ScoreInfo.Beatmap.MD5Hash); + sw.Write(score.ScoreInfo.BeatmapInfo.MD5Hash); sw.Write(score.ScoreInfo.UserString); sw.Write($"lazer-{score.ScoreInfo.UserString}-{score.ScoreInfo.Date}".ComputeMD5Hash()); sw.Write((ushort)(score.ScoreInfo.GetCount300() ?? 0)); diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 890ead40e3..5cf22f7945 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -150,7 +150,8 @@ namespace osu.Game.Scoring public int BeatmapInfoID { get; set; } [JsonIgnore] - public virtual BeatmapInfo Beatmap { get; set; } + [Column("Beatmap")] + public virtual BeatmapInfo BeatmapInfo { get; set; } [JsonIgnore] public long? OnlineScoreID { get; set; } @@ -252,7 +253,7 @@ namespace osu.Game.Scoring return clone; } - public override string ToString() => $"{User} playing {Beatmap}"; + public override string ToString() => $"{User} playing {BeatmapInfo}"; public bool Equals(ScoreInfo other) { diff --git a/osu.Game/Scoring/ScoreManager.cs b/osu.Game/Scoring/ScoreManager.cs index d83b4e3f1d..27d087dc30 100644 --- a/osu.Game/Scoring/ScoreManager.cs +++ b/osu.Game/Scoring/ScoreManager.cs @@ -67,7 +67,7 @@ namespace osu.Game.Scoring // Compute difficulties asynchronously first to prevent blocking via the GetTotalScore() call below. foreach (var s in scores) { - await difficultyCache.GetDifficultyAsync(s.Beatmap, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false); + await difficultyCache.GetDifficultyAsync(s.BeatmapInfo, s.Ruleset, s.Mods, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); } } @@ -126,7 +126,7 @@ namespace osu.Game.Scoring /// The total score. public async Task GetTotalScoreAsync([NotNull] ScoreInfo score, ScoringMode mode = ScoringMode.Standardised, CancellationToken cancellationToken = default) { - if (score.Beatmap == null) + if (score.BeatmapInfo == null) return score.TotalScore; int beatmapMaxCombo; @@ -147,18 +147,18 @@ namespace osu.Game.Scoring // This score is guaranteed to be an osu!stable score. // The combo must be determined through either the beatmap's max combo value or the difficulty calculator, as lazer's scoring has changed and the score statistics cannot be used. - if (score.Beatmap.MaxCombo != null) - beatmapMaxCombo = score.Beatmap.MaxCombo.Value; + if (score.BeatmapInfo.MaxCombo != null) + beatmapMaxCombo = score.BeatmapInfo.MaxCombo.Value; else { - if (score.Beatmap.ID == 0 || difficulties == null) + if (score.BeatmapInfo.ID == 0 || difficulties == null) { // We don't have enough information (max combo) to compute the score, so use the provided score. return score.TotalScore; } // We can compute the max combo locally after the async beatmap difficulty computation. - var difficulty = await difficulties().GetDifficultyAsync(score.Beatmap, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false); + var difficulty = await difficulties().GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, cancellationToken).ConfigureAwait(false); beatmapMaxCombo = difficulty.MaxCombo; } } diff --git a/osu.Game/Scoring/ScorePerformanceCache.cs b/osu.Game/Scoring/ScorePerformanceCache.cs index bb15983de3..82685e9a04 100644 --- a/osu.Game/Scoring/ScorePerformanceCache.cs +++ b/osu.Game/Scoring/ScorePerformanceCache.cs @@ -34,7 +34,7 @@ namespace osu.Game.Scoring { var score = lookup.ScoreInfo; - var attributes = await difficultyCache.GetDifficultyAsync(score.Beatmap, score.Ruleset, score.Mods, token).ConfigureAwait(false); + var attributes = await difficultyCache.GetDifficultyAsync(score.BeatmapInfo, score.Ruleset, score.Mods, token).ConfigureAwait(false); // Performance calculation requires the beatmap and ruleset to be locally available. If not, return a default value. if (attributes.Attributes == null) diff --git a/osu.Game/Scoring/ScoreStore.cs b/osu.Game/Scoring/ScoreStore.cs index f5c5cd5dad..fd1f5ae3ec 100644 --- a/osu.Game/Scoring/ScoreStore.cs +++ b/osu.Game/Scoring/ScoreStore.cs @@ -17,9 +17,9 @@ namespace osu.Game.Scoring protected override IQueryable AddIncludesForConsumption(IQueryable query) => base.AddIncludesForConsumption(query) - .Include(s => s.Beatmap) - .Include(s => s.Beatmap).ThenInclude(b => b.Metadata) - .Include(s => s.Beatmap).ThenInclude(b => b.BeatmapSet).ThenInclude(s => s.Metadata) + .Include(s => s.BeatmapInfo) + .Include(s => s.BeatmapInfo).ThenInclude(b => b.Metadata) + .Include(s => s.BeatmapInfo).ThenInclude(b => b.BeatmapSet).ThenInclude(s => s.Metadata) .Include(s => s.Ruleset); } } diff --git a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs index 95ccc08608..c3190cd845 100644 --- a/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs +++ b/osu.Game/Screens/OnlinePlay/Multiplayer/Spectate/PlayerArea.cs @@ -84,7 +84,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate Score = score; - gameplayContent.Child = new PlayerIsolationContainer(beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.Beatmap), Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) + gameplayContent.Child = new PlayerIsolationContainer(beatmapManager.GetWorkingBeatmap(Score.ScoreInfo.BeatmapInfo), Score.ScoreInfo.Ruleset, Score.ScoreInfo.Mods) { RelativeSizeAxes = Axes.Both, Child = stack = new OsuScreenStack() diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a05a8f5056..69a1c6c8ce 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -164,7 +164,7 @@ namespace osu.Game.Screens.Play Score = CreateScore(); // ensure the score is in a consistent state with the current player. - Score.ScoreInfo.Beatmap = Beatmap.Value.BeatmapInfo; + Score.ScoreInfo.BeatmapInfo = Beatmap.Value.BeatmapInfo; Score.ScoreInfo.Ruleset = ruleset.RulesetInfo; Score.ScoreInfo.Mods = Mods.Value.ToArray(); diff --git a/osu.Game/Screens/Play/SoloPlayer.cs b/osu.Game/Screens/Play/SoloPlayer.cs index d90e8e0168..675cb71311 100644 --- a/osu.Game/Screens/Play/SoloPlayer.cs +++ b/osu.Game/Screens/Play/SoloPlayer.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play protected override APIRequest CreateSubmissionRequest(Score score, long token) { - var beatmap = score.ScoreInfo.Beatmap; + var beatmap = score.ScoreInfo.BeatmapInfo; Debug.Assert(beatmap.OnlineBeatmapID != null); diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index bcb5e7999f..262d1e8293 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Ranking.Expanded [BackgroundDependencyLoader] private void load(BeatmapDifficultyCache beatmapDifficultyCache) { - var beatmap = score.Beatmap; + var beatmap = score.BeatmapInfo; var metadata = beatmap.BeatmapSet?.Metadata ?? beatmap.Metadata; var creator = metadata.Author?.Username; diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 9bc696948f..5e582a8dcb 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -27,10 +27,10 @@ namespace osu.Game.Screens.Ranking protected override APIRequest FetchScores(Action> scoresCallback) { - if (Score.Beatmap.OnlineBeatmapID == null || Score.Beatmap.Status <= BeatmapSetOnlineStatus.Pending) + if (Score.BeatmapInfo.OnlineBeatmapID == null || Score.BeatmapInfo.Status <= BeatmapSetOnlineStatus.Pending) return null; - getScoreRequest = new GetScoresRequest(Score.Beatmap, Score.Ruleset); + getScoreRequest = new GetScoresRequest(Score.BeatmapInfo, Score.Ruleset); getScoreRequest.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets))); return getScoreRequest; } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index f1ae1f9d73..bc62bcf2b2 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -102,7 +102,7 @@ namespace osu.Game.Screens.Ranking.Statistics // Todo: The placement of this is temporary. Eventually we'll both generate the playable beatmap _and_ run through it in a background task to generate the hit events. Task.Run(() => { - playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.Beatmap).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods ?? Array.Empty()); + playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.BeatmapInfo).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods ?? Array.Empty()); }, loadCancellation.Token).ContinueWith(t => Schedule(() => { var rows = new FillFlowContainer diff --git a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs index 8c33b1ea0b..4970db8955 100644 --- a/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs +++ b/osu.Game/Screens/Select/BeatmapClearScoresDialog.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Select Text = @"Yes. Please.", Action = () => { - Task.Run(() => scoreManager.Delete(scoreManager.QueryScores(s => !s.DeletePending && s.Beatmap.ID == beatmapInfo.ID).ToList())) + Task.Run(() => scoreManager.Delete(scoreManager.QueryScores(s => !s.DeletePending && s.BeatmapInfo.ID == beatmapInfo.ID).ToList())) .ContinueWith(_ => onCompletion); } }, diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 2fdb41a1a1..07300635aa 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -141,7 +141,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (Scope == BeatmapLeaderboardScope.Local) { var scores = scoreManager - .QueryScores(s => !s.DeletePending && s.Beatmap.ID == BeatmapInfo.ID && s.Ruleset.ID == ruleset.Value.ID); + .QueryScores(s => !s.DeletePending && s.BeatmapInfo.ID == BeatmapInfo.ID && s.Ruleset.ID == ruleset.Value.ID); if (filterMods && !mods.Value.Any()) { diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 418cf23ce7..94aa165785 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Select } protected void PresentScore(ScoreInfo score) => - FinaliseSelection(score.Beatmap, score.Ruleset, () => this.Push(new SoloResultsScreen(score, false))); + FinaliseSelection(score.BeatmapInfo, score.Ruleset, () => this.Push(new SoloResultsScreen(score, false))); protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); diff --git a/osu.Game/Screens/Spectate/SpectatorScreen.cs b/osu.Game/Screens/Spectate/SpectatorScreen.cs index 71bcc336f3..7861d4cb72 100644 --- a/osu.Game/Screens/Spectate/SpectatorScreen.cs +++ b/osu.Game/Screens/Spectate/SpectatorScreen.cs @@ -165,7 +165,7 @@ namespace osu.Game.Screens.Spectate { ScoreInfo = new ScoreInfo { - Beatmap = resolvedBeatmap, + BeatmapInfo = resolvedBeatmap, User = user, Mods = spectatorState.Mods.Select(m => m.ToMod(resolvedRuleset)).ToArray(), Ruleset = resolvedRuleset.RulesetInfo, diff --git a/osu.Game/Tests/TestScoreInfo.cs b/osu.Game/Tests/TestScoreInfo.cs index 5ce6aae647..719d31b092 100644 --- a/osu.Game/Tests/TestScoreInfo.cs +++ b/osu.Game/Tests/TestScoreInfo.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }; - Beatmap = new TestBeatmap(ruleset).BeatmapInfo; + BeatmapInfo = new TestBeatmap(ruleset).BeatmapInfo; Ruleset = ruleset; RulesetID = ruleset.ID ?? 0; From 2e3450b3f58e4290289194c0fd58ecf28fe3931c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 4 Oct 2021 20:20:24 +0900 Subject: [PATCH 089/183] Make Mods readonly --- osu.Game/Screens/Play/GameplayState.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index ba08c946d2..d4da17ce37 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.Play /// /// The mods applied to the gameplay. /// - public IReadOnlyList Mods; + public readonly IReadOnlyList Mods; /// /// A bindable tracking the last judgement result applied to any hit object. From 5aae673240e2263cb15d2b9e7c1ad055d17ecaf9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 4 Oct 2021 20:33:54 +0900 Subject: [PATCH 090/183] Use GameplayState --- osu.Game/Screens/Play/GameplayState.cs | 6 +++++ .../HUD/DefaultPerformancePointsCounter.cs | 22 +++++++++---------- osu.Game/Screens/Play/Player.cs | 2 +- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index d4da17ce37..ef4967b34d 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -7,6 +7,7 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; +using osu.Game.Scoring; #nullable enable @@ -32,6 +33,11 @@ namespace osu.Game.Screens.Play /// public readonly IReadOnlyList Mods; + /// + /// The gameplay score. + /// + public Score? Score { get; set; } = null; + /// /// A bindable tracking the last judgement result applied to any hit object. /// diff --git a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs index d93d626c72..3c31848c32 100644 --- a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs @@ -36,9 +36,9 @@ namespace osu.Game.Screens.Play.HUD [Resolved(CanBeNull = true)] private ScoreProcessor scoreProcessor { get; set; } + [Resolved] [CanBeNull] - [Resolved(CanBeNull = true)] - private Player player { get; set; } + private GameplayState gameplayState { get; set; } private TimedDifficultyAttributes[] timedAttributes; private Ruleset gameplayRuleset; @@ -53,10 +53,10 @@ namespace osu.Game.Screens.Play.HUD { Colour = colours.BlueLighter; - if (player != null) + if (gameplayState != null) { - gameplayRuleset = player.GameplayRuleset; - timedAttributes = gameplayRuleset.CreateDifficultyCalculator(new GameplayWorkingBeatmap(player.GameplayBeatmap)).CalculateTimed(player.Mods.Value.ToArray()).ToArray(); + gameplayRuleset = gameplayState.Ruleset; + timedAttributes = gameplayRuleset.CreateDifficultyCalculator(new GameplayWorkingBeatmap(gameplayState.Beatmap)).CalculateTimed(gameplayState.Mods.ToArray()).ToArray(); } } @@ -70,7 +70,7 @@ namespace osu.Game.Screens.Play.HUD private void onNewJudgement(JudgementResult judgement) { - if (player == null || timedAttributes.Length == 0) + if (gameplayState?.Score == null || timedAttributes.Length == 0) return; var attribIndex = Array.BinarySearch(timedAttributes, 0, timedAttributes.Length, new TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null)); @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Play.HUD attribIndex = ~attribIndex - 1; attribIndex = Math.Clamp(attribIndex, 0, timedAttributes.Length - 1); - var ppProcessor = gameplayRuleset.CreatePerformanceCalculator(timedAttributes[attribIndex].Attributes, player.Score.ScoreInfo); + var ppProcessor = gameplayRuleset.CreatePerformanceCalculator(timedAttributes[attribIndex].Attributes, gameplayState.Score.ScoreInfo); Current.Value = (int)(ppProcessor?.Calculate() ?? 0); } @@ -134,18 +134,18 @@ namespace osu.Game.Screens.Play.HUD private class GameplayWorkingBeatmap : WorkingBeatmap { - private readonly GameplayBeatmap gameplayBeatmap; + private readonly IBeatmap gameplayBeatmap; - public GameplayWorkingBeatmap(GameplayBeatmap gameplayBeatmap) + public GameplayWorkingBeatmap(IBeatmap gameplayBeatmap) : base(gameplayBeatmap.BeatmapInfo, null) { this.gameplayBeatmap = gameplayBeatmap; } public override IBeatmap GetPlayableBeatmap(RulesetInfo ruleset, IReadOnlyList mods = null, TimeSpan? timeout = null) - => gameplayBeatmap.PlayableBeatmap; + => gameplayBeatmap; - protected override IBeatmap GetBeatmap() => gameplayBeatmap.PlayableBeatmap; + protected override IBeatmap GetBeatmap() => gameplayBeatmap; protected override Texture GetBackground() => throw new NotImplementedException(); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 15cf8388f0..00907584e1 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -127,7 +127,7 @@ namespace osu.Game.Screens.Play [Cached] [Cached(Type = typeof(IBindable>))] - public new readonly Bindable> Mods = new Bindable>(Array.Empty()); + protected new readonly Bindable> Mods = new Bindable>(Array.Empty()); /// /// Whether failing should be allowed. From 221cc1747c1ec62e05cf6288cda88f876f31f58d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 4 Oct 2021 20:34:08 +0900 Subject: [PATCH 091/183] Drop "default" prefix --- ...erformancePointsCounter.cs => PerformancePointsCounter.cs} | 4 ++-- osu.Game/Skinning/DefaultSkin.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Screens/Play/HUD/{DefaultPerformancePointsCounter.cs => PerformancePointsCounter.cs} (97%) diff --git a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs similarity index 97% rename from osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs rename to osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 3c31848c32..ad0a928d1c 100644 --- a/osu.Game/Screens/Play/HUD/DefaultPerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -28,7 +28,7 @@ using osuTK; namespace osu.Game.Screens.Play.HUD { - public class DefaultPerformancePointsCounter : RollingCounter, ISkinnableDrawable + public class PerformancePointsCounter : RollingCounter, ISkinnableDrawable { public bool UsesFixedAnchor { get; set; } @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Play.HUD private TimedDifficultyAttributes[] timedAttributes; private Ruleset gameplayRuleset; - public DefaultPerformancePointsCounter() + public PerformancePointsCounter() { Current.Value = DisplayedCount = 0; } diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 41b7875cd1..8c1e5313d5 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -68,7 +68,7 @@ namespace osu.Game.Skinning var score = container.OfType().FirstOrDefault(); var accuracy = container.OfType().FirstOrDefault(); var combo = container.OfType().FirstOrDefault(); - var ppCounter = container.OfType().FirstOrDefault(); + var ppCounter = container.OfType().FirstOrDefault(); if (score != null) { @@ -131,7 +131,7 @@ namespace osu.Game.Skinning new SongProgress(), new BarHitErrorMeter(), new BarHitErrorMeter(), - new DefaultPerformancePointsCounter() + new PerformancePointsCounter() } }; From fb63e5ed87fab819899e8a137d0bf7b67ce720c8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 4 Oct 2021 20:35:26 +0900 Subject: [PATCH 092/183] Add todo --- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index ad0a928d1c..740f286246 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -132,6 +132,7 @@ namespace osu.Game.Screens.Play.HUD } } + // Todo: This class shouldn't exist, but requires breaking changes to allow DifficultyCalculator to receive an IBeatmap. private class GameplayWorkingBeatmap : WorkingBeatmap { private readonly IBeatmap gameplayBeatmap; From 1837e1bf3ccb4e471979e1034c3ee92362d49739 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 4 Oct 2021 20:35:53 +0900 Subject: [PATCH 093/183] Share rounding with PerformanceStatistic --- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 740f286246..265c1a9d01 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -79,7 +79,7 @@ namespace osu.Game.Screens.Play.HUD attribIndex = Math.Clamp(attribIndex, 0, timedAttributes.Length - 1); var ppProcessor = gameplayRuleset.CreatePerformanceCalculator(timedAttributes[attribIndex].Attributes, gameplayState.Score.ScoreInfo); - Current.Value = (int)(ppProcessor?.Calculate() ?? 0); + Current.Value = (int)Math.Round(ppProcessor?.Calculate() ?? 0, MidpointRounding.AwayFromZero); } protected override LocalisableString FormatCount(int count) => count.ToString(@"D"); From 0b0316e27ee68696d090cb4410f13e9aa0117045 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 4 Oct 2021 20:59:31 +0900 Subject: [PATCH 094/183] Fix missing CanBeNull --- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 265c1a9d01..154b6accb8 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Play.HUD [Resolved(CanBeNull = true)] private ScoreProcessor scoreProcessor { get; set; } - [Resolved] + [Resolved(CanBeNull = true)] [CanBeNull] private GameplayState gameplayState { get; set; } From d1e7191f94fa1de0d3e7911951a5cb9179125a17 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 4 Oct 2021 20:59:51 +0900 Subject: [PATCH 095/183] Pass score into GameplayState --- osu.Game/Screens/Play/Player.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 00907584e1..69093db883 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -162,6 +162,7 @@ namespace osu.Game.Screens.Play return; Score = CreateScore(); + GameplayState.Score = Score; // ensure the score is in a consistent state with the current player. Score.ScoreInfo.BeatmapInfo = Beatmap.Value.BeatmapInfo; From d120678e306e9dc85cfb0e8d2fd82efd96c3a7cb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 4 Oct 2021 21:13:14 +0900 Subject: [PATCH 096/183] Fix redundant default value --- osu.Game/Screens/Play/GameplayState.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index ef4967b34d..9c83eddb45 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -36,7 +36,7 @@ namespace osu.Game.Screens.Play /// /// The gameplay score. /// - public Score? Score { get; set; } = null; + public Score? Score { get; set; } /// /// A bindable tracking the last judgement result applied to any hit object. From 15ec315ec62a9b32e9d6a81fcd9a2ddffbcabf9c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 01:14:58 +0900 Subject: [PATCH 097/183] Fix test runs hanging due to missing `ConfigureAwait` --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index d1c23f1442..4cc71717ff 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -909,7 +909,7 @@ namespace osu.Game.Tests.Beatmaps.IO var manager = osu.Dependencies.Get(); - var importedSet = await manager.Import(new ImportTask(temp)); + var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false); ensureLoaded(osu); @@ -924,7 +924,7 @@ namespace osu.Game.Tests.Beatmaps.IO var manager = osu.Dependencies.Get(); - var importedSet = await manager.Import(new ImportTask(temp)); + var importedSet = await manager.Import(new ImportTask(temp)).ConfigureAwait(false); ensureLoaded(osu); From 86df4919f7ab8207e62d9b49cdcd06bbb0c6e6c5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Oct 2021 11:06:24 +0900 Subject: [PATCH 098/183] Fix skin fallbacks test --- osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs index 7d4673c901..7398527f57 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs @@ -42,6 +42,7 @@ namespace osu.Game.Tests.Visual.Gameplay public void TestEmptyLegacyBeatmapSkinFallsBack() { CreateSkinTest(SkinInfo.Default, () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null)); + AddUntilStep("wait for hud load", () => Player.ChildrenOfType().All(c => c.ComponentsLoaded)); AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value)); } From 593da79bbc96bd18fe6bf0eb35c5758fb1c16153 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Oct 2021 11:26:13 +0900 Subject: [PATCH 099/183] Further asyncify load process --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 9 +++++++++ .../Screens/Play/HUD/PerformancePointsCounter.cs | 15 ++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index c46ab93ece..e6c287112f 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -17,6 +17,7 @@ using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Rulesets; +using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.UI; @@ -147,6 +148,14 @@ namespace osu.Game.Beatmaps }, token, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); } + public Task GetTimedDifficultyAttributesAsync(WorkingBeatmap beatmap, Ruleset ruleset, Mod[] mods, CancellationToken token = default) + { + return Task.Factory.StartNew(() => ruleset.CreateDifficultyCalculator(beatmap).CalculateTimed(mods).ToArray(), + token, + TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, + updateScheduler); + } + /// /// Retrieves the that describes a star rating. /// diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 154b6accb8..b02ab440a0 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -3,8 +3,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio.Track; @@ -40,7 +43,10 @@ namespace osu.Game.Screens.Play.HUD [CanBeNull] private GameplayState gameplayState { get; set; } + [CanBeNull] private TimedDifficultyAttributes[] timedAttributes; + + [CanBeNull] private Ruleset gameplayRuleset; public PerformancePointsCounter() @@ -49,14 +55,15 @@ namespace osu.Game.Screens.Play.HUD } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache, CancellationToken cancellationToken) { Colour = colours.BlueLighter; if (gameplayState != null) { gameplayRuleset = gameplayState.Ruleset; - timedAttributes = gameplayRuleset.CreateDifficultyCalculator(new GameplayWorkingBeatmap(gameplayState.Beatmap)).CalculateTimed(gameplayState.Mods.ToArray()).ToArray(); + difficultyCache.GetTimedDifficultyAttributesAsync(new GameplayWorkingBeatmap(gameplayState.Beatmap), gameplayRuleset, gameplayState.Mods.ToArray(), cancellationToken) + .ContinueWith(r => Schedule(() => timedAttributes = r.Result), TaskContinuationOptions.OnlyOnRanToCompletion); } } @@ -70,9 +77,11 @@ namespace osu.Game.Screens.Play.HUD private void onNewJudgement(JudgementResult judgement) { - if (gameplayState?.Score == null || timedAttributes.Length == 0) + if (gameplayState?.Score == null || timedAttributes == null || timedAttributes.Length == 0) return; + Debug.Assert(gameplayRuleset != null); + var attribIndex = Array.BinarySearch(timedAttributes, 0, timedAttributes.Length, new TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null)); if (attribIndex < 0) attribIndex = ~attribIndex - 1; From 5624dd9af67376b73038e4f75570927f161ff602 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Oct 2021 12:07:41 +0900 Subject: [PATCH 100/183] Fix incorrect CancellationToken usage Apparently I wrote the BDL system and don't know how this works. I believe you need `CancellationToken?` or CanBeNull=true, however that doesn't actually play well when actually using the token in code... --- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index b02ab440a0..13b94e6cd6 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -49,20 +49,22 @@ namespace osu.Game.Screens.Play.HUD [CanBeNull] private Ruleset gameplayRuleset; + private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); + public PerformancePointsCounter() { Current.Value = DisplayedCount = 0; } [BackgroundDependencyLoader] - private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache, CancellationToken cancellationToken) + private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache) { Colour = colours.BlueLighter; if (gameplayState != null) { gameplayRuleset = gameplayState.Ruleset; - difficultyCache.GetTimedDifficultyAttributesAsync(new GameplayWorkingBeatmap(gameplayState.Beatmap), gameplayRuleset, gameplayState.Mods.ToArray(), cancellationToken) + difficultyCache.GetTimedDifficultyAttributesAsync(new GameplayWorkingBeatmap(gameplayState.Beatmap), gameplayRuleset, gameplayState.Mods.ToArray(), loadCancellationSource.Token) .ContinueWith(r => Schedule(() => timedAttributes = r.Result), TaskContinuationOptions.OnlyOnRanToCompletion); } } @@ -101,6 +103,8 @@ namespace osu.Game.Screens.Play.HUD if (scoreProcessor != null) scoreProcessor.NewJudgement -= onNewJudgement; + + loadCancellationSource?.Cancel(); } private class TextComponent : CompositeDrawable, IHasText From b41fa41c8539d6370fb4d3efd8539ec664176ff2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 14:28:56 +0900 Subject: [PATCH 101/183] Rename `APIRequest.Result` to `Response` --- .../Online/TestDummyAPIRequestHandling.cs | 2 +- .../TestSceneUpdateableBeatmapBackgroundSprite.cs | 4 ++-- osu.Game.Tournament/TournamentGameBase.cs | 4 ++-- osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs | 2 +- osu.Game/Database/UserLookupCache.cs | 2 +- osu.Game/Online/API/APIRequest.cs | 15 ++++++++++----- osu.Game/Overlays/RankingsOverlay.cs | 6 +++--- .../Screens/OnlinePlay/Components/RoomManager.cs | 2 +- 8 files changed, 21 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs index aa29d76843..91c6b6c008 100644 --- a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs +++ b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs @@ -42,7 +42,7 @@ namespace osu.Game.Tests.Online AddAssert("response event fired", () => response != null); - AddAssert("request has response", () => request.Result == response); + AddAssert("request has response", () => request.Response == response); } [Test] diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs index 198cc70e01..74cd675a05 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneUpdateableBeatmapBackgroundSprite.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.UserInterface var req = new GetBeatmapSetRequest(1); api.Queue(req); - AddUntilStep("wait for api response", () => req.Result != null); + AddUntilStep("wait for api response", () => req.Response != null); TestUpdateableBeatmapBackgroundSprite background = null; @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.UserInterface Child = background = new TestUpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both, - Beatmap = { Value = new BeatmapInfo { BeatmapSet = req.Result?.ToBeatmapSet(rulesets) } } + Beatmap = { Value = new BeatmapInfo { BeatmapSet = req.Response?.ToBeatmapSet(rulesets) } } }; }); diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 2e4ed9d5b1..bdf7269c83 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -182,7 +182,7 @@ namespace osu.Game.Tournament { var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID }); API.Perform(req); - b.BeatmapInfo = req.Result?.ToBeatmapInfo(RulesetStore); + b.BeatmapInfo = req.Response?.ToBeatmapInfo(RulesetStore); addedInfo = true; } @@ -203,7 +203,7 @@ namespace osu.Game.Tournament { var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID }); req.Perform(API); - b.BeatmapInfo = req.Result?.ToBeatmapInfo(RulesetStore); + b.BeatmapInfo = req.Response?.ToBeatmapInfo(RulesetStore); addedInfo = true; } diff --git a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs index e1faf6005b..1fe120557d 100644 --- a/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapOnlineLookupQueue.cs @@ -78,7 +78,7 @@ namespace osu.Game.Beatmaps // intentionally blocking to limit web request concurrency api.Perform(req); - var res = req.Result; + var res = req.Response; if (res != null) { diff --git a/osu.Game/Database/UserLookupCache.cs b/osu.Game/Database/UserLookupCache.cs index 13c37ddfe9..ff81637efb 100644 --- a/osu.Game/Database/UserLookupCache.cs +++ b/osu.Game/Database/UserLookupCache.cs @@ -115,7 +115,7 @@ namespace osu.Game.Database createNewTask(); } - List foundUsers = request.Result?.Users; + List foundUsers = request.Response?.Users; if (foundUsers != null) { diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index cf17ed4b5d..d60c9cfe65 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.IO.Network; using osu.Framework.Logging; @@ -17,7 +18,11 @@ namespace osu.Game.Online.API { protected override WebRequest CreateWebRequest() => new OsuJsonWebRequest(Uri); - public T Result { get; private set; } + /// + /// The deserialised response object. May be null if the request or deserialisation failed. + /// + [CanBeNull] + public T Response { get; private set; } /// /// Invoked on successful completion of an API request. @@ -27,21 +32,21 @@ namespace osu.Game.Online.API protected APIRequest() { - base.Success += () => Success?.Invoke(Result); + base.Success += () => Success?.Invoke(Response); } protected override void PostProcess() { base.PostProcess(); - Result = ((OsuJsonWebRequest)WebRequest)?.ResponseObject; + Response = ((OsuJsonWebRequest)WebRequest)?.ResponseObject; } internal void TriggerSuccess(T result) { - if (Result != null) + if (Response != null) throw new InvalidOperationException("Attempted to trigger success more than once"); - Result = result; + Response = result; TriggerSuccess(); } diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index b8bdef925e..2263d54d7b 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -146,16 +146,16 @@ namespace osu.Game.Overlays switch (userRequest.Type) { case UserRankingsType.Performance: - return new PerformanceTable(1, userRequest.Result.Users); + return new PerformanceTable(1, userRequest.Response.Users); case UserRankingsType.Score: - return new ScoresTable(1, userRequest.Result.Users); + return new ScoresTable(1, userRequest.Response.Users); } return null; case GetCountryRankingsRequest countryRequest: - return new CountriesTable(1, countryRequest.Result.Countries); + return new CountriesTable(1, countryRequest.Response.Countries); } return null; diff --git a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs index a64d89b699..381849189d 100644 --- a/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs +++ b/osu.Game/Screens/OnlinePlay/Components/RoomManager.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.OnlinePlay.Components req.Failure += exception => { - onError?.Invoke(req.Result?.Error ?? exception.Message); + onError?.Invoke(req.Response?.Error ?? exception.Message); }; api.Queue(req); From d3b9660148d3ebc79660915c1db35b779b001402 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 14:41:14 +0900 Subject: [PATCH 102/183] Move common interface implementations to extension methods --- .../BeatmapMetadataRomanisationTest.cs | 4 +- osu.Game/Beatmaps/BeatmapInfo.cs | 7 +-- osu.Game/Beatmaps/BeatmapInfoExtensions.cs | 37 +++++++++++++++ osu.Game/Beatmaps/BeatmapMetadata.cs | 7 +-- .../Beatmaps/BeatmapMetadataInfoExtensions.cs | 46 +++++++++++++++++++ osu.Game/Beatmaps/IBeatmapInfo.cs | 30 ------------ osu.Game/Beatmaps/IBeatmapMetadataInfo.cs | 43 ----------------- osu.Game/Overlays/Music/PlaylistItem.cs | 2 +- .../OnlinePlay/DrawableRoomPlaylistItem.cs | 2 +- .../Lounge/Components/DrawableRoom.cs | 2 +- .../Select/Carousel/CarouselBeatmap.cs | 2 +- 11 files changed, 91 insertions(+), 91 deletions(-) create mode 100644 osu.Game/Beatmaps/BeatmapInfoExtensions.cs create mode 100644 osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs diff --git a/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs b/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs index dab4825919..9926acf772 100644 --- a/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs +++ b/osu.Game.Tests/Localisation/BeatmapMetadataRomanisationTest.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Localisation Title = "Romanised title", TitleUnicode = "Unicode Title" }; - var romanisableString = metadata.ToRomanisableString(); + var romanisableString = metadata.GetDisplayTitleRomanisable(); Assert.AreEqual(metadata.ToString(), romanisableString.Romanised); Assert.AreEqual($"{metadata.ArtistUnicode} - {metadata.TitleUnicode}", romanisableString.Original); @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Localisation Artist = "Romanised Artist", Title = "Romanised title" }; - var romanisableString = metadata.ToRomanisableString(); + var romanisableString = metadata.GetDisplayTitleRomanisable(); Assert.AreEqual(romanisableString.Romanised, romanisableString.Original); } diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index cd5f8fb9a1..ac5b5d7a8a 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -7,7 +7,6 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Newtonsoft.Json; -using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Rulesets; @@ -152,11 +151,7 @@ namespace osu.Game.Beatmaps [JsonIgnore] public DifficultyRating DifficultyRating => BeatmapDifficultyCache.GetDifficultyRating(StarDifficulty); - public IEnumerable SearchableTerms => ((IBeatmapInfo)this).SearchableTerms; - - public override string ToString() => ((IBeatmapInfo)this).DisplayTitle; - - public RomanisableString ToRomanisableString() => ((IBeatmapInfo)this).DisplayTitleRomanisable; + public override string ToString() => this.GetDisplayTitle(); public bool Equals(BeatmapInfo other) { diff --git a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs new file mode 100644 index 0000000000..deab8b915a --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapInfoExtensions.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.Linq; +using osu.Framework.Localisation; + +namespace osu.Game.Beatmaps +{ + public static class BeatmapInfoExtensions + { + /// + /// A user-presentable display title representing this beatmap. + /// + public static string GetDisplayTitle(this IBeatmapInfo beatmapInfo) => $"{getClosestMetadata(beatmapInfo)} {getVersionString(beatmapInfo)}".Trim(); + + /// + /// A user-presentable display title representing this beatmap, with localisation handling for potentially romanisable fields. + /// + public static RomanisableString GetDisplayTitleRomanisable(this IBeatmapInfo beatmapInfo) + { + var metadata = getClosestMetadata(beatmapInfo).GetDisplayTitleRomanisable(); + var versionString = getVersionString(beatmapInfo); + + return new RomanisableString($"{metadata.GetPreferred(true)} {versionString}".Trim(), $"{metadata.GetPreferred(false)} {versionString}".Trim()); + } + + public static string[] GetSearchableTerms(this IBeatmapInfo beatmapInfo) => new[] + { + beatmapInfo.DifficultyName + }.Concat(getClosestMetadata(beatmapInfo).GetSearchableTerms()).Where(s => !string.IsNullOrEmpty(s)).ToArray(); + + private static string getVersionString(IBeatmapInfo beatmapInfo) => string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? string.Empty : $"[{beatmapInfo.DifficultyName}]"; + + // temporary helper methods until we figure which metadata should be where. + private static IBeatmapMetadataInfo getClosestMetadata(IBeatmapInfo beatmapInfo) => (beatmapInfo.Metadata ?? beatmapInfo.BeatmapSet.Metadata)!; + } +} diff --git a/osu.Game/Beatmaps/BeatmapMetadata.cs b/osu.Game/Beatmaps/BeatmapMetadata.cs index 3da80580cb..711533e118 100644 --- a/osu.Game/Beatmaps/BeatmapMetadata.cs +++ b/osu.Game/Beatmaps/BeatmapMetadata.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using Newtonsoft.Json; -using osu.Framework.Localisation; using osu.Framework.Testing; using osu.Game.Database; using osu.Game.Users; @@ -87,11 +86,7 @@ namespace osu.Game.Beatmaps public bool Equals(BeatmapMetadata other) => ((IBeatmapMetadataInfo)this).Equals(other); - public override string ToString() => ((IBeatmapMetadataInfo)this).DisplayTitle; - - public RomanisableString ToRomanisableString() => ((IBeatmapMetadataInfo)this).DisplayTitleRomanisable; - - public IEnumerable SearchableTerms => ((IBeatmapMetadataInfo)this).SearchableTerms; + public override string ToString() => this.GetDisplayTitle(); string IBeatmapMetadataInfo.Author => AuthorString; } diff --git a/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs new file mode 100644 index 0000000000..ee946eeeec --- /dev/null +++ b/osu.Game/Beatmaps/BeatmapMetadataInfoExtensions.cs @@ -0,0 +1,46 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Localisation; + +namespace osu.Game.Beatmaps +{ + public static class BeatmapMetadataInfoExtensions + { + /// + /// An array of all searchable terms provided in contained metadata. + /// + public static string[] GetSearchableTerms(this IBeatmapMetadataInfo metadataInfo) => new[] + { + metadataInfo.Author, + metadataInfo.Artist, + metadataInfo.ArtistUnicode, + metadataInfo.Title, + metadataInfo.TitleUnicode, + metadataInfo.Source, + metadataInfo.Tags + }.Where(s => !string.IsNullOrEmpty(s)).ToArray(); + + /// + /// A user-presentable display title representing this metadata. + /// + public static string GetDisplayTitle(this IBeatmapMetadataInfo metadataInfo) + { + string author = string.IsNullOrEmpty(metadataInfo.Author) ? string.Empty : $"({metadataInfo.Author})"; + return $"{metadataInfo.Artist} - {metadataInfo.Title} {author}".Trim(); + } + + /// + /// A user-presentable display title representing this beatmap, with localisation handling for potentially romanisable fields. + /// + public static RomanisableString GetDisplayTitleRomanisable(this IBeatmapMetadataInfo metadataInfo) + { + string author = string.IsNullOrEmpty(metadataInfo.Author) ? string.Empty : $"({metadataInfo.Author})"; + var artistUnicode = string.IsNullOrEmpty(metadataInfo.ArtistUnicode) ? metadataInfo.Artist : metadataInfo.ArtistUnicode; + var titleUnicode = string.IsNullOrEmpty(metadataInfo.TitleUnicode) ? metadataInfo.Title : metadataInfo.TitleUnicode; + + return new RomanisableString($"{artistUnicode} - {titleUnicode} {author}".Trim(), $"{metadataInfo.Artist} - {metadataInfo.Title} {author}".Trim()); + } + } +} diff --git a/osu.Game/Beatmaps/IBeatmapInfo.cs b/osu.Game/Beatmaps/IBeatmapInfo.cs index fb30b0279c..6a3f1b43d8 100644 --- a/osu.Game/Beatmaps/IBeatmapInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapInfo.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; -using osu.Framework.Localisation; using osu.Game.Database; using osu.Game.Rulesets; @@ -64,33 +62,5 @@ namespace osu.Game.Beatmaps /// The basic star rating for this beatmap (with no mods applied). /// double StarRating { get; } - - /// - /// A user-presentable display title representing this metadata. - /// - string DisplayTitle => $"{Metadata} {versionString}".Trim(); - - /// - /// A user-presentable display title representing this beatmap, with localisation handling for potentially romanisable fields. - /// - RomanisableString DisplayTitleRomanisable - { - get - { - var metadata = closestMetadata.DisplayTitleRomanisable; - - return new RomanisableString($"{metadata.GetPreferred(true)} {versionString}".Trim(), $"{metadata.GetPreferred(false)} {versionString}".Trim()); - } - } - - string[] SearchableTerms => new[] - { - DifficultyName - }.Concat(closestMetadata.SearchableTerms).Where(s => !string.IsNullOrEmpty(s)).ToArray(); - - private string versionString => string.IsNullOrEmpty(DifficultyName) ? string.Empty : $"[{DifficultyName}]"; - - // temporary helper methods until we figure which metadata should be where. - private IBeatmapMetadataInfo closestMetadata => (Metadata ?? BeatmapSet.Metadata)!; } } diff --git a/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs b/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs index d0dae296a0..55aee7d7bc 100644 --- a/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapMetadataInfo.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; -using osu.Framework.Localisation; #nullable enable @@ -65,47 +63,6 @@ namespace osu.Game.Beatmaps /// string BackgroundFile { get; } - /// - /// A user-presentable display title representing this metadata. - /// - string DisplayTitle - { - get - { - string author = string.IsNullOrEmpty(Author) ? string.Empty : $"({Author})"; - return $"{Artist} - {Title} {author}".Trim(); - } - } - - /// - /// A user-presentable display title representing this metadata, with localisation handling for potentially romanisable fields. - /// - RomanisableString DisplayTitleRomanisable - { - get - { - string author = string.IsNullOrEmpty(Author) ? string.Empty : $"({Author})"; - var artistUnicode = string.IsNullOrEmpty(ArtistUnicode) ? Artist : ArtistUnicode; - var titleUnicode = string.IsNullOrEmpty(TitleUnicode) ? Title : TitleUnicode; - - return new RomanisableString($"{artistUnicode} - {titleUnicode} {author}".Trim(), $"{Artist} - {Title} {author}".Trim()); - } - } - - /// - /// An array of all searchable terms provided in contained metadata. - /// - string[] SearchableTerms => new[] - { - Author, - Artist, - ArtistUnicode, - Title, - TitleUnicode, - Source, - Tags - }.Where(s => !string.IsNullOrEmpty(s)).ToArray(); - bool IEquatable.Equals(IBeatmapMetadataInfo? other) { if (other == null) diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 571b14428e..ef25de77c6 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Music { Padding = new MarginPadding { Left = 5 }; - FilterTerms = item.Metadata.SearchableTerms; + FilterTerms = item.Metadata.GetSearchableTerms(); } [BackgroundDependencyLoader] diff --git a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs index 69eb857661..585b024623 100644 --- a/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/OnlinePlay/DrawableRoomPlaylistItem.cs @@ -108,7 +108,7 @@ namespace osu.Game.Screens.OnlinePlay difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value, requiredMods) { Size = new Vector2(32) }; beatmapText.Clear(); - beatmapText.AddLink(Item.Beatmap.Value.ToRomanisableString(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString(), null, text => + beatmapText.AddLink(Item.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString(), null, text => { text.Truncate = true; text.RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs index 03d13c353a..acd87ed864 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/Components/DrawableRoom.cs @@ -379,7 +379,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components if (item.NewValue?.Beatmap.Value != null) { statusText.Text = "Currently playing "; - beatmapText.AddLink(item.NewValue.Beatmap.Value.ToRomanisableString(), + beatmapText.AddLink(item.NewValue.Beatmap.Value.GetDisplayTitleRomanisable(), LinkAction.OpenBeatmap, item.NewValue.Beatmap.Value.OnlineBeatmapID.ToString(), creationParameters: s => diff --git a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs index 3f729d9477..d8c5aa760e 100644 --- a/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs +++ b/osu.Game/Screens/Select/Carousel/CarouselBeatmap.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Select.Carousel if (match) { - var terms = BeatmapInfo.SearchableTerms; + var terms = BeatmapInfo.GetSearchableTerms(); foreach (var criteriaTerm in criteria.SearchTerms) match &= terms.Any(term => term.Contains(criteriaTerm, StringComparison.InvariantCultureIgnoreCase)); From a5aa32811aaf215f7d96d601a82d44362d140ad3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 14:49:59 +0900 Subject: [PATCH 103/183] Remove null check suppression and add non-null fallback --- osu.Game/Beatmaps/BeatmapInfoExtensions.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs index deab8b915a..eba19ac1a1 100644 --- a/osu.Game/Beatmaps/BeatmapInfoExtensions.cs +++ b/osu.Game/Beatmaps/BeatmapInfoExtensions.cs @@ -32,6 +32,7 @@ namespace osu.Game.Beatmaps private static string getVersionString(IBeatmapInfo beatmapInfo) => string.IsNullOrEmpty(beatmapInfo.DifficultyName) ? string.Empty : $"[{beatmapInfo.DifficultyName}]"; // temporary helper methods until we figure which metadata should be where. - private static IBeatmapMetadataInfo getClosestMetadata(IBeatmapInfo beatmapInfo) => (beatmapInfo.Metadata ?? beatmapInfo.BeatmapSet.Metadata)!; + private static IBeatmapMetadataInfo getClosestMetadata(IBeatmapInfo beatmapInfo) => + beatmapInfo.Metadata ?? beatmapInfo.BeatmapSet?.Metadata ?? new BeatmapMetadata(); } } From e19be8ebe4eb49f456b61cd8f26c1143dad606ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 14:48:10 +0900 Subject: [PATCH 104/183] Make `GameplayState.Score` immutable --- osu.Game/Screens/Play/GameplayState.cs | 8 +++++--- osu.Game/Screens/Play/Player.cs | 20 ++++++++++---------- osu.Game/Screens/Play/ReplayPlayer.cs | 2 +- osu.Game/Screens/Play/SpectatorPlayer.cs | 3 ++- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayState.cs b/osu.Game/Screens/Play/GameplayState.cs index 9c83eddb45..44f72022f7 100644 --- a/osu.Game/Screens/Play/GameplayState.cs +++ b/osu.Game/Screens/Play/GameplayState.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using osu.Framework.Bindables; using osu.Game.Beatmaps; @@ -36,7 +37,7 @@ namespace osu.Game.Screens.Play /// /// The gameplay score. /// - public Score? Score { get; set; } + public readonly Score Score; /// /// A bindable tracking the last judgement result applied to any hit object. @@ -45,11 +46,12 @@ namespace osu.Game.Screens.Play private readonly Bindable lastJudgementResult = new Bindable(); - public GameplayState(IBeatmap beatmap, Ruleset ruleset, IReadOnlyList mods) + public GameplayState(IBeatmap beatmap, Ruleset ruleset, IReadOnlyList? mods = null, Score? score = null) { Beatmap = beatmap; Ruleset = ruleset; - Mods = mods; + Score = score ?? new Score(); + Mods = mods ?? ArraySegment.Empty; } /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 69093db883..a688330f54 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -161,14 +161,6 @@ namespace osu.Game.Screens.Play if (!LoadedBeatmapSuccessfully) return; - Score = CreateScore(); - GameplayState.Score = Score; - - // ensure the score is in a consistent state with the current player. - Score.ScoreInfo.BeatmapInfo = Beatmap.Value.BeatmapInfo; - Score.ScoreInfo.Ruleset = ruleset.RulesetInfo; - Score.ScoreInfo.Mods = Mods.Value.ToArray(); - PrepareReplay(); ScoreProcessor.NewJudgement += result => ScoreProcessor.PopulateScore(Score.ScoreInfo); @@ -226,7 +218,14 @@ namespace osu.Game.Screens.Play InternalChild = GameplayClockContainer = CreateGameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime); - dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, Mods.Value)); + Score = CreateScore(playableBeatmap); + + // ensure the score is in a consistent state with the current player. + Score.ScoreInfo.BeatmapInfo = Beatmap.Value.BeatmapInfo; + Score.ScoreInfo.Ruleset = ruleset.RulesetInfo; + Score.ScoreInfo.Mods = Mods.Value.ToArray(); + + dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, Mods.Value, Score)); AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); @@ -989,8 +988,9 @@ namespace osu.Game.Screens.Play /// /// Creates the player's . /// + /// /// The . - protected virtual Score CreateScore() => new Score + protected virtual Score CreateScore(IBeatmap beatmap) => new Score { ScoreInfo = new ScoreInfo { User = api.LocalUser.Value }, }; diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index eefea737cf..93054b7bb5 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -41,7 +41,7 @@ namespace osu.Game.Screens.Play DrawableRuleset?.SetReplayScore(Score); } - protected override Score CreateScore() => createScore(GameplayState.Beatmap, Mods.Value); + protected override Score CreateScore(IBeatmap beatmap) => createScore(beatmap, Mods.Value); // Don't re-import replay scores as they're already present in the database. protected override Task ImportScore(Score score) => Task.CompletedTask; diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index fbb4fb5699..f6a89e7fa9 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Online.Spectator; @@ -79,7 +80,7 @@ namespace osu.Game.Screens.Play NonFrameStableSeek(score.Replay.Frames[0].Time); } - protected override Score CreateScore() => score; + protected override Score CreateScore(IBeatmap beatmap) => score; protected override ResultsScreen CreateResults(ScoreInfo score) => new SpectatorResultsScreen(score); From 7176dc95e5d9a100cd7486122031b8b7dcd2caea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 14:51:53 +0900 Subject: [PATCH 105/183] Revert `Player.Score` to `protected` --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a688330f54..ecc65c6bb0 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -137,7 +137,7 @@ namespace osu.Game.Screens.Play public readonly PlayerConfiguration Configuration; - internal Score Score { get; private set; } + protected Score Score { get; private set; } /// /// Create a new player instance. From b6af93d43445b96db2adae8ebee954891b4b35d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 15:10:56 +0900 Subject: [PATCH 106/183] Apply some code quality refactoring --- .../Difficulty/DifficultyCalculator.cs | 19 +++++++++---------- .../Play/HUD/PerformancePointsCounter.cs | 19 +++++++------------ 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 3d90cc59f4..49a4b2c265 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -234,33 +234,32 @@ namespace osu.Game.Rulesets.Difficulty this.baseBeatmap = baseBeatmap; } + public readonly List HitObjects = new List(); + + IReadOnlyList IBeatmap.HitObjects => HitObjects; + + #region Delegated IBeatmap implementation + public BeatmapInfo BeatmapInfo { get => baseBeatmap.BeatmapInfo; set => baseBeatmap.BeatmapInfo = value; } - public BeatmapMetadata Metadata => baseBeatmap.Metadata; - public ControlPointInfo ControlPointInfo { get => baseBeatmap.ControlPointInfo; set => baseBeatmap.ControlPointInfo = value; } + public BeatmapMetadata Metadata => baseBeatmap.Metadata; public List Breaks => baseBeatmap.Breaks; - public double TotalBreakTime => baseBeatmap.TotalBreakTime; - - public readonly List HitObjects = new List(); - - IReadOnlyList IBeatmap.HitObjects => HitObjects; - public IEnumerable GetStatistics() => baseBeatmap.GetStatistics(); - public double GetMostCommonBeatLength() => baseBeatmap.GetMostCommonBeatLength(); - public IBeatmap Clone() => new ProgressiveCalculationBeatmap(baseBeatmap.Clone()); + + #endregion } } } diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 13b94e6cd6..7babc90427 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; @@ -46,9 +45,6 @@ namespace osu.Game.Screens.Play.HUD [CanBeNull] private TimedDifficultyAttributes[] timedAttributes; - [CanBeNull] - private Ruleset gameplayRuleset; - private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); public PerformancePointsCounter() @@ -63,8 +59,8 @@ namespace osu.Game.Screens.Play.HUD if (gameplayState != null) { - gameplayRuleset = gameplayState.Ruleset; - difficultyCache.GetTimedDifficultyAttributesAsync(new GameplayWorkingBeatmap(gameplayState.Beatmap), gameplayRuleset, gameplayState.Mods.ToArray(), loadCancellationSource.Token) + var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap); + difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, gameplayState.Mods.ToArray(), loadCancellationSource.Token) .ContinueWith(r => Schedule(() => timedAttributes = r.Result), TaskContinuationOptions.OnlyOnRanToCompletion); } } @@ -82,15 +78,14 @@ namespace osu.Game.Screens.Play.HUD if (gameplayState?.Score == null || timedAttributes == null || timedAttributes.Length == 0) return; - Debug.Assert(gameplayRuleset != null); - - var attribIndex = Array.BinarySearch(timedAttributes, 0, timedAttributes.Length, new TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null)); + int attribIndex = Array.BinarySearch(timedAttributes, 0, timedAttributes.Length, new TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null)); if (attribIndex < 0) attribIndex = ~attribIndex - 1; attribIndex = Math.Clamp(attribIndex, 0, timedAttributes.Length - 1); - var ppProcessor = gameplayRuleset.CreatePerformanceCalculator(timedAttributes[attribIndex].Attributes, gameplayState.Score.ScoreInfo); - Current.Value = (int)Math.Round(ppProcessor?.Calculate() ?? 0, MidpointRounding.AwayFromZero); + var calculator = gameplayState.Ruleset.CreatePerformanceCalculator(timedAttributes[attribIndex].Attributes, gameplayState.Score.ScoreInfo); + + Current.Value = (int)Math.Round(calculator?.Calculate() ?? 0, MidpointRounding.AwayFromZero); } protected override LocalisableString FormatCount(int count) => count.ToString(@"D"); @@ -145,7 +140,7 @@ namespace osu.Game.Screens.Play.HUD } } - // Todo: This class shouldn't exist, but requires breaking changes to allow DifficultyCalculator to receive an IBeatmap. + // TODO: This class shouldn't exist, but requires breaking changes to allow DifficultyCalculator to receive an IBeatmap. private class GameplayWorkingBeatmap : WorkingBeatmap { private readonly IBeatmap gameplayBeatmap; From 81a13566bc36574c943d9c4aa37fc7b47dcdd711 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 15:23:34 +0900 Subject: [PATCH 107/183] Adjust default location slightly, fix alignment of "pp" subtext --- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 3 ++- osu.Game/Skinning/DefaultSkin.cs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 7babc90427..2fc3f23190 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -133,7 +133,8 @@ namespace osu.Game.Screens.Play.HUD Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, Text = @"pp", - Font = OsuFont.Numeric.With(size: 8) + Font = OsuFont.Numeric.With(size: 8), + Padding = new MarginPadding { Bottom = 1.5f }, // align baseline better } } }; diff --git a/osu.Game/Skinning/DefaultSkin.cs b/osu.Game/Skinning/DefaultSkin.cs index 8c1e5313d5..8e03bddb4d 100644 --- a/osu.Game/Skinning/DefaultSkin.cs +++ b/osu.Game/Skinning/DefaultSkin.cs @@ -84,7 +84,7 @@ namespace osu.Game.Skinning if (ppCounter != null) { - ppCounter.Y = score.Position.Y + score.ScreenSpaceDrawQuad.Size.Y; + ppCounter.Y = score.Position.Y + ppCounter.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).Y - 4; ppCounter.Origin = Anchor.TopCentre; ppCounter.Anchor = Anchor.TopCentre; } From 676df55a0e40df9a48e3661cf4f6ed89a90828d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 15:39:29 +0900 Subject: [PATCH 108/183] Fade display out during rewind (as the value displayed is no longer valid) --- .../Graphics/UserInterface/RollingCounter.cs | 12 +++++---- .../Rulesets/Scoring/JudgementProcessor.cs | 7 +++++ .../Play/HUD/PerformancePointsCounter.cs | 26 +++++++++++++++++++ 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/RollingCounter.cs b/osu.Game/Graphics/UserInterface/RollingCounter.cs index 67b0b6a06b..16555075d1 100644 --- a/osu.Game/Graphics/UserInterface/RollingCounter.cs +++ b/osu.Game/Graphics/UserInterface/RollingCounter.cs @@ -25,7 +25,9 @@ namespace osu.Game.Graphics.UserInterface set => current.Current = value; } - private IHasText displayedCountSpriteText; + private IHasText displayedCountText; + + public Drawable DrawableCount { get; private set; } /// /// If true, the roll-up duration will be proportional to change in value. @@ -72,16 +74,16 @@ namespace osu.Game.Graphics.UserInterface [BackgroundDependencyLoader] private void load() { - displayedCountSpriteText = CreateText(); + displayedCountText = CreateText(); UpdateDisplay(); - Child = (Drawable)displayedCountSpriteText; + Child = DrawableCount = (Drawable)displayedCountText; } protected void UpdateDisplay() { - if (displayedCountSpriteText != null) - displayedCountSpriteText.Text = FormatCount(DisplayedCount); + if (displayedCountText != null) + displayedCountText.Text = FormatCount(DisplayedCount); } protected override void LoadComplete() diff --git a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs index 201a05e569..ed4a16f0e8 100644 --- a/osu.Game/Rulesets/Scoring/JudgementProcessor.cs +++ b/osu.Game/Rulesets/Scoring/JudgementProcessor.cs @@ -18,6 +18,11 @@ namespace osu.Game.Rulesets.Scoring /// public event Action NewJudgement; + /// + /// Invoked when a judgement is reverted, usually due to rewinding gameplay. + /// + public event Action JudgementReverted; + /// /// The maximum number of hits that can be judged. /// @@ -71,6 +76,8 @@ namespace osu.Game.Rulesets.Scoring JudgedHits--; RevertResultInternal(result); + + JudgementReverted?.Invoke(result); } /// diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 2fc3f23190..b82a85691a 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -42,6 +42,9 @@ namespace osu.Game.Screens.Play.HUD [CanBeNull] private GameplayState gameplayState { get; set; } + [Resolved] + private GameplayClock gameplayClock { get; set; } + [CanBeNull] private TimedDifficultyAttributes[] timedAttributes; @@ -70,7 +73,24 @@ namespace osu.Game.Screens.Play.HUD base.LoadComplete(); if (scoreProcessor != null) + { scoreProcessor.NewJudgement += onNewJudgement; + scoreProcessor.JudgementReverted += onJudgementReverted; + } + } + + private bool isValid; + + protected bool IsValid + { + set + { + if (value == isValid) + return; + + isValid = value; + DrawableCount.FadeTo(isValid ? 1 : 0.3f, 1000, Easing.OutQuint); + } } private void onNewJudgement(JudgementResult judgement) @@ -86,6 +106,12 @@ namespace osu.Game.Screens.Play.HUD var calculator = gameplayState.Ruleset.CreatePerformanceCalculator(timedAttributes[attribIndex].Attributes, gameplayState.Score.ScoreInfo); Current.Value = (int)Math.Round(calculator?.Calculate() ?? 0, MidpointRounding.AwayFromZero); + IsValid = true; + } + + private void onJudgementReverted(JudgementResult obj) + { + IsValid = false; } protected override LocalisableString FormatCount(int count) => count.ToString(@"D"); From 45b63cbad9dbb31a35cf2da3d3a88b2de8a5460a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Oct 2021 16:03:25 +0900 Subject: [PATCH 109/183] Remove unnecessary dependency --- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index b82a85691a..c554a038ed 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -42,9 +42,6 @@ namespace osu.Game.Screens.Play.HUD [CanBeNull] private GameplayState gameplayState { get; set; } - [Resolved] - private GameplayClock gameplayClock { get; set; } - [CanBeNull] private TimedDifficultyAttributes[] timedAttributes; From 565e888f587e6bed57318d17eff71167e6820325 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 16:40:07 +0900 Subject: [PATCH 110/183] Tidy up attribute retrieval code --- .../Play/HUD/PerformancePointsCounter.cs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index c554a038ed..fe34dd1d22 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -92,25 +92,32 @@ namespace osu.Game.Screens.Play.HUD private void onNewJudgement(JudgementResult judgement) { - if (gameplayState?.Score == null || timedAttributes == null || timedAttributes.Length == 0) + var attrib = getAttributeAtTime(judgement); + + if (gameplayState == null || attrib == null) return; - int attribIndex = Array.BinarySearch(timedAttributes, 0, timedAttributes.Length, new TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null)); - if (attribIndex < 0) - attribIndex = ~attribIndex - 1; - attribIndex = Math.Clamp(attribIndex, 0, timedAttributes.Length - 1); - - var calculator = gameplayState.Ruleset.CreatePerformanceCalculator(timedAttributes[attribIndex].Attributes, gameplayState.Score.ScoreInfo); + var calculator = gameplayState.Ruleset.CreatePerformanceCalculator(attrib, gameplayState.Score.ScoreInfo); Current.Value = (int)Math.Round(calculator?.Calculate() ?? 0, MidpointRounding.AwayFromZero); IsValid = true; } - private void onJudgementReverted(JudgementResult obj) + [CanBeNull] + private DifficultyAttributes getAttributeAtTime(JudgementResult judgement) { - IsValid = false; + if (timedAttributes == null || timedAttributes.Length == 0) + return null; + + int attribIndex = Array.BinarySearch(timedAttributes, 0, timedAttributes.Length, new TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null)); + if (attribIndex < 0) + attribIndex = ~attribIndex - 1; + + return timedAttributes[Math.Clamp(attribIndex, 0, timedAttributes.Length - 1)].Attributes; } + private void onJudgementReverted(JudgementResult obj) => IsValid = false; + protected override LocalisableString FormatCount(int count) => count.ToString(@"D"); protected override IHasText CreateText() => new TextComponent(); From eeb5f3d5191f87f20e9582caabab5792ab69bf2e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 16:48:54 +0900 Subject: [PATCH 111/183] Add basic test scene --- .../TestScenePerformancePointsCounter.cs | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs new file mode 100644 index 0000000000..350d08f63d --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs @@ -0,0 +1,83 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Diagnostics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD; +using osuTK; + +namespace osu.Game.Tests.Visual.Gameplay +{ + public class TestScenePerformancePointsCounter : OsuTestScene + { + [Cached] + private GameplayState gameplayState; + + [Cached] + private ScoreProcessor scoreProcessor; + + private int iteration; + + public TestScenePerformancePointsCounter() + { + var ruleset = CreateRuleset(); + + Debug.Assert(ruleset != null); + + var beatmap = CreateWorkingBeatmap(ruleset.RulesetInfo) + .GetPlayableBeatmap(ruleset.RulesetInfo); + + gameplayState = new GameplayState(beatmap, ruleset); + scoreProcessor = new ScoreProcessor(); + } + + protected override Ruleset CreateRuleset() => new OsuRuleset(); + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Create counter", () => + { + Child = new PerformancePointsCounter + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(5), + }; + }); + + AddRepeatStep("Add judgement", () => + { + var scoreInfo = gameplayState.Score.ScoreInfo; + + scoreInfo.MaxCombo = iteration * 1000; + scoreInfo.Accuracy = 1; + scoreInfo.Statistics[HitResult.Great] = iteration * 1000; + + scoreProcessor.ApplyResult(new OsuJudgementResult(new HitObject + { + StartTime = iteration * 10000, + }, new OsuJudgement()) + { + Type = HitResult.Perfect, + }); + + iteration++; + }, 10); + + AddStep("Revert judgement", () => + { + scoreProcessor.RevertResult(new JudgementResult(new HitObject(), new OsuJudgement())); + }); + } + } +} From fa7f11d906f8214dc15b01b04b405f8f957909ca Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 16:51:49 +0900 Subject: [PATCH 112/183] Add easing to rolling counter value --- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index fe34dd1d22..ab10399903 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -34,6 +34,10 @@ namespace osu.Game.Screens.Play.HUD { public bool UsesFixedAnchor { get; set; } + protected override bool IsRollingProportional => true; + + protected override double RollingDuration => 1000; + [CanBeNull] [Resolved(CanBeNull = true)] private ScoreProcessor scoreProcessor { get; set; } From 599d82e383f167632719758554b024fb4ab58bf4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 16:59:54 +0900 Subject: [PATCH 113/183] Avoid returning a live `IEnumerable` --- osu.Game/Beatmaps/BeatmapDifficultyCache.cs | 4 ++-- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 10 +++++++--- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 8 ++++---- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs index e6c287112f..3777365088 100644 --- a/osu.Game/Beatmaps/BeatmapDifficultyCache.cs +++ b/osu.Game/Beatmaps/BeatmapDifficultyCache.cs @@ -148,9 +148,9 @@ namespace osu.Game.Beatmaps }, token, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); } - public Task GetTimedDifficultyAttributesAsync(WorkingBeatmap beatmap, Ruleset ruleset, Mod[] mods, CancellationToken token = default) + public Task> GetTimedDifficultyAttributesAsync(WorkingBeatmap beatmap, Ruleset ruleset, Mod[] mods, CancellationToken token = default) { - return Task.Factory.StartNew(() => ruleset.CreateDifficultyCalculator(beatmap).CalculateTimed(mods).ToArray(), + return Task.Factory.StartNew(() => ruleset.CreateDifficultyCalculator(beatmap).CalculateTimed(mods), token, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index 49a4b2c265..a7c4790366 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -58,12 +58,14 @@ namespace osu.Game.Rulesets.Difficulty return CreateDifficultyAttributes(Beatmap, playableMods, skills, clockRate); } - public IEnumerable CalculateTimed(params Mod[] mods) + public List CalculateTimed(params Mod[] mods) { preProcess(mods); + var attribs = new List(); + if (!Beatmap.HitObjects.Any()) - yield break; + return attribs; var skills = CreateSkills(Beatmap, playableMods, clockRate); var progressiveBeatmap = new ProgressiveCalculationBeatmap(Beatmap); @@ -75,8 +77,10 @@ namespace osu.Game.Rulesets.Difficulty foreach (var skill in skills) skill.ProcessInternal(hitObject); - yield return new TimedDifficultyAttributes(hitObject.EndTime, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate)); + attribs.Add(new TimedDifficultyAttributes(hitObject.EndTime, CreateDifficultyAttributes(progressiveBeatmap, playableMods, skills, clockRate))); } + + return attribs; } /// diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index ab10399903..514369735e 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Play.HUD private GameplayState gameplayState { get; set; } [CanBeNull] - private TimedDifficultyAttributes[] timedAttributes; + private List timedAttributes; private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); @@ -110,14 +110,14 @@ namespace osu.Game.Screens.Play.HUD [CanBeNull] private DifficultyAttributes getAttributeAtTime(JudgementResult judgement) { - if (timedAttributes == null || timedAttributes.Length == 0) + if (timedAttributes == null || timedAttributes.Count == 0) return null; - int attribIndex = Array.BinarySearch(timedAttributes, 0, timedAttributes.Length, new TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null)); + int attribIndex = timedAttributes.BinarySearch(new TimedDifficultyAttributes(judgement.HitObject.GetEndTime(), null)); if (attribIndex < 0) attribIndex = ~attribIndex - 1; - return timedAttributes[Math.Clamp(attribIndex, 0, timedAttributes.Length - 1)].Attributes; + return timedAttributes[Math.Clamp(attribIndex, 0, timedAttributes.Count - 1)].Attributes; } private void onJudgementReverted(JudgementResult obj) => IsValid = false; From 04538a69e403fcbd8bbcf3e8e2baea5afc00191e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 17:10:24 +0900 Subject: [PATCH 114/183] Add assert tests --- .../TestScenePerformancePointsCounter.cs | 54 +++++++++++++------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs index 350d08f63d..26f4bda171 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Diagnostics; +using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Testing; @@ -26,6 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay private ScoreProcessor scoreProcessor; private int iteration; + private PerformancePointsCounter counter; public TestScenePerformancePointsCounter() { @@ -47,37 +49,55 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("Create counter", () => { - Child = new PerformancePointsCounter + iteration = 0; + + Child = counter = new PerformancePointsCounter { Anchor = Anchor.Centre, Origin = Anchor.Centre, Scale = new Vector2(5), }; }); + } - AddRepeatStep("Add judgement", () => - { - var scoreInfo = gameplayState.Score.ScoreInfo; + [Test] + public void TestBasicCounting() + { + AddAssert("counter displaying zero", () => counter.Current.Value == 0); - scoreInfo.MaxCombo = iteration * 1000; - scoreInfo.Accuracy = 1; - scoreInfo.Statistics[HitResult.Great] = iteration * 1000; + AddRepeatStep("Add judgement", applyOneJudgement, 10); - scoreProcessor.ApplyResult(new OsuJudgementResult(new HitObject - { - StartTime = iteration * 10000, - }, new OsuJudgement()) - { - Type = HitResult.Perfect, - }); - - iteration++; - }, 10); + AddUntilStep("counter non-zero", () => counter.Current.Value > 0); AddStep("Revert judgement", () => { scoreProcessor.RevertResult(new JudgementResult(new HitObject(), new OsuJudgement())); }); + + AddUntilStep("counter faded", () => counter.Child.Alpha < 1); + + AddStep("Add judgement", applyOneJudgement); + + AddUntilStep("counter opaque", () => counter.Child.Alpha == 1); + } + + private void applyOneJudgement() + { + var scoreInfo = gameplayState.Score.ScoreInfo; + + scoreInfo.MaxCombo = iteration * 1000; + scoreInfo.Accuracy = 1; + scoreInfo.Statistics[HitResult.Great] = iteration * 1000; + + scoreProcessor.ApplyResult(new OsuJudgementResult(new HitObject + { + StartTime = iteration * 10000, + }, new OsuJudgement()) + { + Type = HitResult.Perfect, + }); + + iteration++; } } } From f64226ded6da72516c59a65047b19b1eaf4427b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 17:10:32 +0900 Subject: [PATCH 115/183] Fix display not displaying correctly after initial load --- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 514369735e..da821d76ad 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -51,6 +51,8 @@ namespace osu.Game.Screens.Play.HUD private readonly CancellationTokenSource loadCancellationSource = new CancellationTokenSource(); + private JudgementResult lastJudgement; + public PerformancePointsCounter() { Current.Value = DisplayedCount = 0; @@ -65,7 +67,12 @@ namespace osu.Game.Screens.Play.HUD { var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap); difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, gameplayState.Mods.ToArray(), loadCancellationSource.Token) - .ContinueWith(r => Schedule(() => timedAttributes = r.Result), TaskContinuationOptions.OnlyOnRanToCompletion); + .ContinueWith(r => Schedule(() => + { + timedAttributes = r.Result; + if (lastJudgement != null) + onNewJudgement(lastJudgement); + }), TaskContinuationOptions.OnlyOnRanToCompletion); } } @@ -96,6 +103,8 @@ namespace osu.Game.Screens.Play.HUD private void onNewJudgement(JudgementResult judgement) { + lastJudgement = judgement; + var attrib = getAttributeAtTime(judgement); if (gameplayState == null || attrib == null) From 1e4da8112088b5a6a315fa4b962e3e7f5245c39a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 17:14:09 +0900 Subject: [PATCH 116/183] Fix import notifications not showing correct text --- osu.Game/Database/ArchiveModelManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 9ad2dec12e..ee1a7e2900 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -197,7 +197,7 @@ namespace osu.Game.Database else { notification.CompletionText = imported.Count == 1 - ? $"Imported {imported.First()}!" + ? $"Imported {imported.First().Value}!" : $"Imported {imported.Count} {HumanisedModelName}s!"; if (imported.Count > 0 && PostImport != null) From 0859c336de52dcb5d69492f02ef5de704d782653 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 17:24:36 +0900 Subject: [PATCH 117/183] Also dim counter during initial calculation phase --- .../Gameplay/TestScenePerformancePointsCounter.cs | 1 + osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs index 26f4bda171..c7d2204de2 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs @@ -68,6 +68,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddRepeatStep("Add judgement", applyOneJudgement, 10); AddUntilStep("counter non-zero", () => counter.Current.Value > 0); + AddUntilStep("counter opaque", () => counter.Child.Alpha == 1); AddStep("Revert judgement", () => { diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index da821d76ad..9eac62fe73 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -38,6 +38,8 @@ namespace osu.Game.Screens.Play.HUD protected override double RollingDuration => 1000; + private const float alpha_when_invalid = 0.3f; + [CanBeNull] [Resolved(CanBeNull = true)] private ScoreProcessor scoreProcessor { get; set; } @@ -70,6 +72,7 @@ namespace osu.Game.Screens.Play.HUD .ContinueWith(r => Schedule(() => { timedAttributes = r.Result; + IsValid = true; if (lastJudgement != null) onNewJudgement(lastJudgement); }), TaskContinuationOptions.OnlyOnRanToCompletion); @@ -97,7 +100,7 @@ namespace osu.Game.Screens.Play.HUD return; isValid = value; - DrawableCount.FadeTo(isValid ? 1 : 0.3f, 1000, Easing.OutQuint); + DrawableCount.FadeTo(isValid ? 1 : alpha_when_invalid, 1000, Easing.OutQuint); } } @@ -133,7 +136,10 @@ namespace osu.Game.Screens.Play.HUD protected override LocalisableString FormatCount(int count) => count.ToString(@"D"); - protected override IHasText CreateText() => new TextComponent(); + protected override IHasText CreateText() => new TextComponent + { + Alpha = alpha_when_invalid + }; protected override void Dispose(bool isDisposing) { From 2be44188efbd6befbd4a39242a9f823509d7e35c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 17:59:38 +0900 Subject: [PATCH 118/183] Add missing null checks --- osu.Game/Overlays/RankingsOverlay.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Overlays/RankingsOverlay.cs b/osu.Game/Overlays/RankingsOverlay.cs index 2263d54d7b..80ce2e038d 100644 --- a/osu.Game/Overlays/RankingsOverlay.cs +++ b/osu.Game/Overlays/RankingsOverlay.cs @@ -143,6 +143,9 @@ namespace osu.Game.Overlays switch (request) { case GetUserRankingsRequest userRequest: + if (userRequest.Response == null) + return null; + switch (userRequest.Type) { case UserRankingsType.Performance: @@ -155,7 +158,12 @@ namespace osu.Game.Overlays return null; case GetCountryRankingsRequest countryRequest: + { + if (countryRequest.Response == null) + return null; + return new CountriesTable(1, countryRequest.Response.Countries); + } } return null; From 94153e8bba3293d7141ab4091d0ab3203c20280f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 18:06:24 +0900 Subject: [PATCH 119/183] Fix `TestDifficultyIconSelectingForDifferentRuleset` potentially failing due to async load --- osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index 70224ae9f2..067f1cabb4 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -714,10 +714,11 @@ namespace osu.Game.Tests.Visual.SongSelect }); FilterableDifficultyIcon difficultyIcon = null; - AddStep("Find an icon for different ruleset", () => + AddUntilStep("Find an icon for different ruleset", () => { difficultyIcon = set.ChildrenOfType() - .First(icon => icon.Item.BeatmapInfo.Ruleset.ID == 3); + .FirstOrDefault(icon => icon.Item.BeatmapInfo.Ruleset.ID == 3); + return difficultyIcon != null; }); AddAssert("Check ruleset is osu!", () => Ruleset.Value.ID == 0); From 5d708b612d3c4326c56a308b5e42723d00d91ed0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 18:17:20 +0900 Subject: [PATCH 120/183] Fix delete local score test not waiting for "fetch" to complete Even though this is a completely local operation in this case, there's still a level of asynchronous operation which was recent introduced with the score ordering: https://github.com/ppy/osu/blob/853cf6feaa165e833ecb7ca18c6cdffe8ca6e005/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs#L159 This means there is a brief period where the `Scores` property is null, after `Reset()` is called in the re-fetch procedure. --- .../Visual/UserInterface/TestSceneDeleteLocalScore.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs index d0a76bac27..189b143a35 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneDeleteLocalScore.cs @@ -162,6 +162,8 @@ namespace osu.Game.Tests.Visual.UserInterface InputManager.Click(MouseButton.Left); }); + AddUntilStep("wait for fetch", () => leaderboard.Scores != null); + AddUntilStep("score removed from leaderboard", () => leaderboard.Scores.All(s => s.OnlineScoreID != scoreBeingDeleted.OnlineScoreID)); } From e6efdae7c95967543caedd282f91cf03b2ea953d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 18:53:20 +0900 Subject: [PATCH 121/183] Add various logging output in an atttempt to figure multiplayer test failure --- osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs | 3 +++ osu.Game/Tests/Visual/ScreenTestScene.cs | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index a8fda19c60..c040ab27e4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Graphics; using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -68,6 +69,8 @@ namespace osu.Game.Tests.Visual.Multiplayer LoadScreen(dependenciesScreen = new DependenciesScreen(client)); }); + AddUntilStep("wait for dependencies screen", () => Stack.CurrentScreen is DependenciesScreen); + AddUntilStep("wait for dependencies to start load", () => dependenciesScreen.LoadState > LoadState.Ready); AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded); AddStep("load multiplayer", () => LoadScreen(multiplayerScreen)); diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs index b30be05ac4..966c513269 100644 --- a/osu.Game/Tests/Visual/ScreenTestScene.cs +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -4,6 +4,8 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Logging; +using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Overlays; using osu.Game.Screens; @@ -32,6 +34,9 @@ namespace osu.Game.Tests.Visual content = new Container { RelativeSizeAxes = Axes.Both }, DialogOverlay = new DialogOverlay() }); + + Stack.ScreenPushed += (lastScreen, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed → {newScreen}"); + Stack.ScreenExited += (lastScreen, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed ← {newScreen}"); } protected void LoadScreen(OsuScreen screen) => Stack.Push(screen); From 98fef6ece2d39efb59ea89384a7799b5f7c0efba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Oct 2021 19:08:30 +0900 Subject: [PATCH 122/183] Handle judgement reverts with actual display updates --- .../Gameplay/TestScenePerformancePointsCounter.cs | 8 ++++++-- .../Screens/Play/HUD/PerformancePointsCounter.cs | 15 ++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs index c7d2204de2..4c48d52acd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePerformancePointsCounter.cs @@ -63,6 +63,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestBasicCounting() { + int previousValue = 0; + AddAssert("counter displaying zero", () => counter.Current.Value == 0); AddRepeatStep("Add judgement", applyOneJudgement, 10); @@ -72,14 +74,16 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("Revert judgement", () => { + previousValue = counter.Current.Value; + scoreProcessor.RevertResult(new JudgementResult(new HitObject(), new OsuJudgement())); }); - AddUntilStep("counter faded", () => counter.Child.Alpha < 1); + AddUntilStep("counter decreased", () => counter.Current.Value < previousValue); AddStep("Add judgement", applyOneJudgement); - AddUntilStep("counter opaque", () => counter.Child.Alpha == 1); + AddUntilStep("counter non-zero", () => counter.Current.Value > 0); } private void applyOneJudgement() diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 9eac62fe73..2ae7b5660a 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -74,7 +74,7 @@ namespace osu.Game.Screens.Play.HUD timedAttributes = r.Result; IsValid = true; if (lastJudgement != null) - onNewJudgement(lastJudgement); + onJudgementChanged(lastJudgement); }), TaskContinuationOptions.OnlyOnRanToCompletion); } } @@ -85,8 +85,8 @@ namespace osu.Game.Screens.Play.HUD if (scoreProcessor != null) { - scoreProcessor.NewJudgement += onNewJudgement; - scoreProcessor.JudgementReverted += onJudgementReverted; + scoreProcessor.NewJudgement += onJudgementChanged; + scoreProcessor.JudgementReverted += onJudgementChanged; } } @@ -104,14 +104,17 @@ namespace osu.Game.Screens.Play.HUD } } - private void onNewJudgement(JudgementResult judgement) + private void onJudgementChanged(JudgementResult judgement) { lastJudgement = judgement; var attrib = getAttributeAtTime(judgement); if (gameplayState == null || attrib == null) + { + IsValid = false; return; + } var calculator = gameplayState.Ruleset.CreatePerformanceCalculator(attrib, gameplayState.Score.ScoreInfo); @@ -132,8 +135,6 @@ namespace osu.Game.Screens.Play.HUD return timedAttributes[Math.Clamp(attribIndex, 0, timedAttributes.Count - 1)].Attributes; } - private void onJudgementReverted(JudgementResult obj) => IsValid = false; - protected override LocalisableString FormatCount(int count) => count.ToString(@"D"); protected override IHasText CreateText() => new TextComponent @@ -146,7 +147,7 @@ namespace osu.Game.Screens.Play.HUD base.Dispose(isDisposing); if (scoreProcessor != null) - scoreProcessor.NewJudgement -= onNewJudgement; + scoreProcessor.NewJudgement -= onJudgementChanged; loadCancellationSource?.Cancel(); } From 12da27cde728b0d17517e23306b0e180be9e75d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 5 Oct 2021 20:52:40 +0200 Subject: [PATCH 123/183] Add test coverage for loading process on channel join --- .../Visual/Online/TestSceneChatOverlay.cs | 38 ++++++++++++++++++- .../Online/API/Requests/GetMessagesRequest.cs | 6 +-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 609e637914..9562b41363 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -18,6 +19,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Chat; using osu.Game.Overlays; +using osu.Game.Overlays.Chat; using osu.Game.Overlays.Chat.Selection; using osu.Game.Overlays.Chat.Tabs; using osu.Game.Users; @@ -41,6 +43,9 @@ namespace osu.Game.Tests.Visual.Online private Channel channel2 => channels[1]; private Channel channel3 => channels[2]; + [CanBeNull] + private Func> onGetMessages; + [Resolved] private GameHost host { get; set; } @@ -79,6 +84,8 @@ namespace osu.Game.Tests.Visual.Online { AddStep("register request handling", () => { + onGetMessages = null; + ((DummyAPIAccess)API).HandleRequest = req => { switch (req) @@ -102,6 +109,12 @@ namespace osu.Game.Tests.Visual.Online } return true; + + case GetMessagesRequest getMessages: + var messages = onGetMessages?.Invoke(getMessages.Channel); + if (messages != null) + getMessages.TriggerSuccess(messages); + return true; } return false; @@ -122,14 +135,37 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestSelectingChannelClosesSelector() + public void TestChannelSelection() { AddAssert("Selector is visible", () => chatOverlay.SelectionOverlayState == Visibility.Visible); + AddStep("Setup get message response", () => onGetMessages = channel => + { + if (channel == channel1) + { + return new List + { + new Message(1) + { + ChannelId = channel1.Id, + Content = "hello from channel 1!", + Sender = new User + { + Id = 2, + Username = "test_user" + } + } + }; + } + + return null; + }); AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); AddStep("Switch to channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); AddAssert("Current channel is channel 1", () => currentChannel == channel1); + AddUntilStep("Loading spinner hidden", () => chatOverlay.ChildrenOfType().All(spinner => !spinner.IsPresent)); + AddAssert("Channel message shown", () => chatOverlay.ChildrenOfType().Count() == 1); AddAssert("Channel selector was closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden); } diff --git a/osu.Game/Online/API/Requests/GetMessagesRequest.cs b/osu.Game/Online/API/Requests/GetMessagesRequest.cs index 36e81a9348..651f8a06c5 100644 --- a/osu.Game/Online/API/Requests/GetMessagesRequest.cs +++ b/osu.Game/Online/API/Requests/GetMessagesRequest.cs @@ -8,13 +8,13 @@ namespace osu.Game.Online.API.Requests { public class GetMessagesRequest : APIRequest> { - private readonly Channel channel; + public readonly Channel Channel; public GetMessagesRequest(Channel channel) { - this.channel = channel; + Channel = channel; } - protected override string Target => $@"chat/channels/{channel.Id}/messages"; + protected override string Target => $@"chat/channels/{Channel.Id}/messages"; } } From a5b07ce4feaf88cf2f2f5eee6e43b166a8207737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 5 Oct 2021 20:53:06 +0200 Subject: [PATCH 124/183] Fix backwards containment check in chat channel load callback --- osu.Game/Overlays/ChatOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 20d637d957..4b27335c7c 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -285,7 +285,7 @@ namespace osu.Game.Overlays return; // check once more to ensure the channel hasn't since been removed from the loaded channels list (may have been left by some automated means). - if (loadedChannels.Contains(loaded)) + if (!loadedChannels.Contains(loaded)) return; loading.Hide(); From baa8baaa1efd762b650c4e2b9e409f700e601fe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 5 Oct 2021 21:12:35 +0200 Subject: [PATCH 125/183] Fix "most played beatmap" request breakage after property rename --- .../Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs index 15f67eda47..10f7ca6fe2 100644 --- a/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs +++ b/osu.Game/Online/API/Requests/Responses/APIUserMostPlayedBeatmap.cs @@ -15,7 +15,7 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("count")] public int PlayCount { get; set; } - [JsonProperty] + [JsonProperty("beatmap")] private BeatmapInfo beatmapInfo { get; set; } [JsonProperty] From 777763a55095147c307b32e502aa5fd9220f5066 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 05:27:36 +0900 Subject: [PATCH 126/183] Add more comprehensive (and failing) test coverage of replay download button --- .../Gameplay/TestSceneReplayDownloadButton.cs | 84 +++++++++++++++++-- 1 file changed, 75 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 1809332bce..5dd4ecf30b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -8,34 +8,97 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Scoring; using osu.Game.Users; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Rulesets; using osu.Game.Screens.Ranking; +using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { [TestFixture] - public class TestSceneReplayDownloadButton : OsuTestScene + public class TestSceneReplayDownloadButton : OsuManualInputManagerTestScene { [Resolved] private RulesetStore rulesets { get; set; } private TestReplayDownloadButton downloadButton; - public TestSceneReplayDownloadButton() + [Test] + public void TestDisplayStates() { - createButton(true); + AddStep(@"create button with replay", () => + { + Child = downloadButton = new TestReplayDownloadButton(getScoreInfo(true)) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + }); + + AddUntilStep("wait for load", () => downloadButton.IsLoaded); + + AddStep("click button", () => downloadButton.TriggerClick()); + AddStep(@"downloading state", () => downloadButton.SetDownloadState(DownloadState.Downloading)); AddStep(@"locally available state", () => downloadButton.SetDownloadState(DownloadState.LocallyAvailable)); AddStep(@"not downloaded state", () => downloadButton.SetDownloadState(DownloadState.NotDownloaded)); - createButton(false); - createButtonNoScore(); } - private void createButton(bool withReplay) + [Test] + public void TestButtonWithReplayStartsDownload() { - AddStep(withReplay ? @"create button with replay" : "create button without replay", () => + bool downloadStarted = false; + bool downloadFinished = false; + + AddStep(@"create button with replay", () => { - Child = downloadButton = new TestReplayDownloadButton(getScoreInfo(withReplay)) + downloadStarted = false; + downloadFinished = false; + + Child = downloadButton = new TestReplayDownloadButton(getScoreInfo(true)) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + + downloadButton.State.BindValueChanged(state => + { + switch (state.NewValue) + { + case DownloadState.Downloading: + downloadStarted = true; + break; + } + + switch (state.OldValue) + { + case DownloadState.Downloading: + downloadFinished = true; + break; + } + }); + }); + + AddUntilStep("wait for load", () => downloadButton.IsLoaded); + + AddAssert("state is available", () => downloadButton.State.Value == DownloadState.NotDownloaded); + + AddStep("click button", () => + { + InputManager.MoveMouseTo(downloadButton); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("state entered downloading", () => downloadStarted); + AddUntilStep("state left downloading", () => downloadFinished); + } + + [Test] + public void TestButtonWithoutReplay() + { + AddStep("create button without replay", () => + { + Child = downloadButton = new TestReplayDownloadButton(getScoreInfo(false)) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -45,7 +108,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for load", () => downloadButton.IsLoaded); } - private void createButtonNoScore() + [Test] + public void CreateButtonWithNoScore() { AddStep("create button with null score", () => { @@ -78,6 +142,8 @@ namespace osu.Game.Tests.Visual.Gameplay { public void SetDownloadState(DownloadState state) => State.Value = state; + public new Bindable State => base.State; + public TestReplayDownloadButton(ScoreInfo score) : base(score) { From 5a4474e1b2560896c3f09dc2d4fc94c437b70d88 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 05:28:11 +0900 Subject: [PATCH 127/183] Fix incorrect DI retrieval in `ReplayDownloadButton` --- osu.Game/Screens/Ranking/ReplayDownloadButton.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index d96b6989b4..e644eb671a 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Ranking } [BackgroundDependencyLoader(true)] - private void load(OsuGame game, ScoreModelDownloader scores) + private void load(OsuGame game, ScoreManager scores) { InternalChild = shakeContainer = new ShakeContainer { @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Ranking break; case DownloadState.NotDownloaded: - scores.Download(Model.Value); + scores.Download(Model.Value, false); break; case DownloadState.Importing: From 1f6a31355c514e809ca61a64f3a82e3a608e1777 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 05:30:49 +0900 Subject: [PATCH 128/183] Remove unused using statement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Tests/Visual/ScreenTestScene.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs index 966c513269..aa46b516bf 100644 --- a/osu.Game/Tests/Visual/ScreenTestScene.cs +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; -using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Overlays; using osu.Game.Screens; From 1a784b788dba00e79be4011b2af652d9250d3be4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 05:31:07 +0900 Subject: [PATCH 129/183] Fix incorrect load state check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs index c040ab27e4..80217a7726 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTeamVersus.cs @@ -70,7 +70,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); AddUntilStep("wait for dependencies screen", () => Stack.CurrentScreen is DependenciesScreen); - AddUntilStep("wait for dependencies to start load", () => dependenciesScreen.LoadState > LoadState.Ready); + AddUntilStep("wait for dependencies to start load", () => dependenciesScreen.LoadState > LoadState.NotLoaded); AddUntilStep("wait for dependencies to load", () => dependenciesScreen.IsLoaded); AddStep("load multiplayer", () => LoadScreen(multiplayerScreen)); From 31c0c7a8881e56a02b44794a3d2406548e79b69a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 05:49:04 +0900 Subject: [PATCH 130/183] Remove pointless (and incorrect) click step --- osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 5dd4ecf30b..8197e09594 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -37,8 +37,6 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for load", () => downloadButton.IsLoaded); - AddStep("click button", () => downloadButton.TriggerClick()); - AddStep(@"downloading state", () => downloadButton.SetDownloadState(DownloadState.Downloading)); AddStep(@"locally available state", () => downloadButton.SetDownloadState(DownloadState.LocallyAvailable)); AddStep(@"not downloaded state", () => downloadButton.SetDownloadState(DownloadState.NotDownloaded)); From d6f25e07ccc453bc06de28fef5eefaa2540e1e4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 05:49:18 +0900 Subject: [PATCH 131/183] Add assert coverage of non-downloadable states --- .../Visual/Gameplay/TestSceneReplayDownloadButton.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 8197e09594..5e2374cbcb 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Online; @@ -9,6 +10,8 @@ using osu.Game.Scoring; using osu.Game.Users; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Testing; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Screens.Ranking; using osuTK.Input; @@ -104,6 +107,9 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddUntilStep("wait for load", () => downloadButton.IsLoaded); + + AddAssert("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); + AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value); } [Test] @@ -119,6 +125,9 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddUntilStep("wait for load", () => downloadButton.IsLoaded); + + AddAssert("state is not downloaded", () => downloadButton.State.Value == DownloadState.NotDownloaded); + AddAssert("button is not enabled", () => !downloadButton.ChildrenOfType().First().Enabled.Value); } private ScoreInfo getScoreInfo(bool replayAvailable) From 4d5696959b0435bf4bcb0fad96b7c233eaf2bdb6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 05:52:28 +0900 Subject: [PATCH 132/183] Remove unnecessary access modifier in interface Co-authored-by: Dan Balasescu --- osu.Game/Rulesets/IRulesetInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/IRulesetInfo.cs b/osu.Game/Rulesets/IRulesetInfo.cs index ded3ac4b58..779433dc81 100644 --- a/osu.Game/Rulesets/IRulesetInfo.cs +++ b/osu.Game/Rulesets/IRulesetInfo.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets /// string InstantiationInfo { get; } - public Ruleset? CreateInstance() + Ruleset? CreateInstance() { var type = Type.GetType(InstantiationInfo); From 4f59fc15a50910eb4716f3968779e64b28856acc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 05:54:37 +0900 Subject: [PATCH 133/183] Mark `BeatmapSet` as nullable for the time being --- osu.Game/Beatmaps/IBeatmapInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/IBeatmapInfo.cs b/osu.Game/Beatmaps/IBeatmapInfo.cs index 6a3f1b43d8..3d51c5d4b6 100644 --- a/osu.Game/Beatmaps/IBeatmapInfo.cs +++ b/osu.Game/Beatmaps/IBeatmapInfo.cs @@ -31,7 +31,7 @@ namespace osu.Game.Beatmaps /// /// The beatmap set this beatmap is part of. /// - IBeatmapSetInfo BeatmapSet { get; } + IBeatmapSetInfo? BeatmapSet { get; } /// /// The playable length in milliseconds of this beatmap. From df182ba92ba94030c136a816ecc2c2cf8fb80e12 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 6 Oct 2021 11:27:13 +0900 Subject: [PATCH 134/183] Set fQ to recommended value from BASS developer to prevent filter calculations from overflowing when approaching nyquist --- osu.Game/Audio/Effects/Filter.cs | 43 ++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/osu.Game/Audio/Effects/Filter.cs b/osu.Game/Audio/Effects/Filter.cs index 142e6b8fff..00b617ffa1 100644 --- a/osu.Game/Audio/Effects/Filter.cs +++ b/osu.Game/Audio/Effects/Filter.cs @@ -10,7 +10,7 @@ namespace osu.Game.Audio.Effects { public class Filter : Component, ITransformableFilter { - public readonly int MaxCutoff; + public readonly int MaxCutoff = 22049; // nyquist - 1hz private readonly AudioMixer mixer; private readonly BQFParameters filter; private readonly BQFType type; @@ -18,36 +18,29 @@ namespace osu.Game.Audio.Effects public BindableNumber Cutoff { get; } /// - /// A BiQuad filter that performs a filter-sweep when toggled on or off. + /// A Component that implements a BASS FX BiQuad Filter Effect. /// - /// The mixer this effect should be attached to. + /// The mixer this effect should be applied to. /// The type of filter (e.g. LowPass, HighPass, etc) public Filter(AudioMixer mixer, BQFType type = BQFType.LowPass) { this.mixer = mixer; this.type = type; - var initialCutoff = 1; + int initialCutoff; - // These max cutoff values are a work-around for BASS' BiQuad filters behaving weirdly when approaching nyquist. - // Note that these values assume a sample rate of 44100 (as per BassAudioMixer in osu.Framework) - // See also https://www.un4seen.com/forum/?topic=19542.0 for more information. switch (type) { case BQFType.HighPass: - MaxCutoff = 21968; // beyond this value, the high-pass cuts out + initialCutoff = 1; break; case BQFType.LowPass: - MaxCutoff = initialCutoff = 14000; // beyond (roughly) this value, the low-pass filter audibly wraps/reflects - break; - - case BQFType.BandPass: - MaxCutoff = 16000; // beyond (roughly) this value, the band-pass filter audibly wraps/reflects + initialCutoff = MaxCutoff; break; default: - MaxCutoff = 22050; // default to nyquist for other filter types, TODO: handle quirks of other filter types + initialCutoff = 500; // A default that should ensure audio remains audible for other filters. break; } @@ -59,11 +52,12 @@ namespace osu.Game.Audio.Effects filter = new BQFParameters { lFilter = type, - fCenter = initialCutoff + fCenter = initialCutoff, + fBandwidth = 0, + fQ = 0.7f // This allows fCenter to go up to 22049hz (nyquist - 1hz) without overflowing and causing weird filter behaviour (see: https://www.un4seen.com/forum/?topic=19542.0) }; attachFilter(); - Cutoff.ValueChanged += updateFilter; Cutoff.Value = initialCutoff; } @@ -74,9 +68,7 @@ namespace osu.Game.Audio.Effects private void updateFilter(ValueChangedEvent cutoff) { - // This is another workaround for quirks in BASS' BiQuad filters. - // Because the cutoff can't be set above ~14khz (i.e. outside of human hearing range) without the aforementioned wrapping/reflecting quirk occuring, we instead - // remove the effect from the mixer when the cutoff is at maximum so that a LowPass filter isn't always attenuating high frequencies just by existing. + // Workaround for weird behaviour when rapidly setting fCenter of a low-pass filter to nyquist - 1hz. if (type == BQFType.LowPass) { if (cutoff.NewValue >= MaxCutoff) @@ -89,6 +81,19 @@ namespace osu.Game.Audio.Effects attachFilter(); } + // Workaround for weird behaviour when rapidly setting fCenter of a high-pass filter to 1hz. + if (type == BQFType.HighPass) + { + if (cutoff.NewValue <= 1) + { + detachFilter(); + return; + } + + if (cutoff.OldValue <= 1 && cutoff.NewValue > 1) + attachFilter(); + } + var filterIndex = mixer.Effects.IndexOf(filter); if (filterIndex < 0) return; From 266e62794e4c1f892f05e4ddbdd2b8a0b3eba289 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 6 Oct 2021 11:50:14 +0900 Subject: [PATCH 135/183] Tweak cutoff effect for new Q value --- osu.Game/Overlays/DialogOverlay.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 6016d16d29..bd20b74970 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -82,13 +82,15 @@ namespace osu.Game.Overlays { base.PopIn(); this.FadeIn(PopupDialog.ENTER_DURATION, Easing.OutQuint); - lpFilter.CutoffTo(2000).Then().CutoffTo(150, 100, Easing.OutCubic); + lpFilter.CutoffTo(300, 100, Easing.OutCubic); } protected override void PopOut() { base.PopOut(); + lpFilter.CutoffTo(lpFilter.MaxCutoff, 100, Easing.InCubic); + if (CurrentDialog?.State.Value == Visibility.Visible) { CurrentDialog.Hide(); @@ -96,7 +98,6 @@ namespace osu.Game.Overlays } this.FadeOut(PopupDialog.EXIT_DURATION, Easing.InSine); - lpFilter.CutoffTo(2000, 100, Easing.InCubic).Then().CutoffTo(lpFilter.MaxCutoff); } public override bool OnPressed(KeyBindingPressEvent e) From ffbb7a9b1a45692305c984a1a7fc42d37db2993d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 12:22:32 +0900 Subject: [PATCH 136/183] Remove incorrect csproj change Co-authored-by: Dan Balasescu --- osu.Game/osu.Game.csproj | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 05c587bcc0..4877ddf725 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -43,7 +43,4 @@ - - - From 6e797ddcac59f939f7fed68a9f0a91c1bf9a5143 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 12:41:17 +0900 Subject: [PATCH 137/183] Add test coverage of creating, saving and loading a new beatmap --- .../Visual/Editing/TestSceneEditorSaving.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs new file mode 100644 index 0000000000..2258a209e2 --- /dev/null +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -0,0 +1,62 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Framework.Input; +using osu.Framework.Testing; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit; +using osu.Game.Screens.Edit; +using osu.Game.Screens.Menu; +using osu.Game.Screens.Select; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.Editing +{ + public class TestSceneEditorSaving : OsuGameTestScene + { + private Editor editor => Game.ChildrenOfType().FirstOrDefault(); + + private EditorBeatmap editorBeatmap => (EditorBeatmap)editor.Dependencies.Get(typeof(EditorBeatmap)); + + /// + /// Tests the general expected flow of creating a new beatmap, saving it, then loading it back from song select. + /// + [Test] + public void TestNewBeatmapSaveThenLoad() + { + AddStep("set default beatmap", () => Game.Beatmap.SetDefault()); + + PushAndConfirm(() => new EditorLoader()); + + AddUntilStep("wait for editor load", () => editor != null); + + AddStep("Add timing point", () => editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint())); + + AddStep("Enter compose mode", () => InputManager.Key(Key.F1)); + AddUntilStep("Wait for compose mode load", () => editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); + + AddStep("Change to placement mode", () => InputManager.Key(Key.Number2)); + AddStep("Move to playfield", () => InputManager.MoveMouseTo(Game.ScreenSpaceDrawQuad.Centre)); + AddStep("Place single hitcircle", () => InputManager.Click(MouseButton.Left)); + + AddStep("Save and exit", () => + { + InputManager.Keys(PlatformAction.Save); + InputManager.Key(Key.Escape); + }); + + AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu); + + PushAndConfirm(() => new PlaySongSelect()); + + AddUntilStep("Wait for beatmap selected", () => !Game.Beatmap.IsDefault); + AddStep("Open options", () => InputManager.Key(Key.F3)); + AddStep("Enter editor", () => InputManager.Key(Key.Number5)); + + AddUntilStep("Wait for editor load", () => editor != null); + AddAssert("Beatmap contains single hitcircle", () => editorBeatmap.HitObjects.Count == 1); + } + } +} From 007b33cd88d465253613f6d74d05c93c8a6a0882 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 12:05:11 +0900 Subject: [PATCH 138/183] Add missing methods to interfaces --- osu.Game/Beatmaps/BeatmapModelManager.cs | 4 ++-- osu.Game/Beatmaps/IBeatmapModelManager.cs | 20 ++++++++++++++++++++ osu.Game/Beatmaps/IWorkingBeatmapCache.cs | 12 ++++++++++++ 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Beatmaps/IBeatmapModelManager.cs diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 250d6653d5..787559899a 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -32,7 +32,7 @@ namespace osu.Game.Beatmaps /// Handles ef-core storage of beatmaps. /// [ExcludeFromDynamicCompile] - public class BeatmapModelManager : ArchiveModelManager + public class BeatmapModelManager : ArchiveModelManager, IBeatmapModelManager { /// /// Fired when a single difficulty has been hidden. @@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps /// /// The game working beatmap cache, used to invalidate entries on changes. /// - public WorkingBeatmapCache WorkingBeatmapCache { private get; set; } + public IWorkingBeatmapCache WorkingBeatmapCache { private get; set; } private readonly Bindable> beatmapRestored = new Bindable>(); diff --git a/osu.Game/Beatmaps/IBeatmapModelManager.cs b/osu.Game/Beatmaps/IBeatmapModelManager.cs new file mode 100644 index 0000000000..8c243c2b77 --- /dev/null +++ b/osu.Game/Beatmaps/IBeatmapModelManager.cs @@ -0,0 +1,20 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Database; + +namespace osu.Game.Beatmaps +{ + public interface IBeatmapModelManager : IModelManager + { + /// + /// Provide an online lookup queue component to handle populating online beatmap metadata. + /// + BeatmapOnlineLookupQueue OnlineLookupQueue { set; } + + /// + /// Provide a working beatmap cache, used to invalidate entries on changes. + /// + IWorkingBeatmapCache WorkingBeatmapCache { set; } + } +} diff --git a/osu.Game/Beatmaps/IWorkingBeatmapCache.cs b/osu.Game/Beatmaps/IWorkingBeatmapCache.cs index 881e734292..3eb33f10d6 100644 --- a/osu.Game/Beatmaps/IWorkingBeatmapCache.cs +++ b/osu.Game/Beatmaps/IWorkingBeatmapCache.cs @@ -11,5 +11,17 @@ namespace osu.Game.Beatmaps /// The beatmap to lookup. /// A instance correlating to the provided . WorkingBeatmap GetWorkingBeatmap(BeatmapInfo beatmapInfo); + + /// + /// Invalidate a cache entry if it exists. + /// + /// The beatmap set info to invalidate any cached entries for. + void Invalidate(BeatmapSetInfo beatmapSetInfo); + + /// + /// Invalidate a cache entry if it exists. + /// + /// The beatmap info to invalidate any cached entries for. + void Invalidate(BeatmapInfo beatmapInfo); } } From 8ffaa491e7303289b35de86eb1fb85e3a9097e81 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 12:05:30 +0900 Subject: [PATCH 139/183] Fix `BeatmapModelManager` not receiving `WorkingBeatmapCache` --- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 91d5b16204..240db22c00 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -45,6 +45,7 @@ namespace osu.Game.Beatmaps workingBeatmapCache = CreateWorkingBeatmapCache(audioManager, resources, new FileStore(contextFactory, storage).Store, defaultBeatmap, host); workingBeatmapCache.BeatmapManager = beatmapModelManager; + beatmapModelManager.WorkingBeatmapCache = workingBeatmapCache; if (performOnlineLookups) { @@ -305,6 +306,9 @@ namespace osu.Game.Beatmaps public WorkingBeatmap GetWorkingBeatmap(BeatmapInfo importedBeatmap) => workingBeatmapCache.GetWorkingBeatmap(importedBeatmap); + void IWorkingBeatmapCache.Invalidate(BeatmapSetInfo beatmapSetInfo) => workingBeatmapCache.Invalidate(beatmapSetInfo); + void IWorkingBeatmapCache.Invalidate(BeatmapInfo beatmapInfo) => workingBeatmapCache.Invalidate(beatmapInfo); + #endregion #region Implementation of IModelFileManager From 90fdaf18c027ac08b3cd15e636105709dd6903e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 12:40:25 +0900 Subject: [PATCH 140/183] Fix `PushAndConfirm` potentially failing if new screen quickly pushes a child screen --- osu.Game/Tests/Visual/OsuGameTestScene.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index 881c4bab02..c025cf85c7 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -83,8 +83,17 @@ namespace osu.Game.Tests.Visual protected void PushAndConfirm(Func newScreen) { Screen screen = null; - AddStep("Push new screen", () => Game.ScreenStack.Push(screen = newScreen())); - AddUntilStep("Wait for new screen", () => Game.ScreenStack.CurrentScreen == screen && screen.IsLoaded); + IScreen previousScreen = null; + + AddStep("Push new screen", () => + { + previousScreen = Game.ScreenStack.CurrentScreen; + Game.ScreenStack.Push(screen = newScreen()); + }); + + AddUntilStep("Wait for new screen", () => screen.IsLoaded + && Game.ScreenStack.CurrentScreen != previousScreen + && previousScreen.GetChildScreen() == screen); } protected void ConfirmAtMainMenu() => AddUntilStep("Wait for main menu", () => Game.ScreenStack.CurrentScreen is MainMenu menu && menu.IsLoaded); From d9849bcf4995b69bf493d9dcaab88950c40f3a4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 13:14:39 +0900 Subject: [PATCH 141/183] Fix dragging on an editor file selection text box causing repeated popover display Local fix and no tests as this is a pretty weird usage of `TextBox`. We'll probably want to change it to not use a textbox eventually. Closes #14969. --- osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs index fd43349793..f833bc49f7 100644 --- a/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs +++ b/osu.Game/Screens/Edit/Setup/FileChooserLabelledTextBox.cs @@ -89,6 +89,13 @@ namespace osu.Game.Screens.Edit.Setup { public Action OnFocused; + protected override bool OnDragStart(DragStartEvent e) + { + // This text box is intended to be "read only" without actually specifying that. + // As such we don't want to allow the user to select its content with a drag. + return false; + } + protected override void OnFocus(FocusEvent e) { OnFocused?.Invoke(); From b339c149d86b076a34a5af3701eb751a6689034b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 2 Oct 2021 12:34:29 +0900 Subject: [PATCH 142/183] Copy `BaseDifficulty` to `Beatmap` and move all write operations across --- .../CatchSelectionBlueprintTestScene.cs | 2 +- ...TestSceneBananaShowerPlacementBlueprint.cs | 3 +- .../TestSceneJuiceStreamPlacementBlueprint.cs | 11 +++-- .../TestSceneJuiceStreamSelectionBlueprint.cs | 2 +- .../Beatmaps/CatchBeatmapProcessor.cs | 2 +- .../Difficulty/CatchDifficultyCalculator.cs | 6 +-- .../Edit/DrawableCatchEditorRuleset.cs | 2 +- .../UI/DrawableCatchRuleset.cs | 4 +- .../Beatmaps/ManiaBeatmapConverter.cs | 6 +-- .../Legacy/DistanceObjectPatternGenerator.cs | 2 +- .../Patterns/Legacy/PatternGenerator.cs | 2 +- .../Difficulty/ManiaDifficultyCalculator.cs | 2 +- .../UI/DrawableManiaRuleset.cs | 2 +- .../Editor/TestSceneOsuDistanceSnapGrid.cs | 2 +- .../TestSceneGameplayCursor.cs | 4 +- .../TestSceneMissHitWindowJudgements.cs | 4 +- .../TestSceneObjectOrderedHitPolicy.cs | 2 +- .../Difficulty/OsuDifficultyCalculator.cs | 4 +- .../Edit/Checks/CheckTooShortSpinners.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs | 2 +- .../Replays/OsuAutoGenerator.cs | 2 +- .../Statistics/AccuracyHeatmap.cs | 2 +- .../UI/Cursor/OsuCursorContainer.cs | 2 +- .../Beatmaps/TaikoBeatmapConverter.cs | 14 +++--- .../Difficulty/TaikoDifficultyCalculator.cs | 2 +- .../Scoring/TaikoHealthProcessor.cs | 4 +- .../Formats/LegacyBeatmapDecoderTest.cs | 2 +- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 4 +- ...tSceneHitObjectComposerDistanceSnapping.cs | 12 ++--- .../TestSceneDrainingHealthProcessor.cs | 4 +- .../TestSceneDrawableScrollingRuleset.cs | 4 +- .../Gameplay/TestSceneGameplayRewinding.cs | 2 +- .../TestScenePlaylistsRoomSubScreen.cs | 4 +- osu.Game/Beatmaps/Beatmap.cs | 49 +++++++++++++++---- .../Beatmaps/Formats/JsonBeatmapDecoder.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 6 +-- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 16 +++--- osu.Game/Beatmaps/IBeatmap.cs | 5 ++ osu.Game/Beatmaps/WorkingBeatmap.cs | 6 +-- .../Difficulty/DifficultyCalculator.cs | 7 +++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 2 +- osu.Game/Rulesets/Mods/DifficultyBindable.cs | 2 +- .../Scoring/DrainingHealthProcessor.cs | 2 +- .../UI/Scrolling/DrawableScrollingRuleset.cs | 6 +-- osu.Game/Screens/Edit/EditorBeatmap.cs | 8 ++- .../Screens/Edit/Setup/DifficultySection.cs | 16 +++--- .../Tests/Beatmaps/BeatmapConversionTest.cs | 2 + .../Visual/PlacementBlueprintTestScene.cs | 16 +++--- 49 files changed, 163 insertions(+), 108 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs index a458771550..7d806b119e 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor protected CatchSelectionBlueprintTestScene() { EditorBeatmap = new EditorBeatmap(new CatchBeatmap()); - EditorBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = 0; + EditorBeatmap.Difficulty.CircleSize = 0; EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 100 diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs index e3811b7669..cca3701a60 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneBananaShowerPlacementBlueprint.cs @@ -5,6 +5,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Catch.Edit.Blueprints; using osu.Game.Rulesets.Catch.Edit.Blueprints.Components; @@ -26,7 +27,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor protected override void AddHitObject(DrawableHitObject hitObject) { // Create nested bananas (but positions are not randomized because beatmap processing is not done). - hitObject.HitObject.ApplyDefaults(new ControlPointInfo(), Beatmap.Value.BeatmapInfo.BaseDifficulty); + hitObject.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); base.AddHitObject(hitObject); } diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs index cd1fa31b61..981efc9a13 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamPlacementBlueprint.cs @@ -4,9 +4,9 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; -using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Edit.Blueprints; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; @@ -23,11 +23,12 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor private JuiceStream lastObject => LastObject?.HitObject as JuiceStream; - [BackgroundDependencyLoader] - private void load() + protected override IBeatmap GetPlayableBeatmap() { - Beatmap.Value.BeatmapInfo.BaseDifficulty.SliderTickRate = 5; - Beatmap.Value.BeatmapInfo.BaseDifficulty.SliderMultiplier = velocity * 10; + var playable = base.GetPlayableBeatmap(); + playable.Difficulty.SliderTickRate = 5; + playable.Difficulty.SliderMultiplier = velocity * 10; + return playable; } [Test] diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs index 5e73a89069..155d033dd0 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/TestSceneJuiceStreamSelectionBlueprint.cs @@ -223,7 +223,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor X = x, Path = sliderPath, }; - EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = velocity; + EditorBeatmap.Difficulty.SliderMultiplier = velocity; EditorBeatmap.Add(hitObject); EditorBeatmap.Update(hitObject); Assert.That(hitObject.Velocity, Is.EqualTo(velocity)); diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index a891ec6c0a..87cc2c45e8 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -211,7 +211,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps palpableObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); - double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2; + double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.Difficulty) / 2; // Todo: This is wrong. osu!stable calculated hyperdashes using the full catcher size, excluding the margins. // This should theoretically cause impossible scenarios, but practically, likely due to the size of the playfield, it doesn't seem possible. diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs index 5b1f613f8d..03a76f10ef 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchDifficultyCalculator.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty return new CatchDifficultyAttributes { Mods = mods, Skills = skills }; // this is the same as osu!, so there's potential to share the implementation... maybe - double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; + double preempt = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; return new CatchDifficultyAttributes { @@ -69,10 +69,10 @@ namespace osu.Game.Rulesets.Catch.Difficulty protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) { - halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) * 0.5f; + halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.Difficulty) * 0.5f; // For circle sizes above 5.5, reduce the catcher width further to simulate imperfect gameplay. - halfCatcherWidth *= 1 - (Math.Max(0, beatmap.BeatmapInfo.BaseDifficulty.CircleSize - 5.5f) * 0.0625f); + halfCatcherWidth *= 1 - (Math.Max(0, beatmap.Difficulty.CircleSize - 5.5f) * 0.0625f); return new Skill[] { diff --git a/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs b/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs index 0344709d45..9a7528d90c 100644 --- a/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs +++ b/osu.Game.Rulesets.Catch/Edit/DrawableCatchEditorRuleset.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Catch.Edit { } - protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.BeatmapInfo.BaseDifficulty); + protected override Playfield CreatePlayfield() => new CatchEditorPlayfield(Beatmap.Difficulty); } } diff --git a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs index ba6e9224c9..a8ec9f1d2f 100644 --- a/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/UI/DrawableCatchRuleset.cs @@ -27,14 +27,14 @@ namespace osu.Game.Rulesets.Catch.UI : base(ruleset, beatmap, mods) { Direction.Value = ScrollingDirection.Down; - TimeRange.Value = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450); + TimeRange.Value = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450); } protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new CatchFramedReplayInputHandler(replay); protected override ReplayRecorder CreateReplayRecorder(Score score) => new CatchReplayRecorder(score, (CatchPlayfield)Playfield); - protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.BeatmapInfo.BaseDifficulty); + protected override Playfield CreatePlayfield() => new CatchPlayfield(Beatmap.Difficulty); public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new CatchPlayfieldAdjustmentContainer(); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 01e44ef9e4..ebfbaccd31 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -42,8 +42,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps { IsForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo); - var roundedCircleSize = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.CircleSize); - var roundedOverallDifficulty = Math.Round(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); + var roundedCircleSize = Math.Round(beatmap.Difficulty.CircleSize); + var roundedOverallDifficulty = Math.Round(beatmap.Difficulty.OverallDifficulty); if (IsForCurrentRuleset) { @@ -81,7 +81,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps protected override Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken) { - IBeatmapDifficultyInfo difficulty = original.BeatmapInfo.BaseDifficulty; + IBeatmapDifficultyInfo difficulty = original.Difficulty; int seed = (int)MathF.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)MathF.Round(difficulty.ApproachRate); Random = new FastRandom(seed); diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 26e5d381e2..380efff69f 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy StartTime = (int)Math.Round(hitObject.StartTime); // This matches stable's calculation. - EndTime = (int)Math.Floor(StartTime + distanceData.Distance * beatLength * SpanCount * 0.01 / beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier); + EndTime = (int)Math.Floor(StartTime + distanceData.Distance * beatLength * SpanCount * 0.01 / beatmap.Difficulty.SliderMultiplier); SegmentDuration = (EndTime - StartTime) / SpanCount; } diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs index d65e78bb49..eaf0ea0f2b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/PatternGenerator.cs @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (drainTime == 0) drainTime = 10000; - IBeatmapDifficultyInfo difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty; + IBeatmapDifficultyInfo difficulty = OriginalBeatmap.Difficulty; conversionDifficulty = ((difficulty.DrainRate + Math.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + (double)OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15; conversionDifficulty = Math.Min(conversionDifficulty.Value, 12); diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs index fc29eadedc..9140e8afce 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyCalculator.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty return new ManiaDifficultyAttributes { Mods = mods, Skills = skills }; HitWindows hitWindows = new ManiaHitWindows(); - hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); + hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); return new ManiaDifficultyAttributes { diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index 614a7b00c7..3b7da8d9ba 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Mania.UI { // Mania doesn't care about global velocity p.Velocity = 1; - p.BaseBeatLength *= Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier; + p.BaseBeatLength *= Beatmap.Difficulty.SliderMultiplier; // For non-mania beatmap, speed changes should only happen through timing points if (!isForCurrentRuleset) diff --git a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs index 9af2a99470..851be2b2f2 100644 --- a/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/Editor/TestSceneOsuDistanceSnapGrid.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor [SetUp] public void Setup() => Schedule(() => { - editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; + editorBeatmap.Difficulty.SliderMultiplier = 1; editorBeatmap.ControlPointInfo.Clear(); editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index 41d9bf7132..f09aad8b49 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddSliderStep("circle size", 0f, 10f, 0f, val => { config.SetValue(OsuSetting.AutoCursorSize, true); - gameplayState.Beatmap.BeatmapInfo.BaseDifficulty.CircleSize = val; + gameplayState.Beatmap.Difficulty.CircleSize = val; Scheduler.AddOnce(() => loadContent(false)); }); @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Osu.Tests public void TestSizing(int circleSize, float userScale) { AddStep($"set user scale to {userScale}", () => config.SetValue(OsuSetting.GameplayCursorSize, userScale)); - AddStep($"adjust cs to {circleSize}", () => gameplayState.Beatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSize); + AddStep($"adjust cs to {circleSize}", () => gameplayState.Beatmap.Difficulty.CircleSize = circleSize); AddStep("turn on autosizing", () => config.SetValue(OsuSetting.AutoCursorSize, true)); AddStep("load content", () => loadContent()); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs index af67ab5839..a5629119b6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests }; var hitWindows = new OsuHitWindows(); - hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); + hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); CreateModTest(new ModTestData { @@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Osu.Tests }; var hitWindows = new OsuHitWindows(); - hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); + hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); CreateModTest(new ModTestData { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs index cfce80a2b2..ececfb0586 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneObjectOrderedHitPolicy.cs @@ -400,9 +400,9 @@ namespace osu.Game.Rulesets.Osu.Tests Beatmap.Value = CreateWorkingBeatmap(new Beatmap { HitObjects = hitObjects, + Difficulty = new BeatmapDifficulty { SliderTickRate = 3 }, BeatmapInfo = { - BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 }, Ruleset = new OsuRuleset().RulesetInfo }, }); diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index a8f10f44dc..17a611817c 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty double starRating = basePerformance > 0.00001 ? Math.Cbrt(1.12) * 0.027 * (Math.Cbrt(100000 / Math.Pow(2, 1 / 1.1) * basePerformance) + 4) : 0; - double preempt = (int)IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450) / clockRate; + double preempt = (int)IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.ApproachRate, 1800, 1200, 450) / clockRate; int maxCombo = beatmap.HitObjects.Count; // Add the ticks + tail of the slider. 1 is subtracted because the head circle would be counted twice (once for the slider itself in the line above) @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty protected override Skill[] CreateSkills(IBeatmap beatmap, Mod[] mods, double clockRate) { HitWindows hitWindows = new OsuHitWindows(); - hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); + hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); // Todo: These int casts are temporary to achieve 1:1 results with osu!stable, and should be removed in the future hitWindowGreat = (int)(hitWindows.WindowFor(HitResult.Great)) / clockRate; diff --git a/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs b/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs index 0d0c3d9e69..f0aade1b7f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs +++ b/osu.Game.Rulesets.Osu/Edit/Checks/CheckTooShortSpinners.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Checks public IEnumerable Run(BeatmapVerifierContext context) { - double od = context.Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty; + double od = context.Beatmap.Difficulty.OverallDifficulty; // These are meant to reflect the duration necessary for auto to score at least 1000 points on the spinner. // It's difficult to eliminate warnings here, as auto achieving 1000 points depends on the approach angle on some spinners. diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs index b0c655b106..bf70a63ab5 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModTarget.cs @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Osu.Mods .Select(beat => { var newCircle = new HitCircle(); - newCircle.ApplyDefaults(controlPointInfo, osuBeatmap.BeatmapInfo.BaseDifficulty); + newCircle.ApplyDefaults(controlPointInfo, osuBeatmap.Difficulty); newCircle.StartTime = beat; return (OsuHitObject)newCircle; }).ToList(); diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index b88bf9108b..e231550e3e 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Replays : base(beatmap, mods) { defaultHitWindows = new OsuHitWindows(); - defaultHitWindows.SetDifficulty(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); + defaultHitWindows.SetDifficulty(Beatmap.Difficulty.OverallDifficulty); } #endregion diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index cb769c31b8..24a660e69e 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -179,7 +179,7 @@ namespace osu.Game.Rulesets.Osu.Statistics return; // Todo: This should probably not be done like this. - float radius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (playableBeatmap.BeatmapInfo.BaseDifficulty.CircleSize - 5) / 5) / 2; + float radius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (playableBeatmap.Difficulty.CircleSize - 5) / 5) / 2; foreach (var e in score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle))) { diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index cfe83d0106..d1d9ee9f4d 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor if (autoCursorScale.Value && state != null) { // if we have a beatmap available, let's get its circle size to figure out an automatic cursor scale modifier. - scale *= GetScaleForCircleSize(state.Beatmap.BeatmapInfo.BaseDifficulty.CircleSize); + scale *= GetScaleForCircleSize(state.Beatmap.Difficulty.CircleSize); } cursorScale.Value = scale; diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 3b5b972c01..643a656199 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -46,11 +46,11 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps protected override Beatmap ConvertBeatmap(IBeatmap original, CancellationToken cancellationToken) { - if (!(original.BeatmapInfo.BaseDifficulty is TaikoMutliplierAppliedDifficulty)) + if (!(original.Difficulty is TaikoMutliplierAppliedDifficulty)) { // Rewrite the beatmap info to add the slider velocity multiplier original.BeatmapInfo = original.BeatmapInfo.Clone(); - original.BeatmapInfo.BaseDifficulty = new TaikoMutliplierAppliedDifficulty(original.BeatmapInfo.BaseDifficulty); + original.Difficulty = new TaikoMutliplierAppliedDifficulty(original.Difficulty); } Beatmap converted = base.ConvertBeatmap(original, cancellationToken); @@ -108,7 +108,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps StartTime = obj.StartTime, Samples = obj.Samples, Duration = taikoDuration, - TickRate = beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate == 3 ? 3 : 4 + TickRate = beatmap.Difficulty.SliderTickRate == 3 ? 3 : 4 }; } @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps case IHasDuration endTimeData: { - double hitMultiplier = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; + double hitMultiplier = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; yield return new Swell { @@ -164,10 +164,10 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps else beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier; - double sliderScoringPointDistance = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate; + double sliderScoringPointDistance = osu_base_scoring_distance * beatmap.Difficulty.SliderMultiplier / beatmap.Difficulty.SliderTickRate; // The velocity and duration of the taiko hit object - calculated as the velocity of a drum roll. - double taikoVelocity = sliderScoringPointDistance * beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate; + double taikoVelocity = sliderScoringPointDistance * beatmap.Difficulty.SliderTickRate; taikoDuration = (int)(distance / taikoVelocity * beatLength); if (isForCurrentRuleset) @@ -183,7 +183,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps beatLength = timingPoint.BeatLength; // If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat - tickSpacing = Math.Min(beatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, (double)taikoDuration / spans); + tickSpacing = Math.Min(beatLength / beatmap.Difficulty.SliderTickRate, (double)taikoDuration / spans); return tickSpacing > 0 && distance / osuVelocity * 1000 < 2 * beatLength; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index 18d06c069f..e755bb2325 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty starRating = rescale(starRating); HitWindows hitWindows = new TaikoHitWindows(); - hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); + hitWindows.SetDifficulty(beatmap.Difficulty.OverallDifficulty); return new TaikoDifficultyAttributes { diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs index 94cd411d7b..0d6ce44255 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoHealthProcessor.cs @@ -40,8 +40,8 @@ namespace osu.Game.Rulesets.Taiko.Scoring { base.ApplyBeatmap(beatmap); - hpMultiplier = 1 / (object_count_factor * Math.Max(1, beatmap.HitObjects.OfType().Count()) * IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.5, 0.75, 0.98)); - hpMissMultiplier = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, 0.0018, 0.0075, 0.0120); + hpMultiplier = 1 / (object_count_factor * Math.Max(1, beatmap.HitObjects.OfType().Count()) * IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.5, 0.75, 0.98)); + hpMissMultiplier = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, 0.0018, 0.0075, 0.0120); } protected override double GetHealthIncreaseFor(JudgementResult result) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index a4bf8c92e3..b5e1fa204f 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -129,7 +129,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var resStream = TestResources.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) using (var stream = new LineBufferedReader(resStream)) { - var difficulty = decoder.Decode(stream).BeatmapInfo.BaseDifficulty; + var difficulty = decoder.Decode(stream).Difficulty; Assert.AreEqual(6.5f, difficulty.DrainRate); Assert.AreEqual(4, difficulty.CircleSize); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index 1fc3abef9a..9ec2f37569 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -84,7 +84,7 @@ namespace osu.Game.Tests.Beatmaps.Formats public void TestDecodeDifficulty() { var beatmap = decodeAsJson(normal); - var difficulty = beatmap.BeatmapInfo.BaseDifficulty; + var difficulty = beatmap.Difficulty; Assert.AreEqual(6.5f, difficulty.DrainRate); Assert.AreEqual(4, difficulty.CircleSize); Assert.AreEqual(8, difficulty.OverallDifficulty); @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Beatmaps.Formats processor.PreProcess(); foreach (var o in converted.HitObjects) - o.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty); + o.ApplyDefaults(converted.ControlPointInfo, converted.Difficulty); processor.PostProcess(); var beatmap = converted.Serialize().Deserialize(); diff --git a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs index bd34eaff63..a40a6dac4c 100644 --- a/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editing/TestSceneHitObjectComposerDistanceSnapping.cs @@ -53,7 +53,7 @@ namespace osu.Game.Tests.Editing BeatDivisor.Value = 1; - composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; + composer.EditorBeatmap.Difficulty.SliderMultiplier = 1; composer.EditorBeatmap.ControlPointInfo.Clear(); composer.EditorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 1 }); @@ -64,7 +64,7 @@ namespace osu.Game.Tests.Editing [TestCase(2)] public void TestSliderMultiplier(float multiplier) { - AddStep($"set multiplier = {multiplier}", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = multiplier); + AddStep($"set multiplier = {multiplier}", () => composer.EditorBeatmap.Difficulty.SliderMultiplier = multiplier); assertSnapDistance(100 * multiplier); } @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Editing assertDurationToDistance(500, 50); assertDurationToDistance(1000, 100); - AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2); + AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.Difficulty.SliderMultiplier = 2); assertDurationToDistance(500, 100); assertDurationToDistance(1000, 200); @@ -118,7 +118,7 @@ namespace osu.Game.Tests.Editing assertDistanceToDuration(50, 500); assertDistanceToDuration(100, 1000); - AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2); + AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.Difficulty.SliderMultiplier = 2); assertDistanceToDuration(100, 500); assertDistanceToDuration(200, 1000); @@ -143,7 +143,7 @@ namespace osu.Game.Tests.Editing assertSnappedDuration(200, 2000); assertSnappedDuration(250, 3000); - AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2); + AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.Difficulty.SliderMultiplier = 2); assertSnappedDuration(0, 0); assertSnappedDuration(50, 0); @@ -175,7 +175,7 @@ namespace osu.Game.Tests.Editing assertSnappedDistance(200, 200); assertSnappedDistance(250, 200); - AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2); + AddStep("set slider multiplier = 2", () => composer.EditorBeatmap.Difficulty.SliderMultiplier = 2); assertSnappedDistance(50, 0); assertSnappedDistance(100, 0); diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs index 7264083338..296c5cef76 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs @@ -165,7 +165,7 @@ namespace osu.Game.Tests.Gameplay { var beatmap = new Beatmap { - BeatmapInfo = { BaseDifficulty = { DrainRate = 10 } }, + Difficulty = { DrainRate = 10 } }; beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = 0 }); @@ -200,7 +200,7 @@ namespace osu.Game.Tests.Gameplay { var beatmap = new Beatmap { - BeatmapInfo = { BaseDifficulty = { DrainRate = 10 } }, + Difficulty = { DrainRate = 10 } }; for (double time = startTime; time <= endTime; time += 100) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 75a5eec6f7..3f10d7892d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -182,7 +182,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var beatmap = createBeatmap(); beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); - beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2; + beatmap.Difficulty.SliderMultiplier = 2; createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); AddStep("adjust time range", () => drawableRuleset.TimeRange.Value = 5000); @@ -196,7 +196,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var beatmap = createBeatmap(); beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); - beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2; + beatmap.Difficulty.SliderMultiplier = 2; createTest(beatmap); AddStep("adjust time range", () => drawableRuleset.TimeRange.Value = 2000); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs index 73c6970482..814b41cdbc 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var beatmap = new Beatmap { - BeatmapInfo = { BaseDifficulty = { ApproachRate = 9 } }, + Difficulty = { ApproachRate = 9 }, }; for (int i = 0; i < 15; i++) diff --git a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs index d8ec89a94e..deb9a22184 100644 --- a/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs +++ b/osu.Game.Tests/Visual/Playlists/TestScenePlaylistsRoomSubScreen.cs @@ -137,11 +137,11 @@ namespace osu.Game.Tests.Visual.Playlists InputManager.Click(MouseButton.Left); }); - AddAssert("match has altered beatmap", () => match.Beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty.CircleSize == 1); + AddAssert("match has altered beatmap", () => match.Beatmap.Value.Beatmap.Difficulty.CircleSize == 1); AddStep("re-import original beatmap", () => manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait()); - AddAssert("match has original beatmap", () => match.Beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty.CircleSize != 1); + AddAssert("match has original beatmap", () => match.Beatmap.Value.Beatmap.Difficulty.CircleSize != 1); } private class TestPlaylistsRoomSubScreen : PlaylistsRoomSubScreen diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index e5b6a4bc44..f2a60c4711 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -18,17 +18,48 @@ namespace osu.Game.Beatmaps public class Beatmap : IBeatmap where T : HitObject { - public BeatmapInfo BeatmapInfo { get; set; } = new BeatmapInfo + private BeatmapDifficulty difficulty = new BeatmapDifficulty(); + + public BeatmapDifficulty Difficulty { - Metadata = new BeatmapMetadata + get => difficulty; + set { - Artist = @"Unknown", - Title = @"Unknown", - AuthorString = @"Unknown Creator", - }, - Version = @"Normal", - BaseDifficulty = new BeatmapDifficulty() - }; + difficulty = value; + + if (beatmapInfo != null) + beatmapInfo.BaseDifficulty = new BeatmapDifficulty(difficulty); + } + } + + private BeatmapInfo beatmapInfo; + + public BeatmapInfo BeatmapInfo + { + get => beatmapInfo; + set + { + beatmapInfo = value; + + if (beatmapInfo != null) + Difficulty = new BeatmapDifficulty(beatmapInfo.BaseDifficulty); + } + } + + public Beatmap() + { + beatmapInfo = new BeatmapInfo + { + Metadata = new BeatmapMetadata + { + Artist = @"Unknown", + Title = @"Unknown", + AuthorString = @"Unknown Creator", + }, + Version = @"Normal", + BaseDifficulty = Difficulty, + }; + } [JsonIgnore] public BeatmapMetadata Metadata => BeatmapInfo?.Metadata ?? BeatmapInfo?.BeatmapSet?.Metadata; diff --git a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs index 988968fa42..0d5c48f64d 100644 --- a/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/JsonBeatmapDecoder.cs @@ -18,7 +18,7 @@ namespace osu.Game.Beatmaps.Formats stream.ReadToEnd().DeserializeInto(output); foreach (var hitObject in output.HitObjects) - hitObject.ApplyDefaults(output.ControlPointInfo, output.BeatmapInfo.BaseDifficulty); + hitObject.ApplyDefaults(output.ControlPointInfo, output.Difficulty); } } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 4b5eaafa4a..f71b148008 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -67,7 +67,7 @@ namespace osu.Game.Beatmaps.Formats this.beatmap.HitObjects = this.beatmap.HitObjects.OrderBy(h => h.StartTime).ToList(); foreach (var hitObject in this.beatmap.HitObjects) - hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.BeatmapInfo.BaseDifficulty); + hitObject.ApplyDefaults(this.beatmap.ControlPointInfo, this.beatmap.Difficulty); } protected override bool ShouldSkipLine(string line) => base.ShouldSkipLine(line) || line.StartsWith(' ') || line.StartsWith('_'); @@ -276,7 +276,7 @@ namespace osu.Game.Beatmaps.Formats { var pair = SplitKeyVal(line); - var difficulty = beatmap.BeatmapInfo.BaseDifficulty; + var difficulty = beatmap.Difficulty; switch (pair.Key) { @@ -444,7 +444,7 @@ namespace osu.Game.Beatmaps.Formats if (obj != null) { - obj.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); + obj.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); beatmap.HitObjects.Add(obj); } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index aef13b8872..74b3c178cd 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -141,17 +141,17 @@ namespace osu.Game.Beatmaps.Formats { writer.WriteLine("[Difficulty]"); - writer.WriteLine(FormattableString.Invariant($"HPDrainRate: {beatmap.BeatmapInfo.BaseDifficulty.DrainRate}")); - writer.WriteLine(FormattableString.Invariant($"CircleSize: {beatmap.BeatmapInfo.BaseDifficulty.CircleSize}")); - writer.WriteLine(FormattableString.Invariant($"OverallDifficulty: {beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty}")); - writer.WriteLine(FormattableString.Invariant($"ApproachRate: {beatmap.BeatmapInfo.BaseDifficulty.ApproachRate}")); + writer.WriteLine(FormattableString.Invariant($"HPDrainRate: {beatmap.Difficulty.DrainRate}")); + writer.WriteLine(FormattableString.Invariant($"CircleSize: {beatmap.Difficulty.CircleSize}")); + writer.WriteLine(FormattableString.Invariant($"OverallDifficulty: {beatmap.Difficulty.OverallDifficulty}")); + writer.WriteLine(FormattableString.Invariant($"ApproachRate: {beatmap.Difficulty.ApproachRate}")); // Taiko adjusts the slider multiplier (see: LEGACY_TAIKO_VELOCITY_MULTIPLIER) writer.WriteLine(beatmap.BeatmapInfo.RulesetID == 1 - ? FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / LEGACY_TAIKO_VELOCITY_MULTIPLIER}") - : FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier}")); + ? FormattableString.Invariant($"SliderMultiplier: {beatmap.Difficulty.SliderMultiplier / LEGACY_TAIKO_VELOCITY_MULTIPLIER}") + : FormattableString.Invariant($"SliderMultiplier: {beatmap.Difficulty.SliderMultiplier}")); - writer.WriteLine(FormattableString.Invariant($"SliderTickRate: {beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate}")); + writer.WriteLine(FormattableString.Invariant($"SliderTickRate: {beatmap.Difficulty.SliderTickRate}")); } private void handleEvents(TextWriter writer) @@ -285,7 +285,7 @@ namespace osu.Game.Beatmaps.Formats break; case 3: - int totalColumns = (int)Math.Max(1, beatmap.BeatmapInfo.BaseDifficulty.CircleSize); + int totalColumns = (int)Math.Max(1, beatmap.Difficulty.CircleSize); position.X = (int)Math.Ceiling(((IHasXPosition)hitObject).X * (512f / totalColumns)); break; } diff --git a/osu.Game/Beatmaps/IBeatmap.cs b/osu.Game/Beatmaps/IBeatmap.cs index f61dd269e1..3f598cd1e5 100644 --- a/osu.Game/Beatmaps/IBeatmap.cs +++ b/osu.Game/Beatmaps/IBeatmap.cs @@ -20,6 +20,11 @@ namespace osu.Game.Beatmaps /// BeatmapMetadata Metadata { get; } + /// + /// This beatmap's difficulty settings. + /// + public BeatmapDifficulty Difficulty { get; set; } + /// /// The control points in this beatmap. /// diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index c4c5c89f28..c7ccfe976a 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -120,14 +120,14 @@ namespace osu.Game.Beatmaps if (mods.Any(m => m is IApplicableToDifficulty)) { converted.BeatmapInfo = converted.BeatmapInfo.Clone(); - converted.BeatmapInfo.BaseDifficulty = converted.BeatmapInfo.BaseDifficulty.Clone(); + converted.Difficulty = converted.Difficulty.Clone(); foreach (var mod in mods.OfType()) { if (cancellationSource.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); - mod.ApplyToDifficulty(converted.BeatmapInfo.BaseDifficulty); + mod.ApplyToDifficulty(converted.Difficulty); } } @@ -146,7 +146,7 @@ namespace osu.Game.Beatmaps if (cancellationSource.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); - obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty, cancellationSource.Token); + obj.ApplyDefaults(converted.ControlPointInfo, converted.Difficulty, cancellationSource.Token); } } catch (OperationCanceledException) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index a7c4790366..7e3e5d5417 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -257,6 +257,13 @@ namespace osu.Game.Rulesets.Difficulty } public BeatmapMetadata Metadata => baseBeatmap.Metadata; + + public BeatmapDifficulty Difficulty + { + get => baseBeatmap.Difficulty; + set => baseBeatmap.Difficulty = value; + } + public List Breaks => baseBeatmap.Breaks; public double TotalBreakTime => baseBeatmap.TotalBreakTime; public IEnumerable GetStatistics() => baseBeatmap.GetStatistics(); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 8090fcbd32..b41e0442bc 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -392,7 +392,7 @@ namespace osu.Game.Rulesets.Edit public override float GetBeatSnapDistanceAt(double referenceTime) { DifficultyControlPoint difficultyPoint = EditorBeatmap.ControlPointInfo.DifficultyPointAt(referenceTime); - return (float)(100 * EditorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / BeatSnapProvider.BeatDivisor); + return (float)(100 * EditorBeatmap.Difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / BeatSnapProvider.BeatDivisor); } public override float DurationToDistance(double referenceTime, double duration) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index b7529f39ca..0c0c5990d1 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -112,7 +112,7 @@ namespace osu.Game.Rulesets.Edit /// Invokes , /// refreshing and parameters for the . /// - protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.BaseDifficulty); + protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.Difficulty); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Parent?.ReceivePositionalInputAt(screenSpacePos) ?? false; diff --git a/osu.Game/Rulesets/Mods/DifficultyBindable.cs b/osu.Game/Rulesets/Mods/DifficultyBindable.cs index e4304795f2..6cfae0b085 100644 --- a/osu.Game/Rulesets/Mods/DifficultyBindable.cs +++ b/osu.Game/Rulesets/Mods/DifficultyBindable.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mods /// /// A function that can extract the current value of this setting from a beatmap difficulty for display purposes. /// - public Func ReadCurrentFromDifficulty; + public Func ReadCurrentFromDifficulty; public float Precision { diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index 85693abb93..dfeb6b4788 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets.Scoring .First() ))); - targetMinimumHealth = IBeatmapDifficultyInfo.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, min_health_target, mid_health_target, max_health_target); + targetMinimumHealth = IBeatmapDifficultyInfo.DifficultyRange(beatmap.Difficulty.DrainRate, min_health_target, mid_health_target, max_health_target); // Add back a portion of the amount of HP to be drained, depending on the lenience requested. targetMinimumHealth += drainLenience * (1 - targetMinimumHealth); diff --git a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs index 7b30bb9574..041c5ebef5 100644 --- a/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs +++ b/osu.Game/Rulesets/UI/Scrolling/DrawableScrollingRuleset.cs @@ -133,7 +133,7 @@ namespace osu.Game.Rulesets.UI.Scrolling maxDuration = duration; // The slider multiplier is post-multiplied to determine the final velocity, but for relative scale beat lengths // the multiplier should not affect the effective timing point (the longest in the beatmap), so it is factored out here - baseBeatLength = timingPoints[i].BeatLength / Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier; + baseBeatLength = timingPoints[i].BeatLength / Beatmap.Difficulty.SliderMultiplier; } } } @@ -155,7 +155,7 @@ namespace osu.Game.Rulesets.UI.Scrolling return new MultiplierControlPoint(c.Time) { - Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier, + Velocity = Beatmap.Difficulty.SliderMultiplier, BaseBeatLength = baseBeatLength, TimingPoint = lastTimingPoint, DifficultyPoint = lastDifficultyPoint @@ -172,7 +172,7 @@ namespace osu.Game.Rulesets.UI.Scrolling ControlPoints.AddRange(timingChanges); if (ControlPoints.Count == 0) - ControlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier }); + ControlPoints.Add(new MultiplierControlPoint { Velocity = Beatmap.Difficulty.SliderMultiplier }); } protected override void LoadComplete() diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 3402bf653a..64eb6225fa 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -86,6 +86,12 @@ namespace osu.Game.Screens.Edit public BeatmapMetadata Metadata => PlayableBeatmap.Metadata; + public BeatmapDifficulty Difficulty + { + get => PlayableBeatmap.Difficulty; + set => PlayableBeatmap.Difficulty = value; + } + public ControlPointInfo ControlPointInfo { get => PlayableBeatmap.ControlPointInfo; @@ -286,7 +292,7 @@ namespace osu.Game.Screens.Edit /// public void Clear() => RemoveRange(HitObjects.ToArray()); - private void processHitObject(HitObject hitObject) => hitObject.ApplyDefaults(ControlPointInfo, BeatmapInfo.BaseDifficulty); + private void processHitObject(HitObject hitObject) => hitObject.ApplyDefaults(ControlPointInfo, PlayableBeatmap.Difficulty); private void trackStartTime(HitObject hitObject) { diff --git a/osu.Game/Screens/Edit/Setup/DifficultySection.cs b/osu.Game/Screens/Edit/Setup/DifficultySection.cs index a8800d524f..75c6a89a66 100644 --- a/osu.Game/Screens/Edit/Setup/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Setup/DifficultySection.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.Edit.Setup Label = "Object Size", FixedLabelWidth = LABEL_WIDTH, Description = "The size of all hit objects", - Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.CircleSize) + Current = new BindableFloat(Beatmap.Difficulty.CircleSize) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Edit.Setup Label = "Health Drain", FixedLabelWidth = LABEL_WIDTH, Description = "The rate of passive health drain throughout playable time", - Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.DrainRate) + Current = new BindableFloat(Beatmap.Difficulty.DrainRate) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Edit.Setup Label = "Approach Rate", FixedLabelWidth = LABEL_WIDTH, Description = "The speed at which objects are presented to the player", - Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate) + Current = new BindableFloat(Beatmap.Difficulty.ApproachRate) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Edit.Setup Label = "Overall Difficulty", FixedLabelWidth = LABEL_WIDTH, Description = "The harshness of hit windows and difficulty of special objects (ie. spinners)", - Current = new BindableFloat(Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty) + Current = new BindableFloat(Beatmap.Difficulty.OverallDifficulty) { Default = BeatmapDifficulty.DEFAULT_DIFFICULTY, MinValue = 0, @@ -87,10 +87,10 @@ namespace osu.Game.Screens.Edit.Setup { // for now, update these on commit rather than making BeatmapMetadata bindables. // after switching database engines we can reconsider if switching to bindables is a good direction. - Beatmap.BeatmapInfo.BaseDifficulty.CircleSize = circleSizeSlider.Current.Value; - Beatmap.BeatmapInfo.BaseDifficulty.DrainRate = healthDrainSlider.Current.Value; - Beatmap.BeatmapInfo.BaseDifficulty.ApproachRate = approachRateSlider.Current.Value; - Beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty = overallDifficultySlider.Current.Value; + Beatmap.Difficulty.CircleSize = circleSizeSlider.Current.Value; + Beatmap.Difficulty.DrainRate = healthDrainSlider.Current.Value; + Beatmap.Difficulty.ApproachRate = approachRateSlider.Current.Value; + Beatmap.Difficulty.OverallDifficulty = overallDifficultySlider.Current.Value; Beatmap.UpdateAllHitObjects(); } diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 64f1ee4a7a..54fb287661 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -111,6 +111,8 @@ namespace osu.Game.Tests.Beatmaps var converterResult = new Dictionary>(); + beatmap.BeatmapInfo.BaseDifficulty = beatmap.Difficulty; + var working = new ConversionWorkingBeatmap(beatmap) { ConversionGenerated = (o, r, c) => diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index 42cf826bd4..b2f5b1754f 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -24,24 +25,25 @@ namespace osu.Game.Tests.Visual base.Content.Add(HitObjectContainer = CreateHitObjectContainer().With(c => c.Clock = new FramedClock(new StopwatchClock()))); } - [BackgroundDependencyLoader] - private void load() - { - Beatmap.Value.BeatmapInfo.BaseDifficulty.CircleSize = 2; - } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.CacheAs(new EditorClock()); - var playable = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); + var playable = GetPlayableBeatmap(); dependencies.CacheAs(new EditorBeatmap(playable)); return dependencies; } + protected virtual IBeatmap GetPlayableBeatmap() + { + var playable = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); + playable.Difficulty.CircleSize = 2; + return playable; + } + protected override void LoadComplete() { base.LoadComplete(); From 0ab8dcc2a0cc227c22f3a2d6eb9fa8756a2d542c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 15:10:42 +0900 Subject: [PATCH 143/183] Fix taiko weird difficulty multiplier failing on double convert --- .../Beatmaps/TaikoBeatmapConverter.cs | 27 ++++++++++++++-- osu.Game/Beatmaps/Beatmap.cs | 6 ++-- osu.Game/Beatmaps/BeatmapDifficulty.cs | 31 ++++++++++--------- 3 files changed, 43 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 643a656199..ef8cf5cb53 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -10,6 +10,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Utils; using System.Threading; +using JetBrains.Annotations; using osu.Game.Audio; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Formats; @@ -49,7 +50,6 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps if (!(original.Difficulty is TaikoMutliplierAppliedDifficulty)) { // Rewrite the beatmap info to add the slider velocity multiplier - original.BeatmapInfo = original.BeatmapInfo.Clone(); original.Difficulty = new TaikoMutliplierAppliedDifficulty(original.Difficulty); } @@ -196,9 +196,30 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps public TaikoMutliplierAppliedDifficulty(IBeatmapDifficultyInfo difficulty) { CopyFrom(difficulty); - - SliderMultiplier *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; } + + [UsedImplicitly] + public TaikoMutliplierAppliedDifficulty() + { + } + + #region Overrides of BeatmapDifficulty + + public override void CopyTo(BeatmapDifficulty other) + { + base.CopyTo(other); + if (!(other is TaikoMutliplierAppliedDifficulty)) + SliderMultiplier /= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; + } + + public override void CopyFrom(IBeatmapDifficultyInfo other) + { + base.CopyFrom(other); + if (!(other is TaikoMutliplierAppliedDifficulty)) + SliderMultiplier *= LegacyBeatmapEncoder.LEGACY_TAIKO_VELOCITY_MULTIPLIER; + } + + #endregion } } } diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index f2a60c4711..b2211e26cf 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -28,7 +28,7 @@ namespace osu.Game.Beatmaps difficulty = value; if (beatmapInfo != null) - beatmapInfo.BaseDifficulty = new BeatmapDifficulty(difficulty); + beatmapInfo.BaseDifficulty = difficulty.Clone(); } } @@ -41,8 +41,8 @@ namespace osu.Game.Beatmaps { beatmapInfo = value; - if (beatmapInfo != null) - Difficulty = new BeatmapDifficulty(beatmapInfo.BaseDifficulty); + if (beatmapInfo?.BaseDifficulty != null) + Difficulty = beatmapInfo.BaseDifficulty.Clone(); } } diff --git a/osu.Game/Beatmaps/BeatmapDifficulty.cs b/osu.Game/Beatmaps/BeatmapDifficulty.cs index 2bb0787b4c..dfd21469fa 100644 --- a/osu.Game/Beatmaps/BeatmapDifficulty.cs +++ b/osu.Game/Beatmaps/BeatmapDifficulty.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Game.Database; namespace osu.Game.Beatmaps @@ -43,31 +44,31 @@ namespace osu.Game.Beatmaps /// public BeatmapDifficulty Clone() { - var diff = new BeatmapDifficulty(); + var diff = (BeatmapDifficulty)Activator.CreateInstance(GetType()); CopyTo(diff); return diff; } - public void CopyFrom(IBeatmapDifficultyInfo difficulty) + public virtual void CopyFrom(IBeatmapDifficultyInfo other) { - ApproachRate = difficulty.ApproachRate; - DrainRate = difficulty.DrainRate; - CircleSize = difficulty.CircleSize; - OverallDifficulty = difficulty.OverallDifficulty; + ApproachRate = other.ApproachRate; + DrainRate = other.DrainRate; + CircleSize = other.CircleSize; + OverallDifficulty = other.OverallDifficulty; - SliderMultiplier = difficulty.SliderMultiplier; - SliderTickRate = difficulty.SliderTickRate; + SliderMultiplier = other.SliderMultiplier; + SliderTickRate = other.SliderTickRate; } - public void CopyTo(BeatmapDifficulty difficulty) + public virtual void CopyTo(BeatmapDifficulty other) { - difficulty.ApproachRate = ApproachRate; - difficulty.DrainRate = DrainRate; - difficulty.CircleSize = CircleSize; - difficulty.OverallDifficulty = OverallDifficulty; + other.ApproachRate = ApproachRate; + other.DrainRate = DrainRate; + other.CircleSize = CircleSize; + other.OverallDifficulty = OverallDifficulty; - difficulty.SliderMultiplier = SliderMultiplier; - difficulty.SliderTickRate = SliderTickRate; + other.SliderMultiplier = SliderMultiplier; + other.SliderTickRate = SliderTickRate; } } } From 222997f2b9bce92d37364d188d115f8a0995cfaf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 15:18:21 +0900 Subject: [PATCH 144/183] Copy difficulty settings back out to `BeatmapInfo` on editor changes --- osu.Game/Screens/Edit/Editor.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 2ff0101dc0..b8a2d3612e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -332,6 +332,8 @@ namespace osu.Game.Screens.Edit // no longer new after first user-triggered save. isNewBeatmap = false; + playableBeatmap.BeatmapInfo.BaseDifficulty.CopyFrom(playableBeatmap.Difficulty); + // apply any set-level metadata changes. beatmapManager.Update(playableBeatmap.BeatmapInfo.BeatmapSet); From 3803f2f4624293b7c948a4ee9b6eb3211572a853 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 16:07:27 +0900 Subject: [PATCH 145/183] Fix leaderboard potentially displaying the wrong scores Closes #14762. This class is ugly. I think the whole process should be clened up once we have correctly-scheduled `SynchronizationContext`s. There's not much saving it as long as all these interdispersed `Schedule`s around required. --- osu.Game/Online/Leaderboards/Leaderboard.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index 4f8b27602b..e3ac9f603d 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -255,6 +255,7 @@ namespace osu.Game.Online.Leaderboards } private APIRequest getScoresRequest; + private ScheduledDelegate getScoresRequestCallback; protected abstract bool IsOnlineScope { get; } @@ -282,13 +283,16 @@ namespace osu.Game.Online.Leaderboards getScoresRequest?.Cancel(); getScoresRequest = null; + getScoresRequestCallback?.Cancel(); + getScoresRequestCallback = null; + pendingUpdateScores?.Cancel(); pendingUpdateScores = Schedule(() => { PlaceholderState = PlaceholderState.Retrieving; loading.Show(); - getScoresRequest = FetchScores(scores => Schedule(() => + getScoresRequest = FetchScores(scores => getScoresRequestCallback = Schedule(() => { Scores = scores.ToArray(); PlaceholderState = Scores.Any() ? PlaceholderState.Successful : PlaceholderState.NoScores; @@ -297,7 +301,7 @@ namespace osu.Game.Online.Leaderboards if (getScoresRequest == null) return; - getScoresRequest.Failure += e => Schedule(() => + getScoresRequest.Failure += e => getScoresRequestCallback = Schedule(() => { if (e is OperationCanceledException) return; From 456cfd62bf50fb58da56594555fa338be0380f14 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Oct 2021 16:46:24 +0900 Subject: [PATCH 146/183] Fix intermittent score panel test failure --- osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index 6f3b3028be..b7b7407428 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -221,6 +221,8 @@ namespace osu.Game.Tests.Visual.Ranking list.SelectedScore.Value = middleScore; }); + AddUntilStep("wait for all scores to be visible", () => list.ChildrenOfType().All(t => t.IsPresent)); + assertScoreState(highestScore, false); assertScoreState(middleScore, true); assertScoreState(lowestScore, false); From fc0c3e8758a5e3817881069da5a26e27b80c557e Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 6 Oct 2021 17:32:28 +0900 Subject: [PATCH 147/183] Attach filters at load time --- osu.Game.Tests/Visual/Audio/TestSceneFilter.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Audio/TestSceneFilter.cs b/osu.Game.Tests/Visual/Audio/TestSceneFilter.cs index 79d8f7da8c..c639da63d3 100644 --- a/osu.Game.Tests/Visual/Audio/TestSceneFilter.cs +++ b/osu.Game.Tests/Visual/Audio/TestSceneFilter.cs @@ -18,7 +18,6 @@ namespace osu.Game.Tests.Visual.Audio public class TestSceneFilter : OsuTestScene { private WorkingBeatmap testBeatmap; - private OsuSpriteText lowpassText; private OsuSpriteText highpassText; private Filter lowpassFilter; @@ -28,12 +27,12 @@ namespace osu.Game.Tests.Visual.Audio private void load(AudioManager audio) { testBeatmap = new WaveformTestBeatmap(audio); - lowpassFilter = new Filter(audio.TrackMixer); - highpassFilter = new Filter(audio.TrackMixer, BQFType.HighPass); Add(new FillFlowContainer { Children = new Drawable[] { + lowpassFilter = new Filter(audio.TrackMixer), + highpassFilter = new Filter(audio.TrackMixer, BQFType.HighPass), lowpassText = new OsuSpriteText { Padding = new MarginPadding(20), @@ -74,15 +73,8 @@ namespace osu.Game.Tests.Visual.Audio private void testFilter(Filter filter, int cutoffFrom, int cutoffTo) { - Add(filter); - AddStep("Prepare Track", () => - { - testBeatmap.LoadTrack(); - }); - AddStep("Play Track", () => - { - testBeatmap.Track.Start(); - }); + AddStep("Load Track", () => testBeatmap.LoadTrack()); + AddStep("Play Track", () => testBeatmap.Track.Start()); AddWaitStep("Let track play", 10); AddStep("Filter Sweep", () => { From bd0c98614435d91526409b78f1de14f18a00a3da Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 6 Oct 2021 17:34:24 +0900 Subject: [PATCH 148/183] Add asserts to ensure correct state when attaching/detaching the filter --- osu.Game/Audio/Effects/Filter.cs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game/Audio/Effects/Filter.cs b/osu.Game/Audio/Effects/Filter.cs index 00b617ffa1..2036851cad 100644 --- a/osu.Game/Audio/Effects/Filter.cs +++ b/osu.Game/Audio/Effects/Filter.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using ManagedBass.Fx; using osu.Framework.Audio.Mixing; using osu.Framework.Bindables; @@ -44,7 +45,7 @@ namespace osu.Game.Audio.Effects break; } - Cutoff = new BindableNumber + Cutoff = new BindableNumber(initialCutoff) { MinValue = 1, MaxValue = MaxCutoff @@ -57,14 +58,24 @@ namespace osu.Game.Audio.Effects fQ = 0.7f // This allows fCenter to go up to 22049hz (nyquist - 1hz) without overflowing and causing weird filter behaviour (see: https://www.un4seen.com/forum/?topic=19542.0) }; - attachFilter(); + // Don't start attached if this is low-pass or high-pass filter (as they have special auto-attach/detach logic) + if (type != BQFType.LowPass && type != BQFType.HighPass) + attachFilter(); + Cutoff.ValueChanged += updateFilter; - Cutoff.Value = initialCutoff; } - private void attachFilter() => mixer.Effects.Add(filter); + private void attachFilter() + { + Debug.Assert(!mixer.Effects.Contains(filter)); + mixer.Effects.Add(filter); + } - private void detachFilter() => mixer.Effects.Remove(filter); + private void detachFilter() + { + Debug.Assert(mixer.Effects.Contains(filter)); + mixer.Effects.Remove(filter); + } private void updateFilter(ValueChangedEvent cutoff) { From 99fb86878e42a75d162a2742b45f3c6f9779f79b Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Wed, 6 Oct 2021 18:31:56 +0900 Subject: [PATCH 149/183] Only detach filter on disposal if attached --- osu.Game/Audio/Effects/Filter.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Audio/Effects/Filter.cs b/osu.Game/Audio/Effects/Filter.cs index 2036851cad..428a69bb19 100644 --- a/osu.Game/Audio/Effects/Filter.cs +++ b/osu.Game/Audio/Effects/Filter.cs @@ -118,7 +118,9 @@ namespace osu.Game.Audio.Effects protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - detachFilter(); + + if (mixer.Effects.Contains(filter)) + detachFilter(); } } } From 34269d48e5e41a26d2e94e6b367ee6a583cf649e Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 6 Oct 2021 12:25:19 +0100 Subject: [PATCH 150/183] Use global multipliers instead of multiplying skill values --- .../Difficulty/OsuPerformanceCalculator.cs | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 62e0dea4fa..9d5887c26e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -40,11 +40,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty countMeh = Score.Statistics.GetValueOrDefault(HitResult.Meh); countMiss = Score.Statistics.GetValueOrDefault(HitResult.Miss); - if (mods.Any(h => h is OsuModRelax)) - { - countMiss += countOk + countMeh; - } - // Custom multipliers for NoFail and SpunOut. double multiplier = 1.12; // This is being adjusted to keep the final pp value scaled around what it used to be when changing things. @@ -54,6 +49,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (mods.Any(m => m is OsuModSpunOut)) multiplier *= 1.0 - Math.Pow((double)Attributes.SpinnerCount / totalHits, 0.85); + if (mods.Any(h => h is OsuModRelax)) + { + countMiss += countOk + countMeh; + multiplier *= 0.6; + } + double aimValue = computeAimValue(); double speedValue = computeSpeedValue(); double accuracyValue = computeAccuracyValue(); @@ -95,11 +96,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty aimValue *= lengthBonus; - if (mods.Any(h => h is OsuModRelax)) - { - aimValue *= 0.6; - } - // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (countMiss > 0) aimValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), countMiss); @@ -136,11 +132,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty { double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0; - if (mods.Any(h => h is OsuModRelax)) - { - speedValue *= 0.6; - } - // Longer maps are worth more. double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + (totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0); @@ -222,11 +213,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (mods.Any(h => h is OsuModHidden)) flashlightValue *= 1.3; - if (mods.Any(h => h is OsuModRelax)) - { - flashlightValue *= 0.6; - } - // Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses. if (countMiss > 0) flashlightValue *= 0.97 * Math.Pow(1 - Math.Pow((double)countMiss / totalHits, 0.775), Math.Pow(countMiss, .875)); From 433e7cd4030ff1e2bd428b3112c23934369afccf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 21:26:30 +0900 Subject: [PATCH 151/183] Fix rate mods not working if pp counter is displayed --- osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs | 5 +++-- .../Screens/Play/HUD/PerformancePointsCounter.cs | 12 ++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs index a7c4790366..1143549f7f 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyCalculator.cs @@ -110,10 +110,11 @@ namespace osu.Game.Rulesets.Difficulty private void preProcess(Mod[] mods) { playableMods = mods.Select(m => m.DeepClone()).ToArray(); - Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); + + Beatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, playableMods); var track = new TrackVirtual(10000); - mods.OfType().ForEach(m => m.ApplyToTrack(track)); + playableMods.OfType().ForEach(m => m.ApplyToTrack(track)); clockRate = track.Rate; } diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 2ae7b5660a..15d9f9517b 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -60,6 +60,8 @@ namespace osu.Game.Screens.Play.HUD Current.Value = DisplayedCount = 0; } + private Mod[] clonedMods; + [BackgroundDependencyLoader] private void load(OsuColour colours, BeatmapDifficultyCache difficultyCache) { @@ -67,8 +69,10 @@ namespace osu.Game.Screens.Play.HUD if (gameplayState != null) { + clonedMods = gameplayState.Mods.Select(m => m.DeepClone()).ToArray(); + var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap); - difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, gameplayState.Mods.ToArray(), loadCancellationSource.Token) + difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, gameplayState.Mods.Select(m => m.DeepClone()).ToArray(), loadCancellationSource.Token) .ContinueWith(r => Schedule(() => { timedAttributes = r.Result; @@ -116,7 +120,11 @@ namespace osu.Game.Screens.Play.HUD return; } - var calculator = gameplayState.Ruleset.CreatePerformanceCalculator(attrib, gameplayState.Score.ScoreInfo); + // awkward but we need to make sure the true mods are not passed to PerformanceCalculator as it makes a mess of track applications. + var scoreInfo = gameplayState.Score.ScoreInfo.DeepClone(); + scoreInfo.Mods = clonedMods; + + var calculator = gameplayState.Ruleset.CreatePerformanceCalculator(attrib, scoreInfo); Current.Value = (int)Math.Round(calculator?.Calculate() ?? 0, MidpointRounding.AwayFromZero); IsValid = true; From 9705c7b546ff0ebe61e7bae5bd5ea8e3fdbcadd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Oct 2021 21:30:30 +0900 Subject: [PATCH 152/183] Use cloned mods in one more place --- osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs index 15d9f9517b..ef289c2a20 100644 --- a/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs +++ b/osu.Game/Screens/Play/HUD/PerformancePointsCounter.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Play.HUD clonedMods = gameplayState.Mods.Select(m => m.DeepClone()).ToArray(); var gameplayWorkingBeatmap = new GameplayWorkingBeatmap(gameplayState.Beatmap); - difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, gameplayState.Mods.Select(m => m.DeepClone()).ToArray(), loadCancellationSource.Token) + difficultyCache.GetTimedDifficultyAttributesAsync(gameplayWorkingBeatmap, gameplayState.Ruleset, clonedMods, loadCancellationSource.Token) .ContinueWith(r => Schedule(() => { timedAttributes = r.Result; From d6eab02d9273ff87cd648db7d2b4d2b278813ef9 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 6 Oct 2021 16:53:33 +0100 Subject: [PATCH 153/183] Return 0 for speed with relax --- osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs | 3 +++ osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index 4c8d0b2ce6..651fd056f0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -37,6 +37,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty double speedRating = Math.Sqrt(skills[1].DifficultyValue()) * difficulty_multiplier; double flashlightRating = Math.Sqrt(skills[2].DifficultyValue()) * difficulty_multiplier; + if (mods.Any(h => h is OsuModRelax)) + speedRating = 0.0; + double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000; double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000; double baseFlashlightPerformance = 0.0; diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index e8169ba660..b4421d8750 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -81,9 +81,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills } } - if (Mods.Any(m => m is OsuModRelax)) - speedBonus = 0.0; - return (1 + (speedBonus - 1) * 0.75) * angleBonus * (0.95 + speedBonus * Math.Pow(distance / single_spacing_threshold, 3.5)) From 57c069e0e1a2ec625c449c427b49b9051c21f1f6 Mon Sep 17 00:00:00 2001 From: apollo-dw <83023433+apollo-dw@users.noreply.github.com> Date: Wed, 6 Oct 2021 23:50:23 +0100 Subject: [PATCH 154/183] Remove unused strings --- osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs index b4421d8750..9364b11048 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Speed.cs @@ -7,8 +7,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Difficulty.Preprocessing; using osu.Game.Rulesets.Osu.Objects; using osu.Framework.Utils; -using System.Linq; -using osu.Game.Rulesets.Osu.Mods; namespace osu.Game.Rulesets.Osu.Difficulty.Skills { From 5f129ae33c515adc30874c1df6cd475769e5d1a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 14:53:36 +0900 Subject: [PATCH 155/183] Remove local overridden storage of `Mods` in `Player` Not required and only causing headaches. Accessing mods should now be done via `GameplayState`. Closes #14912. --- osu.Game/Screens/Play/Player.cs | 39 ++++++++++++++------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index ecc65c6bb0..e2ed71e28a 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; @@ -125,15 +124,11 @@ namespace osu.Game.Screens.Play public DimmableStoryboard DimmableStoryboard { get; private set; } - [Cached] - [Cached(Type = typeof(IBindable>))] - protected new readonly Bindable> Mods = new Bindable>(Array.Empty()); - /// /// Whether failing should be allowed. /// By default, this checks whether all selected mods allow failing. /// - protected virtual bool CheckModsAllowFailure() => Mods.Value.OfType().All(m => m.PerformFail()); + protected virtual bool CheckModsAllowFailure() => GameplayState.Mods.OfType().All(m => m.PerformFail()); public readonly PlayerConfiguration Configuration; @@ -179,12 +174,12 @@ namespace osu.Game.Screens.Play [BackgroundDependencyLoader(true)] private void load(AudioManager audio, OsuConfigManager config, OsuGameBase game) { - Mods.Value = base.Mods.Value.Select(m => m.DeepClone()).ToArray(); + var gameplayMods = Mods.Value.Select(m => m.DeepClone()).ToArray(); if (Beatmap.Value is DummyWorkingBeatmap) return; - IBeatmap playableBeatmap = loadPlayableBeatmap(); + IBeatmap playableBeatmap = loadPlayableBeatmap(gameplayMods); if (playableBeatmap == null) return; @@ -199,12 +194,12 @@ namespace osu.Game.Screens.Play if (game is OsuGame osuGame) LocalUserPlaying.BindTo(osuGame.LocalUserPlaying); - DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value); + DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, gameplayMods); dependencies.CacheAs(DrawableRuleset); ScoreProcessor = ruleset.CreateScoreProcessor(); ScoreProcessor.ApplyBeatmap(playableBeatmap); - ScoreProcessor.Mods.BindTo(Mods); + ScoreProcessor.Mods.Value = gameplayMods; dependencies.CacheAs(ScoreProcessor); @@ -223,9 +218,9 @@ namespace osu.Game.Screens.Play // ensure the score is in a consistent state with the current player. Score.ScoreInfo.BeatmapInfo = Beatmap.Value.BeatmapInfo; Score.ScoreInfo.Ruleset = ruleset.RulesetInfo; - Score.ScoreInfo.Mods = Mods.Value.ToArray(); + Score.ScoreInfo.Mods = gameplayMods; - dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, Mods.Value, Score)); + dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score)); AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); @@ -302,13 +297,13 @@ namespace osu.Game.Screens.Play // this is required for mods that apply transforms to these processors. ScoreProcessor.OnLoadComplete += _ => { - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in gameplayMods.OfType()) mod.ApplyToScoreProcessor(ScoreProcessor); }; HealthProcessor.OnLoadComplete += _ => { - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in gameplayMods.OfType()) mod.ApplyToHealthProcessor(HealthProcessor); }; @@ -356,7 +351,7 @@ namespace osu.Game.Screens.Play // display the cursor above some HUD elements. DrawableRuleset.Cursor?.CreateProxy() ?? new Container(), DrawableRuleset.ResumeOverlay?.CreateProxy() ?? new Container(), - HUDOverlay = new HUDOverlay(DrawableRuleset, Mods.Value) + HUDOverlay = new HUDOverlay(DrawableRuleset, GameplayState.Mods) { HoldToQuit = { @@ -467,7 +462,7 @@ namespace osu.Game.Screens.Play } } - private IBeatmap loadPlayableBeatmap() + private IBeatmap loadPlayableBeatmap(Mod[] gameplayMods) { IBeatmap playable; @@ -481,7 +476,7 @@ namespace osu.Game.Screens.Play try { - playable = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Mods.Value); + playable = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, gameplayMods); } catch (BeatmapInvalidForRulesetException) { @@ -489,7 +484,7 @@ namespace osu.Game.Screens.Play rulesetInfo = Beatmap.Value.BeatmapInfo.Ruleset; ruleset = rulesetInfo.CreateInstance(); - playable = Beatmap.Value.GetPlayableBeatmap(rulesetInfo, Mods.Value); + playable = Beatmap.Value.GetPlayableBeatmap(rulesetInfo, gameplayMods); } if (playable.HitObjects.Count == 0) @@ -789,7 +784,7 @@ namespace osu.Game.Screens.Play failAnimation.Start(); - if (Mods.Value.OfType().Any(m => m.RestartOnFail)) + if (GameplayState.Mods.OfType().Any(m => m.RestartOnFail)) Restart(); return true; @@ -919,17 +914,17 @@ namespace osu.Game.Screens.Play storyboardReplacesBackground.Value = Beatmap.Value.Storyboard.ReplacesBackground && Beatmap.Value.Storyboard.HasDrawable; - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in GameplayState.Mods.OfType()) mod.ApplyToPlayer(this); - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in GameplayState.Mods.OfType()) mod.ApplyToHUD(HUDOverlay); // Our mods are local copies of the global mods so they need to be re-applied to the track. // This is done through the music controller (for now), because resetting speed adjustments on the beatmap track also removes adjustments provided by DrawableTrack. // Todo: In the future, player will receive in a track and will probably not have to worry about this... musicController.ResetTrackAdjustments(); - foreach (var mod in Mods.Value.OfType()) + foreach (var mod in GameplayState.Mods.OfType()) mod.ApplyToTrack(musicController.CurrentTrack); updateGameplayState(); From 697f53c4450882f3c3e70262a4c167422e9921a0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 15:00:37 +0900 Subject: [PATCH 156/183] Fix test failure due to reference of `Player.Mods` --- osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs index 8160a62991..aee15a145c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePlayerLoader.cs @@ -205,7 +205,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for loader to become current", () => loader.IsCurrentScreen()); AddStep("mouse in centre", () => InputManager.MoveMouseTo(loader.ScreenSpaceDrawQuad.Centre)); AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); - AddStep("retrieve mods", () => playerMod1 = (TestMod)player.Mods.Value.Single()); + AddStep("retrieve mods", () => playerMod1 = (TestMod)player.GameplayState.Mods.Single()); AddAssert("game mods not applied", () => gameMod.Applied == false); AddAssert("player mods applied", () => playerMod1.Applied); @@ -217,7 +217,7 @@ namespace osu.Game.Tests.Visual.Gameplay }); AddUntilStep("wait for player to be current", () => player.IsCurrentScreen()); - AddStep("retrieve mods", () => playerMod2 = (TestMod)player.Mods.Value.Single()); + AddStep("retrieve mods", () => playerMod2 = (TestMod)player.GameplayState.Mods.Single()); AddAssert("game mods not applied", () => gameMod.Applied == false); AddAssert("player has different mods", () => playerMod1 != playerMod2); AddAssert("player mods applied", () => playerMod2.Applied); From a57b080f10050c8c9630def0fdcbf99f1718e97a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 15:27:57 +0900 Subject: [PATCH 157/183] Avoid showing the disclaimer in game tests No real performance gain, but this is handy to bypass when actually using one of these tests to test something. --- osu.Game/Tests/Visual/OsuGameTestScene.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index c025cf85c7..dba73b0024 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -126,6 +126,8 @@ namespace osu.Game.Tests.Visual public new Bindable> SelectedMods => base.SelectedMods; + public override Version AssemblyVersion => new Version(0, 0); + // if we don't do this, when running under nUnit the version that gets populated is that of nUnit. public override string Version => "test game"; From 290c9755e261d2732b85f305feb9ba6b0a5535ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 15:46:50 +0900 Subject: [PATCH 158/183] Always use circles intro for `OsuGame` tests The triangles intro tracks video time, which is not adjusted based on the game's playback rate (ie. it runs in realtime even for headless tests). Maybe we want to make the triangles video adjust its rate along with tests? --- osu.Game/Tests/Visual/OsuGameTestScene.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index dba73b0024..94715dfc1a 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -22,6 +22,7 @@ using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Menu; using osuTK.Graphics; +using IntroSequence = osu.Game.Configuration.IntroSequence; namespace osu.Game.Tests.Visual { @@ -144,6 +145,9 @@ namespace osu.Game.Tests.Visual protected override void LoadComplete() { base.LoadComplete(); + + LocalConfig.SetValue(OsuSetting.IntroSequence, IntroSequence.Circles); + API.Login("Rhythm Champion", "osu!"); Dependencies.Get().SetValue(Static.MutedAudioNotificationShownOnce, true); From 0bd5136a293c99e31a3ead1af16f989027666b82 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 15:47:59 +0900 Subject: [PATCH 159/183] Fix `TestOverlayClosing` occasionally failing due to running too fast --- .../Visual/Navigation/TestSceneScreenNavigation.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index aeb800f58a..ce437e7299 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -350,13 +350,13 @@ namespace osu.Game.Tests.Visual.Navigation // since most overlays use a scroll container that absorbs on mouse down NowPlayingOverlay nowPlayingOverlay = null; - AddStep("enter menu", () => InputManager.Key(Key.Enter)); + AddUntilStep("Wait for now playing load", () => (nowPlayingOverlay = Game.ChildrenOfType().FirstOrDefault()) != null); - AddStep("get and press now playing hotkey", () => - { - nowPlayingOverlay = Game.ChildrenOfType().Single(); - InputManager.Key(Key.F6); - }); + AddStep("enter menu", () => InputManager.Key(Key.Enter)); + AddUntilStep("toolbar displayed", () => Game.Toolbar.State.Value == Visibility.Visible); + + AddStep("open now playing", () => InputManager.Key(Key.F6)); + AddUntilStep("now playing is visible", () => nowPlayingOverlay.State.Value == Visibility.Visible); // drag tests @@ -417,7 +417,7 @@ namespace osu.Game.Tests.Visual.Navigation pushEscape(); // returns to osu! logo AddStep("Hold escape", () => InputManager.PressKey(Key.Escape)); - AddUntilStep("Wait for intro", () => Game.ScreenStack.CurrentScreen is IntroTriangles); + AddUntilStep("Wait for intro", () => Game.ScreenStack.CurrentScreen is IntroScreen); AddStep("Release escape", () => InputManager.ReleaseKey(Key.Escape)); AddUntilStep("Wait for game exit", () => Game.ScreenStack.CurrentScreen == null); AddStep("test dispose doesn't crash", () => Game.Dispose()); From 7e0379441c692c5eed9f16ef8b6e36bed6bf6549 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 16:18:47 +0900 Subject: [PATCH 160/183] Change `TestSceneOsuGame` to use `OsuGameTestScene` to avoid async disposal deadlock The original implementation was done so in a way that the nested `OsuGame` would be disposed via the async queue, causing a deadlock for 10-20s during test runs. `OsuGameTestScene` was already fixed to avoid this, so consuming it here seems like the easy fix. --- .../Visual/Navigation/TestSceneOsuGame.cs | 30 +++---------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs index 7327d4053a..353ead131e 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs @@ -34,7 +34,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.Navigation { [TestFixture] - public class TestSceneOsuGame : OsuTestScene + public class TestSceneOsuGame : OsuGameTestScene { private IReadOnlyList requiredGameDependencies => new[] { @@ -84,34 +84,12 @@ namespace osu.Game.Tests.Visual.Navigation typeof(PreviewTrackManager), }; - private OsuGame game; - [Resolved] private OsuGameBase gameBase { get; set; } [Resolved] private GameHost host { get; set; } - [SetUpSteps] - public void SetUpSteps() - { - AddStep("create game", () => - { - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - }; - - AddGame(game = new OsuGame()); - }); - - AddUntilStep("wait for load", () => game.IsLoaded); - } - [Test] public void TestNullRulesetHandled() { @@ -127,8 +105,8 @@ namespace osu.Game.Tests.Visual.Navigation [Test] public void TestSwitchThreadExecutionMode() { - AddStep("Change thread mode to multi threaded", () => { game.Dependencies.Get().SetValue(FrameworkSetting.ExecutionMode, ExecutionMode.MultiThreaded); }); - AddStep("Change thread mode to single thread", () => { game.Dependencies.Get().SetValue(FrameworkSetting.ExecutionMode, ExecutionMode.SingleThread); }); + AddStep("Change thread mode to multi threaded", () => { Game.Dependencies.Get().SetValue(FrameworkSetting.ExecutionMode, ExecutionMode.MultiThreaded); }); + AddStep("Change thread mode to single thread", () => { Game.Dependencies.Get().SetValue(FrameworkSetting.ExecutionMode, ExecutionMode.SingleThread); }); } [Test] @@ -154,7 +132,7 @@ namespace osu.Game.Tests.Visual.Navigation { foreach (var type in requiredGameDependencies) { - if (game.Dependencies.Get(type) == null) + if (Game.Dependencies.Get(type) == null) throw new InvalidOperationException($"{type} has not been cached"); } From c41271ea78871e4b39af13521ff93ed853552e80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 16:26:24 +0900 Subject: [PATCH 161/183] Fix hidden test failures --- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs index af64be78f8..ed9da36b05 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs @@ -120,7 +120,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods private bool checkSomeHit() => Player.ScoreProcessor.JudgedHits >= 4; private bool objectWithIncreasedVisibilityHasIndex(int index) - => Player.Mods.Value.OfType().Single().FirstObject == Player.GameplayState.Beatmap.HitObjects[index]; + => Player.GameplayState.Mods.OfType().Single().FirstObject == Player.GameplayState.Beatmap.HitObjects[index]; private class TestOsuModHidden : OsuModHidden { From f88d8989605d2cd54e4af6ed26f5b096db88c386 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 16:38:22 +0900 Subject: [PATCH 162/183] Allow intro screens to be created without loading a `MainMenu` --- osu.Game/Screens/Loader.cs | 8 +++++--- osu.Game/Screens/Menu/IntroCircles.cs | 7 +++++++ osu.Game/Screens/Menu/IntroScreen.cs | 25 +++++++++++++++++++++---- osu.Game/Screens/Menu/IntroTriangles.cs | 6 ++++++ osu.Game/Screens/Menu/IntroWelcome.cs | 7 +++++++ 5 files changed, 46 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 0bfabdaa15..41097a4c74 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -49,14 +49,16 @@ namespace osu.Game.Screens switch (introSequence) { case IntroSequence.Circles: - return new IntroCircles(); + return new IntroCircles(createMainMenu); case IntroSequence.Welcome: - return new IntroWelcome(); + return new IntroWelcome(createMainMenu); default: - return new IntroTriangles(); + return new IntroTriangles(createMainMenu); } + + MainMenu createMainMenu() => new MainMenu(); } protected virtual ShaderPrecompiler CreateShaderPrecompiler() => new ShaderPrecompiler(); diff --git a/osu.Game/Screens/Menu/IntroCircles.cs b/osu.Game/Screens/Menu/IntroCircles.cs index a1b8c3a203..2792d05f75 100644 --- a/osu.Game/Screens/Menu/IntroCircles.cs +++ b/osu.Game/Screens/Menu/IntroCircles.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -20,6 +22,11 @@ namespace osu.Game.Screens.Menu private Sample welcome; + public IntroCircles([CanBeNull] Func createNextScreen = null) + : base(createNextScreen) + { + } + [BackgroundDependencyLoader] private void load(AudioManager audio) { diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index fbd33cad67..32fb9f1d6d 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -1,15 +1,17 @@ // 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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Utils; using osu.Framework.Screens; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.IO.Archives; @@ -55,7 +57,7 @@ namespace osu.Game.Screens.Menu private LeasedBindable beatmap; - private MainMenu mainMenu; + private OsuScreen nextScreen; [Resolved] private AudioManager audio { get; set; } @@ -63,12 +65,20 @@ namespace osu.Game.Screens.Menu [Resolved] private MusicController musicController { get; set; } + [CanBeNull] + private readonly Func createNextScreen; + /// /// Whether the is provided by osu! resources, rather than a user beatmap. /// Only valid during or after . /// protected bool UsingThemedIntro { get; private set; } + protected IntroScreen([CanBeNull] Func createNextScreen = null) + { + this.createNextScreen = createNextScreen; + } + [BackgroundDependencyLoader] private void load(OsuConfigManager config, SkinManager skinManager, BeatmapManager beatmaps, Framework.Game game) { @@ -214,14 +224,21 @@ namespace osu.Game.Screens.Menu } } - protected void PrepareMenuLoad() => LoadComponentAsync(mainMenu = new MainMenu()); + protected void PrepareMenuLoad() + { + nextScreen = createNextScreen?.Invoke(); + + if (nextScreen != null) + LoadComponentAsync(nextScreen); + } protected void LoadMenu() { beatmap.Return(); DidLoadMenu = true; - this.Push(mainMenu); + if (nextScreen != null) + this.Push(nextScreen); } } } diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index a8ca17cec1..48ced63182 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -44,6 +45,11 @@ namespace osu.Game.Screens.Menu private DecoupleableInterpolatingFramedClock decoupledClock; private TrianglesIntroSequence intro; + public IntroTriangles([CanBeNull] Func createNextScreen = null) + : base(createNextScreen) + { + } + [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index f74043b045..639591cfef 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using JetBrains.Annotations; using osuTK; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -32,6 +34,11 @@ namespace osu.Game.Screens.Menu private BackgroundScreenDefault background; + public IntroWelcome([CanBeNull] Func createNextScreen = null) + : base(createNextScreen) + { + } + [BackgroundDependencyLoader] private void load(AudioManager audio) { From d0001f760d82bd1953da17b39898d5ac2a4d96a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 16:50:05 +0900 Subject: [PATCH 163/183] Group applicable comment above new addition --- osu.Game/Tests/Visual/OsuGameTestScene.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuGameTestScene.cs b/osu.Game/Tests/Visual/OsuGameTestScene.cs index 94715dfc1a..77db697cb6 100644 --- a/osu.Game/Tests/Visual/OsuGameTestScene.cs +++ b/osu.Game/Tests/Visual/OsuGameTestScene.cs @@ -127,9 +127,8 @@ namespace osu.Game.Tests.Visual public new Bindable> SelectedMods => base.SelectedMods; + // if we don't apply these changes, when running under nUnit the version that gets populated is that of nUnit. public override Version AssemblyVersion => new Version(0, 0); - - // if we don't do this, when running under nUnit the version that gets populated is that of nUnit. public override string Version => "test game"; protected override Loader CreateLoader() => new TestLoader(); From ca9c79b758f531d617c450cb9c685e604f3876e5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Oct 2021 16:51:42 +0900 Subject: [PATCH 164/183] Use object initialiser (fixes CI inspection) --- .../Editor/CatchSelectionBlueprintTestScene.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs index 7d806b119e..d4c2c0f0af 100644 --- a/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Catch.Tests/Editor/CatchSelectionBlueprintTestScene.cs @@ -29,8 +29,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Editor protected CatchSelectionBlueprintTestScene() { - EditorBeatmap = new EditorBeatmap(new CatchBeatmap()); - EditorBeatmap.Difficulty.CircleSize = 0; + EditorBeatmap = new EditorBeatmap(new CatchBeatmap()) { Difficulty = { CircleSize = 0 } }; EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 100 From 0df409c0503654f987b29e44031f9af6a1ea27c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 17:16:44 +0900 Subject: [PATCH 165/183] Move difficulty copy to `BeatmapModelManager.Save` --- osu.Game/Beatmaps/BeatmapModelManager.cs | 2 ++ osu.Game/Screens/Edit/Editor.cs | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 787559899a..4d698d2c92 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -192,6 +192,8 @@ namespace osu.Game.Beatmaps { var setInfo = beatmapInfo.BeatmapSet; + beatmapInfo.BaseDifficulty.CopyFrom(beatmapContent.Difficulty); + using (var stream = new MemoryStream()) { using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index b8a2d3612e..2ff0101dc0 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -332,8 +332,6 @@ namespace osu.Game.Screens.Edit // no longer new after first user-triggered save. isNewBeatmap = false; - playableBeatmap.BeatmapInfo.BaseDifficulty.CopyFrom(playableBeatmap.Difficulty); - // apply any set-level metadata changes. beatmapManager.Update(playableBeatmap.BeatmapInfo.BeatmapSet); From 7dd7d35fc84550971b3271baafc390d319e63f3c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 17:19:34 +0900 Subject: [PATCH 166/183] Remove unnecessary difficulty copy operation --- osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs index 54fb287661..64f1ee4a7a 100644 --- a/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs +++ b/osu.Game/Tests/Beatmaps/BeatmapConversionTest.cs @@ -111,8 +111,6 @@ namespace osu.Game.Tests.Beatmaps var converterResult = new Dictionary>(); - beatmap.BeatmapInfo.BaseDifficulty = beatmap.Difficulty; - var working = new ConversionWorkingBeatmap(beatmap) { ConversionGenerated = (o, r, c) => From d3efec3c045f99b3a749a966c708796fcbc02058 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 17:22:10 +0900 Subject: [PATCH 167/183] Remove unnecessary clone operations --- osu.Game/Beatmaps/WorkingBeatmap.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index c7ccfe976a..18adecb7aa 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -119,9 +119,6 @@ namespace osu.Game.Beatmaps // Apply difficulty mods if (mods.Any(m => m is IApplicableToDifficulty)) { - converted.BeatmapInfo = converted.BeatmapInfo.Clone(); - converted.Difficulty = converted.Difficulty.Clone(); - foreach (var mod in mods.OfType()) { if (cancellationSource.IsCancellationRequested) From e586fee09133d56d9df22cf32f598f1ab6a5e765 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Oct 2021 17:46:30 +0900 Subject: [PATCH 168/183] Remove unused usings --- osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs index 353ead131e..2706ff5ceb 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneOsuGame.cs @@ -7,11 +7,8 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; using osu.Framework.Platform; -using osu.Framework.Testing; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -29,7 +26,6 @@ using osu.Game.Scoring; using osu.Game.Screens.Menu; using osu.Game.Skinning; using osu.Game.Utils; -using osuTK.Graphics; namespace osu.Game.Tests.Visual.Navigation { From ee66414e4f0119c7b36f72fb745fd833a64c9645 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 17:49:13 +0900 Subject: [PATCH 169/183] Move difficulty copy inside context retrieval --- osu.Game/Beatmaps/BeatmapModelManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapModelManager.cs b/osu.Game/Beatmaps/BeatmapModelManager.cs index 4d698d2c92..9c0fc5ef8a 100644 --- a/osu.Game/Beatmaps/BeatmapModelManager.cs +++ b/osu.Game/Beatmaps/BeatmapModelManager.cs @@ -192,8 +192,6 @@ namespace osu.Game.Beatmaps { var setInfo = beatmapInfo.BeatmapSet; - beatmapInfo.BaseDifficulty.CopyFrom(beatmapContent.Difficulty); - using (var stream = new MemoryStream()) { using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) @@ -204,6 +202,8 @@ namespace osu.Game.Beatmaps using (ContextFactory.GetForWrite()) { beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == beatmapInfo.ID); + beatmapInfo.BaseDifficulty.CopyFrom(beatmapContent.Difficulty); + var metadata = beatmapInfo.Metadata ?? setInfo.Metadata; // grab the original file (or create a new one if not found). From 5c48340520cb5b8df5ae8852f01c0b1f0239aa43 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 7 Oct 2021 14:15:16 +0900 Subject: [PATCH 170/183] Add filter effect to beatmap loading --- osu.Game/Screens/Play/PlayerLoader.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 969527a758..4168dda867 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Transforms; using osu.Framework.Input; using osu.Framework.Screens; using osu.Framework.Threading; +using osu.Game.Audio.Effects; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -63,6 +64,8 @@ namespace osu.Game.Screens.Play private readonly BindableDouble volumeAdjustment = new BindableDouble(1); + private Filter lpFilter; + protected bool BackgroundBrightnessReduction { set @@ -127,7 +130,7 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load(SessionStatics sessionStatics) + private void load(SessionStatics sessionStatics, AudioManager audio) { muteWarningShownOnce = sessionStatics.GetBindable(Static.MutedAudioNotificationShownOnce); batteryWarningShownOnce = sessionStatics.GetBindable(Static.LowBatteryNotificationShownOnce); @@ -159,7 +162,8 @@ namespace osu.Game.Screens.Play new InputSettings() } }, - idleTracker = new IdleTracker(750) + idleTracker = new IdleTracker(750), + lpFilter = new Filter(audio.TrackMixer) }); if (Beatmap.Value.BeatmapInfo.EpilepsyWarning) @@ -191,6 +195,7 @@ namespace osu.Game.Screens.Play epilepsyWarning.DimmableBackground = b; }); + lpFilter.CutoffTo(500, 100, Easing.OutCubic); Beatmap.Value.Track.AddAdjustment(AdjustableProperty.Volume, volumeAdjustment); content.ScaleTo(0.7f); @@ -229,6 +234,7 @@ namespace osu.Game.Screens.Play // stop the track before removing adjustment to avoid a volume spike. Beatmap.Value.Track.Stop(); Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment); + lpFilter.CutoffTo(lpFilter.MaxCutoff); } public override bool OnExiting(IScreen next) @@ -242,6 +248,7 @@ namespace osu.Game.Screens.Play BackgroundBrightnessReduction = false; Beatmap.Value.Track.RemoveAdjustment(AdjustableProperty.Volume, volumeAdjustment); + lpFilter.CutoffTo(lpFilter.MaxCutoff, 100, Easing.InCubic); return base.OnExiting(next); } From cc209f0f2d71a0a27ab52cd3e1d4a1642997e5b4 Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 7 Oct 2021 14:15:47 +0900 Subject: [PATCH 171/183] Add filter effect to fail sequence --- osu.Game/Screens/Play/Player.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 9927467bd6..c3b733ae58 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -16,6 +16,7 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; +using osu.Game.Audio.Effects; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Containers; @@ -62,6 +63,8 @@ namespace osu.Game.Screens.Play private readonly Bindable samplePlaybackDisabled = new Bindable(); + private Filter lpFilter; + /// /// Whether gameplay should pause when the game window focus is lost. /// @@ -227,6 +230,7 @@ namespace osu.Game.Screens.Play AddInternal(GameplayBeatmap = new GameplayBeatmap(playableBeatmap)); AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); + AddInternal(lpFilter = new Filter(audio.TrackMixer)); dependencies.CacheAs(GameplayBeatmap); @@ -788,6 +792,7 @@ namespace osu.Game.Screens.Play if (PauseOverlay.State.Value == Visibility.Visible) PauseOverlay.Hide(); + lpFilter.CutoffTo(300, 2500, Easing.OutCubic); failAnimation.Start(); if (Mods.Value.OfType().Any(m => m.RestartOnFail)) @@ -800,6 +805,7 @@ namespace osu.Game.Screens.Play private void onFailComplete() { GameplayClockContainer.Stop(); + lpFilter.CutoffTo(lpFilter.MaxCutoff); FailOverlay.Retries = RestartCount; FailOverlay.Show(); From f6458aa26d9b7cba848faea8ab1a1e75ad8e88ec Mon Sep 17 00:00:00 2001 From: Jamie Taylor Date: Thu, 7 Oct 2021 17:56:11 +0900 Subject: [PATCH 172/183] Add filter effect to collection management dialog --- osu.Game/Collections/ManageCollectionsDialog.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index 680fec904f..c239396a41 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Game.Audio.Effects; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -19,6 +21,7 @@ namespace osu.Game.Collections { private const double enter_duration = 500; private const double exit_duration = 200; + private Filter lpFilter; [Resolved(CanBeNull = true)] private CollectionManager collectionManager { get; set; } @@ -36,7 +39,7 @@ namespace osu.Game.Collections } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, AudioManager audio) { Children = new Drawable[] { @@ -108,7 +111,8 @@ namespace osu.Game.Collections }, } } - } + }, + lpFilter = new Filter(audio.TrackMixer) }; } @@ -116,6 +120,7 @@ namespace osu.Game.Collections { base.PopIn(); + lpFilter.CutoffTo(300, 100, Easing.OutCubic); this.FadeIn(enter_duration, Easing.OutQuint); this.ScaleTo(0.9f).Then().ScaleTo(1f, enter_duration, Easing.OutQuint); } @@ -124,6 +129,7 @@ namespace osu.Game.Collections { base.PopOut(); + lpFilter.CutoffTo(lpFilter.MaxCutoff, 100, Easing.InCubic); this.FadeOut(exit_duration, Easing.OutQuint); this.ScaleTo(0.9f, exit_duration); From 189358240d4e11857062381d1c6b7e1f5873a9e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 18:39:48 +0900 Subject: [PATCH 173/183] Fix intro tests and move steps out of constructor --- osu.Game.Tests/Visual/Menus/IntroTestScene.cs | 13 +++++++++---- .../Visual/Menus/TestSceneIntroCircles.cs | 3 +-- .../Visual/Menus/TestSceneIntroTriangles.cs | 3 +-- .../Visual/Menus/TestSceneIntroWelcome.cs | 3 +-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs index f71d13ed35..6e1f789bfe 100644 --- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs +++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs @@ -5,7 +5,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; -using osu.Framework.Screens; using osu.Game.Screens; using osu.Game.Screens.Menu; using osuTK; @@ -21,6 +20,8 @@ namespace osu.Game.Tests.Visual.Menus protected OsuScreenStack IntroStack; + private IntroScreen intro; + protected IntroTestScene() { Children = new Drawable[] @@ -39,7 +40,11 @@ namespace osu.Game.Tests.Visual.Menus Position = new Vector2(0.5f), } }; + } + [Test] + public void TestPlayIntro() + { AddStep("restart sequence", () => { logo.FinishTransforms(); @@ -52,12 +57,12 @@ namespace osu.Game.Tests.Visual.Menus RelativeSizeAxes = Axes.Both, }); - IntroStack.Push(CreateScreen()); + IntroStack.Push(intro = CreateScreen()); }); - AddUntilStep("wait for menu", () => IntroStack.CurrentScreen is MainMenu); + AddUntilStep("wait for menu", () => intro.DidLoadMenu); } - protected abstract IScreen CreateScreen(); + protected abstract IntroScreen CreateScreen(); } } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs index 107734cc8d..ffc99185fb 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroCircles.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Screens; using osu.Game.Screens.Menu; namespace osu.Game.Tests.Visual.Menus @@ -10,6 +9,6 @@ namespace osu.Game.Tests.Visual.Menus [TestFixture] public class TestSceneIntroCircles : IntroTestScene { - protected override IScreen CreateScreen() => new IntroCircles(); + protected override IntroScreen CreateScreen() => new IntroCircles(); } } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs index df79584167..8f01e0321b 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Screens; using osu.Game.Screens.Menu; namespace osu.Game.Tests.Visual.Menus @@ -10,6 +9,6 @@ namespace osu.Game.Tests.Visual.Menus [TestFixture] public class TestSceneIntroTriangles : IntroTestScene { - protected override IScreen CreateScreen() => new IntroTriangles(); + protected override IntroScreen CreateScreen() => new IntroTriangles(); } } diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs index 5f135febf4..71681dd03e 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; -using osu.Framework.Screens; using osu.Framework.Utils; using osu.Game.Screens.Menu; @@ -11,7 +10,7 @@ namespace osu.Game.Tests.Visual.Menus [TestFixture] public class TestSceneIntroWelcome : IntroTestScene { - protected override IScreen CreateScreen() => new IntroWelcome(); + protected override IntroScreen CreateScreen() => new IntroWelcome(); public TestSceneIntroWelcome() { From 0348c6c7e596a54977085ebd5d603bad47507ea7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 18:47:52 +0900 Subject: [PATCH 174/183] Apply some renaming and code quality fixes --- .../Visual/Audio/TestSceneFilter.cs | 4 +-- .../Effects/{Filter.cs => AudioFilter.cs} | 29 +++++++++++++------ osu.Game/Overlays/DialogOverlay.cs | 2 +- 3 files changed, 23 insertions(+), 12 deletions(-) rename osu.Game/Audio/Effects/{Filter.cs => AudioFilter.cs} (80%) diff --git a/osu.Game.Tests/Visual/Audio/TestSceneFilter.cs b/osu.Game.Tests/Visual/Audio/TestSceneFilter.cs index c639da63d3..6e5c8e8850 100644 --- a/osu.Game.Tests/Visual/Audio/TestSceneFilter.cs +++ b/osu.Game.Tests/Visual/Audio/TestSceneFilter.cs @@ -66,10 +66,10 @@ namespace osu.Game.Tests.Visual.Audio } [Test] - public void TestLowPass() => testFilter(lowpassFilter, lowpassFilter.MaxCutoff, 0); + public void TestLowPass() => testFilter(lowpassFilter, AudioFilter.MAX_LOWPASS_CUTOFF, 0); [Test] - public void TestHighPass() => testFilter(highpassFilter, 0, highpassFilter.MaxCutoff); + public void TestHighPass() => testFilter(highpassFilter, 0, AudioFilter.MAX_LOWPASS_CUTOFF); private void testFilter(Filter filter, int cutoffFrom, int cutoffTo) { diff --git a/osu.Game/Audio/Effects/Filter.cs b/osu.Game/Audio/Effects/AudioFilter.cs similarity index 80% rename from osu.Game/Audio/Effects/Filter.cs rename to osu.Game/Audio/Effects/AudioFilter.cs index 428a69bb19..611ece5bdb 100644 --- a/osu.Game/Audio/Effects/Filter.cs +++ b/osu.Game/Audio/Effects/AudioFilter.cs @@ -11,11 +11,19 @@ namespace osu.Game.Audio.Effects { public class Filter : Component, ITransformableFilter { - public readonly int MaxCutoff = 22049; // nyquist - 1hz + /// + /// The maximum cutoff frequency that can be used with a low-pass filter. + /// This is equal to nyquist - 1hz. + /// + public const int MAX_LOWPASS_CUTOFF = 22049; // nyquist - 1hz + private readonly AudioMixer mixer; private readonly BQFParameters filter; private readonly BQFType type; + /// + /// The current cutoff of this filter. + /// public BindableNumber Cutoff { get; } /// @@ -37,7 +45,7 @@ namespace osu.Game.Audio.Effects break; case BQFType.LowPass: - initialCutoff = MaxCutoff; + initialCutoff = MAX_LOWPASS_CUTOFF; break; default: @@ -48,8 +56,9 @@ namespace osu.Game.Audio.Effects Cutoff = new BindableNumber(initialCutoff) { MinValue = 1, - MaxValue = MaxCutoff + MaxValue = MAX_LOWPASS_CUTOFF }; + filter = new BQFParameters { lFilter = type, @@ -82,13 +91,13 @@ namespace osu.Game.Audio.Effects // Workaround for weird behaviour when rapidly setting fCenter of a low-pass filter to nyquist - 1hz. if (type == BQFType.LowPass) { - if (cutoff.NewValue >= MaxCutoff) + if (cutoff.NewValue >= MAX_LOWPASS_CUTOFF) { detachFilter(); return; } - if (cutoff.OldValue >= MaxCutoff && cutoff.NewValue < MaxCutoff) + if (cutoff.OldValue >= MAX_LOWPASS_CUTOFF && cutoff.NewValue < MAX_LOWPASS_CUTOFF) attachFilter(); } @@ -108,11 +117,13 @@ namespace osu.Game.Audio.Effects var filterIndex = mixer.Effects.IndexOf(filter); if (filterIndex < 0) return; - var existingFilter = mixer.Effects[filterIndex] as BQFParameters; - if (existingFilter == null) return; + if (mixer.Effects[filterIndex] is BQFParameters existingFilter) + { + existingFilter.fCenter = cutoff.NewValue; - existingFilter.fCenter = cutoff.NewValue; - mixer.Effects[filterIndex] = existingFilter; + // required to update effect with new parameters. + mixer.Effects[filterIndex] = existingFilter; + } } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index bd20b74970..5f4ab35a03 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -89,7 +89,7 @@ namespace osu.Game.Overlays { base.PopOut(); - lpFilter.CutoffTo(lpFilter.MaxCutoff, 100, Easing.InCubic); + lpFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 100, Easing.InCubic); if (CurrentDialog?.State.Value == Visibility.Visible) { From e578046b206dcf0d2a87da7c075fde8f90b29023 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 18:49:30 +0900 Subject: [PATCH 175/183] Rename `Filter` -> `AudioFilter` --- ...{TestSceneFilter.cs => TestSceneAudioFilter.cs} | 14 ++++++++------ osu.Game/Audio/Effects/AudioFilter.cs | 4 ++-- osu.Game/Overlays/DialogOverlay.cs | 8 ++++---- 3 files changed, 14 insertions(+), 12 deletions(-) rename osu.Game.Tests/Visual/Audio/{TestSceneFilter.cs => TestSceneAudioFilter.cs} (89%) diff --git a/osu.Game.Tests/Visual/Audio/TestSceneFilter.cs b/osu.Game.Tests/Visual/Audio/TestSceneAudioFilter.cs similarity index 89% rename from osu.Game.Tests/Visual/Audio/TestSceneFilter.cs rename to osu.Game.Tests/Visual/Audio/TestSceneAudioFilter.cs index 6e5c8e8850..cf93273ae2 100644 --- a/osu.Game.Tests/Visual/Audio/TestSceneFilter.cs +++ b/osu.Game.Tests/Visual/Audio/TestSceneAudioFilter.cs @@ -15,13 +15,15 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Tests.Visual.Audio { - public class TestSceneFilter : OsuTestScene + public class TestSceneAudioFilter : OsuTestScene { private WorkingBeatmap testBeatmap; + private OsuSpriteText lowpassText; + private AudioFilter highpassFilter; + + private AudioFilter lowpassFilter; private OsuSpriteText highpassText; - private Filter lowpassFilter; - private Filter highpassFilter; [BackgroundDependencyLoader] private void load(AudioManager audio) @@ -31,8 +33,8 @@ namespace osu.Game.Tests.Visual.Audio { Children = new Drawable[] { - lowpassFilter = new Filter(audio.TrackMixer), - highpassFilter = new Filter(audio.TrackMixer, BQFType.HighPass), + lowpassFilter = new AudioFilter(audio.TrackMixer), + highpassFilter = new AudioFilter(audio.TrackMixer, BQFType.HighPass), lowpassText = new OsuSpriteText { Padding = new MarginPadding(20), @@ -71,7 +73,7 @@ namespace osu.Game.Tests.Visual.Audio [Test] public void TestHighPass() => testFilter(highpassFilter, 0, AudioFilter.MAX_LOWPASS_CUTOFF); - private void testFilter(Filter filter, int cutoffFrom, int cutoffTo) + private void testFilter(AudioFilter filter, int cutoffFrom, int cutoffTo) { AddStep("Load Track", () => testBeatmap.LoadTrack()); AddStep("Play Track", () => testBeatmap.Track.Start()); diff --git a/osu.Game/Audio/Effects/AudioFilter.cs b/osu.Game/Audio/Effects/AudioFilter.cs index 611ece5bdb..ee48bdd7d9 100644 --- a/osu.Game/Audio/Effects/AudioFilter.cs +++ b/osu.Game/Audio/Effects/AudioFilter.cs @@ -9,7 +9,7 @@ using osu.Framework.Graphics; namespace osu.Game.Audio.Effects { - public class Filter : Component, ITransformableFilter + public class AudioFilter : Component, ITransformableFilter { /// /// The maximum cutoff frequency that can be used with a low-pass filter. @@ -31,7 +31,7 @@ namespace osu.Game.Audio.Effects /// /// The mixer this effect should be applied to. /// The type of filter (e.g. LowPass, HighPass, etc) - public Filter(AudioMixer mixer, BQFType type = BQFType.LowPass) + public AudioFilter(AudioMixer mixer, BQFType type = BQFType.LowPass) { this.mixer = mixer; this.type = type; diff --git a/osu.Game/Overlays/DialogOverlay.cs b/osu.Game/Overlays/DialogOverlay.cs index 5f4ab35a03..9db0f34d1b 100644 --- a/osu.Game/Overlays/DialogOverlay.cs +++ b/osu.Game/Overlays/DialogOverlay.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays protected override string PopInSampleName => "UI/dialog-pop-in"; protected override string PopOutSampleName => "UI/dialog-pop-out"; - private Filter lpFilter; + private AudioFilter lowPassFilter; public PopupDialog CurrentDialog { get; private set; } @@ -42,7 +42,7 @@ namespace osu.Game.Overlays [BackgroundDependencyLoader] private void load(AudioManager audio) { - AddInternal(lpFilter = new Filter(audio.TrackMixer)); + AddInternal(lowPassFilter = new AudioFilter(audio.TrackMixer)); } public void Push(PopupDialog dialog) @@ -82,14 +82,14 @@ namespace osu.Game.Overlays { base.PopIn(); this.FadeIn(PopupDialog.ENTER_DURATION, Easing.OutQuint); - lpFilter.CutoffTo(300, 100, Easing.OutCubic); + lowPassFilter.CutoffTo(300, 100, Easing.OutCubic); } protected override void PopOut() { base.PopOut(); - lpFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 100, Easing.InCubic); + lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 100, Easing.InCubic); if (CurrentDialog?.State.Value == Visibility.Visible) { From b88d4f19137012aafe01370b1ef6a2ba529dee02 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 19:58:34 +0900 Subject: [PATCH 176/183] Fix weird edge case in `TestSceneIntroWelcome` --- osu.Game.Tests/Visual/Menus/IntroTestScene.cs | 2 +- osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs index 6e1f789bfe..bfea97410a 100644 --- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs +++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Menus } [Test] - public void TestPlayIntro() + public virtual void TestPlayIntro() { AddStep("restart sequence", () => { diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs index 71681dd03e..9081be3dd6 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs @@ -12,8 +12,10 @@ namespace osu.Game.Tests.Visual.Menus { protected override IntroScreen CreateScreen() => new IntroWelcome(); - public TestSceneIntroWelcome() + public override void TestPlayIntro() { + base.TestPlayIntro(); + AddUntilStep("wait for load", () => MusicController.TrackLoaded); AddAssert("correct track", () => Precision.AlmostEquals(MusicController.CurrentTrack.Length, 48000, 1)); AddAssert("check if menu music loops", () => MusicController.CurrentTrack.Looping); From ba84da6ef89892658677a7f67b50b8e1e43df0b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 20:08:02 +0900 Subject: [PATCH 177/183] Tidy up test scene --- .../Visual/Audio/TestSceneAudioFilter.cs | 77 +++++++++++++------ 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/Audio/TestSceneAudioFilter.cs b/osu.Game.Tests/Visual/Audio/TestSceneAudioFilter.cs index cf93273ae2..211543a881 100644 --- a/osu.Game.Tests/Visual/Audio/TestSceneAudioFilter.cs +++ b/osu.Game.Tests/Visual/Audio/TestSceneAudioFilter.cs @@ -5,11 +5,12 @@ using ManagedBass.Fx; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; using osu.Game.Audio.Effects; -using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -17,18 +18,22 @@ namespace osu.Game.Tests.Visual.Audio { public class TestSceneAudioFilter : OsuTestScene { - private WorkingBeatmap testBeatmap; - private OsuSpriteText lowpassText; + private AudioFilter lowpassFilter; + + private OsuSpriteText highpassText; private AudioFilter highpassFilter; - private AudioFilter lowpassFilter; - private OsuSpriteText highpassText; + private Track track; + + private WaveformTestBeatmap beatmap; [BackgroundDependencyLoader] private void load(AudioManager audio) { - testBeatmap = new WaveformTestBeatmap(audio); + beatmap = new WaveformTestBeatmap(audio); + track = beatmap.LoadTrack(); + Add(new FillFlowContainer { Children = new Drawable[] @@ -67,30 +72,56 @@ namespace osu.Game.Tests.Visual.Audio highpassFilter.Cutoff.ValueChanged += e => highpassText.Text = $"High Pass: {e.NewValue}hz"; } - [Test] - public void TestLowPass() => testFilter(lowpassFilter, AudioFilter.MAX_LOWPASS_CUTOFF, 0); - - [Test] - public void TestHighPass() => testFilter(highpassFilter, 0, AudioFilter.MAX_LOWPASS_CUTOFF); - - private void testFilter(AudioFilter filter, int cutoffFrom, int cutoffTo) + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Play Track", () => track.Start()); + waitTrackPlay(); + } + + [Test] + public void TestLowPass() { - AddStep("Load Track", () => testBeatmap.LoadTrack()); - AddStep("Play Track", () => testBeatmap.Track.Start()); - AddWaitStep("Let track play", 10); AddStep("Filter Sweep", () => { - filter.CutoffTo(cutoffFrom).Then() - .CutoffTo(cutoffTo, 2000, cutoffFrom > cutoffTo ? Easing.OutCubic : Easing.InCubic); + lowpassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF).Then() + .CutoffTo(0, 2000, Easing.OutCubic); }); - AddWaitStep("Let track play", 10); + + waitTrackPlay(); + AddStep("Filter Sweep (reverse)", () => { - filter.CutoffTo(cutoffTo).Then() - .CutoffTo(cutoffFrom, 2000, cutoffTo > cutoffFrom ? Easing.OutCubic : Easing.InCubic); + lowpassFilter.CutoffTo(0).Then() + .CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 2000, Easing.InCubic); }); - AddWaitStep("Let track play", 10); - AddStep("Stop track", () => testBeatmap.Track.Stop()); + + waitTrackPlay(); + AddStep("Stop track", () => track.Stop()); } + + [Test] + public void TestHighPass() + { + AddStep("Filter Sweep", () => + { + highpassFilter.CutoffTo(0).Then() + .CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 2000, Easing.InCubic); + }); + + waitTrackPlay(); + + AddStep("Filter Sweep (reverse)", () => + { + highpassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF).Then() + .CutoffTo(0, 2000, Easing.OutCubic); + }); + + waitTrackPlay(); + + AddStep("Stop track", () => track.Stop()); + } + + private void waitTrackPlay() => AddWaitStep("Let track play", 10); } } From 9bf29503fb94a0726c65d07b0f649e2c72aa9a97 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 20:57:14 +0900 Subject: [PATCH 178/183] Rename remaining usage --- osu.Game/Collections/ManageCollectionsDialog.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/Collections/ManageCollectionsDialog.cs b/osu.Game/Collections/ManageCollectionsDialog.cs index c239396a41..95fbfa0f86 100644 --- a/osu.Game/Collections/ManageCollectionsDialog.cs +++ b/osu.Game/Collections/ManageCollectionsDialog.cs @@ -21,7 +21,8 @@ namespace osu.Game.Collections { private const double enter_duration = 500; private const double exit_duration = 200; - private Filter lpFilter; + + private AudioFilter lowPassFilter; [Resolved(CanBeNull = true)] private CollectionManager collectionManager { get; set; } @@ -112,7 +113,7 @@ namespace osu.Game.Collections } } }, - lpFilter = new Filter(audio.TrackMixer) + lowPassFilter = new AudioFilter(audio.TrackMixer) }; } @@ -120,7 +121,7 @@ namespace osu.Game.Collections { base.PopIn(); - lpFilter.CutoffTo(300, 100, Easing.OutCubic); + lowPassFilter.CutoffTo(300, 100, Easing.OutCubic); this.FadeIn(enter_duration, Easing.OutQuint); this.ScaleTo(0.9f).Then().ScaleTo(1f, enter_duration, Easing.OutQuint); } @@ -129,7 +130,8 @@ namespace osu.Game.Collections { base.PopOut(); - lpFilter.CutoffTo(lpFilter.MaxCutoff, 100, Easing.InCubic); + lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 100, Easing.InCubic); + this.FadeOut(exit_duration, Easing.OutQuint); this.ScaleTo(0.9f, exit_duration); From da96cc73d8b3005e28b444b0afc69eac652da446 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 7 Oct 2021 23:40:47 +0900 Subject: [PATCH 179/183] Fix dual specification of `SuspensionHandler` and move fields around slightly --- osu.Game/Screens/Play/Player.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 620b026060..30ba2bfe6d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -62,8 +62,6 @@ namespace osu.Game.Screens.Play private readonly Bindable samplePlaybackDisabled = new Bindable(); - private AudioFilter lowPassFilter; - /// /// Whether gameplay should pause when the game window focus is lost. /// @@ -217,7 +215,7 @@ namespace osu.Game.Screens.Play InternalChild = GameplayClockContainer = CreateGameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime); AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); - AddInternal(lowPassFilter = new AudioFilter(audio.TrackMixer)); + AddInternal(failLowPassFilter = new AudioFilter(audio.TrackMixer)); Score = CreateScore(playableBeatmap); @@ -228,8 +226,6 @@ namespace osu.Game.Screens.Play dependencies.CacheAs(GameplayState = new GameplayState(playableBeatmap, ruleset, gameplayMods, Score)); - AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); - var rulesetSkinProvider = new RulesetSkinProvidingContainer(ruleset, playableBeatmap, Beatmap.Value.Skin); // load the skinning hierarchy first. @@ -774,6 +770,8 @@ namespace osu.Game.Screens.Play private FailAnimation failAnimation; + private AudioFilter failLowPassFilter; + private bool onFail() { if (!CheckModsAllowFailure()) @@ -788,7 +786,7 @@ namespace osu.Game.Screens.Play if (PauseOverlay.State.Value == Visibility.Visible) PauseOverlay.Hide(); - lowPassFilter.CutoffTo(300, 2500, Easing.OutCubic); + failLowPassFilter.CutoffTo(300, 2500, Easing.OutCubic); failAnimation.Start(); if (GameplayState.Mods.OfType().Any(m => m.RestartOnFail)) @@ -801,7 +799,7 @@ namespace osu.Game.Screens.Play private void onFailComplete() { GameplayClockContainer.Stop(); - lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF); + failLowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF); FailOverlay.Retries = RestartCount; FailOverlay.Show(); From 436ead421a71e0ca941d7e0f20aefd4f25fc83b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Oct 2021 12:27:04 +0900 Subject: [PATCH 180/183] Move low pass fail effect to `FailAnimation` --- osu.Game/Screens/Play/FailAnimation.cs | 10 +++++++++- osu.Game/Screens/Play/Player.cs | 6 ------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/FailAnimation.cs b/osu.Game/Screens/Play/FailAnimation.cs index 71bea2a145..e250791b72 100644 --- a/osu.Game/Screens/Play/FailAnimation.cs +++ b/osu.Game/Screens/Play/FailAnimation.cs @@ -10,7 +10,9 @@ using osu.Framework.Allocation; using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Utils; +using osu.Game.Audio.Effects; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Drawables; using osuTK; @@ -22,7 +24,7 @@ namespace osu.Game.Screens.Play /// Manage the animation to be applied when a player fails. /// Single file; automatically disposed after use. /// - public class FailAnimation : Component + public class FailAnimation : CompositeDrawable { public Action OnComplete; @@ -32,6 +34,8 @@ namespace osu.Game.Screens.Play private Track track; + private AudioFilter failLowPassFilter; + private const float duration = 2500; private Sample failSample; @@ -46,6 +50,8 @@ namespace osu.Game.Screens.Play { track = beatmap.Value.Track; failSample = audio.Samples.Get(@"Gameplay/failsound"); + + AddInternal(failLowPassFilter = new AudioFilter(audio.TrackMixer)); } private bool started; @@ -68,6 +74,8 @@ namespace osu.Game.Screens.Play Expire(); }); + failLowPassFilter.CutoffTo(300, duration, Easing.OutCubic); + track.AddAdjustment(AdjustableProperty.Frequency, trackFreq); applyToPlayfield(drawableRuleset.Playfield); diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 30ba2bfe6d..090210e611 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -15,7 +15,6 @@ using osu.Framework.Input.Events; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; -using osu.Game.Audio.Effects; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics.Containers; @@ -215,7 +214,6 @@ namespace osu.Game.Screens.Play InternalChild = GameplayClockContainer = CreateGameplayClockContainer(Beatmap.Value, DrawableRuleset.GameplayStartTime); AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); - AddInternal(failLowPassFilter = new AudioFilter(audio.TrackMixer)); Score = CreateScore(playableBeatmap); @@ -770,8 +768,6 @@ namespace osu.Game.Screens.Play private FailAnimation failAnimation; - private AudioFilter failLowPassFilter; - private bool onFail() { if (!CheckModsAllowFailure()) @@ -786,7 +782,6 @@ namespace osu.Game.Screens.Play if (PauseOverlay.State.Value == Visibility.Visible) PauseOverlay.Hide(); - failLowPassFilter.CutoffTo(300, 2500, Easing.OutCubic); failAnimation.Start(); if (GameplayState.Mods.OfType().Any(m => m.RestartOnFail)) @@ -799,7 +794,6 @@ namespace osu.Game.Screens.Play private void onFailComplete() { GameplayClockContainer.Stop(); - failLowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF); FailOverlay.Retries = RestartCount; FailOverlay.Show(); From 2856aef4eb17ee91f8d8f5c0a7852fae2e517870 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Oct 2021 13:51:12 +0900 Subject: [PATCH 181/183] Add exception to catch any incorrect defaults of `Bindable` --- osu.Game/Overlays/Settings/SettingsTextBox.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/osu.Game/Overlays/Settings/SettingsTextBox.cs b/osu.Game/Overlays/Settings/SettingsTextBox.cs index d28dbf1068..68562802cf 100644 --- a/osu.Game/Overlays/Settings/SettingsTextBox.cs +++ b/osu.Game/Overlays/Settings/SettingsTextBox.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using osu.Framework.Bindables; using osu.Framework.Graphics; namespace osu.Game.Overlays.Settings @@ -13,5 +15,17 @@ namespace osu.Game.Overlays.Settings RelativeSizeAxes = Axes.X, CommitOnFocusLost = true }; + + public override Bindable Current + { + get => base.Current; + set + { + if (value.Default == null) + throw new InvalidOperationException($"Bindable settings of type {nameof(Bindable)} should have a non-null default value."); + + base.Current = value; + } + } } } From 672664dce7c88beb56328e0af53377465d04ea2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Oct 2021 13:55:20 +0900 Subject: [PATCH 182/183] Fix all remaining cases of incorrect `Bindable` defaults --- osu.Game.Tournament/Models/SeedingResult.cs | 2 +- osu.Game.Tournament/Models/TournamentRound.cs | 4 ++-- osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs | 2 +- osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tournament/Models/SeedingResult.cs b/osu.Game.Tournament/Models/SeedingResult.cs index 87aaf8bf36..d37c967762 100644 --- a/osu.Game.Tournament/Models/SeedingResult.cs +++ b/osu.Game.Tournament/Models/SeedingResult.cs @@ -10,7 +10,7 @@ namespace osu.Game.Tournament.Models { public List Beatmaps = new List(); - public Bindable Mod = new Bindable(); + public Bindable Mod = new Bindable(string.Empty); public Bindable Seed = new BindableInt { diff --git a/osu.Game.Tournament/Models/TournamentRound.cs b/osu.Game.Tournament/Models/TournamentRound.cs index 08b3143be1..ab39605d07 100644 --- a/osu.Game.Tournament/Models/TournamentRound.cs +++ b/osu.Game.Tournament/Models/TournamentRound.cs @@ -14,8 +14,8 @@ namespace osu.Game.Tournament.Models [Serializable] public class TournamentRound { - public readonly Bindable Name = new Bindable(); - public readonly Bindable Description = new Bindable(); + public readonly Bindable Name = new Bindable(string.Empty); + public readonly Bindable Description = new Bindable(string.Empty); public readonly BindableInt BestOf = new BindableInt(9) { Default = 9, MinValue = 3, MaxValue = 23 }; diff --git a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs index 6e4fc8fe1a..1d8c4e7476 100644 --- a/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/RoundEditorScreen.cs @@ -149,7 +149,7 @@ namespace osu.Game.Tournament.Screens.Editors private readonly Bindable beatmapId = new Bindable(); - private readonly Bindable mods = new Bindable(); + private readonly Bindable mods = new Bindable(string.Empty); private readonly Container drawableContainer; diff --git a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs index b64a3993e6..d5b55823a5 100644 --- a/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/SeedingEditorScreen.cs @@ -149,7 +149,7 @@ namespace osu.Game.Tournament.Screens.Editors private readonly Bindable beatmapId = new Bindable(); - private readonly Bindable score = new Bindable(); + private readonly Bindable score = new Bindable(string.Empty); private readonly Container drawableContainer; From 9dc035757fc3e1b763e227de33d442d8641381e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Oct 2021 13:51:41 +0900 Subject: [PATCH 183/183] Fix weird textbox behaviour when entering a random mod seed overflowing int backing --- osu.Game/Overlays/Settings/SettingsNumberBox.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Settings/SettingsNumberBox.cs b/osu.Game/Overlays/Settings/SettingsNumberBox.cs index 2fbe522479..d36aa2bfc2 100644 --- a/osu.Game/Overlays/Settings/SettingsNumberBox.cs +++ b/osu.Game/Overlays/Settings/SettingsNumberBox.cs @@ -36,6 +36,7 @@ namespace osu.Game.Overlays.Settings { numberBox = new OutlinedNumberBox { + LengthLimit = 9, // limited to less than a value that could overflow int32 backing. Margin = new MarginPadding { Top = 5 }, RelativeSizeAxes = Axes.X, CommitOnFocusLost = true