From e71a9668a537b2616b71b8f192c37671e2447553 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Apr 2020 21:32:55 +0300 Subject: [PATCH 001/871] Disallow draining in non-draining sections --- .../Scoring/DrainingHealthProcessor.cs | 57 ++++++++++++++++--- 1 file changed, 50 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index fffcbb3c9f..b36e42326c 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; @@ -47,6 +49,34 @@ namespace osu.Game.Rulesets.Scoring private double targetMinimumHealth; private double drainRate = 1; + private readonly List<(double startTime, double endTime)> nonDrainSections = new List<(double, double)>(); + private int currentNonDrainSection; + + private bool isInNonDrainSection + { + get + { + if (nonDrainSections.Count == 0) + return false; + + var time = Time.Current; + + if (time > nonDrainSections[currentNonDrainSection].endTime) + { + while (time > nonDrainSections[currentNonDrainSection].endTime && currentNonDrainSection < nonDrainSections.Count - 1) + currentNonDrainSection++; + } + else + { + while (time < nonDrainSections[currentNonDrainSection].startTime && currentNonDrainSection > 0) + currentNonDrainSection--; + } + + var closestSection = nonDrainSections[currentNonDrainSection]; + return time >= closestSection.startTime && time <= closestSection.endTime; + } + } + /// /// Creates a new . /// @@ -60,23 +90,36 @@ namespace osu.Game.Rulesets.Scoring { base.Update(); - if (!IsBreakTime.Value) - { - // When jumping in and out of gameplay time within a single frame, health should only be drained for the period within the gameplay time - double lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, drainStartTime, gameplayEndTime); - double currentGameplayTime = Math.Clamp(Time.Current, drainStartTime, gameplayEndTime); + if (isInNonDrainSection) + return; - Health.Value -= drainRate * (currentGameplayTime - lastGameplayTime); - } + // When jumping in and out of gameplay time within a single frame, health should only be drained for the period within the gameplay time + double lastGameplayTime = Math.Clamp(Time.Current - Time.Elapsed, drainStartTime, gameplayEndTime); + double currentGameplayTime = Math.Clamp(Time.Current, drainStartTime, gameplayEndTime); + + Health.Value -= drainRate * (currentGameplayTime - lastGameplayTime); } public override void ApplyBeatmap(IBeatmap beatmap) { + nonDrainSections.Clear(); + this.beatmap = beatmap; if (beatmap.HitObjects.Count > 0) gameplayEndTime = beatmap.HitObjects[^1].GetEndTime(); + // Ranges between the end of last hit object before a break + // and the start of first hit object after a break should + // not allow HP draining. (with break periods in) + foreach (BreakPeriod b in beatmap.Breaks) + { + var startTime = beatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < b.StartTime)?.GetEndTime() ?? double.MinValue; + var endTime = beatmap.HitObjects.FirstOrDefault(h => h.StartTime > b.EndTime)?.StartTime ?? double.MaxValue; + + nonDrainSections.Add((startTime, endTime)); + } + targetMinimumHealth = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, min_health_target, mid_health_target, max_health_target); base.ApplyBeatmap(beatmap); From 7fab07670ed8ff6725bc4b8c1c543e0101af9664 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Apr 2020 21:35:09 +0300 Subject: [PATCH 002/871] Remove no longer necessary usage of IsBreakTime --- osu.Game/Rulesets/Scoring/HealthProcessor.cs | 5 ----- osu.Game/Screens/Play/Player.cs | 2 -- 2 files changed, 7 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HealthProcessor.cs b/osu.Game/Rulesets/Scoring/HealthProcessor.cs index 45edc0f4a3..1535fe4d00 100644 --- a/osu.Game/Rulesets/Scoring/HealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/HealthProcessor.cs @@ -26,11 +26,6 @@ namespace osu.Game.Rulesets.Scoring /// public readonly BindableDouble Health = new BindableDouble(1) { MinValue = 0, MaxValue = 1 }; - /// - /// Whether gameplay is currently in a break. - /// - public readonly IBindable IsBreakTime = new Bindable(); - /// /// Whether this ScoreProcessor has already triggered the failed state. /// diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 4597ae760c..f2051790db 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -243,8 +243,6 @@ namespace osu.Game.Screens.Play Breaks = working.Beatmap.Breaks } }); - - HealthProcessor.IsBreakTime.BindTo(breakTracker.IsBreakTime); } private void addOverlayComponents(Container target, WorkingBeatmap working) From c902ba40860b2757ea960925322bf9731b4eeefc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 5 Apr 2020 21:46:07 +0300 Subject: [PATCH 003/871] Add test cases for HP draining not applied before a break and after it --- .../TestSceneDrainingHealthProcessor.cs | 87 ++++++++++++++----- 1 file changed, 66 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs index 885abb61b5..2f83ea4832 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs @@ -1,13 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Utils; using osu.Framework.Testing; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -18,7 +20,6 @@ namespace osu.Game.Tests.Gameplay [HeadlessTest] public class TestSceneDrainingHealthProcessor : OsuTestScene { - private Bindable breakTime; private HealthProcessor processor; private ManualClock clock; @@ -41,6 +42,55 @@ namespace osu.Game.Tests.Gameplay assertHealthEqualTo(1); } + [Test] + public void TestHealthNotDrainedBeforeBreak() + { + createProcessor(createBeatmap(0, 2000, + new BreakPeriod(400, 600), new BreakPeriod(1200, 1400))); + + setTime(300); + setHealth(1); + + setTime(400); + assertHealthEqualTo(1); + + setTime(1100); + setHealth(1); + + setTime(1200); + assertHealthEqualTo(1); + } + + [Test] + public void TestHealthNotDrainedDuringBreak() + { + createProcessor(createBeatmap(0, 2000, new BreakPeriod(0, 1200))); + + setTime(700); + assertHealthEqualTo(1); + setTime(900); + assertHealthEqualTo(1); + } + + [Test] + public void TestHealthNotDrainedAfterBreak() + { + createProcessor(createBeatmap(0, 2000, + new BreakPeriod(400, 600), new BreakPeriod(1200, 1400))); + + setTime(600); + setHealth(1); + + setTime(700); + assertHealthEqualTo(1); + + setTime(1400); + setHealth(1); + + setTime(1500); + assertHealthEqualTo(1); + } + [Test] public void TestHealthNotDrainedAfterGameplayEnd() { @@ -54,18 +104,6 @@ namespace osu.Game.Tests.Gameplay assertHealthEqualTo(1); } - [Test] - public void TestHealthNotDrainedDuringBreak() - { - createProcessor(createBeatmap(0, 2000)); - setBreak(true); - - setTime(700); - assertHealthEqualTo(1); - setTime(900); - assertHealthEqualTo(1); - } - [Test] public void TestHealthDrainedDuringGameplay() { @@ -112,30 +150,39 @@ namespace osu.Game.Tests.Gameplay assertHealthNotEqualTo(1); } - private Beatmap createBeatmap(double startTime, double endTime) + private Beatmap createBeatmap(double startTime, double endTime, params BreakPeriod[] breaks) { var beatmap = new Beatmap { BeatmapInfo = { BaseDifficulty = { DrainRate = 5 } }, }; - for (double time = startTime; time <= endTime; time += 100) + double time = startTime; + + while (time <= endTime) + { beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = time }); + // leave a 100ms gap between the start and end of a break period. + time += (getCurrentBreak(breaks, time)?.Duration ?? 0) + 100; + } + + beatmap.Breaks.AddRange(breaks); + + static BreakPeriod getCurrentBreak(IEnumerable breaks, double time) => + breaks?.FirstOrDefault(b => time >= b.StartTime && time <= b.EndTime); + return beatmap; } private void createProcessor(Beatmap beatmap) => AddStep("create processor", () => { - breakTime = new Bindable(); - Child = processor = new DrainingHealthProcessor(beatmap.HitObjects[0].StartTime).With(d => { d.RelativeSizeAxes = Axes.Both; d.Clock = new FramedClock(clock = new ManualClock()); }); - processor.IsBreakTime.BindTo(breakTime); processor.ApplyBeatmap(beatmap); }); @@ -143,8 +190,6 @@ namespace osu.Game.Tests.Gameplay private void setHealth(double health) => AddStep($"set health = {health}", () => processor.Health.Value = health); - private void setBreak(bool enabled) => AddStep($"{(enabled ? "enable" : "disable")} break", () => breakTime.Value = enabled); - private void assertHealthEqualTo(double value) => AddAssert($"health = {value}", () => Precision.AlmostEquals(value, processor.Health.Value, 0.0001f)); From 235d3046c65d3e65fe924067ad57096537c86a92 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 11 Apr 2020 04:22:23 +0300 Subject: [PATCH 004/871] Move ruleset dependencies caching to its own container --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 93 ++--------- .../UI/DrawableRulesetDependencies.cs | 148 ++++++++++++++++++ 2 files changed, 157 insertions(+), 84 deletions(-) create mode 100644 osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 5062c92afe..0a46f5207e 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -11,20 +11,15 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using System; using System.Collections.Generic; -using System.IO; using System.Linq; using System.Threading; -using System.Threading.Tasks; using JetBrains.Annotations; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Input.Events; -using osu.Framework.IO.Stores; using osu.Game.Configuration; using osu.Game.Graphics.Cursor; using osu.Game.Input.Handlers; @@ -113,6 +108,8 @@ namespace osu.Game.Rulesets.UI private OnScreenDisplay onScreenDisplay; + private DrawableRulesetDependencies dependencies; + /// /// Creates a ruleset visualisation for the provided ruleset and beatmap. /// @@ -147,30 +144,15 @@ namespace osu.Game.Rulesets.UI protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies = new DrawableRulesetDependencies(Ruleset, base.CreateChildDependencies(parent)); - var resources = Ruleset.CreateResourceStore(); - - if (resources != null) - { - textureStore = new TextureStore(new TextureLoaderStore(new NamespacedResourceStore(resources, "Textures"))); - textureStore.AddStore(dependencies.Get()); - dependencies.Cache(textureStore); - - localSampleStore = dependencies.Get().GetSampleStore(new NamespacedResourceStore(resources, "Samples")); - localSampleStore.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY; - dependencies.CacheAs(new FallbackSampleStore(localSampleStore, dependencies.Get())); - } + textureStore = dependencies.TextureStore; + localSampleStore = dependencies.SampleStore; + Config = dependencies.RulesetConfigManager; onScreenDisplay = dependencies.Get(); - - Config = dependencies.Get().GetConfigFor(Ruleset); - if (Config != null) - { - dependencies.Cache(Config); onScreenDisplay?.BeginTracking(this, Config); - } return dependencies; } @@ -362,13 +344,14 @@ namespace osu.Game.Rulesets.UI { base.Dispose(isDisposing); - localSampleStore?.Dispose(); - if (Config != null) { onScreenDisplay?.StopTracking(this, Config); Config = null; } + + // Dispose the components created by this dependency container. + dependencies.Dispose(); } } @@ -519,62 +502,4 @@ namespace osu.Game.Rulesets.UI { } } - - /// - /// A sample store which adds a fallback source. - /// - /// - /// This is a temporary implementation to workaround ISampleStore limitations. - /// - public class FallbackSampleStore : ISampleStore - { - private readonly ISampleStore primary; - private readonly ISampleStore secondary; - - public FallbackSampleStore(ISampleStore primary, ISampleStore secondary) - { - this.primary = primary; - this.secondary = secondary; - } - - public SampleChannel Get(string name) => primary.Get(name) ?? secondary.Get(name); - - public Task GetAsync(string name) => primary.GetAsync(name) ?? secondary.GetAsync(name); - - public Stream GetStream(string name) => primary.GetStream(name) ?? secondary.GetStream(name); - - public IEnumerable GetAvailableResources() => throw new NotSupportedException(); - - public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); - - public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); - - public BindableNumber Volume => throw new NotSupportedException(); - - public BindableNumber Balance => throw new NotSupportedException(); - - public BindableNumber Frequency => throw new NotSupportedException(); - - public BindableNumber Tempo => throw new NotSupportedException(); - - public IBindable GetAggregate(AdjustableProperty type) => throw new NotSupportedException(); - - public IBindable AggregateVolume => throw new NotSupportedException(); - - public IBindable AggregateBalance => throw new NotSupportedException(); - - public IBindable AggregateFrequency => throw new NotSupportedException(); - - public IBindable AggregateTempo => throw new NotSupportedException(); - - public int PlaybackConcurrency - { - get => throw new NotSupportedException(); - set => throw new NotSupportedException(); - } - - public void Dispose() - { - } - } } diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs new file mode 100644 index 0000000000..33b340a974 --- /dev/null +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -0,0 +1,148 @@ +// 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.Threading.Tasks; +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.Textures; +using osu.Framework.IO.Stores; +using osu.Game.Rulesets.Configuration; + +namespace osu.Game.Rulesets.UI +{ + public class DrawableRulesetDependencies : DependencyContainer, IDisposable + { + /// + /// The texture store to be used for the ruleset. + /// + public TextureStore TextureStore { get; private set; } + + /// + /// The sample store to be used for the ruleset. + /// + /// + /// This is the local sample store pointing to the ruleset sample resources, + /// the cached sample store () retrieves from + /// this store and falls back to the parent store if this store doesn't have the requested sample. + /// + public ISampleStore SampleStore { get; private set; } + + /// + /// The ruleset config manager. + /// + public IRulesetConfigManager RulesetConfigManager { get; private set; } + + public DrawableRulesetDependencies(Ruleset ruleset, IReadOnlyDependencyContainer parent) + : base(parent) + { + var resources = ruleset.CreateResourceStore(); + + if (resources != null) + { + TextureStore = new TextureStore(new TextureLoaderStore(new NamespacedResourceStore(resources, @"Textures"))); + TextureStore.AddStore(parent.Get()); + Cache(TextureStore); + + SampleStore = parent.Get().GetSampleStore(new NamespacedResourceStore(resources, @"Samples")); + SampleStore.PlaybackConcurrency = OsuGameBase.SAMPLE_CONCURRENCY; + CacheAs(new FallbackSampleStore(SampleStore, parent.Get())); + } + + RulesetConfigManager = parent.Get().GetConfigFor(ruleset); + if (RulesetConfigManager != null) + Cache(RulesetConfigManager); + } + + #region Disposal + + ~DrawableRulesetDependencies() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + private bool isDisposed; + + protected void Dispose(bool disposing) + { + if (isDisposed) + return; + + isDisposed = true; + + SampleStore?.Dispose(); + RulesetConfigManager = null; + } + + #endregion + } + + /// + /// A sample store which adds a fallback source. + /// + /// + /// This is a temporary implementation to workaround ISampleStore limitations. + /// + public class FallbackSampleStore : ISampleStore + { + private readonly ISampleStore primary; + private readonly ISampleStore secondary; + + public FallbackSampleStore(ISampleStore primary, ISampleStore secondary) + { + this.primary = primary; + this.secondary = secondary; + } + + public SampleChannel Get(string name) => primary.Get(name) ?? secondary.Get(name); + + public Task GetAsync(string name) => primary.GetAsync(name) ?? secondary.GetAsync(name); + + public Stream GetStream(string name) => primary.GetStream(name) ?? secondary.GetStream(name); + + public IEnumerable GetAvailableResources() => throw new NotSupportedException(); + + public void AddAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); + + public void RemoveAdjustment(AdjustableProperty type, BindableNumber adjustBindable) => throw new NotSupportedException(); + + public BindableNumber Volume => throw new NotSupportedException(); + + public BindableNumber Balance => throw new NotSupportedException(); + + public BindableNumber Frequency => throw new NotSupportedException(); + + public BindableNumber Tempo => throw new NotSupportedException(); + + public IBindable GetAggregate(AdjustableProperty type) => throw new NotSupportedException(); + + public IBindable AggregateVolume => throw new NotSupportedException(); + + public IBindable AggregateBalance => throw new NotSupportedException(); + + public IBindable AggregateFrequency => throw new NotSupportedException(); + + public IBindable AggregateTempo => throw new NotSupportedException(); + + public int PlaybackConcurrency + { + get => throw new NotSupportedException(); + set => throw new NotSupportedException(); + } + + public void Dispose() + { + } + } +} From 2b4208bebfa4e81894d7a9a107701f474478325b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 11 Apr 2020 04:23:31 +0300 Subject: [PATCH 005/871] Cache ruleset dependencies if the scene tests ruleset-specific components --- .../Rulesets/Testing/IRulesetTestScene.cs | 20 +++++++++++++++++++ osu.Game/Tests/Visual/OsuTestScene.cs | 13 +++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Rulesets/Testing/IRulesetTestScene.cs diff --git a/osu.Game/Rulesets/Testing/IRulesetTestScene.cs b/osu.Game/Rulesets/Testing/IRulesetTestScene.cs new file mode 100644 index 0000000000..e8b8a79eb5 --- /dev/null +++ b/osu.Game/Rulesets/Testing/IRulesetTestScene.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. + +namespace osu.Game.Rulesets.Testing +{ + /// + /// An interface that can be assigned to test scenes to indicate + /// that the test scene is testing ruleset-specific components. + /// This is to cache required ruleset dependencies for the components. + /// + public interface IRulesetTestScene + { + /// + /// Retrieves the ruleset that is going + /// to be tested by this test scene. + /// + /// The . + Ruleset CreateRuleset(); + } +} diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index d1d8059cb1..eb1905cbe1 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -20,6 +20,8 @@ using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Testing; +using osu.Game.Rulesets.UI; using osu.Game.Screens; using osu.Game.Storyboards; using osu.Game.Tests.Beatmaps; @@ -36,6 +38,8 @@ namespace osu.Game.Tests.Visual protected new OsuScreenDependencies Dependencies { get; private set; } + private DrawableRulesetDependencies rulesetDependencies; + private Lazy localStorage; protected Storage LocalStorage => localStorage.Value; @@ -64,7 +68,12 @@ namespace osu.Game.Tests.Visual protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { - Dependencies = new OsuScreenDependencies(false, base.CreateChildDependencies(parent)); + var baseDependencies = base.CreateChildDependencies(parent); + + if (this is IRulesetTestScene rts) + baseDependencies = rulesetDependencies = new DrawableRulesetDependencies(rts.CreateRuleset(), baseDependencies); + + Dependencies = new OsuScreenDependencies(false, baseDependencies); Beatmap = Dependencies.Beatmap; Beatmap.SetDefault(); @@ -142,6 +151,8 @@ namespace osu.Game.Tests.Visual { base.Dispose(isDisposing); + rulesetDependencies?.Dispose(); + if (Beatmap?.Value.TrackLoaded == true) Beatmap.Value.Track.Stop(); From e10c973aa69b8b59df985c35debac260647b3845 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 11 Apr 2020 04:24:34 +0300 Subject: [PATCH 006/871] Add test cases for behaviour of ruleset dependencies caching on tests --- .../Gameplay/TestSceneStoryboardSamples.cs | 6 +- .../Resources/{ => Samples}/test-sample.mp3 | Bin .../Resources/Textures/test-image.png | Bin 0 -> 4852 bytes .../Testing/TestSceneRulesetTestScene.cs | 80 ++++++++++++++++++ 4 files changed, 83 insertions(+), 3 deletions(-) rename osu.Game.Tests/Resources/{ => Samples}/test-sample.mp3 (100%) create mode 100644 osu.Game.Tests/Resources/Textures/test-image.png create mode 100644 osu.Game.Tests/Testing/TestSceneRulesetTestScene.cs diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 84506739ab..8adf6064f5 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -60,11 +60,11 @@ namespace osu.Game.Tests.Gameplay this.resourceName = resourceName; } - public byte[] Get(string name) => name == resourceName ? TestResources.GetStore().Get("Resources/test-sample.mp3") : null; + public byte[] Get(string name) => name == resourceName ? TestResources.GetStore().Get("Resources/Samples/test-sample.mp3") : null; - public Task GetAsync(string name) => name == resourceName ? TestResources.GetStore().GetAsync("Resources/test-sample.mp3") : null; + public Task GetAsync(string name) => name == resourceName ? TestResources.GetStore().GetAsync("Resources/Samples/test-sample.mp3") : null; - public Stream GetStream(string name) => name == resourceName ? TestResources.GetStore().GetStream("Resources/test-sample.mp3") : null; + public Stream GetStream(string name) => name == resourceName ? TestResources.GetStore().GetStream("Resources/Samples/test-sample.mp3") : null; public IEnumerable GetAvailableResources() => new[] { resourceName }; diff --git a/osu.Game.Tests/Resources/test-sample.mp3 b/osu.Game.Tests/Resources/Samples/test-sample.mp3 similarity index 100% rename from osu.Game.Tests/Resources/test-sample.mp3 rename to osu.Game.Tests/Resources/Samples/test-sample.mp3 diff --git a/osu.Game.Tests/Resources/Textures/test-image.png b/osu.Game.Tests/Resources/Textures/test-image.png new file mode 100644 index 0000000000000000000000000000000000000000..5d0092edc89e6cb91a46c1dacba724642e3d880d GIT binary patch literal 4852 zcmZ`-c{~$-_;(DOqh^-oY-8@ZMvjQ4$(H-bky?h4=<+S8khak<%-lMZE3#x$Q8~lR z5#?6NO-jyk<@o9M-|vs#>+|}2p4anyKF{;KKYzSmujhHyL*VYEhxBG8Bu6dfu#|JUSV=->w{J$Qa zN|VE9#OBYT4brkp##+lA8x0;sJ}Rmtaf^rn0RiV@Gf5W$j8LfqA3H9p8z29*`&Zq( za-kJRDoJw*_%UF5?*1tk?)DEWDu<#tWiG9K`Xyh8U?5qnKC_{#{+u?UdfjL-*T*tk z)Z74#4i%@rGU1K{Zx8MCkJKdYN^glry!rDWkbKdHA2@7dw}o#44uW(5mNxJO0UOwN z+YFRxLwBLABPV2tlZrZ5TMb?v8Jvml>Uz=q0u;7%I9ja1p&S4}U~V<*4f1yidBEX^ z;*~u4eSKbdvc~~h9%bgO&rX#RT7LAE{&)4xh=oT)lom5xEq(<|_?bR>rd&{*@=+T} z8wBjgZyhE7@`S!`^HC|s)CGLVkz$>Fw~GAP|NK61_09N-cQ(u1T~@0@zxLqbjE@v> z0z;;?jw+0P0Tx9%)6&G90lM{@e||c0h>fwSAz!PmS`E%{&NYfboAEfmn4y+6(Qo{! z6nHh<7ZPr;&Mz?C>K41`)et`_H25;8=jUMc&6S^n98!DL;_f%gx`Vv<364Fwaj|Aa z_GMW!5-bb@ajuNLPapML-+02ZBW_YdG$2+lrN+`+9cF1pZhaC5M*sx4z0 zEp~-jkvoF$1CiR*bYnh%-D!DB55hhN#h7gW?x|4UT1bfZi($)f+kM;0J;}{1PVNA> zg9-mY)L56Vm9n^45p$7K1GTUO zer`ogOqcXSoO7-C6s{)@Wl=gvh#ANjfYEV`&3X|7?s;fqrn7~2xW)wp7W6*cE5dzg zDpQgK9oSm&c>*ZWac#I=?s@My%!}ma?l@);b1*M%?@_?gQCs(oDXeM7VZI(D?v;r_ z(gj!F!H`c88f_`f{^@DzVc3Wwz^tXY=2eM<&4we9kmGq-0T%J05(7B$??=SNox$k8 z(`MS-ra}fP$T`B`vh2nhg$U}Fdp%*+qj=6Cd-|QDFAn;pBQWjk?W!?$k9r-9~V|0$O+cL#IvPl*WyfSFJ9|+GGb4Rs#UE*khvpku9{l+ zTIE5QXXGnn1}id^r7^G|2pWe8rjht`gmZgI~wPBD;Ce)`9m zX7$w=DSyD+VZX0osn4c)2)w9$RR=7KIVYSTVh#S*zB)3e@patq%qM94Nk~gQU54w> zBa|%?b{&3?fz(g!2z~}PXi#AKk#T#mI*LH(KD*K*DaUaT%i)~QP)uw+>DT1P0!<}6 zjvuchc-$U5DW=kjK(-$Ip~K5*&o*)jC2 zdP3P5f`Ey=X>pkw(ry}TrymBlMek}*`ohPSc(*h{fq36l+#{ckK5ItM(w_3*ONzV| z6UnV(A9|C__7K5D$=+Y?o&qzQ41dvUo{26040+JO{^Rn`qL>;ur!QQp$rJ7++jvO@ zL>3(ny(WccE_pErljZ$T?)KoF7jNeMa}08o*K%j!3hf9ilsZ^+G)nW+Z{-_k)lwwy zY1TK*k2ipSl-a$T{-*##onF^sHupV=Le3|LEL6g){nj4!M%|nIF2qE>vV1@2PhWLq z?DtQ1pOw=Feo{aiNxByHi)^a!R14TMkit{5p+M*I-djoR&-*cD8XN>nMKUh|YZ`wE zPUs27bVg@;$&uM>aMC{{$W^5hloh`7dpg`a_vFQg7fyD0+xfAp$G+^Vx(u%v)?|Wpg$maQ_QKOv zb(cHSk7Dgrf^OV@{8wgWLUJ1-#D9%6P+Bs6WJ| zv(fJ8g-&Z%^~JbG8`xiKG|xLLlvCuPPr>4;#e&XBSr~MY7rWzDuq4Kl@amT(wfNwy zMU!1cZ925+_o3bUpXJlw6357s7iH($e`3^mCwhHfSC6L_SIYw30`ymBj}e47R+J{r zA}-)QU0rx{lf3m%VWBALwV1Ol#AftYRp!0|z}W5;|K5d1whUEe3_7VkxjP1hd7b;+ z((v1XAIHP|L7`=v`-x76#sP0mZr?MxyUEb1fy=L0oEKutG@Vq!4#$NKiu(#Z5eqv+ zCDY4S+djkTDj9$v2n3no=_MC??XJV1IH$+xdkH85Y>znHicZ`#W5E}aM4&?~90P+T zXPN0HeFACmbX}xSpl*L8t`qt5pgZI2g^zdQobnyij?tu8%I6)BKrn==^AU8#iXpTi zg=$6Q;P2x51w=XaQmB+TRj)7Vyle>8UkTGGzB2vccIAPlOwT=D=U~$sGdWe@#l11SyF_s16?` zM3+Yh;7bp141O!J-gE_ELsvBPPJiO~c$Np*5C(#@RF}xL4j-y2)WKj?A(Q%|=p1Qm zC@mmW4VWro%VM)shZWtSc~6=e<4aT{4K-*NyPKnxFkB?=Diz=yHzldbKA}S6Vaf8K zEk`JI#;F?4ENI2;(*-J&`w>K!`8prsz(fZ88d)v|I$5VeGl>JRD|a?@6>roHE8bW$ zzQ}+l+K{vE8+l^B3kz+ur@>|a#83O~(=6Ik^lr?5%^iA|Gl_lZ zVlcHz|BH8zox>(+%_IK#I{*G}B{H5kq0ZaOgxbn`bzcWV1>6;LW*BJjUaQj-{%oE& zODiJE{WclDP7cY3i-jwe4kK-8MJdJ&c~aLX%Fa=)-OS}~zEt#iLHJ8%& zxbBPSawqG=aG)eYmi$4nL^$&40skf-(<=ba6h@e$+qKkqv;)){9>psX=*j>D!68)k z!=XDLg}}hw+T}lZtXzirKnoMifL}q_G8Ag}FD1T31_>}54}bUCZb?F6 zRP%dfPaQ&!7Vp9e#oWQ|BTKp^9kN(A!XAbAfCTW!J-sO$#a z-}B6EzVvityo(x@=Id!XK4Tej^Y96gW3)v}>GhS3Y?>i8qj8*%U>S~%4lP$<}J55ya2C2Coft9vZ;2mD+jpO<%mcf0Zs!C@0CNb#UCYmk;VE56 z3*|hNi*4Fbm)vb17DbPM=~wutHNax@vPOG#J8&TYKEC4Z+ z^YA(;<#hhsSXgJ55HXM35gT-Gg6H8?z*WCJ_-UTV4KoHHa$Ti^U~P)If=|53sC!qt zG!hH}q~sY`&oGo4amuRzp?*6;fqF@d1DICukS;N8Q*4oGCjqT2a#O8ofLS@Ewoj_J zBlxKtfEQ3OnLB)44fOm~-fsYc1*2nh@4Xr6bLL*jCNtLl6Y0)8%`7;H+vjn*`RvLX z#pyM?rvY>B!jcf4AqvO961)ogFnToy$qa*(GVYutH2w4etHN|k7b{FB{}T@~BX|yT z!ux_X28rKep%zX1`PSX0;A7uk!SDM_uUr_^N|Iffku1Ic7XjROEV~+?&)uTHf7>#M z<(WlAC$;e|s!WQBX^3C#LGd+LFh?+G?!aD#N6l(eBLg74>A;K^Y-OiDRB=xn8P3RD@*rPZ*m%KGl|vx!?W5JUkE@{1a|F6TxxsS zj&zqsFikW*llE~HeHV=}-=()4D>q7pNl%BF1puT?hdlk2dP~=_;1RQhP*&V8he8gpUuq3e7@pyk1G=;YTV$&bZ)`JBy`|6R2#}%!lo6;dDbH+D%&b(&#Fk|5MMbytRpcyH z9lvD6uY#N0f8{PAi8+{B-=_MZO?DbvmOeq^@&WgOe8bgrdQ&$d<2ZPQiW7vohQl+T zDR9i^3a&$yZ);&lH;pA}tNDdK;<2?N4?&_8wt81p_JZ(a%SP6N0zXs%;>Y5uDZhRl!{btsxJl8Csochh>}YvPp!0l8xg_^=-=B0hCr zru{S3_$FLnV&TM_#h3SVR?V?^=j7;K2z)6E#>$o zp61iJ^;OfdQysF9g31u#ffjtG3~0?A8b`FL470j^bWlJb1s>_!&+-Z!Yx$e6u9}Y| zExpIG%qi46yAHH8sF9zi{`^c%w9}3C+#)@}>ir4-WtALtgpOOwn0s}*nCRz0HS|Mr zufl>CcluB7p|Y12Pz;m%Ak`d@KHc7(4K?z9De0FsAdeEbi(~vS5U7o9zOAEjGH**0 zC03FT*3fVvxh286mSia^a4^WlGK1Vj)Xxow{EByHbHa%bw-W#@YGxiKMDjNM=}r5F zP%sTpVj1xup?NqLqT-nGQm?si>6+qJGYKb_lA&Zn->#00PCkajtz?ORi)r4ujoQ>Y zvz5mwh+%izoq5n{U5-Y^sB2n7DaQA9%m@a0G5{rvFr{zQ?>w%gX@Vz+1$2)+gJ41bt`i!*Ag$HNH2cx6p^E4U4I;~s zAg`O;+&)S9HmiHM#5&|1M69PXh96G-}{AJ^^w?KqY^}9b`ztjtW z-zKl$$ycD5kxRG#4V6@`S$kgdCtf2R@e3vG2|l!*zOlC6VQm9_H?-any~9WJ^tJT# mkLc+&yII5jkHf{w{y_mZ|9^*. Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio.Track; +using osu.Framework.Configuration.Tracking; +using osu.Framework.Graphics.Textures; +using osu.Framework.IO.Stores; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Configuration; +using osu.Game.Rulesets.Difficulty; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Testing; +using osu.Game.Rulesets.UI; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Testing +{ + public class TestSceneRulesetTestScene : OsuTestScene, IRulesetTestScene + { + [Test] + public void TestRetrieveTexture() + { + AddAssert("ruleset texture retrieved", () => + Dependencies.Get().Get(@"test-image") != null); + } + + [Test] + public void TestRetrieveSample() + { + AddAssert("ruleset sample retrieved", () => + Dependencies.Get().Get(@"test-sample") != null); + } + + [Test] + public void TestResolveConfigManager() + { + AddAssert("ruleset config resolved", () => + Dependencies.Get() != null); + } + + public Ruleset CreateRuleset() => new TestRuleset(); + + private class TestRuleset : Ruleset + { + public override string Description => string.Empty; + public override string ShortName => string.Empty; + + public TestRuleset() + { + // temporary ID to let RulesetConfigCache pass our + // config manager to the ruleset dependencies. + RulesetInfo.ID = -1; + } + + public override IResourceStore CreateResourceStore() => new NamespacedResourceStore(TestResources.GetStore(), @"Resources"); + public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new TestRulesetConfigManager(); + + public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException(); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException(); + } + + private class TestRulesetConfigManager : IRulesetConfigManager + { + public void Load() => throw new NotImplementedException(); + public bool Save() => throw new NotImplementedException(); + public TrackedSettings CreateTrackedSettings() => throw new NotImplementedException(); + public void LoadInto(TrackedSettings settings) => throw new NotImplementedException(); + public void Dispose() => throw new NotImplementedException(); + } + } +} From a314a6119a9461155fdb083976f45121c704d796 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 11 Apr 2020 05:13:04 +0300 Subject: [PATCH 007/871] Fix CI issues --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 8 -------- osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 0a46f5207e..265c6a7319 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -14,10 +14,8 @@ using System.Collections.Generic; using System.Linq; using System.Threading; using JetBrains.Annotations; -using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Configuration; @@ -58,10 +56,6 @@ namespace osu.Game.Rulesets.UI private readonly Lazy playfield; - private TextureStore textureStore; - - private ISampleStore localSampleStore; - /// /// The playfield. /// @@ -146,8 +140,6 @@ namespace osu.Game.Rulesets.UI { dependencies = new DrawableRulesetDependencies(Ruleset, base.CreateChildDependencies(parent)); - textureStore = dependencies.TextureStore; - localSampleStore = dependencies.SampleStore; Config = dependencies.RulesetConfigManager; onScreenDisplay = dependencies.Get(); diff --git a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs index 33b340a974..168e937256 100644 --- a/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs +++ b/osu.Game/Rulesets/UI/DrawableRulesetDependencies.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.UI /// /// The texture store to be used for the ruleset. /// - public TextureStore TextureStore { get; private set; } + public TextureStore TextureStore { get; } /// /// The sample store to be used for the ruleset. @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.UI /// the cached sample store () retrieves from /// this store and falls back to the parent store if this store doesn't have the requested sample. /// - public ISampleStore SampleStore { get; private set; } + public ISampleStore SampleStore { get; } /// /// The ruleset config manager. From 97340da2f296eb1d88ac70c7addfb5cf2dd76a23 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 12 Apr 2020 02:24:36 +0300 Subject: [PATCH 008/871] Add null-conditional to acesses in dispose methods Such a terrible mistake, the finalizer may be called while the dependencies have an instance but the local itself doesn't have a value yet. --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 265c6a7319..06f8715929 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -343,7 +343,7 @@ namespace osu.Game.Rulesets.UI } // Dispose the components created by this dependency container. - dependencies.Dispose(); + dependencies?.Dispose(); } } From 67bd7bfa3905aa96f21f2225a517e2e369e80540 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 17 Apr 2020 06:17:15 +0300 Subject: [PATCH 009/871] Add `CreateRuleset` in OsuTestScene for scenes that depend on it --- osu.Game/Tests/Visual/OsuTestScene.cs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 5dc8714c07..8058a074ef 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -70,7 +70,18 @@ namespace osu.Game.Tests.Visual Beatmap.SetDefault(); Ruleset = Dependencies.Ruleset; - Ruleset.SetDefault(); + + var definedRuleset = CreateRuleset()?.RulesetInfo; + + if (definedRuleset != null) + { + // Set global ruleset bindable to the ruleset defined + // for this test scene and disallow changing it. + Ruleset.Value = definedRuleset; + Ruleset.Disabled = true; + } + else + Ruleset.SetDefault(); SelectedMods = Dependencies.Mods; SelectedMods.SetDefault(); @@ -124,6 +135,14 @@ namespace osu.Game.Tests.Visual [Resolved] protected AudioManager Audio { get; private set; } + /// + /// Creates the ruleset to be used for this test scene. + /// + /// + /// When testing against ruleset-specific components, this method must be overriden to their ruleset. + /// + protected virtual Ruleset CreateRuleset() => null; + protected virtual IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset); protected WorkingBeatmap CreateWorkingBeatmap(RulesetInfo ruleset) => @@ -135,7 +154,8 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - Ruleset.Value = rulesets.AvailableRulesets.First(); + if (!Ruleset.Disabled) + Ruleset.Value = rulesets.AvailableRulesets.First(); } protected override void Dispose(bool isDisposing) From 5fa6bcb5a3f73b080873f48ee34a7dcec0a9da58 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 17 Apr 2020 11:17:14 +0300 Subject: [PATCH 010/871] Move `SkinnableTestScene` into using the global `CreateRuleset` method --- .../CatchSkinnableTestScene.cs | 2 +- .../Skinning/ManiaSkinnableTestScene.cs | 4 ++-- .../OsuSkinnableTestScene.cs | 2 +- .../TaikoSkinnableTestScene.cs | 2 +- osu.Game/Tests/Visual/SkinnableTestScene.cs | 13 ++++++++----- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs index 0c46b078b5..c0060af74a 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(CatchLegacySkinTransformer), }; - protected override Ruleset CreateRulesetForSkinProvider() => new CatchRuleset(); + protected override Ruleset CreateRuleset() => new CatchRuleset(); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index a3c1d518c5..f41ba4db42 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -34,8 +34,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning typeof(ManiaSettingsSubsection) }; - protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset(); - protected ManiaSkinnableTestScene() { scrollingInfo.Direction.Value = ScrollingDirection.Down; @@ -60,6 +58,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning AddStep("change direction to up", () => scrollingInfo.Direction.Value = ScrollingDirection.Up); } + protected override Ruleset CreateRuleset() => new ManiaRuleset(); + private class TestScrollingInfo : IScrollingInfo { public readonly Bindable Direction = new Bindable(); diff --git a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs index 90ebbd9f04..1458270193 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Osu.Tests typeof(OsuLegacySkinTransformer), }; - protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); + protected override Ruleset CreateRuleset() => new OsuRuleset(); } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs index 6db2a6907f..98e6c2ec52 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Taiko.Tests typeof(TaikoLegacySkinTransformer), }; - protected override Ruleset CreateRulesetForSkinProvider() => new TaikoRuleset(); + protected override Ruleset CreateRuleset() => new TaikoRuleset(); } } diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index ace24c0d7e..d648afd504 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -30,11 +29,15 @@ namespace osu.Game.Tests.Visual protected SkinnableTestScene() : base(2, 3) { + // avoid running silently incorrectly. + if (CreateRuleset() == null) + { + throw new InvalidOperationException( + $"No ruleset provided, override {nameof(CreateRuleset)} to the ruleset belonging to the skinnable content." + + "This is required to add the legacy skin transformer for the content to behave as expected."); + } } - // Required to be part of the per-ruleset implementation to construct the newer version of the Ruleset. - protected abstract Ruleset CreateRulesetForSkinProvider(); - [BackgroundDependencyLoader] private void load(AudioManager audio, SkinManager skinManager) { @@ -107,7 +110,7 @@ namespace osu.Game.Tests.Visual { new OutlineBox { Alpha = autoSize ? 1 : 0 }, mainProvider.WithChild( - new SkinProvidingContainer(CreateRulesetForSkinProvider().CreateLegacySkinProvider(mainProvider, beatmap)) + new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider, beatmap)) { Child = created, RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None, From 92df4e3a9eb8ad56c6da99b088d0159a419c8110 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 17 Apr 2020 10:32:12 +0300 Subject: [PATCH 011/871] Remove `PlayerTestScene` constructor and use `CreateRuleset` method instead --- .../TestSceneAutoJuiceStream.cs | 7 +------ .../TestSceneBananaShower.cs | 8 +------ .../TestSceneCatchPlayer.cs | 10 ++++++--- .../TestSceneCatchStacker.cs | 8 +------ .../TestSceneHyperDash.cs | 8 +------ .../TestSceneJuiceStream.cs | 8 +------ .../TestSceneManiaPlayer.cs | 19 +++++++++++++++++ .../TestScenePlayer.cs | 15 ------------- .../TestSceneHitCircleLongCombo.cs | 8 +------ .../TestSceneOsuPlayer.cs | 10 ++++++--- .../TestSceneSkinFallbacks.cs | 3 +-- .../TestSceneSwellJudgements.cs | 8 +------ .../TestSceneTaikoPlayer.cs | 19 +++++++++++++++++ .../TestSceneTaikoSuddenDeath.cs | 7 +------ .../Gameplay/TestSceneHitObjectSamples.cs | 10 ++------- .../Visual/Gameplay/TestPlayerTestScene.cs | 16 ++++++++++++++ .../Gameplay/TestSceneGameplayRewinding.cs | 8 +------ .../Visual/Gameplay/TestScenePause.cs | 4 +--- .../Gameplay/TestScenePauseWhenInactive.cs | 8 +------ osu.Game/Tests/Visual/PlayerTestScene.cs | 21 +++++++------------ 20 files changed, 89 insertions(+), 116 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs delete mode 100644 osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs create mode 100644 osu.Game.Tests/Visual/Gameplay/TestPlayerTestScene.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index ed7bfb9a44..7c2304694f 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -12,13 +12,8 @@ using osuTK; namespace osu.Game.Rulesets.Catch.Tests { - public class TestSceneAutoJuiceStream : PlayerTestScene + public class TestSceneAutoJuiceStream : TestSceneCatchPlayer { - public TestSceneAutoJuiceStream() - : base(new CatchRuleset()) - { - } - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { var beatmap = new Beatmap diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs index 024c4cefb0..56f94e609f 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneBananaShower.cs @@ -8,12 +8,11 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects.Drawables; using osu.Game.Rulesets.Catch.UI; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestSceneBananaShower : PlayerTestScene + public class TestSceneBananaShower : TestSceneCatchPlayer { public override IReadOnlyList RequiredTypes => new[] { @@ -26,11 +25,6 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(DrawableCatchRuleset), }; - public TestSceneBananaShower() - : base(new CatchRuleset()) - { - } - [Test] public void TestBananaShower() { diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs index 9836a7811a..722f3b5a3b 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.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 System.Collections.Generic; using NUnit.Framework; using osu.Game.Tests.Visual; @@ -9,9 +11,11 @@ namespace osu.Game.Rulesets.Catch.Tests [TestFixture] public class TestSceneCatchPlayer : PlayerTestScene { - public TestSceneCatchPlayer() - : base(new CatchRuleset()) + public override IReadOnlyList RequiredTypes => new[] { - } + typeof(CatchRuleset), + }; + + protected override Ruleset CreateRuleset() => new CatchRuleset(); } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs index 9ce46ad6ba..44672b6526 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs @@ -4,18 +4,12 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestSceneCatchStacker : PlayerTestScene + public class TestSceneCatchStacker : TestSceneCatchPlayer { - public TestSceneCatchStacker() - : base(new CatchRuleset()) - { - } - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { var beatmap = new Beatmap diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index 49ff9df4d7..75b8b68c14 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -10,24 +10,18 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; -using osu.Game.Tests.Visual; using osuTK; namespace osu.Game.Rulesets.Catch.Tests { [TestFixture] - public class TestSceneHyperDash : PlayerTestScene + public class TestSceneHyperDash : TestSceneCatchPlayer { public override IReadOnlyList RequiredTypes => new[] { typeof(CatcherArea), }; - public TestSceneHyperDash() - : base(new CatchRuleset()) - { - } - protected override bool Autoplay => true; [Test] diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs index cbc87459e1..ffcf61a4bf 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs @@ -7,18 +7,12 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; -using osu.Game.Tests.Visual; using osuTK; namespace osu.Game.Rulesets.Catch.Tests { - public class TestSceneJuiceStream : PlayerTestScene + public class TestSceneJuiceStream : TestSceneCatchPlayer { - public TestSceneJuiceStream() - : base(new CatchRuleset()) - { - } - [Test] public void TestJuiceStreamEndingCombo() { diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs new file mode 100644 index 0000000000..11663605e2 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs @@ -0,0 +1,19 @@ +// 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.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class TestSceneManiaPlayer : PlayerTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ManiaRuleset), + }; + + protected override Ruleset CreateRuleset() => new ManiaRuleset(); + } +} diff --git a/osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs b/osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs deleted file mode 100644 index cd25d162d0..0000000000 --- a/osu.Game.Rulesets.Mania.Tests/TestScenePlayer.cs +++ /dev/null @@ -1,15 +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 osu.Game.Tests.Visual; - -namespace osu.Game.Rulesets.Mania.Tests -{ - public class TestScenePlayer : PlayerTestScene - { - public TestScenePlayer() - : base(new ManiaRuleset()) - { - } - } -} diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs index b99cd523ff..8cf29ddfbf 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLongCombo.cs @@ -4,19 +4,13 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Tests.Visual; using osuTK; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestSceneHitCircleLongCombo : PlayerTestScene + public class TestSceneHitCircleLongCombo : TestSceneOsuPlayer { - public TestSceneHitCircleLongCombo() - : base(new OsuRuleset()) - { - } - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { var beatmap = new Beatmap diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs index 0a33b09ba8..102f8bf841 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.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 System.Collections.Generic; using NUnit.Framework; using osu.Game.Tests.Visual; @@ -9,9 +11,11 @@ namespace osu.Game.Rulesets.Osu.Tests [TestFixture] public class TestSceneOsuPlayer : PlayerTestScene { - public TestSceneOsuPlayer() - : base(new OsuRuleset()) + public override IReadOnlyList RequiredTypes => new[] { - } + typeof(OsuRuleset), + }; + + protected override Ruleset CreateRuleset() => new OsuRuleset(); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs index d39e24fc1f..b357e20ee8 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSkinFallbacks.cs @@ -25,13 +25,12 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests { [TestFixture] - public class TestSceneSkinFallbacks : PlayerTestScene + public class TestSceneSkinFallbacks : TestSceneOsuPlayer { private readonly TestSource testUserSkin; private readonly TestSource testBeatmapSkin; public TestSceneSkinFallbacks() - : base(new OsuRuleset()) { testUserSkin = new TestSource("user"); testBeatmapSkin = new TestSource("beatmap"); diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs index 303f0163b1..965cde0f3f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneSwellJudgements.cs @@ -5,17 +5,11 @@ using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests { - public class TestSceneSwellJudgements : PlayerTestScene + public class TestSceneSwellJudgements : TestSceneTaikoPlayer { - public TestSceneSwellJudgements() - : base(new TaikoRuleset()) - { - } - [Test] public void TestZeroTickTimeOffsets() { diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs new file mode 100644 index 0000000000..4c5ab7eabf --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs @@ -0,0 +1,19 @@ +// 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.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class TestSceneTaikoPlayer : PlayerTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(TaikoRuleset) + }; + + protected override Ruleset CreateRuleset() => new TaikoRuleset(); + } +} diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs index 2ab041e191..aaa634648a 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoSuddenDeath.cs @@ -11,13 +11,8 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Taiko.Tests { - public class TestSceneTaikoSuddenDeath : PlayerTestScene + public class TestSceneTaikoSuddenDeath : TestSceneTaikoPlayer { - public TestSceneTaikoSuddenDeath() - : base(new TaikoRuleset()) - { - } - protected override bool AllowFail => true; protected override TestPlayer CreatePlayer(Ruleset ruleset) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index f611f2717e..7d3d8b7f16 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -16,17 +16,16 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Rulesets; -using osu.Game.Rulesets.Osu; using osu.Game.Skinning; using osu.Game.Storyboards; using osu.Game.Tests.Resources; -using osu.Game.Tests.Visual; +using osu.Game.Tests.Visual.Gameplay; using osu.Game.Users; namespace osu.Game.Tests.Gameplay { [HeadlessTest] - public class TestSceneHitObjectSamples : PlayerTestScene + public class TestSceneHitObjectSamples : TestPlayerTestScene { private readonly SkinInfo userSkinInfo = new SkinInfo(); @@ -44,11 +43,6 @@ namespace osu.Game.Tests.Gameplay protected override bool HasCustomSteps => true; - public TestSceneHitObjectSamples() - : base(new OsuRuleset()) - { - } - private SkinSourceDependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) diff --git a/osu.Game.Tests/Visual/Gameplay/TestPlayerTestScene.cs b/osu.Game.Tests/Visual/Gameplay/TestPlayerTestScene.cs new file mode 100644 index 0000000000..2130171449 --- /dev/null +++ b/osu.Game.Tests/Visual/Gameplay/TestPlayerTestScene.cs @@ -0,0 +1,16 @@ +// 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.Rulesets; +using osu.Game.Rulesets.Osu; + +namespace osu.Game.Tests.Visual.Gameplay +{ + /// + /// A with an arbitrary ruleset value to test with. + /// + public abstract class TestPlayerTestScene : PlayerTestScene + { + protected override Ruleset CreateRuleset() => new OsuRuleset(); + } +} diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs index 310746d179..744eeed022 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs @@ -10,23 +10,17 @@ using osu.Framework.Utils; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Rulesets; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Storyboards; using osuTK; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneGameplayRewinding : PlayerTestScene + public class TestSceneGameplayRewinding : TestPlayerTestScene { [Resolved] private AudioManager audioManager { get; set; } - public TestSceneGameplayRewinding() - : base(new OsuRuleset()) - { - } - private Track track; protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 944e6ca6be..411265d600 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -10,14 +10,13 @@ using osu.Framework.Testing; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Rulesets; -using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; using osuTK; using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { - public class TestScenePause : PlayerTestScene + public class TestScenePause : TestPlayerTestScene { protected new PausePlayer Player => (PausePlayer)base.Player; @@ -26,7 +25,6 @@ namespace osu.Game.Tests.Visual.Gameplay protected override Container Content => content; public TestScenePause() - : base(new OsuRuleset()) { base.Content.Add(content = new MenuCursorContainer { RelativeSizeAxes = Axes.Both }); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs index a83320048b..20911bfa4d 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs @@ -8,12 +8,11 @@ using osu.Framework.Platform; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Rulesets; -using osu.Game.Rulesets.Osu; namespace osu.Game.Tests.Visual.Gameplay { [HeadlessTest] // we alter unsafe properties on the game host to test inactive window state. - public class TestScenePauseWhenInactive : PlayerTestScene + public class TestScenePauseWhenInactive : TestPlayerTestScene { protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { @@ -27,11 +26,6 @@ namespace osu.Game.Tests.Visual.Gameplay [Resolved] private GameHost host { get; set; } - public TestScenePauseWhenInactive() - : base(new OsuRuleset()) - { - } - [Test] public void TestDoesntPauseDuringIntro() { diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 9e852719e0..f5e78fbbd1 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -19,15 +19,8 @@ namespace osu.Game.Tests.Visual /// protected virtual bool HasCustomSteps { get; } = false; - private readonly Ruleset ruleset; - protected TestPlayer Player; - protected PlayerTestScene(Ruleset ruleset) - { - this.ruleset = ruleset; - } - protected OsuConfigManager LocalConfig; [BackgroundDependencyLoader] @@ -53,7 +46,7 @@ namespace osu.Game.Tests.Visual action?.Invoke(); - AddStep(ruleset.RulesetInfo.Name, LoadPlayer); + AddStep(CreateRuleset().RulesetInfo.Name, LoadPlayer); AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); } @@ -63,28 +56,28 @@ namespace osu.Game.Tests.Visual protected void LoadPlayer() { - var beatmap = CreateBeatmap(ruleset.RulesetInfo); + var beatmap = CreateBeatmap(Ruleset.Value); Beatmap.Value = CreateWorkingBeatmap(beatmap); - Ruleset.Value = ruleset.RulesetInfo; - SelectedMods.Value = Array.Empty(); + var rulesetInstance = Ruleset.Value.CreateInstance(); + if (!AllowFail) { - var noFailMod = ruleset.GetAllMods().FirstOrDefault(m => m is ModNoFail); + var noFailMod = rulesetInstance.GetAllMods().FirstOrDefault(m => m is ModNoFail); if (noFailMod != null) SelectedMods.Value = new[] { noFailMod }; } if (Autoplay) { - var mod = ruleset.GetAutoplayMod(); + var mod = rulesetInstance.GetAutoplayMod(); if (mod != null) SelectedMods.Value = SelectedMods.Value.Concat(mod.Yield()).ToArray(); } - Player = CreatePlayer(ruleset); + Player = CreatePlayer(rulesetInstance); LoadScreen(Player); } From 155bc8b49a08842297cf1a4eb1b4d9e36d799b55 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 17 Apr 2020 10:56:01 +0300 Subject: [PATCH 012/871] Remove `ModTestScene` ruleset parameter on constructor and use `CreateRuleset` instead --- .../Mods/TestSceneCatchModPerfect.cs | 4 +++- .../Mods/TestSceneManiaModPerfect.cs | 4 +++- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 7 ++----- .../Mods/TestSceneOsuModDoubleTime.cs | 7 ++----- osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs | 4 +++- .../TestSceneMissHitWindowJudgements.cs | 7 ++----- .../Mods/TestSceneTaikoModPerfect.cs | 4 +++- osu.Game/Tests/Visual/ModPerfectTestScene.cs | 7 ++----- osu.Game/Tests/Visual/ModTestScene.cs | 5 ----- 9 files changed, 20 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs index 47e91e50d4..1e69a3f1b6 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods public class TestSceneCatchModPerfect : ModPerfectTestScene { public TestSceneCatchModPerfect() - : base(new CatchRuleset(), new CatchModPerfect()) + : base(new CatchModPerfect()) { } @@ -50,5 +50,7 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods // We only care about testing misses, hits are tested via JuiceStream [TestCase(true)] public void TestTinyDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new TinyDroplet { StartTime = 1000 }), shouldMiss); + + protected override Ruleset CreateRuleset() => new CatchRuleset(); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs index 607d42a1bb..72ef58ec73 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs @@ -11,7 +11,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods public class TestSceneManiaModPerfect : ModPerfectTestScene { public TestSceneManiaModPerfect() - : base(new ManiaRuleset(), new ManiaModPerfect()) + : base(new ManiaModPerfect()) { } @@ -22,5 +22,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods [TestCase(false)] [TestCase(true)] public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote { StartTime = 1000, EndTime = 3000 }), shouldMiss); + + protected override Ruleset CreateRuleset() => new ManiaRuleset(); } } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 69415b70e3..6c5949ca85 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -15,11 +15,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { public class TestSceneOsuModDifficultyAdjust : ModTestScene { - public TestSceneOsuModDifficultyAdjust() - : base(new OsuRuleset()) - { - } - [Test] public void TestNoAdjustment() => CreateModTest(new ModTestData { @@ -82,5 +77,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { return Player.ScoreProcessor.JudgedHits >= 2; } + + protected override Ruleset CreateRuleset() => new OsuRuleset(); } } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs index dcf19ad993..c61ef2724b 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs @@ -10,11 +10,6 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { public class TestSceneOsuModDoubleTime : ModTestScene { - public TestSceneOsuModDoubleTime() - : base(new OsuRuleset()) - { - } - [TestCase(0.5)] [TestCase(1.01)] [TestCase(1.5)] @@ -31,5 +26,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Precision.AlmostEquals(Player.GameplayClockContainer.GameplayClock.Rate, mod.SpeedChange.Value) }); } + + protected override Ruleset CreateRuleset() => new OsuRuleset(); } } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs index b03a894085..ddbbf9554c 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods public class TestSceneOsuModPerfect : ModPerfectTestScene { public TestSceneOsuModPerfect() - : base(new OsuRuleset(), new OsuModPerfect()) + : base(new OsuModPerfect()) { } @@ -48,5 +48,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods CreateHitObjectTest(new HitObjectTestData(spinner), shouldMiss); } + + protected override Ruleset CreateRuleset() => new OsuRuleset(); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs index 5f3596976d..13457ccaf9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs @@ -19,11 +19,6 @@ namespace osu.Game.Rulesets.Osu.Tests { public class TestSceneMissHitWindowJudgements : ModTestScene { - public TestSceneMissHitWindowJudgements() - : base(new OsuRuleset()) - { - } - [Test] public void TestMissViaEarlyHit() { @@ -66,6 +61,8 @@ namespace osu.Game.Rulesets.Osu.Tests }); } + protected override Ruleset CreateRuleset() => new OsuRuleset(); + private class TestAutoMod : OsuModAutoplay { public override Score CreateReplayScore(IBeatmap beatmap) => new Score diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs index 26c90ad295..a9c962bfa0 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods public class TestSceneTaikoModPerfect : ModPerfectTestScene { public TestSceneTaikoModPerfect() - : base(new TestTaikoRuleset(), new TaikoModPerfect()) + : base(new TaikoModPerfect()) { } @@ -29,6 +29,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods [TestCase(true)] public void TestSwell(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Swell { StartTime = 1000, EndTime = 3000 }), shouldMiss); + protected override Ruleset CreateRuleset() => new TestTaikoRuleset(); + private class TestTaikoRuleset : TaikoRuleset { public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new TestTaikoHealthProcessor(); diff --git a/osu.Game/Tests/Visual/ModPerfectTestScene.cs b/osu.Game/Tests/Visual/ModPerfectTestScene.cs index 798947eb40..3565fe751b 100644 --- a/osu.Game/Tests/Visual/ModPerfectTestScene.cs +++ b/osu.Game/Tests/Visual/ModPerfectTestScene.cs @@ -10,13 +10,10 @@ namespace osu.Game.Tests.Visual { public abstract class ModPerfectTestScene : ModTestScene { - private readonly Ruleset ruleset; private readonly ModPerfect mod; - protected ModPerfectTestScene(Ruleset ruleset, ModPerfect mod) - : base(ruleset) + protected ModPerfectTestScene(ModPerfect mod) { - this.ruleset = ruleset; this.mod = mod; } @@ -25,7 +22,7 @@ namespace osu.Game.Tests.Visual Mod = mod, Beatmap = new Beatmap { - BeatmapInfo = { Ruleset = ruleset.RulesetInfo }, + BeatmapInfo = { Ruleset = Ruleset.Value }, HitObjects = { testData.HitObject } }, Autoplay = !shouldMiss, diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 8b41fb5075..c198d6b52c 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -19,11 +19,6 @@ namespace osu.Game.Tests.Visual typeof(ModTestScene) }; - protected ModTestScene(Ruleset ruleset) - : base(ruleset) - { - } - private ModTestData currentTestData; protected void CreateModTest(ModTestData testData) => CreateTest(() => From 7f791dcdf04d3434ea7275de20fd8d369060b062 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 17 Apr 2020 10:57:58 +0300 Subject: [PATCH 013/871] Re-enable ruleset bindable before setting defined ruleset in case it's disabled Happens on cases like restarting the test scene by clicking directly on it on the browser (*where it for some reason reloads the entire test scene*) --- osu.Game/Tests/Visual/OsuTestScene.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 8058a074ef..25ac768272 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -75,6 +75,10 @@ namespace osu.Game.Tests.Visual if (definedRuleset != null) { + // re-enable the bindable in case it was disabled. + // happens when restarting current test scene. + Ruleset.Disabled = false; + // Set global ruleset bindable to the ruleset defined // for this test scene and disallow changing it. Ruleset.Value = definedRuleset; From f893d523f52554b57b390b3f4ee25d302bef9831 Mon Sep 17 00:00:00 2001 From: Endrik Tombak Date: Sun, 19 Apr 2020 12:23:41 +0300 Subject: [PATCH 014/871] User setting for always playing first combo break --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ .../Settings/Sections/Gameplay/GeneralSettings.cs | 6 ++++++ osu.Game/Screens/Play/ComboEffects.cs | 14 +++++++++++--- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 9d31bc9bba..10d11f967e 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -91,6 +91,7 @@ namespace osu.Game.Configuration Set(OsuSetting.FadePlayfieldWhenHealthLow, true); Set(OsuSetting.KeyOverlay, false); Set(OsuSetting.PositionalHitSounds, true); + Set(OsuSetting.AlwaysPlayComboBreak, false); Set(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth); Set(OsuSetting.FloatingComments, false); @@ -180,6 +181,7 @@ namespace osu.Game.Configuration ShowStoryboard, KeyOverlay, PositionalHitSounds, + AlwaysPlayComboBreak, ScoreMeter, FloatingComments, ShowInterface, diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index 93a02ea0e4..f3534e4625 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -67,6 +67,12 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = "Positional hitsounds", Bindable = config.GetBindable(OsuSetting.PositionalHitSounds) }, + new SettingsCheckbox + { + LabelText = "Always play first combo break sound", + Keywords = new[] { "regardless", "combobreak.wav" }, + Bindable = config.GetBindable(OsuSetting.AlwaysPlayComboBreak) + }, new SettingsEnumDropdown { LabelText = "Score meter type", diff --git a/osu.Game/Screens/Play/ComboEffects.cs b/osu.Game/Screens/Play/ComboEffects.cs index 1c4ac921f0..c56ee35cec 100644 --- a/osu.Game/Screens/Play/ComboEffects.cs +++ b/osu.Game/Screens/Play/ComboEffects.cs @@ -5,6 +5,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Game.Audio; +using osu.Game.Configuration; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; @@ -16,27 +17,34 @@ namespace osu.Game.Screens.Play private SkinnableSound comboBreakSample; + private Bindable alwaysPlay; + private bool firstTime = true; + public ComboEffects(ScoreProcessor processor) { this.processor = processor; } [BackgroundDependencyLoader] - private void load() + private void load(OsuConfigManager config) { InternalChild = comboBreakSample = new SkinnableSound(new SampleInfo("combobreak")); + alwaysPlay = config.GetBindable(OsuSetting.AlwaysPlayComboBreak); } protected override void LoadComplete() { base.LoadComplete(); - processor.Combo.BindValueChanged(onComboChange, true); + processor.Combo.BindValueChanged(onComboChange); } private void onComboChange(ValueChangedEvent combo) { - if (combo.NewValue == 0 && combo.OldValue > 20) + if (combo.NewValue == 0 && (combo.OldValue > 20 || alwaysPlay.Value && firstTime)) + { comboBreakSample?.Play(); + firstTime = false; + } } } } From 0a0ea39431ebf6432c0aa7eb071591931074a7fc Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 23 Apr 2020 13:24:18 +0300 Subject: [PATCH 015/871] Mark the top ruleset creation method as can-be-null --- osu.Game/Tests/Visual/OsuTestScene.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 25ac768272..83db86c0a0 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; @@ -145,6 +146,7 @@ namespace osu.Game.Tests.Visual /// /// When testing against ruleset-specific components, this method must be overriden to their ruleset. /// + [CanBeNull] protected virtual Ruleset CreateRuleset() => null; protected virtual IBeatmap CreateBeatmap(RulesetInfo ruleset) => new TestBeatmap(ruleset); From 2fa47992dc58f0c8293be22eba14673391181933 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 23 Apr 2020 13:25:06 +0300 Subject: [PATCH 016/871] Seal the ruleset creation methods and let abstract method take their place Also makes `CreatePlayerRuleset()` and `CreateRulesetForSkinProvider()` not-null to avoid unwanted behaviour with their derivers --- .../CatchSkinnableTestScene.cs | 2 +- .../Mods/TestSceneCatchModPerfect.cs | 4 +-- .../TestSceneCatchPlayer.cs | 2 +- .../Mods/TestSceneManiaModPerfect.cs | 4 +-- .../Skinning/ManiaSkinnableTestScene.cs | 4 +-- .../TestSceneManiaPlayer.cs | 2 +- .../Mods/TestSceneOsuModDifficultyAdjust.cs | 4 +-- .../Mods/TestSceneOsuModDoubleTime.cs | 4 +-- .../Mods/TestSceneOsuModPerfect.cs | 4 +-- .../OsuSkinnableTestScene.cs | 2 +- .../TestSceneMissHitWindowJudgements.cs | 4 +-- .../TestSceneOsuPlayer.cs | 2 +- .../Mods/TestSceneTaikoModPerfect.cs | 4 +-- .../TaikoSkinnableTestScene.cs | 2 +- .../TestSceneTaikoPlayer.cs | 2 +- .../Visual/Gameplay/TestPlayerTestScene.cs | 2 +- osu.Game/Tests/Visual/PlayerTestScene.cs | 30 ++++++++++++++----- osu.Game/Tests/Visual/SkinnableTestScene.cs | 19 +++++++----- 18 files changed, 58 insertions(+), 39 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs index c0060af74a..0c46b078b5 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchSkinnableTestScene.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(CatchLegacySkinTransformer), }; - protected override Ruleset CreateRuleset() => new CatchRuleset(); + protected override Ruleset CreateRulesetForSkinProvider() => new CatchRuleset(); } } diff --git a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs index 1e69a3f1b6..3e06e78dba 100644 --- a/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs +++ b/osu.Game.Rulesets.Catch.Tests/Mods/TestSceneCatchModPerfect.cs @@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods { public class TestSceneCatchModPerfect : ModPerfectTestScene { + protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); + public TestSceneCatchModPerfect() : base(new CatchModPerfect()) { @@ -50,7 +52,5 @@ namespace osu.Game.Rulesets.Catch.Tests.Mods // We only care about testing misses, hits are tested via JuiceStream [TestCase(true)] public void TestTinyDroplet(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new TinyDroplet { StartTime = 1000 }), shouldMiss); - - protected override Ruleset CreateRuleset() => new CatchRuleset(); } } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs index 722f3b5a3b..e1de461e3b 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchPlayer.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Catch.Tests typeof(CatchRuleset), }; - protected override Ruleset CreateRuleset() => new CatchRuleset(); + protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs index 72ef58ec73..2e3b21aed7 100644 --- a/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs +++ b/osu.Game.Rulesets.Mania.Tests/Mods/TestSceneManiaModPerfect.cs @@ -10,6 +10,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods { public class TestSceneManiaModPerfect : ModPerfectTestScene { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + public TestSceneManiaModPerfect() : base(new ManiaModPerfect()) { @@ -22,7 +24,5 @@ namespace osu.Game.Rulesets.Mania.Tests.Mods [TestCase(false)] [TestCase(true)] public void TestHoldNote(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new HoldNote { StartTime = 1000, EndTime = 3000 }), shouldMiss); - - protected override Ruleset CreateRuleset() => new ManiaRuleset(); } } diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs index f41ba4db42..a3c1d518c5 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/ManiaSkinnableTestScene.cs @@ -34,6 +34,8 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning typeof(ManiaSettingsSubsection) }; + protected override Ruleset CreateRulesetForSkinProvider() => new ManiaRuleset(); + protected ManiaSkinnableTestScene() { scrollingInfo.Direction.Value = ScrollingDirection.Down; @@ -58,8 +60,6 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning AddStep("change direction to up", () => scrollingInfo.Direction.Value = ScrollingDirection.Up); } - protected override Ruleset CreateRuleset() => new ManiaRuleset(); - private class TestScrollingInfo : IScrollingInfo { public readonly Bindable Direction = new Bindable(); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs index 11663605e2..f4640fd05b 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaPlayer.cs @@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Mania.Tests typeof(ManiaRuleset), }; - protected override Ruleset CreateRuleset() => new ManiaRuleset(); + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); } } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 6c5949ca85..7c396054f1 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { public class TestSceneOsuModDifficultyAdjust : ModTestScene { + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + [Test] public void TestNoAdjustment() => CreateModTest(new ModTestData { @@ -77,7 +79,5 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { return Player.ScoreProcessor.JudgedHits >= 2; } - - protected override Ruleset CreateRuleset() => new OsuRuleset(); } } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs index c61ef2724b..94ef6140e9 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs @@ -10,6 +10,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { public class TestSceneOsuModDoubleTime : ModTestScene { + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + [TestCase(0.5)] [TestCase(1.01)] [TestCase(1.5)] @@ -26,7 +28,5 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods Precision.AlmostEquals(Player.GameplayClockContainer.GameplayClock.Rate, mod.SpeedChange.Value) }); } - - protected override Ruleset CreateRuleset() => new OsuRuleset(); } } diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs index ddbbf9554c..985baa8cf5 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModPerfect.cs @@ -13,6 +13,8 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods { public class TestSceneOsuModPerfect : ModPerfectTestScene { + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + public TestSceneOsuModPerfect() : base(new OsuModPerfect()) { @@ -48,7 +50,5 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods CreateHitObjectTest(new HitObjectTestData(spinner), shouldMiss); } - - protected override Ruleset CreateRuleset() => new OsuRuleset(); } } diff --git a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs index 1458270193..90ebbd9f04 100644 --- a/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Osu.Tests/OsuSkinnableTestScene.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Osu.Tests typeof(OsuLegacySkinTransformer), }; - protected override Ruleset CreateRuleset() => new OsuRuleset(); + protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); } } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs index 13457ccaf9..f3221ffe32 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneMissHitWindowJudgements.cs @@ -19,6 +19,8 @@ namespace osu.Game.Rulesets.Osu.Tests { public class TestSceneMissHitWindowJudgements : ModTestScene { + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + [Test] public void TestMissViaEarlyHit() { @@ -61,8 +63,6 @@ namespace osu.Game.Rulesets.Osu.Tests }); } - protected override Ruleset CreateRuleset() => new OsuRuleset(); - private class TestAutoMod : OsuModAutoplay { public override Score CreateReplayScore(IBeatmap beatmap) => new Score diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs index 102f8bf841..4ae19624c0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuPlayer.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Osu.Tests typeof(OsuRuleset), }; - protected override Ruleset CreateRuleset() => new OsuRuleset(); + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); } } diff --git a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs index a9c962bfa0..a83cc16413 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Mods/TestSceneTaikoModPerfect.cs @@ -12,6 +12,8 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods { public class TestSceneTaikoModPerfect : ModPerfectTestScene { + protected override Ruleset CreatePlayerRuleset() => new TestTaikoRuleset(); + public TestSceneTaikoModPerfect() : base(new TaikoModPerfect()) { @@ -29,8 +31,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Mods [TestCase(true)] public void TestSwell(bool shouldMiss) => CreateHitObjectTest(new HitObjectTestData(new Swell { StartTime = 1000, EndTime = 3000 }), shouldMiss); - protected override Ruleset CreateRuleset() => new TestTaikoRuleset(); - private class TestTaikoRuleset : TaikoRuleset { public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new TestTaikoHealthProcessor(); diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs index 98e6c2ec52..6db2a6907f 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoSkinnableTestScene.cs @@ -16,6 +16,6 @@ namespace osu.Game.Rulesets.Taiko.Tests typeof(TaikoLegacySkinTransformer), }; - protected override Ruleset CreateRuleset() => new TaikoRuleset(); + protected override Ruleset CreateRulesetForSkinProvider() => new TaikoRuleset(); } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs index 4c5ab7eabf..bc6f664942 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayer.cs @@ -14,6 +14,6 @@ namespace osu.Game.Rulesets.Taiko.Tests typeof(TaikoRuleset) }; - protected override Ruleset CreateRuleset() => new TaikoRuleset(); + protected override Ruleset CreatePlayerRuleset() => new TaikoRuleset(); } } diff --git a/osu.Game.Tests/Visual/Gameplay/TestPlayerTestScene.cs b/osu.Game.Tests/Visual/Gameplay/TestPlayerTestScene.cs index 2130171449..bbf0136b00 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestPlayerTestScene.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestPlayerTestScene.cs @@ -11,6 +11,6 @@ namespace osu.Game.Tests.Visual.Gameplay /// public abstract class TestPlayerTestScene : PlayerTestScene { - protected override Ruleset CreateRuleset() => new OsuRuleset(); + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); } } diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index f5e78fbbd1..53abf83e72 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Testing; @@ -23,6 +24,22 @@ namespace osu.Game.Tests.Visual protected OsuConfigManager LocalConfig; + /// + /// Creates the ruleset for setting up the component. + /// + [NotNull] + protected abstract Ruleset CreatePlayerRuleset(); + + protected sealed override Ruleset CreateRuleset() => CreatePlayerRuleset(); + + [NotNull] + private readonly Ruleset ruleset; + + protected PlayerTestScene() + { + ruleset = CreatePlayerRuleset(); + } + [BackgroundDependencyLoader] private void load() { @@ -46,7 +63,7 @@ namespace osu.Game.Tests.Visual action?.Invoke(); - AddStep(CreateRuleset().RulesetInfo.Name, LoadPlayer); + AddStep(ruleset.Description, LoadPlayer); AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); } @@ -56,28 +73,27 @@ namespace osu.Game.Tests.Visual protected void LoadPlayer() { - var beatmap = CreateBeatmap(Ruleset.Value); + var beatmap = CreateBeatmap(ruleset.RulesetInfo); Beatmap.Value = CreateWorkingBeatmap(beatmap); + Ruleset.Value = ruleset.RulesetInfo; SelectedMods.Value = Array.Empty(); - var rulesetInstance = Ruleset.Value.CreateInstance(); - if (!AllowFail) { - var noFailMod = rulesetInstance.GetAllMods().FirstOrDefault(m => m is ModNoFail); + var noFailMod = ruleset.GetAllMods().FirstOrDefault(m => m is ModNoFail); if (noFailMod != null) SelectedMods.Value = new[] { noFailMod }; } if (Autoplay) { - var mod = rulesetInstance.GetAutoplayMod(); + var mod = ruleset.GetAutoplayMod(); if (mod != null) SelectedMods.Value = SelectedMods.Value.Concat(mod.Yield()).ToArray(); } - Player = CreatePlayer(rulesetInstance); + Player = CreatePlayer(ruleset); LoadScreen(Player); } diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index d648afd504..98164031b0 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Text.RegularExpressions; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; @@ -13,6 +14,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -26,16 +28,17 @@ namespace osu.Game.Tests.Visual private Skin specialSkin; private Skin oldSkin; + /// + /// Creates the ruleset for adding the ruleset-specific skin transforming component. + /// + [NotNull] + protected abstract Ruleset CreateRulesetForSkinProvider(); + + protected sealed override Ruleset CreateRuleset() => CreateRulesetForSkinProvider(); + protected SkinnableTestScene() : base(2, 3) { - // avoid running silently incorrectly. - if (CreateRuleset() == null) - { - throw new InvalidOperationException( - $"No ruleset provided, override {nameof(CreateRuleset)} to the ruleset belonging to the skinnable content." - + "This is required to add the legacy skin transformer for the content to behave as expected."); - } } [BackgroundDependencyLoader] @@ -110,7 +113,7 @@ namespace osu.Game.Tests.Visual { new OutlineBox { Alpha = autoSize ? 1 : 0 }, mainProvider.WithChild( - new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider, beatmap)) + new SkinProvidingContainer(CreateRulesetForSkinProvider().CreateLegacySkinProvider(mainProvider, beatmap)) { Child = created, RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None, From 836efe3f7c697448b82a1f3b053ef0dd85f3efc0 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Thu, 7 May 2020 08:07:22 +0200 Subject: [PATCH 017/871] Initial commit --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 4 +++- .../Settings/Sections/General/UpdateSettings.cs | 13 ++++++++++++- osu.Game/Updater/SimpleUpdateManager.cs | 4 +++- osu.Game/Updater/UpdateManager.cs | 6 ++++++ 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index ade8460dd7..b287dd6527 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -37,10 +37,12 @@ namespace osu.Desktop.Updater if (game.IsDeployedBuild) { Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger)); - Schedule(() => Task.Run(() => checkForUpdateAsync())); + CheckForUpdate(); } } + public override void CheckForUpdate() => Schedule(() => Task.Run(() => checkForUpdateAsync())); + private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) { // should we schedule a retry on completion of this check? diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 188c9c05ef..71deeee693 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -5,15 +5,19 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; using osu.Game.Configuration; +using osu.Game.Updater; namespace osu.Game.Overlays.Settings.Sections.General { public class UpdateSettings : SettingsSubsection { + [Resolved(CanBeNull = true)] + private UpdateManager updateManager { get; set; } + protected override string Header => "Updates"; [BackgroundDependencyLoader] - private void load(Storage storage, OsuConfigManager config) + private void load(Storage storage, OsuConfigManager config, OsuGameBase game) { Add(new SettingsEnumDropdown { @@ -21,6 +25,13 @@ namespace osu.Game.Overlays.Settings.Sections.General Bindable = config.GetBindable(OsuSetting.ReleaseStream), }); + Add(new SettingsButton + { + Text = "Check for updates", + Action = () => updateManager?.CheckForUpdate(), + Enabled = { Value = game.IsDeployedBuild } + }); + if (RuntimeInfo.IsDesktop) { Add(new SettingsButton diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index 1e8a96444f..41248ed796 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -30,9 +30,11 @@ namespace osu.Game.Updater version = game.Version; if (game.IsDeployedBuild) - Schedule(() => Task.Run(checkForUpdateAsync)); + CheckForUpdate(); } + public override void CheckForUpdate() => Schedule(() => Task.Run(checkForUpdateAsync)); + private async void checkForUpdateAsync() { try diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 28a295215f..f628bde324 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Overlays; @@ -44,6 +45,11 @@ namespace osu.Game.Updater config.Set(OsuSetting.Version, version); } + public virtual void CheckForUpdate() + { + Logger.Log("CheckForUpdate was called on the base class (UpdateManager)", LoggingTarget.Information); + } + private class UpdateCompleteNotification : SimpleNotification { private readonly string version; From c025814f403dc5fcf4f07342791387986c526809 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Thu, 7 May 2020 23:04:18 +0200 Subject: [PATCH 018/871] Finalize changes --- osu.Game/OsuGame.cs | 14 +++++++++++--- .../Settings/Sections/General/UpdateSettings.cs | 7 ++----- osu.Game/Updater/SimpleUpdateManager.cs | 2 +- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index fdc8d94352..00b967c243 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -90,7 +90,7 @@ namespace osu.Game protected BackButton BackButton; - protected SettingsPanel Settings; + protected SettingsOverlay Settings; private VolumeOverlay volume; private OsuLogo osuLogo; @@ -609,6 +609,9 @@ namespace osu.Game loadComponentSingleFile(screenshotManager, Add); + // dependency on notification overlay + loadComponentSingleFile(CreateUpdateManager(), Add, true); + // overlay elements loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true); loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true); @@ -641,7 +644,6 @@ namespace osu.Game chatOverlay.State.ValueChanged += state => channelManager.HighPollRate.Value = state.NewValue == Visibility.Visible; Add(externalLinkOpener = new ExternalLinkOpener()); - Add(CreateUpdateManager()); // dependency on notification overlay // side overlays which cancel each other. var singleDisplaySideOverlays = new OverlayContainer[] { Settings, notifications }; @@ -765,11 +767,17 @@ namespace osu.Game private Task asyncLoadStream; + /// + /// Schedules loading the provided in a single file. + /// + /// The component to load. + /// The method to invoke for adding the component. + /// Whether to cache the component as type into the game dependencies before any scheduling. private T loadComponentSingleFile(T d, Action add, bool cache = false) where T : Drawable { if (cache) - dependencies.Cache(d); + dependencies.CacheAs(d); if (d is OverlayContainer overlay) overlays.Add(overlay); diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 71deeee693..233a382b54 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -11,13 +11,10 @@ namespace osu.Game.Overlays.Settings.Sections.General { public class UpdateSettings : SettingsSubsection { - [Resolved(CanBeNull = true)] - private UpdateManager updateManager { get; set; } - protected override string Header => "Updates"; [BackgroundDependencyLoader] - private void load(Storage storage, OsuConfigManager config, OsuGameBase game) + private void load(Storage storage, OsuConfigManager config, OsuGameBase game, UpdateManager updateManager) { Add(new SettingsEnumDropdown { @@ -28,7 +25,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Add(new SettingsButton { Text = "Check for updates", - Action = () => updateManager?.CheckForUpdate(), + Action = () => updateManager.CheckForUpdate(), Enabled = { Value = game.IsDeployedBuild } }); diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index 41248ed796..234fe8be8b 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -33,7 +33,7 @@ namespace osu.Game.Updater CheckForUpdate(); } - public override void CheckForUpdate() => Schedule(() => Task.Run(checkForUpdateAsync)); + public override void CheckForUpdate() => Schedule(() => Task.Run(() => checkForUpdateAsync())); private async void checkForUpdateAsync() { From 92872496b86db2681da81cc151223e5707464940 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Thu, 7 May 2020 23:27:28 +0200 Subject: [PATCH 019/871] Convert to method groups because Inspector said so. --- osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs | 2 +- osu.Game/Updater/SimpleUpdateManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 233a382b54..5ddd12f667 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Add(new SettingsButton { Text = "Check for updates", - Action = () => updateManager.CheckForUpdate(), + Action = updateManager.CheckForUpdate, Enabled = { Value = game.IsDeployedBuild } }); diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index 234fe8be8b..41248ed796 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -33,7 +33,7 @@ namespace osu.Game.Updater CheckForUpdate(); } - public override void CheckForUpdate() => Schedule(() => Task.Run(() => checkForUpdateAsync())); + public override void CheckForUpdate() => Schedule(() => Task.Run(checkForUpdateAsync)); private async void checkForUpdateAsync() { From 72b6bb25a5c1125f038d4b079e2e57fd31fdbcd1 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Fri, 8 May 2020 00:33:33 +0200 Subject: [PATCH 020/871] Allow nulls and hide if missing dependencies --- .../Settings/Sections/General/UpdateSettings.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 5ddd12f667..23ca752f6e 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -13,7 +13,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { protected override string Header => "Updates"; - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(Storage storage, OsuConfigManager config, OsuGameBase game, UpdateManager updateManager) { Add(new SettingsEnumDropdown @@ -22,12 +22,15 @@ namespace osu.Game.Overlays.Settings.Sections.General Bindable = config.GetBindable(OsuSetting.ReleaseStream), }); - Add(new SettingsButton + if (game != null && updateManager != null) { - Text = "Check for updates", - Action = updateManager.CheckForUpdate, - Enabled = { Value = game.IsDeployedBuild } - }); + Add(new SettingsButton + { + Text = "Check for updates", + Action = updateManager.CheckForUpdate, + Enabled = { Value = game.IsDeployedBuild } + }); + } if (RuntimeInfo.IsDesktop) { From 477bd7fa613c75a6b535324cf59e42bdb7dce669 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Fri, 8 May 2020 00:35:27 +0200 Subject: [PATCH 021/871] Change to Resolved attribute --- .../Overlays/Settings/Sections/General/UpdateSettings.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 23ca752f6e..5af6a060ee 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -11,10 +11,16 @@ namespace osu.Game.Overlays.Settings.Sections.General { public class UpdateSettings : SettingsSubsection { + [Resolved(CanBeNull = true)] + private OsuGameBase game { get; set; } + + [Resolved(CanBeNull = true)] + private UpdateManager updateManager { get; set; } + protected override string Header => "Updates"; [BackgroundDependencyLoader(true)] - private void load(Storage storage, OsuConfigManager config, OsuGameBase game, UpdateManager updateManager) + private void load(Storage storage, OsuConfigManager config) { Add(new SettingsEnumDropdown { From a7792070bc01f4108515f665f5ff17dc750c25e4 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Fri, 8 May 2020 01:08:17 +0200 Subject: [PATCH 022/871] Final changes to DI fields and values --- .../Settings/Sections/General/UpdateSettings.cs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 5af6a060ee..58966e8a4c 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -11,16 +11,13 @@ namespace osu.Game.Overlays.Settings.Sections.General { public class UpdateSettings : SettingsSubsection { - [Resolved(CanBeNull = true)] - private OsuGameBase game { get; set; } - [Resolved(CanBeNull = true)] private UpdateManager updateManager { get; set; } protected override string Header => "Updates"; - [BackgroundDependencyLoader(true)] - private void load(Storage storage, OsuConfigManager config) + [BackgroundDependencyLoader] + private void load(Storage storage, OsuConfigManager config, OsuGameBase game) { Add(new SettingsEnumDropdown { @@ -28,7 +25,8 @@ namespace osu.Game.Overlays.Settings.Sections.General Bindable = config.GetBindable(OsuSetting.ReleaseStream), }); - if (game != null && updateManager != null) + // We shouldn't display the button for the base UpdateManager (without updating logic) + if (updateManager != null && updateManager.GetType() != typeof(UpdateManager)) { Add(new SettingsButton { From 75e65766ffcf0e3e1820407534bcd0104d469adb Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Fri, 8 May 2020 01:09:16 +0200 Subject: [PATCH 023/871] Annotate dependency --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 00b967c243..899056e179 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -609,7 +609,7 @@ namespace osu.Game loadComponentSingleFile(screenshotManager, Add); - // dependency on notification overlay + // dependency on notification overlay, dependent by settings overlay loadComponentSingleFile(CreateUpdateManager(), Add, true); // overlay elements From e6ad28a1cbb66359faa430446ae1d7b1fbc75b64 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Fri, 8 May 2020 02:09:37 +0200 Subject: [PATCH 024/871] Use property instead of type checking --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 2 ++ osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs | 4 ++-- osu.Game/Updater/SimpleUpdateManager.cs | 2 ++ osu.Game/Updater/UpdateManager.cs | 2 ++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index b287dd6527..2834f1f71d 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -22,6 +22,8 @@ namespace osu.Desktop.Updater { public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager { + public override bool CanPerformUpdate => true; + private UpdateManager updateManager; private NotificationOverlay notificationOverlay; diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 58966e8a4c..b832e8930a 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -25,8 +25,8 @@ namespace osu.Game.Overlays.Settings.Sections.General Bindable = config.GetBindable(OsuSetting.ReleaseStream), }); - // We shouldn't display the button for the base UpdateManager (without updating logic) - if (updateManager != null && updateManager.GetType() != typeof(UpdateManager)) + // We should only display the button for UpdateManagers that do update the client + if (updateManager != null && updateManager.CanPerformUpdate) { Add(new SettingsButton { diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index 41248ed796..5cc42090f4 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -19,6 +19,8 @@ namespace osu.Game.Updater /// public class SimpleUpdateManager : UpdateManager { + public override bool CanPerformUpdate => true; + private string version; [Resolved] diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index f628bde324..f8c8bfe967 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -17,6 +17,8 @@ namespace osu.Game.Updater /// public class UpdateManager : CompositeDrawable { + public virtual bool CanPerformUpdate => false; + [Resolved] private OsuConfigManager config { get; set; } From 7f61f27be1e3031266110c0f64a812bc2a787829 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Fri, 8 May 2020 02:33:12 +0200 Subject: [PATCH 025/871] Use null-conditional operator when checking against UpdateManager Co-authored-by: Dean Herbert --- osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index b832e8930a..6ea9c975de 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -26,7 +26,7 @@ namespace osu.Game.Overlays.Settings.Sections.General }); // We should only display the button for UpdateManagers that do update the client - if (updateManager != null && updateManager.CanPerformUpdate) + if (updateManager?.CanPerformUpdate == true) { Add(new SettingsButton { From 3c24ca08d042782166b0e1a7e1ce7297062e309e Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Fri, 8 May 2020 02:48:27 +0200 Subject: [PATCH 026/871] Check whether the build is deployed within the public check updates method --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 15 +++++++++------ osu.Game/Updater/SimpleUpdateManager.cs | 12 +++++++++--- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 2834f1f71d..a3b21b4bd9 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -26,6 +26,7 @@ namespace osu.Desktop.Updater private UpdateManager updateManager; private NotificationOverlay notificationOverlay; + private OsuGameBase gameBase; public Task PrepareUpdateAsync() => UpdateManager.RestartAppWhenExited(); @@ -34,16 +35,18 @@ namespace osu.Desktop.Updater [BackgroundDependencyLoader] private void load(NotificationOverlay notification, OsuGameBase game) { + gameBase = game; notificationOverlay = notification; - if (game.IsDeployedBuild) - { - Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger)); - CheckForUpdate(); - } + Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger)); + CheckForUpdate(); } - public override void CheckForUpdate() => Schedule(() => Task.Run(() => checkForUpdateAsync())); + public override void CheckForUpdate() + { + if (gameBase.IsDeployedBuild) + Schedule(() => Task.Run(() => checkForUpdateAsync())); + } private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) { diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index 5cc42090f4..8513ea94b4 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -26,16 +26,22 @@ namespace osu.Game.Updater [Resolved] private GameHost host { get; set; } + private OsuGameBase gameBase; + [BackgroundDependencyLoader] private void load(OsuGameBase game) { + gameBase = game; version = game.Version; - if (game.IsDeployedBuild) - CheckForUpdate(); + CheckForUpdate(); } - public override void CheckForUpdate() => Schedule(() => Task.Run(checkForUpdateAsync)); + public override void CheckForUpdate() + { + if (gameBase.IsDeployedBuild) + Schedule(() => Task.Run(checkForUpdateAsync)); + } private async void checkForUpdateAsync() { From ebd1df8c2822a76c683b1b0f01e6e7677c3a8f70 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Fri, 8 May 2020 02:50:58 +0200 Subject: [PATCH 027/871] Change property name to CanCheckForUpdate --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 2 +- .../Overlays/Settings/Sections/General/UpdateSettings.cs | 4 ++-- osu.Game/Updater/SimpleUpdateManager.cs | 2 +- osu.Game/Updater/UpdateManager.cs | 5 ++++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index a3b21b4bd9..5c553f18f4 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -22,7 +22,7 @@ namespace osu.Desktop.Updater { public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager { - public override bool CanPerformUpdate => true; + public override bool CanCheckForUpdate => true; private UpdateManager updateManager; private NotificationOverlay notificationOverlay; diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index b832e8930a..cadffd9d86 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -25,8 +25,8 @@ namespace osu.Game.Overlays.Settings.Sections.General Bindable = config.GetBindable(OsuSetting.ReleaseStream), }); - // We should only display the button for UpdateManagers that do update the client - if (updateManager != null && updateManager.CanPerformUpdate) + // We should only display the button for UpdateManagers that do check for updates + if (updateManager != null && updateManager.CanCheckForUpdate) { Add(new SettingsButton { diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index 8513ea94b4..d4e8aed5ae 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -19,7 +19,7 @@ namespace osu.Game.Updater /// public class SimpleUpdateManager : UpdateManager { - public override bool CanPerformUpdate => true; + public override bool CanCheckForUpdate => true; private string version; diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index f8c8bfe967..41bbfb76a5 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -17,7 +17,10 @@ namespace osu.Game.Updater /// public class UpdateManager : CompositeDrawable { - public virtual bool CanPerformUpdate => false; + /// + /// Whether this UpdateManager is capable of checking for updates. + /// + public virtual bool CanCheckForUpdate => false; [Resolved] private OsuConfigManager config { get; set; } From 1d999bb634de168c5acb1f3abbc5fd12d5596e31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 10 May 2020 18:32:38 +0200 Subject: [PATCH 028/871] Integrate PeriodTracker changes --- .../Scoring/DrainingHealthProcessor.cs | 56 +++++-------------- 1 file changed, 15 insertions(+), 41 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index b36e42326c..1958efdd6f 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -5,9 +5,9 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Timing; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Utils; namespace osu.Game.Rulesets.Scoring { @@ -49,33 +49,7 @@ namespace osu.Game.Rulesets.Scoring private double targetMinimumHealth; private double drainRate = 1; - private readonly List<(double startTime, double endTime)> nonDrainSections = new List<(double, double)>(); - private int currentNonDrainSection; - - private bool isInNonDrainSection - { - get - { - if (nonDrainSections.Count == 0) - return false; - - var time = Time.Current; - - if (time > nonDrainSections[currentNonDrainSection].endTime) - { - while (time > nonDrainSections[currentNonDrainSection].endTime && currentNonDrainSection < nonDrainSections.Count - 1) - currentNonDrainSection++; - } - else - { - while (time < nonDrainSections[currentNonDrainSection].startTime && currentNonDrainSection > 0) - currentNonDrainSection--; - } - - var closestSection = nonDrainSections[currentNonDrainSection]; - return time >= closestSection.startTime && time <= closestSection.endTime; - } - } + private PeriodTracker noDrainPeriodTracker; /// /// Creates a new . @@ -90,7 +64,7 @@ namespace osu.Game.Rulesets.Scoring { base.Update(); - if (isInNonDrainSection) + if (noDrainPeriodTracker?.IsInAny(Time.Current) == true) return; // When jumping in and out of gameplay time within a single frame, health should only be drained for the period within the gameplay time @@ -102,23 +76,23 @@ namespace osu.Game.Rulesets.Scoring public override void ApplyBeatmap(IBeatmap beatmap) { - nonDrainSections.Clear(); - this.beatmap = beatmap; if (beatmap.HitObjects.Count > 0) gameplayEndTime = beatmap.HitObjects[^1].GetEndTime(); - // Ranges between the end of last hit object before a break - // and the start of first hit object after a break should - // not allow HP draining. (with break periods in) - foreach (BreakPeriod b in beatmap.Breaks) - { - var startTime = beatmap.HitObjects.LastOrDefault(h => h.GetEndTime() < b.StartTime)?.GetEndTime() ?? double.MinValue; - var endTime = beatmap.HitObjects.FirstOrDefault(h => h.StartTime > b.EndTime)?.StartTime ?? double.MaxValue; - - nonDrainSections.Add((startTime, endTime)); - } + noDrainPeriodTracker = new PeriodTracker(beatmap.Breaks.Select(breakPeriod => new Period( + beatmap.HitObjects + .Select(hitObject => hitObject.GetEndTime()) + .Where(endTime => endTime < breakPeriod.StartTime) + .DefaultIfEmpty(double.MinValue) + .Last(), + beatmap.HitObjects + .Select(hitObject => hitObject.StartTime) + .Where(startTime => startTime > breakPeriod.EndTime) + .DefaultIfEmpty(double.MaxValue) + .First() + ))); targetMinimumHealth = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, min_health_target, mid_health_target, max_health_target); From e650b10b5e1adfb00123064e043d5768eb0e409f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 11 May 2020 19:03:13 +0200 Subject: [PATCH 029/871] Add test case for maximal break --- .../TestSceneDrainingHealthProcessor.cs | 75 +++++++++---------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs index 2f83ea4832..e50b2231bf 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.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.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Utils; @@ -43,52 +41,61 @@ namespace osu.Game.Tests.Gameplay } [Test] - public void TestHealthNotDrainedBeforeBreak() + public void TestHealthDrainBetweenBreakAndObjects() { - createProcessor(createBeatmap(0, 2000, - new BreakPeriod(400, 600), new BreakPeriod(1200, 1400))); + createProcessor(createBeatmap(0, 2000, new BreakPeriod(325, 375))); - setTime(300); + // 275 300 325 350 375 400 425 + // hitobjects o o + // break [-------------] + // no drain [---------------------------] + + setTime(285); setHealth(1); - setTime(400); - assertHealthEqualTo(1); + setTime(295); + assertHealthNotEqualTo(1); - setTime(1100); + setTime(305); setHealth(1); - setTime(1200); + setTime(315); assertHealthEqualTo(1); + + setTime(365); + assertHealthEqualTo(1); + + setTime(395); + assertHealthEqualTo(1); + + setTime(425); + assertHealthNotEqualTo(1); } [Test] - public void TestHealthNotDrainedDuringBreak() + public void TestHealthDrainDuringMaximalBreak() { - createProcessor(createBeatmap(0, 2000, new BreakPeriod(0, 1200))); + createProcessor(createBeatmap(0, 2000, new BreakPeriod(300, 400))); - setTime(700); - assertHealthEqualTo(1); - setTime(900); - assertHealthEqualTo(1); - } + // 275 300 325 350 375 400 425 + // hitobjects o o + // break [---------------------------] + // no drain [---------------------------] - [Test] - public void TestHealthNotDrainedAfterBreak() - { - createProcessor(createBeatmap(0, 2000, - new BreakPeriod(400, 600), new BreakPeriod(1200, 1400))); - - setTime(600); + setTime(285); setHealth(1); - setTime(700); - assertHealthEqualTo(1); + setTime(295); + assertHealthNotEqualTo(1); - setTime(1400); + setTime(305); setHealth(1); - setTime(1500); + setTime(395); assertHealthEqualTo(1); + + setTime(425); + assertHealthNotEqualTo(1); } [Test] @@ -154,24 +161,16 @@ namespace osu.Game.Tests.Gameplay { var beatmap = new Beatmap { - BeatmapInfo = { BaseDifficulty = { DrainRate = 5 } }, + BeatmapInfo = { BaseDifficulty = { DrainRate = 10 } }, }; - double time = startTime; - - while (time <= endTime) + for (double time = startTime; time <= endTime; time += 100) { beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = time }); - - // leave a 100ms gap between the start and end of a break period. - time += (getCurrentBreak(breaks, time)?.Duration ?? 0) + 100; } beatmap.Breaks.AddRange(breaks); - static BreakPeriod getCurrentBreak(IEnumerable breaks, double time) => - breaks?.FirstOrDefault(b => time >= b.StartTime && time <= b.EndTime); - return beatmap; } From 848a3fb6d74ec3b443029cc048934c1df4ea728f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 11 May 2020 19:06:36 +0200 Subject: [PATCH 030/871] Take hitobject start/end times into account in drain --- osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index 1958efdd6f..982f527517 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -84,12 +84,12 @@ namespace osu.Game.Rulesets.Scoring noDrainPeriodTracker = new PeriodTracker(beatmap.Breaks.Select(breakPeriod => new Period( beatmap.HitObjects .Select(hitObject => hitObject.GetEndTime()) - .Where(endTime => endTime < breakPeriod.StartTime) + .Where(endTime => endTime <= breakPeriod.StartTime) .DefaultIfEmpty(double.MinValue) .Last(), beatmap.HitObjects .Select(hitObject => hitObject.StartTime) - .Where(startTime => startTime > breakPeriod.EndTime) + .Where(startTime => startTime >= breakPeriod.EndTime) .DefaultIfEmpty(double.MaxValue) .First() ))); From 39c36998c99509104c60f23cdce61a70e64e3c59 Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Tue, 12 May 2020 06:06:31 +0200 Subject: [PATCH 031/871] Revert changes that are to be resolved in #9002 --- osu.Game/OsuGame.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 899056e179..294180cb30 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -90,7 +90,7 @@ namespace osu.Game protected BackButton BackButton; - protected SettingsOverlay Settings; + protected SettingsPanel Settings; private VolumeOverlay volume; private OsuLogo osuLogo; @@ -767,17 +767,11 @@ namespace osu.Game private Task asyncLoadStream; - /// - /// Schedules loading the provided in a single file. - /// - /// The component to load. - /// The method to invoke for adding the component. - /// Whether to cache the component as type into the game dependencies before any scheduling. private T loadComponentSingleFile(T d, Action add, bool cache = false) where T : Drawable { if (cache) - dependencies.CacheAs(d); + dependencies.Cache(d); if (d is OverlayContainer overlay) overlays.Add(overlay); From 0ef3bae26a4483fd12c21a34ff38d3fe6f69775d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 May 2020 18:34:51 +0900 Subject: [PATCH 032/871] Expose playfield from IManiaHitObjectComposer --- .../ManiaPlacementBlueprintTestScene.cs | 2 +- .../ManiaSelectionBlueprintTestScene.cs | 2 +- osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs | 2 +- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 2 -- osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs | 2 +- 5 files changed, 4 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs index aac77c9c1c..39d5f50459 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs @@ -49,6 +49,6 @@ namespace osu.Game.Rulesets.Mania.Tests public Column ColumnAt(Vector2 screenSpacePosition) => column; - public int TotalColumns => 1; + public ManiaPlayfield Playfield => null; } } diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs index b598893e8c..d6dee92ba6 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs @@ -33,6 +33,6 @@ namespace osu.Game.Rulesets.Mania.Tests public Column ColumnAt(Vector2 screenSpacePosition) => column; - public int TotalColumns => 1; + public ManiaPlayfield Playfield => null; } } diff --git a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs index f64bab1fae..9b5d290fa8 100644 --- a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs @@ -10,6 +10,6 @@ namespace osu.Game.Rulesets.Mania.Edit { Column ColumnAt(Vector2 screenSpacePosition); - int TotalColumns { get; } + ManiaPlayfield Playfield { get; } } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index dfa933baad..d7c0889c0d 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -42,8 +42,6 @@ namespace osu.Game.Rulesets.Mania.Edit public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo; - public int TotalColumns => Playfield.TotalColumns; - public override (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) { var hoc = Playfield.GetColumn(0).HitObjectContainer; diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 55245198c8..83049ff959 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Mania.Edit maxColumn = obj.Column; } - columnDelta = Math.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn); + columnDelta = Math.Clamp(columnDelta, -minColumn, composer.Playfield.TotalColumns - 1 - maxColumn); foreach (var obj in SelectedHitObjects.OfType()) obj.Column += columnDelta; From 16585f767edb0f877cd04eba61ef662f7ecdca59 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 May 2020 19:17:24 +0900 Subject: [PATCH 033/871] Add initial beat snap grid implementation --- .../TestSceneManiaBeatSnapGrid.cs | 73 ++++++ .../Edit/ManiaBeatSnapGrid.cs | 233 ++++++++++++++++++ osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs | 2 + 3 files changed, 308 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs create mode 100644 osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs new file mode 100644 index 0000000000..84419313e6 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs @@ -0,0 +1,73 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Framework.Timing; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Configuration; +using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Edit; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens.Edit; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [Cached(typeof(IManiaHitObjectComposer))] + public class TestSceneManiaBeatSnapGrid : EditorClockTestScene, IManiaHitObjectComposer + { + [Cached(typeof(IScrollingInfo))] + private ScrollingTestContainer.TestScrollingInfo scrollingInfo = new ScrollingTestContainer.TestScrollingInfo(); + + [Cached(typeof(EditorBeatmap))] + private EditorBeatmap editorBeatmap = new EditorBeatmap(new ManiaBeatmap(new StageDefinition())); + + private readonly ManiaBeatSnapGrid beatSnapGrid; + + public TestSceneManiaBeatSnapGrid() + { + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 200 }); + editorBeatmap.ControlPointInfo.Add(10000, new TimingControlPoint { BeatLength = 200 }); + + BeatDivisor.Value = 3; + + // Some sane defaults + scrollingInfo.Algorithm.Algorithm = ScrollVisualisationMethod.Constant; + scrollingInfo.Direction.Value = ScrollingDirection.Up; + scrollingInfo.TimeRange.Value = 1000; + + Children = new Drawable[] + { + Playfield = new ManiaPlayfield(new List + { + new StageDefinition { Columns = 4 }, + new StageDefinition { Columns = 3 } + }) + { + Clock = new FramedClock(new StopwatchClock()) + }, + beatSnapGrid = new ManiaBeatSnapGrid() + }; + } + + protected override bool OnMouseMove(MouseMoveEvent e) + { + // We're providing a constant scroll algorithm. + float relativePosition = Playfield.Stages[0].HitObjectContainer.ToLocalSpace(e.ScreenSpaceMousePosition).Y / Playfield.Stages[0].HitObjectContainer.DrawHeight; + double timeValue = scrollingInfo.TimeRange.Value * relativePosition; + + beatSnapGrid.SetRange(timeValue, timeValue); + + return true; + } + + public Column ColumnAt(Vector2 screenSpacePosition) => null; + + public ManiaPlayfield Playfield { get; } + } +} diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs new file mode 100644 index 0000000000..5a3fe29770 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -0,0 +1,233 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; +using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osu.Game.Screens.Edit; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public class ManiaBeatSnapGrid : CompositeDrawable + { + [Resolved] + private IManiaHitObjectComposer composer { get; set; } + + [Resolved] + private EditorBeatmap beatmap { get; set; } + + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } + + [Resolved] + private Bindable working { get; set; } + + [Resolved] + private OsuColour colours { get; set; } + + [Resolved] + private BindableBeatDivisor beatDivisor { get; set; } + + private readonly List grids = new List(); + + [BackgroundDependencyLoader] + private void load() + { + foreach (var stage in composer.Playfield.Stages) + { + var grid = new Grid(stage); + grids.Add(grid); + + AddInternal(grid); + } + + beatDivisor.BindValueChanged(_ => createLines(), true); + } + + private void createLines() + { + foreach (var grid in grids) + grid.Clear(); + + for (int i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++) + { + var point = beatmap.ControlPointInfo.TimingPoints[i]; + var until = i + 1 < beatmap.ControlPointInfo.TimingPoints.Count ? beatmap.ControlPointInfo.TimingPoints[i + 1].Time : working.Value.Track.Length; + + int beat = 0; + + for (double t = point.Time; t < until; t += point.BeatLength / beatDivisor.Value) + { + var indexInBeat = beat % beatDivisor.Value; + Color4 colour; + + if (indexInBeat == 0) + colour = BindableBeatDivisor.GetColourFor(1, colours); + else + { + var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); + colour = BindableBeatDivisor.GetColourFor(divisor, colours); + } + + foreach (var grid in grids) + grid.Add(new DrawableGridLine(t, colour)); + + beat++; + } + } + } + + public (Vector2 position, double time)? GetSnappedPosition(Vector2 position) + { + float minDist = float.PositiveInfinity; + DrawableGridLine minDistLine = null; + Vector2 minDistLinePosition = Vector2.Zero; + + foreach (var grid in grids) + { + foreach (var line in grid.AliveObjects.OfType()) + { + Vector2 linePos = line.ToSpaceOfOtherDrawable(line.OriginPosition, this); + float d = Vector2.Distance(position, linePos); + + if (d < minDist) + { + minDist = d; + minDistLine = line; + minDistLinePosition = linePos; + } + } + } + + if (minDistLine == null) + return null; + + float noteOffset = (scrollingInfo.Direction.Value == ScrollingDirection.Up ? 1 : -1) * DefaultNotePiece.NOTE_HEIGHT / 2; + return (new Vector2(position.X, minDistLinePosition.Y + noteOffset), minDistLine.HitObject.StartTime); + } + + public void SetRange(double minTime, double maxTime) => Schedule(() => + { + var linesBefore = new List(); + var linesDuring = new List(); + var linesAfter = new List(); + + foreach (var grid in grids) + { + linesBefore.Clear(); + linesDuring.Clear(); + linesAfter.Clear(); + + foreach (var line in grid.Objects.OfType()) + { + if (line.HitObject.StartTime < minTime) + linesBefore.Add(line); + else if (line.HitObject.StartTime <= maxTime) + linesDuring.Add(line); + else + linesAfter.Add(line); + } + + foreach (var l in linesDuring) + l.Colour = OsuColour.Gray(0.5f); + + for (int i = 0; i < linesBefore.Count; i++) + { + int offset = (linesBefore.Count - i - 1) / beatDivisor.Value; + linesBefore[i].Colour = OsuColour.Gray(0.5f / (offset + 1)); + } + + for (int i = 0; i < linesAfter.Count; i++) + { + int offset = i / beatDivisor.Value; + linesAfter[i].Colour = OsuColour.Gray(0.5f / (offset + 1)); + } + } + }); + + private class Grid : ScrollingHitObjectContainer + { + [Resolved] + private IManiaHitObjectComposer composer { get; set; } + + private readonly Stage stage; + + public Grid(Stage stage) + { + this.stage = stage; + + RelativeSizeAxes = Axes.None; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Clock = composer.Playfield.Clock; + } + + protected override void Update() + { + base.Update(); + + var parentQuad = Parent.ToLocalSpace(stage.HitObjectContainer.ScreenSpaceDrawQuad); + Position = parentQuad.TopLeft; + Size = parentQuad.Size; + } + } + + private class DrawableGridLine : DrawableHitObject + { + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } + + private readonly IBindable direction = new Bindable(); + + public DrawableGridLine(double startTime, Color4 colour) + : base(new HitObject { StartTime = startTime }) + { + RelativeSizeAxes = Axes.X; + Height = 2; + + AddInternal(new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colour + }); + } + + [BackgroundDependencyLoader] + private void load() + { + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + Origin = Anchor = direction.NewValue == ScrollingDirection.Up + ? Anchor.TopLeft + : Anchor.BottomLeft; + } + + protected override void UpdateStateTransforms(ArmedState state) + { + using (BeginAbsoluteSequence(HitObject.StartTime + 1000)) + this.FadeOut(); + } + } + } +} diff --git a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs index 1af7d06998..271e432e8d 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaPlayfield.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.UI [Cached] public class ManiaPlayfield : ScrollingPlayfield { + public IReadOnlyList Stages => stages; + private readonly List stages = new List(); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => stages.Any(s => s.ReceivePositionalInputAt(screenSpacePos)); From 91d1b15d5ad141444259933678490df5db29794c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 May 2020 19:55:07 +0900 Subject: [PATCH 034/871] Integrate grid with the mania composer --- .../Edit/ManiaHitObjectComposer.cs | 58 +++++++++++++++++-- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index d7c0889c0d..11523dd384 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -6,9 +6,12 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mania.Objects; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Input; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; @@ -20,12 +23,27 @@ namespace osu.Game.Rulesets.Mania.Edit public class ManiaHitObjectComposer : HitObjectComposer, IManiaHitObjectComposer { private DrawableManiaEditRuleset drawableRuleset; + private ManiaBeatSnapGrid beatSnapGrid; + private InputManager inputManager; public ManiaHitObjectComposer(Ruleset ruleset) : base(ruleset) { } + [BackgroundDependencyLoader] + private void load() + { + AddInternal(beatSnapGrid = new ManiaBeatSnapGrid()); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + /// /// Retrieves the column that intersects a screen-space position. /// @@ -42,11 +60,43 @@ namespace osu.Game.Rulesets.Mania.Edit public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo; + protected override void Update() + { + base.Update(); + + if (BlueprintContainer.CurrentTool is SelectTool) + { + if (EditorBeatmap.SelectedHitObjects.Any()) + { + beatSnapGrid.SetRange(EditorBeatmap.SelectedHitObjects.Min(h => h.StartTime), EditorBeatmap.SelectedHitObjects.Max(h => h.GetEndTime())); + beatSnapGrid.Show(); + } + else + beatSnapGrid.Hide(); + } + else + { + var placementTime = GetSnappedPosition(ToLocalSpace(inputManager.CurrentState.Mouse.Position), 0).time; + beatSnapGrid.SetRange(placementTime, placementTime); + + beatSnapGrid.Show(); + } + } + public override (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) { - var hoc = Playfield.GetColumn(0).HitObjectContainer; + var beatSnapped = beatSnapGrid.GetSnappedPosition(position); - float targetPosition = hoc.ToLocalSpace(ToScreenSpace(position)).Y; + if (beatSnapped != null) + return beatSnapped.Value; + + return base.GetSnappedPosition(position, getTimeFromPosition(ToScreenSpace(position))); + } + + private double getTimeFromPosition(Vector2 screenSpacePosition) + { + var hoc = Playfield.Stages[0].HitObjectContainer; + float targetPosition = hoc.ToLocalSpace(screenSpacePosition).Y; if (drawableRuleset.ScrollingInfo.Direction.Value == ScrollingDirection.Down) { @@ -56,12 +106,10 @@ namespace osu.Game.Rulesets.Mania.Edit targetPosition = hoc.DrawHeight - targetPosition; } - double targetTime = drawableRuleset.ScrollingInfo.Algorithm.TimeAt(targetPosition, + return drawableRuleset.ScrollingInfo.Algorithm.TimeAt(targetPosition, EditorClock.CurrentTime, drawableRuleset.ScrollingInfo.TimeRange.Value, hoc.DrawHeight); - - return base.GetSnappedPosition(position, targetTime); } protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) From 42c3d892cd93a3671de896af87136244b36856c2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 May 2020 19:55:14 +0900 Subject: [PATCH 035/871] Only update alive lines --- osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index 5a3fe29770..320912ed5b 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Mania.Edit linesDuring.Clear(); linesAfter.Clear(); - foreach (var line in grid.Objects.OfType()) + foreach (var line in grid.AliveObjects.OfType()) { if (line.HitObject.StartTime < minTime) linesBefore.Add(line); From 0e334940745c5958a30a877083907ca391282953 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 May 2020 19:58:39 +0900 Subject: [PATCH 036/871] Fix flashing when changing beat divisor --- osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs | 12 ++++++++++-- .../Edit/ManiaHitObjectComposer.cs | 4 ++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index 320912ed5b..9cb9256a7e 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -119,7 +119,15 @@ namespace osu.Game.Rulesets.Mania.Edit return (new Vector2(position.X, minDistLinePosition.Y + noteOffset), minDistLine.HitObject.StartTime); } - public void SetRange(double minTime, double maxTime) => Schedule(() => + public void SetRange(double minTime, double maxTime) + { + if (LoadState >= LoadState.Ready) + setRange(minTime, maxTime); + else + Schedule(() => setRange(minTime, maxTime)); + } + + private void setRange(double minTime, double maxTime) { var linesBefore = new List(); var linesDuring = new List(); @@ -156,7 +164,7 @@ namespace osu.Game.Rulesets.Mania.Edit linesAfter[i].Colour = OsuColour.Gray(0.5f / (offset + 1)); } } - }); + } private class Grid : ScrollingHitObjectContainer { diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 11523dd384..1266761d12 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -60,9 +60,9 @@ namespace osu.Game.Rulesets.Mania.Edit public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo; - protected override void Update() + protected override void UpdateAfterChildren() { - base.Update(); + base.UpdateAfterChildren(); if (BlueprintContainer.CurrentTool is SelectTool) { From 3441ab457d7d05dffa990117aa7ab3b84ceaa709 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 14 May 2020 20:06:34 +0900 Subject: [PATCH 037/871] Fix hitobjects placed at non-beatsnapped times --- .../Edit/Blueprints/ManiaPlacementBlueprint.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 3fb03d642f..5fe53557b3 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -24,10 +24,15 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints protected Column Column; /// - /// The current mouse position, snapped to the closest column. + /// The current beat-snapped mouse position, snapped to the closest column. /// protected Vector2 SnappedMousePosition { get; private set; } + /// + /// The gameplay time at the current beat-snapped mouse position (). + /// + protected double SnappedTime { get; private set; } + /// /// The width of the closest column to the current mouse position. /// @@ -39,6 +44,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IScrollingInfo scrollingInfo { get; set; } + [Resolved] + private IDistanceSnapProvider snapProvider { get; set; } + protected ManiaPlacementBlueprint(T hitObject) : base(hitObject) { @@ -54,7 +62,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints return base.OnMouseDown(e); HitObject.Column = Column.Index; - BeginPlacement(TimeAt(e.ScreenSpaceMousePosition), true); + BeginPlacement(SnappedTime, true); return true; } @@ -70,6 +78,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints // Snap to the column var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0))); SnappedMousePosition = new Vector2(parentPos.X, Parent.ToLocalSpace(screenSpacePosition).Y); + + SnappedTime = TimeAt(screenSpacePosition); + if (snapProvider != null) + (SnappedMousePosition, SnappedTime) = snapProvider.GetSnappedPosition(SnappedMousePosition, SnappedTime); } protected double TimeAt(Vector2 screenSpacePosition) From 4cbd51feb965fe78ba3429728205715788a83558 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 May 2020 13:08:15 +0900 Subject: [PATCH 038/871] Fix test errors --- .../Edit/Blueprints/ManiaPlacementBlueprint.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 5fe53557b3..184356b89c 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -44,7 +44,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IScrollingInfo scrollingInfo { get; set; } - [Resolved] + [Resolved(CanBeNull = true)] private IDistanceSnapProvider snapProvider { get; set; } protected ManiaPlacementBlueprint(T hitObject) From 392d44e1fbe8fcf6f334fe31134821a01e7adbf5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 May 2020 15:49:50 +0900 Subject: [PATCH 039/871] Always fully display one beat --- .../Edit/ManiaBeatSnapGrid.cs | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index 9cb9256a7e..63e887714b 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.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 osu.Framework.Allocation; @@ -149,6 +150,29 @@ namespace osu.Game.Rulesets.Mania.Edit linesAfter.Add(line); } + // Snapping will always happen on one of the two lines around minTime (the "target" line). + // One of those lines may exist in linesBefore and the other may exist in linesAfter, depending on whether such a line exists, and the target changes when the mid-point is crossed. + // For display purposes, one complete beat is shown at the maximum brightness such that the target line should always be bright. + bool targetLineIsLastLineBefore = false; + + if (linesBefore.Count > 0 && linesAfter.Count > 0) + targetLineIsLastLineBefore = Math.Abs(linesBefore[^1].HitObject.StartTime - minTime) <= Math.Abs(linesAfter[0].HitObject.StartTime - minTime); + else if (linesBefore.Count > 0) + targetLineIsLastLineBefore = true; + + if (targetLineIsLastLineBefore) + { + // Move the last line before to linesDuring + linesDuring.Insert(0, linesBefore[^1]); + linesBefore.RemoveAt(linesBefore.Count - 1); + } + else if (linesAfter.Count > 0) // = false does not guarantee that a line after exists (maybe at the bottom of the screen) + { + // Move the first line after to linesDuring + linesDuring.Insert(0, linesAfter[0]); + linesAfter.RemoveAt(0); + } + foreach (var l in linesDuring) l.Colour = OsuColour.Gray(0.5f); From 1c6c128d1100ca3aa8ddd151c2f46a08701c30ed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 May 2020 15:51:54 +0900 Subject: [PATCH 040/871] Add const --- osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index 63e887714b..77d42a0927 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.Mania.Edit { public class ManiaBeatSnapGrid : CompositeDrawable { + /// + /// The brightness of bar lines one beat around the time range from . + /// + private const float first_beat_brightness = 0.5f; + [Resolved] private IManiaHitObjectComposer composer { get; set; } @@ -174,18 +179,18 @@ namespace osu.Game.Rulesets.Mania.Edit } foreach (var l in linesDuring) - l.Colour = OsuColour.Gray(0.5f); + l.Colour = OsuColour.Gray(first_beat_brightness); for (int i = 0; i < linesBefore.Count; i++) { int offset = (linesBefore.Count - i - 1) / beatDivisor.Value; - linesBefore[i].Colour = OsuColour.Gray(0.5f / (offset + 1)); + linesBefore[i].Colour = OsuColour.Gray(first_beat_brightness / (offset + 1)); } for (int i = 0; i < linesAfter.Count; i++) { int offset = i / beatDivisor.Value; - linesAfter[i].Colour = OsuColour.Gray(0.5f / (offset + 1)); + linesAfter[i].Colour = OsuColour.Gray(first_beat_brightness / (offset + 1)); } } } From 238d87f97611be6b137e8bcc9c30073e318518a8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 May 2020 15:56:32 +0900 Subject: [PATCH 041/871] Add comment about gray usage --- osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index 77d42a0927..31ebb7bc1c 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -178,6 +178,8 @@ namespace osu.Game.Rulesets.Mania.Edit linesAfter.RemoveAt(0); } + // Grays are used rather than transparency since the lines appear on a coloured mania playfield. + foreach (var l in linesDuring) l.Colour = OsuColour.Gray(first_beat_brightness); From 98125102a768b5a711b5cab4367969f68e38b95f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 May 2020 18:07:41 +0900 Subject: [PATCH 042/871] Add cancellation token support to CreateNestedHitObjects() --- osu.Game.Rulesets.Catch/Objects/BananaShower.cs | 5 +++-- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 5 +++-- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 5 +++-- osu.Game.Rulesets.Osu/Objects/Slider.cs | 5 +++-- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 5 +++-- osu.Game.Rulesets.Taiko/Objects/Swell.cs | 5 +++-- .../Objects/TaikoHitObject.cs | 5 +++-- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 3 ++- osu.Game/Rulesets/Objects/HitObject.cs | 17 ++++++++++++++--- 9 files changed, 37 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index c3488aec11..96ab66048a 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.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.Threading; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Types; @@ -14,9 +15,9 @@ namespace osu.Game.Rulesets.Catch.Objects public override Judgement CreateJudgement() => new IgnoreJudgement(); - protected override void CreateNestedHitObjects() + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { - base.CreateNestedHitObjects(); + base.CreateNestedHitObjects(cancellationToken); createBananas(); } diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 01011645bd..4f9a289739 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using System.Threading; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -45,9 +46,9 @@ namespace osu.Game.Rulesets.Catch.Objects TickDistance = scoringDistance / difficulty.SliderTickRate; } - protected override void CreateNestedHitObjects() + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { - base.CreateNestedHitObjects(); + base.CreateNestedHitObjects(cancellationToken); var dropletSamples = Samples.Select(s => new HitSampleInfo { diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index eea2c31260..d8bdaa071b 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Threading; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -91,9 +92,9 @@ namespace osu.Game.Rulesets.Mania.Objects tickSpacing = timingPoint.BeatLength / difficulty.SliderTickRate; } - protected override void CreateNestedHitObjects() + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { - base.CreateNestedHitObjects(); + base.CreateNestedHitObjects(cancellationToken); createTicks(); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index e5d6c20738..bc6fca2338 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -6,6 +6,7 @@ using osu.Game.Rulesets.Objects.Types; using System.Collections.Generic; using osu.Game.Rulesets.Objects; using System.Linq; +using System.Threading; using osu.Framework.Caching; using osu.Game.Audio; using osu.Game.Beatmaps; @@ -133,9 +134,9 @@ namespace osu.Game.Rulesets.Osu.Objects TickDistance = scoringDistance / difficulty.SliderTickRate * TickDistanceMultiplier; } - protected override void CreateNestedHitObjects() + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { - base.CreateNestedHitObjects(); + base.CreateNestedHitObjects(cancellationToken); foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset)) diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index dc2f277e58..8bbad220ac 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -4,6 +4,7 @@ using osu.Game.Rulesets.Objects.Types; using System; using System.Collections.Generic; +using System.Threading; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -73,14 +74,14 @@ namespace osu.Game.Rulesets.Taiko.Objects overallDifficulty = difficulty.OverallDifficulty; } - protected override void CreateNestedHitObjects() + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { createTicks(); RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * overallDifficulty); RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * overallDifficulty); - base.CreateNestedHitObjects(); + base.CreateNestedHitObjects(cancellationToken); } private void createTicks() diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index 2f06066a16..3a4023b3e5 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Threading; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Scoring; @@ -29,9 +30,9 @@ namespace osu.Game.Rulesets.Taiko.Objects set => throw new NotSupportedException($"{nameof(Swell)} cannot be a strong hitobject."); } - protected override void CreateNestedHitObjects() + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { - base.CreateNestedHitObjects(); + base.CreateNestedHitObjects(cancellationToken); for (int i = 0; i < RequiredHits; i++) AddNested(new SwellTick()); diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index c41727557b..206bfcfdb2 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.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.Threading; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -32,9 +33,9 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public virtual bool IsStrong { get; set; } - protected override void CreateNestedHitObjects() + protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { - base.CreateNestedHitObjects(); + base.CreateNestedHitObjects(cancellationToken); if (IsStrong) AddNested(new StrongHitObject { StartTime = this.GetEndTime() }); diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index fb1eb7adbf..49b57c3f30 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.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.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -90,7 +91,7 @@ namespace osu.Game.Rulesets.Edit public abstract void UpdatePosition(Vector2 screenSpacePosition); /// - /// Invokes , + /// Invokes , /// refreshing and parameters for the . /// protected void ApplyDefaultsToHitObject() => HitObject.ApplyDefaults(beatmap.Value.Beatmap.ControlPointInfo, beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty); diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index cffbdbae08..8ff2bdefb3 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using JetBrains.Annotations; using Newtonsoft.Json; using osu.Framework.Bindables; @@ -99,7 +100,8 @@ namespace osu.Game.Rulesets.Objects /// /// The control points. /// The difficulty settings to use. - public void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) + /// The cancellation token. + public void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty, CancellationToken cancellationToken = default) { ApplyDefaultsToSelf(controlPointInfo, difficulty); @@ -108,7 +110,7 @@ namespace osu.Game.Rulesets.Objects nestedHitObjects.Clear(); - CreateNestedHitObjects(); + CreateNestedHitObjects(cancellationToken); if (this is IHasComboInformation hasCombo) { @@ -122,7 +124,7 @@ namespace osu.Game.Rulesets.Objects nestedHitObjects.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); foreach (var h in nestedHitObjects) - h.ApplyDefaults(controlPointInfo, difficulty); + h.ApplyDefaults(controlPointInfo, difficulty, cancellationToken); DefaultsApplied?.Invoke(this); } @@ -136,6 +138,15 @@ namespace osu.Game.Rulesets.Objects HitWindows?.SetDifficulty(difficulty.OverallDifficulty); } + protected virtual void CreateNestedHitObjects(CancellationToken cancellationToken) + { + // ReSharper disable once MethodSupportsCancellation (https://youtrack.jetbrains.com/issue/RIDER-44520) +#pragma warning disable 618 + CreateNestedHitObjects(); +#pragma warning restore 618 + } + + [Obsolete("Use the overload with cancellation support instead.")] // can be removed 20201115 protected virtual void CreateNestedHitObjects() { } From 4079642d58572f7b342071e572b0b21c08e15b8b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 May 2020 18:13:47 +0900 Subject: [PATCH 043/871] Actually pass in the cancellation token --- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index bf2b9944a4..9ea023a030 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -134,7 +134,7 @@ namespace osu.Game.Beatmaps if (cancellationSource.IsCancellationRequested) throw new BeatmapLoadTimeoutException(BeatmapInfo); - obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty); + obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty, cancellationSource.Token); } foreach (var mod in mods.OfType()) From 4719fcc2913eaa002f0ac62f7f47b0a670c78f42 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 May 2020 18:17:39 +0900 Subject: [PATCH 044/871] Actually use the cancellation token --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 4 +++- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 6 ++++-- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- osu.Game/Beatmaps/WorkingBeatmap.cs | 15 +++++++++++---- osu.Game/Rulesets/Objects/SliderEventGenerator.cs | 10 +++++++--- 5 files changed, 26 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 4f9a289739..d32595c2e1 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -59,7 +59,7 @@ namespace osu.Game.Rulesets.Catch.Objects SliderEventDescriptor? lastEvent = null; - foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset)) + foreach (var e in SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken)) { // generate tiny droplets since the last point if (lastEvent != null) @@ -74,6 +74,8 @@ namespace osu.Game.Rulesets.Catch.Objects for (double t = timeBetweenTiny; t < sinceLastTick; t += timeBetweenTiny) { + cancellationToken.ThrowIfCancellationRequested(); + AddNested(new TinyDroplet { StartTime = t + lastEvent.Value.Time, diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index d8bdaa071b..e6f722a8a9 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -96,7 +96,7 @@ namespace osu.Game.Rulesets.Mania.Objects { base.CreateNestedHitObjects(cancellationToken); - createTicks(); + createTicks(cancellationToken); AddNested(Head = new Note { @@ -113,13 +113,15 @@ namespace osu.Game.Rulesets.Mania.Objects }); } - private void createTicks() + private void createTicks(CancellationToken cancellationToken) { if (tickSpacing == 0) return; for (double t = StartTime + tickSpacing; t <= EndTime - tickSpacing; t += tickSpacing) { + cancellationToken.ThrowIfCancellationRequested(); + AddNested(new HoldNoteTick { StartTime = t, diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index bc6fca2338..6ba0e1c6aa 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -139,7 +139,7 @@ namespace osu.Game.Rulesets.Osu.Objects base.CreateNestedHitObjects(cancellationToken); foreach (var e in - SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset)) + SliderEventGenerator.Generate(StartTime, SpanDuration, Velocity, TickDistance, Path.Distance, this.SpanCount(), LegacyLastTickOffset, cancellationToken)) { switch (e.Type) { diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 9ea023a030..8126311cbd 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -129,12 +129,19 @@ namespace osu.Game.Beatmaps processor?.PreProcess(); // Compute default values for hitobjects, including creating nested hitobjects in-case they're needed - foreach (var obj in converted.HitObjects) + try { - if (cancellationSource.IsCancellationRequested) - throw new BeatmapLoadTimeoutException(BeatmapInfo); + foreach (var obj in converted.HitObjects) + { + if (cancellationSource.IsCancellationRequested) + throw new BeatmapLoadTimeoutException(BeatmapInfo); - obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty, cancellationSource.Token); + obj.ApplyDefaults(converted.ControlPointInfo, converted.BeatmapInfo.BaseDifficulty, cancellationSource.Token); + } + } + catch (OperationCanceledException) + { + throw new BeatmapLoadTimeoutException(BeatmapInfo); } foreach (var mod in mods.OfType()) diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index e9ee3833b7..5f1c1cf6a0 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -4,13 +4,14 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace osu.Game.Rulesets.Objects { public static class SliderEventGenerator { public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, - double? legacyLastTickOffset) + double? legacyLastTickOffset, CancellationToken cancellationToken = default) { // A very lenient maximum length of a slider for ticks to be generated. // This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage. @@ -37,7 +38,7 @@ namespace osu.Game.Rulesets.Objects var spanStartTime = startTime + span * spanDuration; var reversed = span % 2 == 1; - var ticks = generateTicks(span, spanStartTime, spanDuration, reversed, length, tickDistance, minDistanceFromEnd); + var ticks = generateTicks(span, spanStartTime, spanDuration, reversed, length, tickDistance, minDistanceFromEnd, cancellationToken); if (reversed) { @@ -108,12 +109,15 @@ namespace osu.Game.Rulesets.Objects /// The length of the path. /// The distance between each tick. /// The distance from the end of the path at which ticks are not allowed to be added. + /// The cancellation token. /// A for each tick. If is true, the ticks will be returned in reverse-StartTime order. private static IEnumerable generateTicks(int spanIndex, double spanStartTime, double spanDuration, bool reversed, double length, double tickDistance, - double minDistanceFromEnd) + double minDistanceFromEnd, CancellationToken cancellationToken = default) { for (var d = tickDistance; d <= length; d += tickDistance) { + cancellationToken.ThrowIfCancellationRequested(); + if (d >= length - minDistanceFromEnd) break; From 6cd1753459451bfe6672e149c1099ee5e514dd55 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 May 2020 18:51:44 +0900 Subject: [PATCH 045/871] Add overload to prevent crashes (bosu) --- osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs | 10 +++++----- osu.Game/Rulesets/Objects/SliderEventGenerator.cs | 8 ++++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs index 9fba0f1668..6c8133660f 100644 --- a/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs +++ b/osu.Game.Tests/Beatmaps/SliderEventGenerationTest.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestSingleSpan() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, null, default).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestRepeat() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 2, null, default).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); @@ -52,7 +52,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestNonEvenTicks() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, 300, span_duration, 2, null, default).ToArray(); Assert.That(events[0].Type, Is.EqualTo(SliderEventType.Head)); Assert.That(events[0].Time, Is.EqualTo(start_time)); @@ -85,7 +85,7 @@ namespace osu.Game.Tests.Beatmaps [Test] public void TestLegacyLastTickOffset() { - var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, 1, span_duration / 2, span_duration, 1, 100, default).ToArray(); Assert.That(events[2].Type, Is.EqualTo(SliderEventType.LegacyLastTick)); Assert.That(events[2].Time, Is.EqualTo(900)); @@ -97,7 +97,7 @@ namespace osu.Game.Tests.Beatmaps const double velocity = 5; const double min_distance = velocity * 10; - var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0).ToArray(); + var events = SliderEventGenerator.Generate(start_time, span_duration, velocity, velocity, span_duration, 2, 0, default).ToArray(); Assert.Multiple(() => { diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index 5f1c1cf6a0..6df0041e7a 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -10,6 +10,14 @@ namespace osu.Game.Rulesets.Objects { public static class SliderEventGenerator { + [Obsolete("Use the overload with cancellation support instead.")] // can be removed 20201115 + public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, + double? legacyLastTickOffset) + { + return Generate(startTime, spanDuration, velocity, tickDistance, totalDistance, spanCount, legacyLastTickOffset, default); + } + + // ReSharper disable once MethodOverloadWithOptionalParameter public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, double? legacyLastTickOffset, CancellationToken cancellationToken = default) { From 65345109991272e138f5e6f9658ee1da9802b127 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 15 May 2020 19:25:14 +0900 Subject: [PATCH 046/871] Use cancellation token in taiko swell/drumroll --- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 6 ++++-- osu.Game.Rulesets.Taiko/Objects/Swell.cs | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 8bbad220ac..7b11bce520 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Taiko.Objects protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { - createTicks(); + createTicks(cancellationToken); RequiredGoodHits = NestedHitObjects.Count * Math.Min(0.15, 0.05 + 0.10 / 6 * overallDifficulty); RequiredGreatHits = NestedHitObjects.Count * Math.Min(0.30, 0.10 + 0.20 / 6 * overallDifficulty); @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Taiko.Objects base.CreateNestedHitObjects(cancellationToken); } - private void createTicks() + private void createTicks(CancellationToken cancellationToken) { if (tickSpacing == 0) return; @@ -93,6 +93,8 @@ namespace osu.Game.Rulesets.Taiko.Objects for (double t = StartTime; t < EndTime + tickSpacing / 2; t += tickSpacing) { + cancellationToken.ThrowIfCancellationRequested(); + AddNested(new DrumRollTick { FirstTick = first, diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index 3a4023b3e5..390f8d1f3b 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -35,7 +35,10 @@ namespace osu.Game.Rulesets.Taiko.Objects base.CreateNestedHitObjects(cancellationToken); for (int i = 0; i < RequiredHits; i++) + { + cancellationToken.ThrowIfCancellationRequested(); AddNested(new SwellTick()); + } } public override Judgement CreateJudgement() => new TaikoSwellJudgement(); From 08bb5cbcbff6e1b42fa1715224aa989cd308b073 Mon Sep 17 00:00:00 2001 From: Shivam Date: Sat, 16 May 2020 02:57:58 +0200 Subject: [PATCH 047/871] Introduce model to store path of stable osu! --- osu.Game.Tournament/Models/StableInfo.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 osu.Game.Tournament/Models/StableInfo.cs diff --git a/osu.Game.Tournament/Models/StableInfo.cs b/osu.Game.Tournament/Models/StableInfo.cs new file mode 100644 index 0000000000..b89160536d --- /dev/null +++ b/osu.Game.Tournament/Models/StableInfo.cs @@ -0,0 +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 osu.Framework.Bindables; + +namespace osu.Game.Tournament.Models +{ + /// + /// Holds the complete data required to operate the tournament system. + /// + [Serializable] + public class StableInfo + { + public Bindable StablePath = new Bindable(string.Empty); + } +} From c40b3b905313a78ee229bdd49589d26c32f27bfd Mon Sep 17 00:00:00 2001 From: Shivam Date: Sat, 16 May 2020 02:59:48 +0200 Subject: [PATCH 048/871] Refactored stable path finding and added json config detection. This also migrates the values found in the other methods to the configuration file. --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 173 ++++++++++++++++++------ 1 file changed, 135 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 53ba597a7e..321a4ad0aa 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -4,6 +4,8 @@ using System; using System.IO; using System.Linq; +using System.Collections.Generic; +using Newtonsoft.Json; using Microsoft.Win32; using osu.Framework.Allocation; using osu.Framework.Logging; @@ -35,7 +37,15 @@ namespace osu.Game.Tournament.IPC private int lastBeatmapId; private ScheduledDelegate scheduled; - public Storage Storage { get; private set; } + [Resolved] + private StableInfo stableInfo { get; set; } + + private const string stable_config = "tournament/stable.json"; + + public Storage IPCStorage { get; private set; } + + [Resolved] + private Storage tournamentStorage { get; set; } [BackgroundDependencyLoader] private void load() @@ -47,7 +57,7 @@ namespace osu.Game.Tournament.IPC { scheduled?.Cancel(); - Storage = null; + IPCStorage = null; try { @@ -56,20 +66,20 @@ namespace osu.Game.Tournament.IPC if (string.IsNullOrEmpty(path)) return null; - Storage = new DesktopStorage(path, host as DesktopGameHost); + IPCStorage = new DesktopStorage(path, host as DesktopGameHost); const string file_ipc_filename = "ipc.txt"; const string file_ipc_state_filename = "ipc-state.txt"; const string file_ipc_scores_filename = "ipc-scores.txt"; const string file_ipc_channel_filename = "ipc-channel.txt"; - if (Storage.Exists(file_ipc_filename)) + if (IPCStorage.Exists(file_ipc_filename)) { scheduled = Scheduler.AddDelayed(delegate { try { - using (var stream = Storage.GetStream(file_ipc_filename)) + using (var stream = IPCStorage.GetStream(file_ipc_filename)) using (var sr = new StreamReader(stream)) { var beatmapId = int.Parse(sr.ReadLine()); @@ -101,7 +111,7 @@ namespace osu.Game.Tournament.IPC try { - using (var stream = Storage.GetStream(file_ipc_channel_filename)) + using (var stream = IPCStorage.GetStream(file_ipc_channel_filename)) using (var sr = new StreamReader(stream)) { ChatChannel.Value = sr.ReadLine(); @@ -114,7 +124,7 @@ namespace osu.Game.Tournament.IPC try { - using (var stream = Storage.GetStream(file_ipc_state_filename)) + using (var stream = IPCStorage.GetStream(file_ipc_state_filename)) using (var sr = new StreamReader(stream)) { State.Value = (TourneyState)Enum.Parse(typeof(TourneyState), sr.ReadLine()); @@ -127,7 +137,7 @@ namespace osu.Game.Tournament.IPC try { - using (var stream = Storage.GetStream(file_ipc_scores_filename)) + using (var stream = IPCStorage.GetStream(file_ipc_scores_filename)) using (var sr = new StreamReader(stream)) { Score1.Value = int.Parse(sr.ReadLine()); @@ -146,54 +156,141 @@ namespace osu.Game.Tournament.IPC Logger.Error(e, "Stable installation could not be found; disabling file based IPC"); } - return Storage; + return IPCStorage; } + private static bool checkExists(string p) => File.Exists(Path.Combine(p, "ipc.txt")); + private string findStablePath() { - static bool checkExists(string p) => File.Exists(Path.Combine(p, "ipc.txt")); - string stableInstallPath = string.Empty; try { - try + List> stableFindMethods = new List> { - stableInstallPath = Environment.GetEnvironmentVariable("OSU_STABLE_PATH"); + findFromJsonConfig, + findFromEnvVar, + findFromRegistry, + findFromLocalAppData, + findFromDotFolder + }; - if (checkExists(stableInstallPath)) + foreach (var r in stableFindMethods) + { + stableInstallPath = r.Invoke(); + + if (stableInstallPath != null) + { return stableInstallPath; - } - catch - { + } } - try - { - using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) - stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); - - if (checkExists(stableInstallPath)) - return stableInstallPath; - } - catch - { - } - - stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); - if (checkExists(stableInstallPath)) - return stableInstallPath; - - stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu"); - if (checkExists(stableInstallPath)) - return stableInstallPath; - - return null; + return stableInstallPath; } finally { Logger.Log($"Stable path for tourney usage: {stableInstallPath}"); } } + + private void saveStablePath() + { + using (var stream = tournamentStorage.GetStream(stable_config, FileAccess.Write, FileMode.Create)) + using (var sw = new StreamWriter(stream)) + { + sw.Write(JsonConvert.SerializeObject(stableInfo, + new JsonSerializerSettings + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + DefaultValueHandling = DefaultValueHandling.Ignore, + })); + } + } + + private string findFromEnvVar() + { + try + { + Logger.Log("Trying to find stable with environment variables"); + string stableInstallPath = Environment.GetEnvironmentVariable("OSU_STABLE_PATH"); + + if (checkExists(stableInstallPath)) + { + stableInfo.StablePath.Value = stableInstallPath; + saveStablePath(); + return stableInstallPath; + } + } + catch + { + } + + return null; + } + + private string findFromJsonConfig() + { + try + { + Logger.Log("Trying to find stable through the json config"); + return stableInfo.StablePath.Value; + } + catch + { + } + + return null; + } + + private string findFromLocalAppData() + { + Logger.Log("Trying to find stable in %LOCALAPPDATA%"); + string stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); + + if (checkExists(stableInstallPath)) + { + stableInfo.StablePath.Value = stableInstallPath; + saveStablePath(); + return stableInstallPath; + } + + return null; + } + + private string findFromDotFolder() + { + Logger.Log("Trying to find stable in dotfolders"); + string stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu"); + + if (checkExists(stableInstallPath)) + { + stableInfo.StablePath.Value = stableInstallPath; + saveStablePath(); + return stableInstallPath; + } + + return null; + } + + private string findFromRegistry() + { + Logger.Log("Trying to find stable in registry"); + + string stableInstallPath; + + using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) + stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); + + if (checkExists(stableInstallPath)) + { + stableInfo.StablePath.Value = stableInstallPath; + saveStablePath(); + return stableInstallPath; + } + + return null; + } } } From 9944a514da95181dd6c4be222b2bd5d3e069dee4 Mon Sep 17 00:00:00 2001 From: Shivam Date: Sat, 16 May 2020 03:00:37 +0200 Subject: [PATCH 049/871] Dependency cache the ipc location file --- osu.Game.Tournament/TournamentGameBase.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 85db9e61fb..31c56c7fc4 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -33,6 +33,8 @@ namespace osu.Game.Tournament { private const string bracket_filename = "bracket.json"; + private const string stable_config = "tournament/stable.json"; + private LadderInfo ladder; private Storage storage; @@ -43,6 +45,7 @@ namespace osu.Game.Tournament private Bindable windowSize; private FileBasedIPC ipc; + private StableInfo stableInfo; private Drawable heightWarning; @@ -71,6 +74,7 @@ namespace osu.Game.Tournament }), true); readBracket(); + readStableConfig(); ladder.CurrentMatch.Value = ladder.Matches.FirstOrDefault(p => p.Current.Value); @@ -141,6 +145,23 @@ namespace osu.Game.Tournament }); } + private void readStableConfig() + { + if (storage.Exists(stable_config)) + { + using (Stream stream = storage.GetStream(stable_config, FileAccess.Read, FileMode.Open)) + using (var sr = new StreamReader(stream)) + { + stableInfo = JsonConvert.DeserializeObject(sr.ReadToEnd()); + } + } + + if (stableInfo == null) + stableInfo = new StableInfo(); + + dependencies.Cache(stableInfo); + } + private void readBracket() { if (storage.Exists(bracket_filename)) From 3fc888ef95f62154cf1e32aa178036926da550cb Mon Sep 17 00:00:00 2001 From: Shivam Date: Sat, 16 May 2020 03:03:10 +0200 Subject: [PATCH 050/871] User interface setup for custom IPC location Right now makes use of another ActionableInfo field. Probably a better idea to add an extra button to the Current IPC Storage actionable field. --- .../Components/IPCNotFoundDialog.cs | 27 ++++ osu.Game.Tournament/Screens/SetupScreen.cs | 34 +++- .../Screens/StablePathSelectScreen.cs | 149 ++++++++++++++++++ 3 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Tournament/Components/IPCNotFoundDialog.cs create mode 100644 osu.Game.Tournament/Screens/StablePathSelectScreen.cs diff --git a/osu.Game.Tournament/Components/IPCNotFoundDialog.cs b/osu.Game.Tournament/Components/IPCNotFoundDialog.cs new file mode 100644 index 0000000000..d4f9edc182 --- /dev/null +++ b/osu.Game.Tournament/Components/IPCNotFoundDialog.cs @@ -0,0 +1,27 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Sprites; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Tournament.Components +{ + public class IPCNotFoundDialog : PopupDialog + { + public IPCNotFoundDialog() + { + BodyText = "Select a directory that contains an osu! Cutting Edge installation"; + + Icon = FontAwesome.Regular.Angry; + HeaderText = @"This is an invalid IPC Directory!"; + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"Alright.", + Action = () => { Expire(); } + } + }; + } + } +} diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index c91379b2d6..93edd73ff8 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -15,6 +15,8 @@ using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Tournament.IPC; +using osu.Framework.Platform; +using osu.Game.Tournament.Models; using osuTK; using osuTK.Graphics; @@ -26,6 +28,7 @@ namespace osu.Game.Tournament.Screens private LoginOverlay loginOverlay; private ActionableInfo resolution; + private const string stable_config = "tournament/stable.json"; [Resolved] private MatchIPCInfo ipc { get; set; } @@ -36,8 +39,17 @@ namespace osu.Game.Tournament.Screens [Resolved] private RulesetStore rulesets { get; set; } + [Resolved(canBeNull: true)] + private TournamentSceneManager sceneManager { get; set; } + private Bindable windowSize; + [Resolved] + private Storage storage { get; set; } + + [Resolved] + private StableInfo stableInfo { get; set; } + [BackgroundDependencyLoader] private void load(FrameworkConfigManager frameworkConfig) { @@ -62,7 +74,6 @@ namespace osu.Game.Tournament.Screens private void reload() { var fileBasedIpc = ipc as FileBasedIPC; - fillFlow.Children = new Drawable[] { new ActionableInfo @@ -74,11 +85,28 @@ namespace osu.Game.Tournament.Screens fileBasedIpc?.LocateStableStorage(); reload(); }, - Value = fileBasedIpc?.Storage?.GetFullPath(string.Empty) ?? "Not found", - Failing = fileBasedIpc?.Storage == null, + Value = fileBasedIpc?.IPCStorage?.GetFullPath(string.Empty) ?? "Not found", + Failing = fileBasedIpc?.IPCStorage == null, Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation, and that it is registered as the default osu! install." }, new ActionableInfo + { + Label = "Custom IPC source", + ButtonText = "Change path", + Action = () => + { + stableInfo.StablePath.BindValueChanged(_ => + { + fileBasedIpc?.LocateStableStorage(); + Schedule(reload); + }); + sceneManager.SetScreen(new StablePathSelectScreen()); + }, + Value = fileBasedIpc?.IPCStorage?.GetFullPath(string.Empty) ?? "Not found", + Failing = fileBasedIpc?.IPCStorage == null, + Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, you can manually select the desired osu! installation that you want to use." + }, + new ActionableInfo { Label = "Current User", ButtonText = "Change Login", diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs new file mode 100644 index 0000000000..1faacc727f --- /dev/null +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -0,0 +1,149 @@ +// 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.IO; +using Newtonsoft.Json; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.Tournament.Models; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays; +using osu.Game.Tournament.IPC; +using osu.Game.Tournament.Components; +using osuTK; + +namespace osu.Game.Tournament.Screens +{ + public class StablePathSelectScreen : TournamentScreen + { + private DirectorySelector directorySelector; + + private const string stable_config = "tournament/stable.json"; + + [Resolved] + private StableInfo stableInfo { get; set; } + + [Resolved] + private MatchIPCInfo ipc { get; set; } + + private DialogOverlay overlay; + + [Resolved(canBeNull: true)] + private TournamentSceneManager sceneManager { get; set; } + + [BackgroundDependencyLoader(true)] + private void load(Storage storage, OsuColour colours) + { + // begin selection in the parent directory of the current storage location + var initialPath = new DirectoryInfo(stableInfo.StablePath.Value).FullName; + + AddInternal(new Container + { + Masking = true, + CornerRadius = 10, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.5f, 0.8f), + Children = new Drawable[] + { + new Box + { + Colour = colours.GreySeafoamDark, + RelativeSizeAxes = Axes.Both, + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Relative, 0.8f), + new Dimension(), + }, + Content = new[] + { + new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Please select a new location", + Font = OsuFont.Default.With(size: 40) + }, + }, + new Drawable[] + { + directorySelector = new DirectorySelector(initialPath) + { + RelativeSizeAxes = Axes.Both, + } + }, + new Drawable[] + { + new TriangleButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 300, + Text = "Select stable path", + Action = () => { start(storage); } + }, + } + } + } + } + }); + } + + private static bool checkExists(string p) => File.Exists(Path.Combine(p, "ipc.txt")); + + private void start(Storage storage) + { + var target = directorySelector.CurrentDirectory.Value.FullName; + + if (checkExists(target)) + { + stableInfo.StablePath.Value = target; + + try + { + using (var stream = storage.GetStream(stable_config, FileAccess.Write, FileMode.Create)) + using (var sw = new StreamWriter(stream)) + { + sw.Write(JsonConvert.SerializeObject(stableInfo, + new JsonSerializerSettings + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + DefaultValueHandling = DefaultValueHandling.Ignore, + })); + } + + sceneManager?.SetScreen(typeof(SetupScreen)); + } + catch (Exception e) + { + Logger.Log($"Error during migration: {e.Message}", level: LogLevel.Error); + } + } + else + { + overlay = new DialogOverlay(); + overlay.Push(new IPCNotFoundDialog()); + AddInternal(overlay); + Logger.Log("Folder is not an osu! stable CE directory"); + // Return an error in the picker that the directory does not contain ipc.txt + } + } + } +} From 2bde4fc3eed85600b421a2cb30e83e310a5f68de Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 16 May 2020 18:17:32 +0900 Subject: [PATCH 051/871] Initial implementation of contracted score panel --- .../Scoring/OsuScoreProcessor.cs | 2 +- .../TestSceneContractedPanelMiddleContent.cs | 118 ++++++++ .../ContractedPanelMiddleContent.cs | 255 ++++++++++++++++++ osu.Game/Screens/Ranking/ScorePanel.cs | 11 +- 4 files changed, 381 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs create mode 100644 osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 1de7d488f3..79a6ea7e92 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Scoring { - internal class OsuScoreProcessor : ScoreProcessor + public class OsuScoreProcessor : ScoreProcessor { protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new OsuJudgementResult(hitObject, judgement); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs new file mode 100644 index 0000000000..af3d13777c --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -0,0 +1,118 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking; +using osu.Game.Screens.Ranking.Contracted; +using osu.Game.Tests.Beatmaps; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Tests.Visual.Ranking +{ + public class TestSceneContractedPanelMiddleContent : OsuTestScene + { + [Resolved] + private RulesetStore rulesetStore { get; set; } + + [Test] + public void TestMapWithKnownMapper() + { + var author = new User { Username = "mapper_name" }; + + AddStep("show example score", () => showPanel(createTestBeatmap(author), createTestScore())); + + AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Text == "mapper_name")); + } + + [Test] + public void TestMapWithUnknownMapper() + { + AddStep("show example score", () => showPanel(createTestBeatmap(null), createTestScore())); + + AddAssert("mapped by text not present", () => + this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text, "mapped", "by"))); + } + + private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score) + { + Child = new ContractedPanelMiddleContentContainer(workingBeatmap, score); + } + + private WorkingBeatmap createTestBeatmap(User author) + { + var beatmap = new TestBeatmap(rulesetStore.GetRuleset(0)); + beatmap.Metadata.Author = author; + beatmap.Metadata.Title = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap title"; + beatmap.Metadata.Artist = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap artist"; + + return new TestWorkingBeatmap(beatmap); + } + + private ScoreInfo createTestScore() => new ScoreInfo + { + User = new User + { + Id = 2, + Username = "peppy", + }, + Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, + Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, + TotalScore = 999999, + Accuracy = 0.95, + MaxCombo = 999, + Rank = ScoreRank.S, + Date = DateTimeOffset.Now, + Statistics = + { + { HitResult.Miss, 1 }, + { HitResult.Meh, 50 }, + { HitResult.Good, 100 }, + { HitResult.Great, 300 }, + } + }; + + private bool containsAny(string text, params string[] stringsToMatch) => stringsToMatch.Any(text.Contains); + + private class ContractedPanelMiddleContentContainer : Container + { + [Cached] + private Bindable workingBeatmap { get; set; } + + public ContractedPanelMiddleContentContainer(WorkingBeatmap beatmap, ScoreInfo score) + { + workingBeatmap = new Bindable(beatmap); + + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Size = new Vector2(ScorePanel.CONTRACTED_WIDTH, 700); + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#353535"), + }, + new ContractedPanelMiddleContent(score), + }; + } + } + } +} diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs new file mode 100644 index 0000000000..5ecb3fbd0b --- /dev/null +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -0,0 +1,255 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Leaderboards; +using osu.Game.Scoring; +using osu.Game.Screens.Play.HUD; +using osu.Game.Users; +using osu.Game.Users.Drawables; +using osu.Game.Utils; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Ranking.Contracted +{ + /// + /// The content that appears in the middle of a contracted . + /// + public class ContractedPanelMiddleContent : CompositeDrawable + { + private readonly ScoreInfo score; + + /// + /// Creates a new . + /// + /// The to display. + public ContractedPanelMiddleContent(ScoreInfo score) + { + this.score = score; + RelativeSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Container + { + Name = "Background", + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerExponent = 2.5f, + CornerRadius = 20, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.25f), + Type = EdgeEffectType.Shadow, + Radius = 1, + Offset = new Vector2(0, 4) + }, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("444") + }, + new UserCoverBackground + { + RelativeSizeAxes = Axes.Both, + User = score.User, + }, + } + }, + new Container + { + Name = "Background overlay", + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Bottom = -1 }, + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + CornerExponent = 2.5f, + CornerRadius = 20, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.5f), Color4Extensions.FromHex("#444")) + } + } + }, + new Container + { + Name = "Foreground", + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new UpdateableAvatar(score.User) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(140), + Masking = true, + CornerExponent = 2.5f, + CornerRadius = 20, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.25f), + Type = EdgeEffectType.Shadow, + Radius = 8, + Offset = new Vector2(0, 4), + } + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = score.UserString, + Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold) + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + ChildrenEnumerable = score.SortedStatistics.Select(s => createStatistic(s.Key.GetDescription(), s.Value.ToString())) + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 10 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new[] + { + createStatistic("Max Combo", $"x{score.MaxCombo}"), + createStatistic("Accuracy", $"{score.Accuracy.FormatAccuracy()}"), + } + }, + new ModDisplay + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + ExpansionMode = ExpansionMode.AlwaysExpanded, + DisplayUnrankedText = false, + Current = { Value = score.Mods }, + Scale = new Vector2(0.5f), + } + } + } + } + } + } + }, + }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Vertical = 5 }, + Child = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = score.TotalScore.ToString(), + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, fixedWidth: true), + Spacing = new Vector2(-1, 0) + }, + }, + new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 2 }, + Child = new DrawableRank(score.Rank) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + } + }, + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + } + } + } + }, + }, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 45), + } + }; + } + + private Drawable createStatistic(string key, string value) => new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = key, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) + }, + new OsuSpriteText + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Text = value, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Colour = Color4Extensions.FromHex("#FFDD55") + } + } + }; + } +} diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index c055df7ccc..bf57cb4dd9 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Contracted; using osu.Game.Screens.Ranking.Expanded; using osuTK; using osuTK.Graphics; @@ -21,12 +22,12 @@ namespace osu.Game.Screens.Ranking /// /// Width of the panel when contracted. /// - private const float contracted_width = 160; + public const float CONTRACTED_WIDTH = 160; /// /// Height of the panel when contracted. /// - private const float contracted_height = 320; + private const float contracted_height = 385; /// /// Width of the panel when expanded. @@ -71,7 +72,7 @@ namespace osu.Game.Screens.Ranking private static readonly ColourInfo expanded_top_layer_colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#444"), Color4Extensions.FromHex("#333")); private static readonly ColourInfo expanded_middle_layer_colour = ColourInfo.GradientVertical(Color4Extensions.FromHex("#555"), Color4Extensions.FromHex("#333")); private static readonly Color4 contracted_top_layer_colour = Color4Extensions.FromHex("#353535"); - private static readonly Color4 contracted_middle_layer_colour = Color4Extensions.FromHex("#444"); + private static readonly Color4 contracted_middle_layer_colour = Color4Extensions.FromHex("#353535"); public event Action StateChanged; @@ -193,10 +194,12 @@ namespace osu.Game.Screens.Ranking break; case PanelState.Contracted: - this.ResizeTo(new Vector2(contracted_width, contracted_height), resize_duration, Easing.OutQuint); + this.ResizeTo(new Vector2(CONTRACTED_WIDTH, contracted_height), resize_duration, Easing.OutQuint); topLayerBackground.FadeColour(contracted_top_layer_colour, resize_duration, Easing.OutQuint); middleLayerBackground.FadeColour(contracted_middle_layer_colour, resize_duration, Easing.OutQuint); + + middleLayerContentContainer.Add(topLayerContent = new ContractedPanelMiddleContent(score).With(d => d.Alpha = 0)); break; } From 3df92925ee704dce60d127c04557d3aaac15c9c3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 16 May 2020 18:22:07 +0900 Subject: [PATCH 052/871] Add score panel test --- .../Visual/Ranking/TestSceneScorePanel.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index 1e55885385..7431002c02 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -107,13 +107,23 @@ namespace osu.Game.Tests.Visual.Ranking addPanelStep(score); } - private void addPanelStep(ScoreInfo score) => AddStep("add panel", () => + [Test] + public void TestContractedPanel() + { + var score = createScore(); + score.Accuracy = 0.925; + score.Rank = ScoreRank.A; + + addPanelStep(score, PanelState.Contracted); + } + + private void addPanelStep(ScoreInfo score, PanelState state = PanelState.Expanded) => AddStep("add panel", () => { Child = new ScorePanel(score) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - State = PanelState.Expanded + State = state }; }); From 9b7b1ef605aa3fc7dbe22682dae4bc496974d28c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 16 May 2020 18:23:18 +0900 Subject: [PATCH 053/871] Add cover urls --- .../Visual/Ranking/TestSceneContractedPanelMiddleContent.cs | 1 + osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs | 1 + 2 files changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs index af3d13777c..f7694c10ec 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -72,6 +72,7 @@ namespace osu.Game.Tests.Visual.Ranking { Id = 2, Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }, Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index 7431002c02..27905f95fd 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -133,6 +133,7 @@ namespace osu.Game.Tests.Visual.Ranking { Id = 2, Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", }, Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, From 8c5ccf574b3e6fbf6e26a85491713f3acf71ab7a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 16 May 2020 18:28:15 +0900 Subject: [PATCH 054/871] Add better fix for 1px bleed --- .../ContractedPanelMiddleContent.cs | 153 ++++++++---------- 1 file changed, 66 insertions(+), 87 deletions(-) diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 5ecb3fbd0b..1d7d5c4130 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -53,22 +53,22 @@ namespace osu.Game.Screens.Ranking.Contracted new Container { RelativeSizeAxes = Axes.Both, + Masking = true, + CornerExponent = 2.5f, + CornerRadius = 20, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.25f), + Type = EdgeEffectType.Shadow, + Radius = 1, + Offset = new Vector2(0, 4) + }, Children = new Drawable[] { - new Container + // Buffered container is used to prevent 1px bleed outside the masking region + new BufferedContainer { - Name = "Background", RelativeSizeAxes = Axes.Both, - Masking = true, - CornerExponent = 2.5f, - CornerRadius = 20, - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.25f), - Type = EdgeEffectType.Shadow, - Radius = 1, - Offset = new Vector2(0, 4) - }, Children = new Drawable[] { new Box @@ -81,95 +81,74 @@ namespace osu.Game.Screens.Ranking.Contracted RelativeSizeAxes = Axes.Both, User = score.User, }, - } - }, - new Container - { - Name = "Background overlay", - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Bottom = -1 }, - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerExponent = 2.5f, - CornerRadius = 20, - Child = new Box + new Box { RelativeSizeAxes = Axes.Both, Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.5f), Color4Extensions.FromHex("#444")) - } + }, } }, - new Container + new FillFlowContainer { - Name = "Foreground", RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10), + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), Children = new Drawable[] { + new UpdateableAvatar(score.User) + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Size = new Vector2(140), + Masking = true, + CornerExponent = 2.5f, + CornerRadius = 20, + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.25f), + Type = EdgeEffectType.Shadow, + Radius = 8, + Offset = new Vector2(0, 4), + } + }, + new OsuSpriteText + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Text = score.UserString, + Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold) + }, new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 10), - Children = new Drawable[] + Spacing = new Vector2(0, 5), + ChildrenEnumerable = score.SortedStatistics.Select(s => createStatistic(s.Key.GetDescription(), s.Value.ToString())) + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 10 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 5), + Children = new[] { - new UpdateableAvatar(score.User) - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Size = new Vector2(140), - Masking = true, - CornerExponent = 2.5f, - CornerRadius = 20, - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.25f), - Type = EdgeEffectType.Shadow, - Radius = 8, - Offset = new Vector2(0, 4), - } - }, - new OsuSpriteText - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - Text = score.UserString, - Font = OsuFont.GetFont(size: 16, weight: FontWeight.SemiBold) - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - ChildrenEnumerable = score.SortedStatistics.Select(s => createStatistic(s.Key.GetDescription(), s.Value.ToString())) - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 10 }, - Direction = FillDirection.Vertical, - Spacing = new Vector2(0, 5), - Children = new[] - { - createStatistic("Max Combo", $"x{score.MaxCombo}"), - createStatistic("Accuracy", $"{score.Accuracy.FormatAccuracy()}"), - } - }, - new ModDisplay - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - AutoSizeAxes = Axes.Both, - ExpansionMode = ExpansionMode.AlwaysExpanded, - DisplayUnrankedText = false, - Current = { Value = score.Mods }, - Scale = new Vector2(0.5f), - } + createStatistic("Max Combo", $"x{score.MaxCombo}"), + createStatistic("Accuracy", $"{score.Accuracy.FormatAccuracy()}"), } + }, + new ModDisplay + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + AutoSizeAxes = Axes.Both, + ExpansionMode = ExpansionMode.AlwaysExpanded, + DisplayUnrankedText = false, + Current = { Value = score.Mods }, + Scale = new Vector2(0.5f), } } } From cfa5a81e7844eb136f7e1fed6a04b3374788709e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 16 May 2020 18:28:25 +0900 Subject: [PATCH 055/871] Cleanup testscene --- osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index 3b8ce7d837..0dbafb18bc 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -10,7 +10,6 @@ using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking; -using osu.Game.Screens.Ranking.Expanded; using osu.Game.Tests.Beatmaps; using osu.Game.Users; @@ -18,7 +17,6 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneScorePanel : OsuTestScene { - [Test] public void TestDRank() { From e3c1112b5ab10e981ce59c1c2a851ecdcf7a0dbb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 16 May 2020 19:00:20 +0900 Subject: [PATCH 056/871] Initial integration into results screen --- osu.Game/Screens/Ranking/ResultsScreen.cs | 57 +++++++++++++++++++++-- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index cfba1e6e3e..f2458d9f1f 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -10,6 +11,9 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Play; @@ -31,11 +35,18 @@ namespace osu.Game.Screens.Ranking [Resolved(CanBeNull = true)] private Player player { get; set; } + [Resolved] + private IAPIProvider api { get; set; } + + [Resolved] + private RulesetStore rulesets { get; set; } + public readonly ScoreInfo Score; private readonly bool allowRetry; private Drawable bottomPanel; + private Container contractedPanels; public ResultsScreen(ScoreInfo score, bool allowRetry = true) { @@ -52,12 +63,29 @@ namespace osu.Game.Screens.Ranking { new ResultsScrollContainer { - Child = new ScorePanel(Score) + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - State = PanelState.Expanded - }, + new ScorePanel(Score) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + State = PanelState.Expanded + }, + new OsuScrollContainer(Direction.Horizontal) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Child = contractedPanels = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both + } + } + } }, bottomPanel = new Container { @@ -105,6 +133,25 @@ namespace osu.Game.Screens.Ranking } } + protected override void LoadComplete() + { + base.LoadComplete(); + + var req = new GetScoresRequest(Score.Beatmap, Score.Ruleset); + + req.Success += r => + { + contractedPanels.ChildrenEnumerable = r.Scores.Select(s => s.CreateScoreInfo(rulesets)).Select(s => new ScorePanel(s) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + State = PanelState.Contracted + }); + }; + + api.Queue(req); + } + public override void OnEntering(IScreen last) { base.OnEntering(last); From 80d188ec91caa05af9c71854a25b8d543aae7f05 Mon Sep 17 00:00:00 2001 From: Shivam Date: Sun, 17 May 2020 22:26:42 +0200 Subject: [PATCH 057/871] Update xmldoc with accurate information about the model --- osu.Game.Tournament/Models/StableInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Models/StableInfo.cs b/osu.Game.Tournament/Models/StableInfo.cs index b89160536d..4818842151 100644 --- a/osu.Game.Tournament/Models/StableInfo.cs +++ b/osu.Game.Tournament/Models/StableInfo.cs @@ -7,7 +7,7 @@ using osu.Framework.Bindables; namespace osu.Game.Tournament.Models { /// - /// Holds the complete data required to operate the tournament system. + /// Holds the path to locate the osu! stable cutting-edge installation. /// [Serializable] public class StableInfo From 4bc858a2159bc2c73033800a48381831f7a42276 Mon Sep 17 00:00:00 2001 From: Shivam Date: Sun, 17 May 2020 22:27:44 +0200 Subject: [PATCH 058/871] Force a read of the location file during detection --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 321a4ad0aa..0454ef4e41 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -232,15 +232,17 @@ namespace osu.Game.Tournament.IPC private string findFromJsonConfig() { - try + Logger.Log("Trying to find stable through the json config"); + if (tournamentStorage.Exists(stable_config)) { - Logger.Log("Trying to find stable through the json config"); - return stableInfo.StablePath.Value; + using (Stream stream = tournamentStorage.GetStream(stable_config, FileAccess.Read, FileMode.Open)) + using (var sr = new StreamReader(stream)) + { + stableInfo = JsonConvert.DeserializeObject(sr.ReadToEnd()); + return stableInfo.StablePath.Value; + } } - catch - { - } - + return null; } From fbbf51851ecad9f379d410cb60594d1ee81310e8 Mon Sep 17 00:00:00 2001 From: Shivam Date: Sun, 17 May 2020 22:28:24 +0200 Subject: [PATCH 059/871] Moved refresh button to directoryselector --- .../Screens/StablePathSelectScreen.cs | 145 ++++++++++++------ 1 file changed, 97 insertions(+), 48 deletions(-) diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index 1faacc727f..8b75bd9290 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -43,75 +43,107 @@ namespace osu.Game.Tournament.Screens private void load(Storage storage, OsuColour colours) { // begin selection in the parent directory of the current storage location - var initialPath = new DirectoryInfo(stableInfo.StablePath.Value).FullName; + var initialPath = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent?.FullName; - AddInternal(new Container + if (!string.IsNullOrEmpty(stableInfo.StablePath.Value)) { - Masking = true, - CornerRadius = 10, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(0.5f, 0.8f), - Children = new Drawable[] + // If the original path info for osu! stable is not empty, set it to the parent directory of that location + initialPath = new DirectoryInfo(stableInfo.StablePath.Value).Parent?.FullName; + } + + AddRangeInternal(new Drawable[] + { + new Container { - new Box + Masking = true, + CornerRadius = 10, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(0.5f, 0.8f), + Children = new Drawable[] { - Colour = colours.GreySeafoamDark, - RelativeSizeAxes = Axes.Both, - }, - new GridContainer - { - RelativeSizeAxes = Axes.Both, - RowDimensions = new[] + new Box { - new Dimension(), - new Dimension(GridSizeMode.Relative, 0.8f), - new Dimension(), + Colour = colours.GreySeafoamDark, + RelativeSizeAxes = Axes.Both, }, - Content = new[] + new GridContainer { - new Drawable[] + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "Please select a new location", - Font = OsuFont.Default.With(size: 40) - }, + new Dimension(), + new Dimension(GridSizeMode.Relative, 0.8f), + new Dimension(), }, - new Drawable[] + Content = new[] { - directorySelector = new DirectorySelector(initialPath) + new Drawable[] { - RelativeSizeAxes = Axes.Both, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "Please select a new location", + Font = OsuFont.Default.With(size: 40) + }, + }, + new Drawable[] + { + directorySelector = new DirectorySelector(initialPath) + { + RelativeSizeAxes = Axes.Both, + } + }, + new Drawable[] + { + new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(20), + Children = new Drawable[] + { + new TriangleButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 300, + Text = "Select stable path", + Action = () => changePath(storage) + }, + new TriangleButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 300, + Text = "Auto detect", + Action = autoDetect + }, + } + } } - }, - new Drawable[] - { - new TriangleButton - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 300, - Text = "Select stable path", - Action = () => { start(storage); } - }, } } - } + }, + }, + new BackButton + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + State = { Value = Visibility.Visible }, + Action = () => sceneManager?.SetScreen(typeof(SetupScreen)) } }); } - private static bool checkExists(string p) => File.Exists(Path.Combine(p, "ipc.txt")); - - private void start(Storage storage) + private void changePath(Storage storage) { var target = directorySelector.CurrentDirectory.Value.FullName; - if (checkExists(target)) + if (File.Exists(Path.Combine(target, "ipc.txt"))) { stableInfo.StablePath.Value = target; @@ -145,5 +177,22 @@ namespace osu.Game.Tournament.Screens // Return an error in the picker that the directory does not contain ipc.txt } } + + private void autoDetect() + { + var fileBasedIpc = ipc as FileBasedIPC; + fileBasedIpc?.LocateStableStorage(); + if (fileBasedIpc?.IPCStorage == null) + { + // Could not auto detect + overlay = new DialogOverlay(); + overlay.Push(new IPCNotFoundDialog()); + AddInternal(overlay); + } + else + { + sceneManager?.SetScreen(typeof(SetupScreen)); + } + } } } From a97100216ca3da33c3aba2b91971d6baf3eb24df Mon Sep 17 00:00:00 2001 From: Shivam Date: Sun, 17 May 2020 22:28:54 +0200 Subject: [PATCH 060/871] Changed behaviour of refresh button in SetupScreen --- osu.Game.Tournament/Screens/SetupScreen.cs | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 93edd73ff8..dcaadc8247 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -82,20 +82,7 @@ namespace osu.Game.Tournament.Screens ButtonText = "Refresh", Action = () => { - fileBasedIpc?.LocateStableStorage(); - reload(); - }, - Value = fileBasedIpc?.IPCStorage?.GetFullPath(string.Empty) ?? "Not found", - Failing = fileBasedIpc?.IPCStorage == null, - Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation, and that it is registered as the default osu! install." - }, - new ActionableInfo - { - Label = "Custom IPC source", - ButtonText = "Change path", - Action = () => - { - stableInfo.StablePath.BindValueChanged(_ => + stableInfo.StablePath.BindValueChanged(_ => { fileBasedIpc?.LocateStableStorage(); Schedule(reload); @@ -104,7 +91,7 @@ namespace osu.Game.Tournament.Screens }, Value = fileBasedIpc?.IPCStorage?.GetFullPath(string.Empty) ?? "Not found", Failing = fileBasedIpc?.IPCStorage == null, - Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, you can manually select the desired osu! installation that you want to use." + Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation." }, new ActionableInfo { From 59b006f9ac8688b75fa6556fb286f27f37cdbbca Mon Sep 17 00:00:00 2001 From: Shivam Date: Sun, 17 May 2020 22:46:43 +0200 Subject: [PATCH 061/871] Make IPC error dialog reusable and inspectcode fixes --- .../{IPCNotFoundDialog.cs => IPCErrorDialog.cs} | 11 +++++------ osu.Game.Tournament/IPC/FileBasedIPC.cs | 3 ++- osu.Game.Tournament/Screens/SetupScreen.cs | 2 +- osu.Game.Tournament/Screens/StablePathSelectScreen.cs | 5 +++-- 4 files changed, 11 insertions(+), 10 deletions(-) rename osu.Game.Tournament/Components/{IPCNotFoundDialog.cs => IPCErrorDialog.cs} (65%) diff --git a/osu.Game.Tournament/Components/IPCNotFoundDialog.cs b/osu.Game.Tournament/Components/IPCErrorDialog.cs similarity index 65% rename from osu.Game.Tournament/Components/IPCNotFoundDialog.cs rename to osu.Game.Tournament/Components/IPCErrorDialog.cs index d4f9edc182..07fd0ac973 100644 --- a/osu.Game.Tournament/Components/IPCNotFoundDialog.cs +++ b/osu.Game.Tournament/Components/IPCErrorDialog.cs @@ -6,14 +6,13 @@ using osu.Game.Overlays.Dialog; namespace osu.Game.Tournament.Components { - public class IPCNotFoundDialog : PopupDialog + public class IPCErrorDialog : PopupDialog { - public IPCNotFoundDialog() + public IPCErrorDialog(string headerText, string bodyText) { - BodyText = "Select a directory that contains an osu! Cutting Edge installation"; - - Icon = FontAwesome.Regular.Angry; - HeaderText = @"This is an invalid IPC Directory!"; + Icon = FontAwesome.Regular.SadTear; + HeaderText = headerText; + BodyText = bodyText; Buttons = new PopupDialogButton[] { new PopupDialogOkButton diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 0454ef4e41..730779a46b 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -233,6 +233,7 @@ namespace osu.Game.Tournament.IPC private string findFromJsonConfig() { Logger.Log("Trying to find stable through the json config"); + if (tournamentStorage.Exists(stable_config)) { using (Stream stream = tournamentStorage.GetStream(stable_config, FileAccess.Read, FileMode.Open)) @@ -242,7 +243,7 @@ namespace osu.Game.Tournament.IPC return stableInfo.StablePath.Value; } } - + return null; } diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index dcaadc8247..4f6d063b10 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -82,7 +82,7 @@ namespace osu.Game.Tournament.Screens ButtonText = "Refresh", Action = () => { - stableInfo.StablePath.BindValueChanged(_ => + stableInfo.StablePath.BindValueChanged(_ => { fileBasedIpc?.LocateStableStorage(); Schedule(reload); diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index 8b75bd9290..35c2272918 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -171,7 +171,7 @@ namespace osu.Game.Tournament.Screens else { overlay = new DialogOverlay(); - overlay.Push(new IPCNotFoundDialog()); + overlay.Push(new IPCErrorDialog("This is an invalid IPC Directory", "Select a directory that contains an osu! stable cutting edge installation and make sure it has an empty ipc.txt file in it.")); AddInternal(overlay); Logger.Log("Folder is not an osu! stable CE directory"); // Return an error in the picker that the directory does not contain ipc.txt @@ -182,11 +182,12 @@ namespace osu.Game.Tournament.Screens { var fileBasedIpc = ipc as FileBasedIPC; fileBasedIpc?.LocateStableStorage(); + if (fileBasedIpc?.IPCStorage == null) { // Could not auto detect overlay = new DialogOverlay(); - overlay.Push(new IPCNotFoundDialog()); + overlay.Push(new IPCErrorDialog("Failed to auto detect", "An osu! stable cutting-edge installation could not be auto detected.\nPlease try and manually point to the directory.")); AddInternal(overlay); } else From 9bfdfbea43e14bbad24554a2d8758327882561a7 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 18 May 2020 00:47:31 +0200 Subject: [PATCH 062/871] Move stablestorage check to path selection screen Also forced stablepath to be empty during auto detection so it checks other sources to load ipc from --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 15 +++++++-------- osu.Game.Tournament/Screens/SetupScreen.cs | 1 - .../Screens/StablePathSelectScreen.cs | 4 ++++ 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 730779a46b..6d1cd7cc3c 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -232,16 +232,15 @@ namespace osu.Game.Tournament.IPC private string findFromJsonConfig() { - Logger.Log("Trying to find stable through the json config"); - - if (tournamentStorage.Exists(stable_config)) + try { - using (Stream stream = tournamentStorage.GetStream(stable_config, FileAccess.Read, FileMode.Open)) - using (var sr = new StreamReader(stream)) - { - stableInfo = JsonConvert.DeserializeObject(sr.ReadToEnd()); + Logger.Log("Trying to find stable through the json config"); + + if (!string.IsNullOrEmpty(stableInfo.StablePath.Value)) return stableInfo.StablePath.Value; - } + } + catch + { } return null; diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 4f6d063b10..e0fc98e031 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -84,7 +84,6 @@ namespace osu.Game.Tournament.Screens { stableInfo.StablePath.BindValueChanged(_ => { - fileBasedIpc?.LocateStableStorage(); Schedule(reload); }); sceneManager.SetScreen(new StablePathSelectScreen()); diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index 35c2272918..5c488ae352 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -142,6 +142,7 @@ namespace osu.Game.Tournament.Screens private void changePath(Storage storage) { var target = directorySelector.CurrentDirectory.Value.FullName; + Logger.Log($"Changing Stable CE location to {target}"); if (File.Exists(Path.Combine(target, "ipc.txt"))) { @@ -161,6 +162,8 @@ namespace osu.Game.Tournament.Screens })); } + var fileBasedIpc = ipc as FileBasedIPC; + fileBasedIpc?.LocateStableStorage(); sceneManager?.SetScreen(typeof(SetupScreen)); } catch (Exception e) @@ -180,6 +183,7 @@ namespace osu.Game.Tournament.Screens private void autoDetect() { + stableInfo.StablePath.Value = string.Empty; // This forces findStablePath() to look elsewhere. var fileBasedIpc = ipc as FileBasedIPC; fileBasedIpc?.LocateStableStorage(); From 7a839c1486cf96d322187474f34d3fd4f63c4b81 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 18 May 2020 00:50:08 +0200 Subject: [PATCH 063/871] Renamed Refresh button to Change source --- osu.Game.Tournament/Screens/SetupScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index e0fc98e031..478240f8b4 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -79,7 +79,7 @@ namespace osu.Game.Tournament.Screens new ActionableInfo { Label = "Current IPC source", - ButtonText = "Refresh", + ButtonText = "Change source", Action = () => { stableInfo.StablePath.BindValueChanged(_ => From a0a54efd4ec7f8ab4b1aaebf965cdd2e693fee4e Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 18 May 2020 01:05:34 +0200 Subject: [PATCH 064/871] Fix test crashing because of sceneManager not being nullable --- osu.Game.Tournament/Screens/SetupScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 478240f8b4..1c479bdec4 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -86,7 +86,7 @@ namespace osu.Game.Tournament.Screens { Schedule(reload); }); - sceneManager.SetScreen(new StablePathSelectScreen()); + sceneManager?.SetScreen(new StablePathSelectScreen()); }, Value = fileBasedIpc?.IPCStorage?.GetFullPath(string.Empty) ?? "Not found", Failing = fileBasedIpc?.IPCStorage == null, From d9bb90078b5f0b9d4c32635417f818fe1c562073 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 May 2020 17:47:47 +0900 Subject: [PATCH 065/871] Move grids to inside columns --- .../Edit/ManiaBeatSnapGrid.cs | 48 +++++++++---------- osu.Game.Rulesets.Mania/UI/Column.cs | 2 + .../UI/Components/ColumnHitObjectArea.cs | 8 ++++ 3 files changed, 32 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index 31ebb7bc1c..9a998366e9 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -7,12 +7,10 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; -using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; @@ -22,7 +20,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Edit { - public class ManiaBeatSnapGrid : CompositeDrawable + public class ManiaBeatSnapGrid : Component { /// /// The brightness of bar lines one beat around the time range from . @@ -54,15 +52,32 @@ namespace osu.Game.Rulesets.Mania.Edit { foreach (var stage in composer.Playfield.Stages) { - var grid = new Grid(stage); - grids.Add(grid); + foreach (var column in stage.Columns) + { + var grid = new Grid(); - AddInternal(grid); + grids.Add(grid); + column.UnderlayElements.Add(grid); + } } beatDivisor.BindValueChanged(_ => createLines(), true); } + public override void Hide() + { + base.Hide(); + foreach (var grid in grids) + grid.Hide(); + } + + public override void Show() + { + base.Show(); + foreach (var grid in grids) + grid.Show(); + } + private void createLines() { foreach (var grid in grids) @@ -145,7 +160,7 @@ namespace osu.Game.Rulesets.Mania.Edit linesDuring.Clear(); linesAfter.Clear(); - foreach (var line in grid.AliveObjects.OfType()) + foreach (var line in grid.Objects.OfType()) { if (line.HitObject.StartTime < minTime) linesBefore.Add(line); @@ -202,30 +217,11 @@ namespace osu.Game.Rulesets.Mania.Edit [Resolved] private IManiaHitObjectComposer composer { get; set; } - private readonly Stage stage; - - public Grid(Stage stage) - { - this.stage = stage; - - RelativeSizeAxes = Axes.None; - } - protected override void LoadComplete() { base.LoadComplete(); - Clock = composer.Playfield.Clock; } - - protected override void Update() - { - base.Update(); - - var parentQuad = Parent.ToLocalSpace(stage.HitObjectContainer.ScreenSpaceDrawQuad); - Position = parentQuad.TopLeft; - Size = parentQuad.Size; - } } private class DrawableGridLine : DrawableHitObject diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 506a07f26b..511d6c8623 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -37,6 +37,8 @@ namespace osu.Game.Rulesets.Mania.UI internal readonly Container TopLevelContainer; + public Container UnderlayElements => hitObjectArea.UnderlayElements; + public Column(int index) { Index = index; diff --git a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs index cb79bf7f43..b365ae45a9 100644 --- a/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs +++ b/osu.Game.Rulesets.Mania/UI/Components/ColumnHitObjectArea.cs @@ -12,6 +12,9 @@ namespace osu.Game.Rulesets.Mania.UI.Components public class ColumnHitObjectArea : HitObjectArea { public readonly Container Explosions; + + public readonly Container UnderlayElements; + private readonly Drawable hitTarget; public ColumnHitObjectArea(int columnIndex, HitObjectContainer hitObjectContainer) @@ -19,6 +22,11 @@ namespace osu.Game.Rulesets.Mania.UI.Components { AddRangeInternal(new[] { + UnderlayElements = new Container + { + RelativeSizeAxes = Axes.Both, + Depth = 2, + }, hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget, columnIndex), _ => new DefaultHitTarget()) { RelativeSizeAxes = Axes.X, From 16e85ae0b1c0399b2a3dce77df4758b3c51f6079 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 May 2020 17:52:04 +0900 Subject: [PATCH 066/871] Remove Grid class --- .../Edit/ManiaBeatSnapGrid.cs | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index 9a998366e9..5b13b1421c 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -45,7 +45,7 @@ namespace osu.Game.Rulesets.Mania.Edit [Resolved] private BindableBeatDivisor beatDivisor { get; set; } - private readonly List grids = new List(); + private readonly List grids = new List(); [BackgroundDependencyLoader] private void load() @@ -54,10 +54,10 @@ namespace osu.Game.Rulesets.Mania.Edit { foreach (var column in stage.Columns) { - var grid = new Grid(); + var lineContainer = new ScrollingHitObjectContainer(); - grids.Add(grid); - column.UnderlayElements.Add(grid); + grids.Add(lineContainer); + column.UnderlayElements.Add(lineContainer); } } @@ -212,18 +212,6 @@ namespace osu.Game.Rulesets.Mania.Edit } } - private class Grid : ScrollingHitObjectContainer - { - [Resolved] - private IManiaHitObjectComposer composer { get; set; } - - protected override void LoadComplete() - { - base.LoadComplete(); - Clock = composer.Playfield.Clock; - } - } - private class DrawableGridLine : DrawableHitObject { [Resolved] From b35b150f3883609e62e31e8bb6216e82b888cc6c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 May 2020 19:32:14 +0900 Subject: [PATCH 067/871] Simplify colouring logic --- .../Edit/ManiaBeatSnapGrid.cs | 66 +++---------------- 1 file changed, 9 insertions(+), 57 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index 5b13b1421c..e771a9753f 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -22,10 +22,7 @@ namespace osu.Game.Rulesets.Mania.Edit { public class ManiaBeatSnapGrid : Component { - /// - /// The brightness of bar lines one beat around the time range from . - /// - private const float first_beat_brightness = 0.5f; + private const double visible_range = 1500; [Resolved] private IManiaHitObjectComposer composer { get; set; } @@ -150,64 +147,19 @@ namespace osu.Game.Rulesets.Mania.Edit private void setRange(double minTime, double maxTime) { - var linesBefore = new List(); - var linesDuring = new List(); - var linesAfter = new List(); - foreach (var grid in grids) { - linesBefore.Clear(); - linesDuring.Clear(); - linesAfter.Clear(); - foreach (var line in grid.Objects.OfType()) { - if (line.HitObject.StartTime < minTime) - linesBefore.Add(line); - else if (line.HitObject.StartTime <= maxTime) - linesDuring.Add(line); + double lineTime = line.HitObject.StartTime; + + if (lineTime >= minTime && lineTime <= maxTime) + line.Colour = Color4.White; else - linesAfter.Add(line); - } - - // Snapping will always happen on one of the two lines around minTime (the "target" line). - // One of those lines may exist in linesBefore and the other may exist in linesAfter, depending on whether such a line exists, and the target changes when the mid-point is crossed. - // For display purposes, one complete beat is shown at the maximum brightness such that the target line should always be bright. - bool targetLineIsLastLineBefore = false; - - if (linesBefore.Count > 0 && linesAfter.Count > 0) - targetLineIsLastLineBefore = Math.Abs(linesBefore[^1].HitObject.StartTime - minTime) <= Math.Abs(linesAfter[0].HitObject.StartTime - minTime); - else if (linesBefore.Count > 0) - targetLineIsLastLineBefore = true; - - if (targetLineIsLastLineBefore) - { - // Move the last line before to linesDuring - linesDuring.Insert(0, linesBefore[^1]); - linesBefore.RemoveAt(linesBefore.Count - 1); - } - else if (linesAfter.Count > 0) // = false does not guarantee that a line after exists (maybe at the bottom of the screen) - { - // Move the first line after to linesDuring - linesDuring.Insert(0, linesAfter[0]); - linesAfter.RemoveAt(0); - } - - // Grays are used rather than transparency since the lines appear on a coloured mania playfield. - - foreach (var l in linesDuring) - l.Colour = OsuColour.Gray(first_beat_brightness); - - for (int i = 0; i < linesBefore.Count; i++) - { - int offset = (linesBefore.Count - i - 1) / beatDivisor.Value; - linesBefore[i].Colour = OsuColour.Gray(first_beat_brightness / (offset + 1)); - } - - for (int i = 0; i < linesAfter.Count; i++) - { - int offset = i / beatDivisor.Value; - linesAfter[i].Colour = OsuColour.Gray(first_beat_brightness / (offset + 1)); + { + double timeSeparation = lineTime < minTime ? minTime - lineTime : lineTime - maxTime; + line.Colour = OsuColour.Gray((float)Math.Max(0, 1 - timeSeparation / visible_range)); + } } } } From 406f39e8bfc281fc91c3e96ce12077423974006f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 18 May 2020 21:27:26 +0900 Subject: [PATCH 068/871] Construct online visible lines --- .../TestSceneManiaBeatSnapGrid.cs | 2 +- .../Edit/ManiaBeatSnapGrid.cs | 135 ++++++++++-------- .../Edit/ManiaHitObjectComposer.cs | 9 +- 3 files changed, 81 insertions(+), 65 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs index 84419313e6..941cf4e7c8 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Mania.Tests float relativePosition = Playfield.Stages[0].HitObjectContainer.ToLocalSpace(e.ScreenSpaceMousePosition).Y / Playfield.Stages[0].HitObjectContainer.DrawHeight; double timeValue = scrollingInfo.TimeRange.Value * relativePosition; - beatSnapGrid.SetRange(timeValue, timeValue); + beatSnapGrid.SelectionTimeRange = (timeValue, timeValue); return true; } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index e771a9753f..a16fb52f01 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Caching; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; @@ -22,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.Edit { public class ManiaBeatSnapGrid : Component { - private const double visible_range = 1500; + private const double visible_range = 750; [Resolved] private IManiaHitObjectComposer composer { get; set; } @@ -44,6 +45,34 @@ namespace osu.Game.Rulesets.Mania.Edit private readonly List grids = new List(); + private readonly Cached lineCache = new Cached(); + + private (double start, double end)? selectionTimeRange; + + public (double start, double end)? SelectionTimeRange + { + get => selectionTimeRange; + set + { + if (value == selectionTimeRange) + return; + + selectionTimeRange = value; + lineCache.Invalidate(); + } + } + + protected override void Update() + { + base.Update(); + + if (!lineCache.IsValid) + { + lineCache.Validate(); + createLines(); + } + } + [BackgroundDependencyLoader] private void load() { @@ -61,49 +90,65 @@ namespace osu.Game.Rulesets.Mania.Edit beatDivisor.BindValueChanged(_ => createLines(), true); } - public override void Hide() - { - base.Hide(); - foreach (var grid in grids) - grid.Hide(); - } - - public override void Show() - { - base.Show(); - foreach (var grid in grids) - grid.Show(); - } - private void createLines() { foreach (var grid in grids) grid.Clear(); - for (int i = 0; i < beatmap.ControlPointInfo.TimingPoints.Count; i++) + if (selectionTimeRange == null) + return; + + var range = selectionTimeRange.Value; + + var timingPoint = beatmap.ControlPointInfo.TimingPointAt(range.start - visible_range); + + double time = timingPoint.Time; + int beat = 0; + + // progress time until in the visible range. + while (time < range.start - visible_range) { - var point = beatmap.ControlPointInfo.TimingPoints[i]; - var until = i + 1 < beatmap.ControlPointInfo.TimingPoints.Count ? beatmap.ControlPointInfo.TimingPoints[i + 1].Time : working.Value.Track.Length; + time += timingPoint.BeatLength / beatDivisor.Value; + beat++; + } - int beat = 0; + while (time < range.end + visible_range) + { + var nextTimingPoint = beatmap.ControlPointInfo.TimingPointAt(time); - for (double t = point.Time; t < until; t += point.BeatLength / beatDivisor.Value) + // switch to the next timing point if we have reached it. + if (nextTimingPoint != timingPoint) { - var indexInBeat = beat % beatDivisor.Value; - Color4 colour; + beat = 0; + timingPoint = nextTimingPoint; + } - if (indexInBeat == 0) - colour = BindableBeatDivisor.GetColourFor(1, colours); + Color4 colour = BindableBeatDivisor.GetColourFor( + BindableBeatDivisor.GetDivisorForBeatIndex(Math.Max(1, beat), beatDivisor.Value), colours); + + foreach (var grid in grids) + grid.Add(new DrawableGridLine(time, colour)); + + beat++; + time += timingPoint.BeatLength / beatDivisor.Value; + } + + foreach (var grid in grids) + { + // required to update ScrollingHitObjectContainer's cache. + grid.UpdateSubTree(); + + foreach (var line in grid.Objects.OfType()) + { + time = line.HitObject.StartTime; + + if (time >= range.start && time <= range.end) + line.Alpha = 1; else { - var divisor = BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value); - colour = BindableBeatDivisor.GetColourFor(divisor, colours); + double timeSeparation = time < range.start ? range.start - time : time - range.end; + line.Alpha = (float)Math.Max(0, 1 - timeSeparation / visible_range); } - - foreach (var grid in grids) - grid.Add(new DrawableGridLine(t, colour)); - - beat++; } } } @@ -112,6 +157,7 @@ namespace osu.Game.Rulesets.Mania.Edit { float minDist = float.PositiveInfinity; DrawableGridLine minDistLine = null; + Vector2 minDistLinePosition = Vector2.Zero; foreach (var grid in grids) @@ -137,33 +183,6 @@ namespace osu.Game.Rulesets.Mania.Edit return (new Vector2(position.X, minDistLinePosition.Y + noteOffset), minDistLine.HitObject.StartTime); } - public void SetRange(double minTime, double maxTime) - { - if (LoadState >= LoadState.Ready) - setRange(minTime, maxTime); - else - Schedule(() => setRange(minTime, maxTime)); - } - - private void setRange(double minTime, double maxTime) - { - foreach (var grid in grids) - { - foreach (var line in grid.Objects.OfType()) - { - double lineTime = line.HitObject.StartTime; - - if (lineTime >= minTime && lineTime <= maxTime) - line.Colour = Color4.White; - else - { - double timeSeparation = lineTime < minTime ? minTime - lineTime : lineTime - maxTime; - line.Colour = OsuColour.Gray((float)Math.Max(0, 1 - timeSeparation / visible_range)); - } - } - } - } - private class DrawableGridLine : DrawableHitObject { [Resolved] diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 1266761d12..475320ece3 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -68,18 +68,15 @@ namespace osu.Game.Rulesets.Mania.Edit { if (EditorBeatmap.SelectedHitObjects.Any()) { - beatSnapGrid.SetRange(EditorBeatmap.SelectedHitObjects.Min(h => h.StartTime), EditorBeatmap.SelectedHitObjects.Max(h => h.GetEndTime())); - beatSnapGrid.Show(); + beatSnapGrid.SelectionTimeRange = (EditorBeatmap.SelectedHitObjects.Min(h => h.StartTime), EditorBeatmap.SelectedHitObjects.Max(h => h.GetEndTime())); } else - beatSnapGrid.Hide(); + beatSnapGrid.SelectionTimeRange = null; } else { var placementTime = GetSnappedPosition(ToLocalSpace(inputManager.CurrentState.Mouse.Position), 0).time; - beatSnapGrid.SetRange(placementTime, placementTime); - - beatSnapGrid.Show(); + beatSnapGrid.SelectionTimeRange = (placementTime, placementTime); } } From d56466e2b9376318edc845c84c361fbf3927161d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 May 2020 19:07:35 +0900 Subject: [PATCH 069/871] Add very basic pooling of grid lines --- .../Edit/ManiaBeatSnapGrid.cs | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index a16fb52f01..067438af39 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -90,10 +90,17 @@ namespace osu.Game.Rulesets.Mania.Edit beatDivisor.BindValueChanged(_ => createLines(), true); } + private readonly Stack availableLines = new Stack(); + private void createLines() { foreach (var grid in grids) - grid.Clear(); + { + foreach (var line in grid.Objects.OfType()) + availableLines.Push(line); + + grid.Clear(false); + } if (selectionTimeRange == null) return; @@ -127,7 +134,15 @@ namespace osu.Game.Rulesets.Mania.Edit BindableBeatDivisor.GetDivisorForBeatIndex(Math.Max(1, beat), beatDivisor.Value), colours); foreach (var grid in grids) - grid.Add(new DrawableGridLine(time, colour)); + { + if (!availableLines.TryPop(out var line)) + line = new DrawableGridLine(); + + line.HitObject.StartTime = time; + line.Colour = colour; + + grid.Add(line); + } beat++; time += timingPoint.BeatLength / beatDivisor.Value; @@ -190,17 +205,13 @@ namespace osu.Game.Rulesets.Mania.Edit private readonly IBindable direction = new Bindable(); - public DrawableGridLine(double startTime, Color4 colour) - : base(new HitObject { StartTime = startTime }) + public DrawableGridLine() + : base(new HitObject()) { RelativeSizeAxes = Axes.X; Height = 2; - AddInternal(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colour - }); + AddInternal(new Box { RelativeSizeAxes = Axes.Both }); } [BackgroundDependencyLoader] From a6f3dc53f72e3100da0cb6d422a872522371e38e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 May 2020 23:01:36 +0900 Subject: [PATCH 070/871] Fix time value not being updated for next timing point --- osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index 067438af39..5803c67b80 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -127,6 +127,7 @@ namespace osu.Game.Rulesets.Mania.Edit if (nextTimingPoint != timingPoint) { beat = 0; + time = nextTimingPoint.Time; timingPoint = nextTimingPoint; } From c28a9bdb804a05ee5fac61b5728931885650cdf5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 May 2020 23:02:20 +0900 Subject: [PATCH 071/871] Move load method up --- .../Edit/ManiaBeatSnapGrid.cs | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index 5803c67b80..0b7834addb 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -51,7 +51,6 @@ namespace osu.Game.Rulesets.Mania.Edit public (double start, double end)? SelectionTimeRange { - get => selectionTimeRange; set { if (value == selectionTimeRange) @@ -62,17 +61,6 @@ namespace osu.Game.Rulesets.Mania.Edit } } - protected override void Update() - { - base.Update(); - - if (!lineCache.IsValid) - { - lineCache.Validate(); - createLines(); - } - } - [BackgroundDependencyLoader] private void load() { @@ -90,6 +78,17 @@ namespace osu.Game.Rulesets.Mania.Edit beatDivisor.BindValueChanged(_ => createLines(), true); } + protected override void Update() + { + base.Update(); + + if (!lineCache.IsValid) + { + lineCache.Validate(); + createLines(); + } + } + private readonly Stack availableLines = new Stack(); private void createLines() From 85156c62efd7900dbce4115e27f96327f4fd0345 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 May 2020 23:05:08 +0900 Subject: [PATCH 072/871] Add xmldoc and address some code quality concerns --- .../Edit/ManiaBeatSnapGrid.cs | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index 0b7834addb..98e15e3fa8 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -21,12 +21,27 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Edit { + /// + /// A grid which displays coloured beat divisor lines in proximity to the selection or placement cursor. + /// public class ManiaBeatSnapGrid : Component { private const double visible_range = 750; - [Resolved] - private IManiaHitObjectComposer composer { get; set; } + /// + /// The range of time values of the current selection. + /// + public (double start, double end)? SelectionTimeRange + { + set + { + if (value == selectionTimeRange) + return; + + selectionTimeRange = value; + lineCache.Invalidate(); + } + } [Resolved] private EditorBeatmap beatmap { get; set; } @@ -49,20 +64,8 @@ namespace osu.Game.Rulesets.Mania.Edit private (double start, double end)? selectionTimeRange; - public (double start, double end)? SelectionTimeRange - { - set - { - if (value == selectionTimeRange) - return; - - selectionTimeRange = value; - lineCache.Invalidate(); - } - } - [BackgroundDependencyLoader] - private void load() + private void load(IManiaHitObjectComposer composer) { foreach (var stage in composer.Playfield.Stages) { From da8729e6bde39ec4255d498cde1e50a7135d5601 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 19 May 2020 23:28:42 +0900 Subject: [PATCH 073/871] 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 650ebde54d..f0f16d3763 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ee6206e166..010ef8578a 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index cbf8600c62..88b0c7dd8a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From d31a59b07466eae570e1127624c30286666339b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 14:55:36 +0900 Subject: [PATCH 074/871] Fix logic results in infinite loop on default timing point return --- osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index 98e15e3fa8..05990eadd7 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -126,7 +126,7 @@ namespace osu.Game.Rulesets.Mania.Edit var nextTimingPoint = beatmap.ControlPointInfo.TimingPointAt(time); // switch to the next timing point if we have reached it. - if (nextTimingPoint != timingPoint) + if (nextTimingPoint.Time > timingPoint.Time) { beat = 0; time = nextTimingPoint.Time; From 0bc3073d49aeff5ed5647fca342f158725222510 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 15:01:32 +0900 Subject: [PATCH 075/871] Fix test failures --- .../TestSceneManiaHitObjectComposer.cs | 14 +++++++++----- osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs index 6274bb1005..bad3d7854e 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs @@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Mania.Tests public void TestDragOffscreenSelectionVerticallyUpScroll() { DrawableHitObject lastObject = null; + double originalTime = 0; Vector2 originalPosition = Vector2.Zero; setScrollStep(ScrollingDirection.Up); @@ -49,6 +50,7 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("seek to last object", () => { lastObject = this.ChildrenOfType().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last()); + originalTime = lastObject.HitObject.StartTime; Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime); }); @@ -64,19 +66,20 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("move mouse downwards", () => { - InputManager.MoveMouseTo(lastObject, new Vector2(0, 20)); + InputManager.MoveMouseTo(lastObject, new Vector2(0, lastObject.ScreenSpaceDrawQuad.Height * 2)); InputManager.ReleaseButton(MouseButton.Left); }); AddAssert("hitobjects not moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 0)); AddAssert("hitobjects moved downwards", () => lastObject.DrawPosition.Y - originalPosition.Y > 0); - AddAssert("hitobjects not moved too far", () => lastObject.DrawPosition.Y - originalPosition.Y < 50); + AddAssert("hitobject has moved time", () => lastObject.HitObject.StartTime == originalTime + 125); } [Test] public void TestDragOffscreenSelectionVerticallyDownScroll() { DrawableHitObject lastObject = null; + double originalTime = 0; Vector2 originalPosition = Vector2.Zero; setScrollStep(ScrollingDirection.Down); @@ -84,6 +87,7 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("seek to last object", () => { lastObject = this.ChildrenOfType().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last()); + originalTime = lastObject.HitObject.StartTime; Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime); }); @@ -99,13 +103,13 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("move mouse upwards", () => { - InputManager.MoveMouseTo(lastObject, new Vector2(0, -20)); + InputManager.MoveMouseTo(lastObject, new Vector2(0, -lastObject.ScreenSpaceDrawQuad.Height * 2)); InputManager.ReleaseButton(MouseButton.Left); }); AddAssert("hitobjects not moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 0)); AddAssert("hitobjects moved upwards", () => originalPosition.Y - lastObject.DrawPosition.Y > 0); - AddAssert("hitobjects not moved too far", () => originalPosition.Y - lastObject.DrawPosition.Y < 50); + AddAssert("hitobject has moved time", () => lastObject.HitObject.StartTime == originalTime + 125); } [Test] @@ -207,7 +211,7 @@ namespace osu.Game.Rulesets.Mania.Tests }; for (int i = 0; i < 10; i++) - EditorBeatmap.Add(new Note { StartTime = 100 * i }); + EditorBeatmap.Add(new Note { StartTime = 125 * i }); } } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index 05990eadd7..fa8f8a755a 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -180,7 +180,7 @@ namespace osu.Game.Rulesets.Mania.Edit foreach (var grid in grids) { - foreach (var line in grid.AliveObjects.OfType()) + foreach (var line in grid.Objects.OfType()) { Vector2 linePos = line.ToSpaceOfOtherDrawable(line.OriginPosition, this); float d = Vector2.Distance(position, linePos); From 3354d48a38c90314caa376481b06a1d0e9f77fc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 17:48:43 +0900 Subject: [PATCH 076/871] Change snapping to be screen space coordinate based --- .../Blueprints/HoldNoteSelectionBlueprint.cs | 2 +- .../Edit/ManiaHitObjectComposer.cs | 16 +++++++++++----- .../TestSceneOsuDistanceSnapGrid.cs | 8 +++++--- .../Sliders/Components/PathControlPointPiece.cs | 4 ++-- .../Sliders/SliderSelectionBlueprint.cs | 2 +- .../Visual/Editing/TestSceneDistanceSnapGrid.cs | 8 +++++--- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 17 +++++++++++------ ...SnapProvider.cs => IPositionSnapProvider.cs} | 11 +++++++++-- .../Rulesets/Edit/OverlaySelectionBlueprint.cs | 2 +- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 3 +++ osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 2 +- .../Compose/Components/BlueprintContainer.cs | 8 ++++---- .../Components/ComposeBlueprintContainer.cs | 5 ++--- .../Edit/Compose/Components/DistanceSnapGrid.cs | 2 +- .../Compose/Components/Timeline/Timeline.cs | 12 ++++++------ .../Timeline/TimelineHitObjectBlueprint.cs | 4 ++-- 16 files changed, 65 insertions(+), 41 deletions(-) rename osu.Game/Rulesets/Edit/{IDistanceSnapProvider.cs => IPositionSnapProvider.cs} (84%) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs index 43d43ef252..1737c4d2e5 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNoteSelectionBlueprint.cs @@ -77,6 +77,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints public override Quad SelectionQuad => ScreenSpaceDrawQuad; - public override Vector2 SelectionPoint => DrawableObject.Head.ScreenSpaceDrawQuad.Centre; + public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.Head.ScreenSpaceDrawQuad.Centre; } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index dfa933baad..9ba2cdeaec 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.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.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; @@ -44,26 +45,31 @@ namespace osu.Game.Rulesets.Mania.Edit public int TotalColumns => Playfield.TotalColumns; - public override (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) + public override (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) + { + throw new NotImplementedException(); + } + + public override (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) { var hoc = Playfield.GetColumn(0).HitObjectContainer; - float targetPosition = hoc.ToLocalSpace(ToScreenSpace(position)).Y; + Vector2 targetPosition = hoc.ToLocalSpace(screenSpacePosition); if (drawableRuleset.ScrollingInfo.Direction.Value == ScrollingDirection.Down) { // We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time. // The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position, // so when scrolling downwards the coordinates need to be flipped. - targetPosition = hoc.DrawHeight - targetPosition; + targetPosition.Y = hoc.DrawHeight - targetPosition.Y; } - double targetTime = drawableRuleset.ScrollingInfo.Algorithm.TimeAt(targetPosition, + double targetTime = drawableRuleset.ScrollingInfo.Algorithm.TimeAt(targetPosition.Y, EditorClock.CurrentTime, drawableRuleset.ScrollingInfo.TimeRange.Value, hoc.DrawHeight); - return base.GetSnappedPosition(position, targetTime); + return (targetPosition, targetTime); } protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index c182aa5d63..f95f76b405 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Cached] private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(); - [Cached(typeof(IDistanceSnapProvider))] + [Cached(typeof(IPositionSnapProvider))] private readonly SnapProvider snapProvider = new SnapProvider(); private TestOsuDistanceSnapGrid grid; @@ -172,9 +172,11 @@ namespace osu.Game.Rulesets.Osu.Tests } } - private class SnapProvider : IDistanceSnapProvider + private class SnapProvider : IPositionSnapProvider { - public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time); + public (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) => (position, 0); + + public (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => (screenSpacePosition, 0); public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index d0c1eb5317..abbef0772f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private IEditorChangeHandler changeHandler { get; set; } [Resolved(CanBeNull = true)] - private IDistanceSnapProvider snapProvider { get; set; } + private IPositionSnapProvider snapProvider { get; set; } [Resolved] private OsuColour colours { get; set; } @@ -162,7 +162,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (ControlPoint == slider.Path.ControlPoints[0]) { // Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account - (Vector2 snappedPosition, double snappedTime) = snapProvider?.GetSnappedPosition(e.MousePosition, slider.StartTime) ?? (e.MousePosition, slider.StartTime); + (Vector2 snappedPosition, double snappedTime) = snapProvider?.SnapScreenSpacePositionToValidTime(e.MousePosition) ?? (e.MousePosition, slider.StartTime); Vector2 movementDelta = snappedPosition - slider.Position; slider.Position += movementDelta; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index b7074b7ee5..6633136673 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -190,7 +190,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders new OsuMenuItem("Add control point", MenuItemType.Standard, () => addControlPoint(rightClickPosition)), }; - public override Vector2 SelectionPoint => ((DrawableSlider)DrawableObject).HeadCircle.ScreenSpaceDrawQuad.Centre; + public override Vector2 ScreenSpaceSelectionPoint => ((DrawableSlider)DrawableObject).HeadCircle.ScreenSpaceDrawQuad.Centre; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => BodyPiece.ReceivePositionalInputAt(screenSpacePos); diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index 417d16fdb0..0e5e88c47a 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -23,7 +23,7 @@ namespace osu.Game.Tests.Visual.Editing [Cached(typeof(EditorBeatmap))] private readonly EditorBeatmap editorBeatmap; - [Cached(typeof(IDistanceSnapProvider))] + [Cached(typeof(IPositionSnapProvider))] private readonly SnapProvider snapProvider = new SnapProvider(); public TestSceneDistanceSnapGrid() @@ -151,9 +151,11 @@ namespace osu.Game.Tests.Visual.Editing => (Vector2.Zero, 0); } - private class SnapProvider : IDistanceSnapProvider + private class SnapProvider : IPositionSnapProvider { - public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => (position, time); + public (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) => (position, 0); + + public (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => (screenSpacePosition, 0); public float GetBeatSnapDistanceAt(double referenceTime) => 10; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 883288d6d7..82e8fc8b10 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -245,8 +245,7 @@ namespace osu.Game.Rulesets.Edit { EditorBeatmap.PlacementObject.Value = hitObject; - if (distanceSnapGrid != null) - hitObject.StartTime = GetSnappedPosition(distanceSnapGrid.ToLocalSpace(inputManager.CurrentState.Mouse.Position), hitObject.StartTime).time; + hitObject.StartTime = SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position).time; } public void EndPlacement(HitObject hitObject, bool commit) @@ -265,7 +264,11 @@ namespace osu.Game.Rulesets.Edit public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject); - public override (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => distanceSnapGrid?.GetSnappedPosition(position) ?? (position, time); + public override (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) => + distanceSnapGrid?.GetSnappedPosition(position) ?? (position, 0); + + public override (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) + => SnapPositionToValidTime(drawableRulesetWrapper.Playfield.ToLocalSpace(screenSpacePosition)); public override float GetBeatSnapDistanceAt(double referenceTime) { @@ -297,8 +300,8 @@ namespace osu.Game.Rulesets.Edit } [Cached(typeof(HitObjectComposer))] - [Cached(typeof(IDistanceSnapProvider))] - public abstract class HitObjectComposer : CompositeDrawable, IDistanceSnapProvider + [Cached(typeof(IPositionSnapProvider))] + public abstract class HitObjectComposer : CompositeDrawable, IPositionSnapProvider { internal HitObjectComposer() { @@ -323,7 +326,9 @@ namespace osu.Game.Rulesets.Edit [CanBeNull] protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable selectedHitObjects) => null; - public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time); + public abstract (Vector2 position, double time) SnapPositionToValidTime(Vector2 position); + + public abstract (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); public abstract float GetBeatSnapDistanceAt(double referenceTime); diff --git a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs similarity index 84% rename from osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs rename to osu.Game/Rulesets/Edit/IPositionSnapProvider.cs index c6e61f68da..93cb605132 100644 --- a/osu.Game/Rulesets/Edit/IDistanceSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs @@ -5,9 +5,16 @@ using osuTK; namespace osu.Game.Rulesets.Edit { - public interface IDistanceSnapProvider + public interface IPositionSnapProvider { - (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time); + /// + /// Given a position (local to the provider), find a valid time snap + /// + /// The local position to be snapped. + /// The time and position post-snapping. + (Vector2 position, double time) SnapPositionToValidTime(Vector2 position); + + (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); /// /// Retrieves the distance between two points within a timing point that are one beat length apart. diff --git a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs index b4ae3f3fba..8202d3a1d1 100644 --- a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Edit public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos); - public override Vector2 SelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre; + public override Vector2 ScreenSpaceSelectionPoint => DrawableObject.ScreenSpaceDrawQuad.Centre; public override Quad SelectionQuad => DrawableObject.ScreenSpaceDrawQuad; diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index fb1eb7adbf..c06e50950c 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -66,7 +66,10 @@ namespace osu.Game.Rulesets.Edit protected void BeginPlacement(double? startTime = null, bool commitStart = false) { HitObject.StartTime = startTime ?? EditorClock.CurrentTime; + + // applies snapping to above time placementHandler.BeginPlacement(HitObject); + PlacementActive |= commitStart; } diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index e6a63eae4f..71256093d5 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -116,7 +116,7 @@ namespace osu.Game.Rulesets.Edit /// /// The screen-space point that causes this to be selected. /// - public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre; + public virtual Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.Centre; /// /// The screen-space quad that outlines this for selections. diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 8910684463..d1cae6b3cd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly BindableList selectedHitObjects = new BindableList(); [Resolved(canBeNull: true)] - private IDistanceSnapProvider snapProvider { get; set; } + private IPositionSnapProvider snapProvider { get; set; } protected BlueprintContainer() { @@ -326,7 +326,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { foreach (var blueprint in SelectionBlueprints) { - if (blueprint.IsAlive && blueprint.IsPresent && rect.Contains(blueprint.SelectionPoint)) + if (blueprint.IsAlive && blueprint.IsPresent && rect.Contains(blueprint.ScreenSpaceSelectionPoint)) blueprint.Select(); else blueprint.Deselect(); @@ -384,7 +384,7 @@ namespace osu.Game.Screens.Edit.Compose.Components // Movement is tracked from the blueprint of the earliest hitobject, since it only makes sense to distance snap from that hitobject movementBlueprint = selectionHandler.SelectedBlueprints.OrderBy(b => b.HitObject.StartTime).First(); - movementBlueprintOriginalPosition = movementBlueprint.SelectionPoint; // todo: unsure if correct + movementBlueprintOriginalPosition = movementBlueprint.ScreenSpaceSelectionPoint; // todo: unsure if correct } /// @@ -405,7 +405,7 @@ namespace osu.Game.Screens.Edit.Compose.Components Vector2 movePosition = movementBlueprintOriginalPosition.Value + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; // Retrieve a snapped position. - (Vector2 snappedPosition, double snappedTime) = snapProvider.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime); + (Vector2 snappedPosition, double snappedTime) = snapProvider.SnapScreenSpacePositionToValidTime(movePosition); // Move the hitobjects. if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, ToScreenSpace(snappedPosition)))) diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 0eb77a8561..bb6094ebe8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -67,10 +67,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updatePlacementPosition(Vector2 screenSpacePosition) { - Vector2 snappedGridPosition = composer.GetSnappedPosition(ToLocalSpace(screenSpacePosition), 0).position; - Vector2 snappedScreenSpacePosition = ToScreenSpace(snappedGridPosition); + Vector2 snappedPlayfieldPosition = composer.SnapScreenSpacePositionToValidTime(screenSpacePosition).position; - currentPlacement.UpdatePosition(snappedScreenSpacePosition); + currentPlacement.UpdatePosition(ToScreenSpace(snappedPlayfieldPosition)); } #endregion diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 3a42938fc1..8a92a2011d 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Edit.Compose.Components protected OsuColour Colours { get; private set; } [Resolved] - protected IDistanceSnapProvider SnapProvider { get; private set; } + protected IPositionSnapProvider SnapProvider { get; private set; } [Resolved] private EditorBeatmap beatmap { get; set; } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 25f3cfc285..1006da28df 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -17,9 +17,9 @@ using osuTK; namespace osu.Game.Screens.Edit.Compose.Components.Timeline { - [Cached(typeof(IDistanceSnapProvider))] + [Cached(typeof(IPositionSnapProvider))] [Cached] - public class Timeline : ZoomableScrollContainer, IDistanceSnapProvider + public class Timeline : ZoomableScrollContainer, IPositionSnapProvider { public readonly Bindable WaveformVisible = new Bindable(); public readonly IBindable Beatmap = new Bindable(); @@ -181,12 +181,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private IBeatSnapProvider beatSnapProvider { get; set; } - public double GetTimeFromScreenSpacePosition(Vector2 position) - => getTimeFromPosition(Content.ToLocalSpace(position)); - - public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => + public (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) => (position, beatSnapProvider.SnapTime(getTimeFromPosition(position))); + public (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 position) => + (position, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(position)))); + private double getTimeFromPosition(Vector2 localPosition) => (localPosition.X / Content.DrawWidth) * track.Length; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 16ba3ba89a..b5eae26f98 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -186,7 +186,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline } } - public override Vector2 SelectionPoint => ScreenSpaceDrawQuad.TopLeft; + public override Vector2 ScreenSpaceSelectionPoint => ScreenSpaceDrawQuad.TopLeft; public class DragBar : Container { @@ -275,7 +275,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline OnDragHandled?.Invoke(e); - var time = timeline.GetTimeFromScreenSpacePosition(e.ScreenSpaceMousePosition); + var time = timeline.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition).time; switch (hitObject) { From c46bfc2532d139838ad6a94e11571f2c81430421 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 18:19:21 +0900 Subject: [PATCH 077/871] Create SnapResult class to hold various snapping results --- .../Edit/ManiaHitObjectComposer.cs | 10 +---- .../TestSceneOsuDistanceSnapGrid.cs | 4 +- .../Components/PathControlPointPiece.cs | 6 +-- .../Editing/TestSceneDistanceSnapGrid.cs | 4 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 17 ++++---- .../Rulesets/Edit/IPositionSnapProvider.cs | 27 ++++++++++--- .../Compose/Components/BlueprintContainer.cs | 15 ++++--- .../Components/ComposeBlueprintContainer.cs | 2 +- .../Compose/Components/Timeline/Timeline.cs | 7 +--- .../Timeline/TimelineHitObjectBlueprint.cs | 39 ++++++++++--------- 10 files changed, 70 insertions(+), 61 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 9ba2cdeaec..f7951fcc5d 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; @@ -45,12 +44,7 @@ namespace osu.Game.Rulesets.Mania.Edit public int TotalColumns => Playfield.TotalColumns; - public override (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) - { - throw new NotImplementedException(); - } - - public override (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) + public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) { var hoc = Playfield.GetColumn(0).HitObjectContainer; @@ -69,7 +63,7 @@ namespace osu.Game.Rulesets.Mania.Edit drawableRuleset.ScrollingInfo.TimeRange.Value, hoc.DrawHeight); - return (targetPosition, targetTime); + return new SnapResult(targetPosition, targetTime); } protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index f95f76b405..0d0be2953b 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -174,9 +174,7 @@ namespace osu.Game.Rulesets.Osu.Tests private class SnapProvider : IPositionSnapProvider { - public (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) => (position, 0); - - public (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => (screenSpacePosition, 0); + public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index abbef0772f..834bf1892f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -162,11 +162,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (ControlPoint == slider.Path.ControlPoints[0]) { // Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account - (Vector2 snappedPosition, double snappedTime) = snapProvider?.SnapScreenSpacePositionToValidTime(e.MousePosition) ?? (e.MousePosition, slider.StartTime); - Vector2 movementDelta = snappedPosition - slider.Position; + var result = snapProvider?.SnapScreenSpacePositionToValidTime(e.MousePosition); + Vector2 movementDelta = (result?.ScreenSpacePosition ?? e.MousePosition) - slider.Position; slider.Position += movementDelta; - slider.StartTime = snappedTime; + slider.StartTime = result?.Time ?? slider.StartTime; // Since control points are relative to the position of the slider, they all need to be offset backwards by the delta for (int i = 1; i < slider.Path.ControlPoints.Count; i++) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs index 0e5e88c47a..8190cf5f89 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneDistanceSnapGrid.cs @@ -153,9 +153,7 @@ namespace osu.Game.Tests.Visual.Editing private class SnapProvider : IPositionSnapProvider { - public (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) => (position, 0); - - public (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => (screenSpacePosition, 0); + public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, 0); public float GetBeatSnapDistanceAt(double referenceTime) => 10; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 82e8fc8b10..7e9bb850af 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -245,7 +245,8 @@ namespace osu.Game.Rulesets.Edit { EditorBeatmap.PlacementObject.Value = hitObject; - hitObject.StartTime = SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position).time; + if (SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position).Time is double time) + hitObject.StartTime = time; } public void EndPlacement(HitObject hitObject, bool commit) @@ -264,11 +265,13 @@ namespace osu.Game.Rulesets.Edit public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject); - public override (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) => - distanceSnapGrid?.GetSnappedPosition(position) ?? (position, 0); + public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) + { + if (distanceSnapGrid == null) return new SnapResult(screenSpacePosition, null); - public override (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) - => SnapPositionToValidTime(drawableRulesetWrapper.Playfield.ToLocalSpace(screenSpacePosition)); + (Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition)); + return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time); + } public override float GetBeatSnapDistanceAt(double referenceTime) { @@ -326,9 +329,7 @@ namespace osu.Game.Rulesets.Edit [CanBeNull] protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable selectedHitObjects) => null; - public abstract (Vector2 position, double time) SnapPositionToValidTime(Vector2 position); - - public abstract (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); + public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); public abstract float GetBeatSnapDistanceAt(double referenceTime); diff --git a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs index 93cb605132..d95800f403 100644 --- a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs @@ -8,13 +8,11 @@ namespace osu.Game.Rulesets.Edit public interface IPositionSnapProvider { /// - /// Given a position (local to the provider), find a valid time snap + /// Given a position, find a valid time snap. /// - /// The local position to be snapped. + /// The screen-space position to be snapped. /// The time and position post-snapping. - (Vector2 position, double time) SnapPositionToValidTime(Vector2 position); - - (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); + SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); /// /// Retrieves the distance between two points within a timing point that are one beat length apart. @@ -55,4 +53,23 @@ namespace osu.Game.Rulesets.Edit /// A value that represents snapped to the closest beat of the timing point. float GetSnappedDistanceFromDistance(double referenceTime, float distance); } + + public class SnapResult + { + /// + /// The screen space position, potentially altered for snapping. + /// + public Vector2 ScreenSpacePosition; + + /// + /// The resultant time for snapping, if a value could be attained. + /// + public double? Time; + + public SnapResult(Vector2 screenSpacePosition, double? time) + { + ScreenSpacePosition = screenSpacePosition; + Time = time; + } + } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index d1cae6b3cd..e38df3d812 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -405,16 +405,19 @@ namespace osu.Game.Screens.Edit.Compose.Components Vector2 movePosition = movementBlueprintOriginalPosition.Value + e.ScreenSpaceMousePosition - e.ScreenSpaceMouseDownPosition; // Retrieve a snapped position. - (Vector2 snappedPosition, double snappedTime) = snapProvider.SnapScreenSpacePositionToValidTime(movePosition); + var result = snapProvider.SnapScreenSpacePositionToValidTime(movePosition); // Move the hitobjects. - if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, ToScreenSpace(snappedPosition)))) + if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, result.ScreenSpacePosition))) return true; - // Apply the start time at the newly snapped-to position - double offset = snappedTime - draggedObject.StartTime; - foreach (HitObject obj in selectionHandler.SelectedHitObjects) - obj.StartTime += offset; + if (result.Time.HasValue) + { + // Apply the start time at the newly snapped-to position + double offset = result.Time.Value - draggedObject.StartTime; + foreach (HitObject obj in selectionHandler.SelectedHitObjects) + obj.StartTime += offset; + } return true; } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index bb6094ebe8..e1a4bca1d6 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updatePlacementPosition(Vector2 screenSpacePosition) { - Vector2 snappedPlayfieldPosition = composer.SnapScreenSpacePositionToValidTime(screenSpacePosition).position; + Vector2 snappedPlayfieldPosition = composer.SnapScreenSpacePositionToValidTime(screenSpacePosition).ScreenSpacePosition; currentPlacement.UpdatePosition(ToScreenSpace(snappedPlayfieldPosition)); } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 1006da28df..ec2b11c0cf 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -181,11 +181,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private IBeatSnapProvider beatSnapProvider { get; set; } - public (Vector2 position, double time) SnapPositionToValidTime(Vector2 position) => - (position, beatSnapProvider.SnapTime(getTimeFromPosition(position))); - - public (Vector2 position, double time) SnapScreenSpacePositionToValidTime(Vector2 position) => - (position, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(position)))); + public SnapResult SnapScreenSpacePositionToValidTime(Vector2 position) => + new SnapResult(position, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(position)))); private double getTimeFromPosition(Vector2 localPosition) => (localPosition.X / Content.DrawWidth) * track.Length; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index b5eae26f98..03e05b75c5 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -275,32 +275,33 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline OnDragHandled?.Invoke(e); - var time = timeline.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition).time; - - switch (hitObject) + if (timeline.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition).Time is double time) { - case IHasRepeats repeatHitObject: - // find the number of repeats which can fit in the requested time. - var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1); - var proposedCount = Math.Max(0, (int)((time - hitObject.StartTime) / lengthOfOneRepeat) - 1); + switch (hitObject) + { + case IHasRepeats repeatHitObject: + // find the number of repeats which can fit in the requested time. + var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1); + var proposedCount = Math.Max(0, (int)((time - hitObject.StartTime) / lengthOfOneRepeat) - 1); - if (proposedCount == repeatHitObject.RepeatCount) - return; + if (proposedCount == repeatHitObject.RepeatCount) + return; - repeatHitObject.RepeatCount = proposedCount; - break; + repeatHitObject.RepeatCount = proposedCount; + break; - case IHasEndTime endTimeHitObject: - var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time)); + case IHasEndTime endTimeHitObject: + var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time)); - if (endTimeHitObject.EndTime == snappedTime) - return; + if (endTimeHitObject.EndTime == snappedTime) + return; - endTimeHitObject.EndTime = snappedTime; - break; + endTimeHitObject.EndTime = snappedTime; + break; + } + + beatmap.UpdateHitObject(hitObject); } - - beatmap.UpdateHitObject(hitObject); } protected override void OnDragEnd(DragEndEvent e) From ffb8d48fc30874ff81a7c4c6be3dad7fe70fc83b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 18:32:36 +0900 Subject: [PATCH 078/871] Fix osu!mania editor placement regressions --- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 ++ .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index f7951fcc5d..3e1d4f2f3a 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Mania.Edit drawableRuleset.ScrollingInfo.TimeRange.Value, hoc.DrawHeight); - return new SnapResult(targetPosition, targetTime); + return new SnapResult(screenSpacePosition, targetTime); } protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 7e9bb850af..5018b7bcb6 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -269,7 +269,9 @@ namespace osu.Game.Rulesets.Edit { if (distanceSnapGrid == null) return new SnapResult(screenSpacePosition, null); + // TODO: move distance snap grid to OsuHitObjectComposer. (Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition)); + return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time); } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index e1a4bca1d6..95e2f41f1f 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -67,9 +67,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updatePlacementPosition(Vector2 screenSpacePosition) { - Vector2 snappedPlayfieldPosition = composer.SnapScreenSpacePositionToValidTime(screenSpacePosition).ScreenSpacePosition; + var snapResult = composer.SnapScreenSpacePositionToValidTime(screenSpacePosition); - currentPlacement.UpdatePosition(ToScreenSpace(snappedPlayfieldPosition)); + currentPlacement.UpdatePosition(snapResult.ScreenSpacePosition); } #endregion From 23bf0d000e1013594129a57e24020d4c0e5312b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 18:40:55 +0900 Subject: [PATCH 079/871] Implement mania beat snapping support --- .../Edit/ManiaHitObjectComposer.cs | 9 ++++++++- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 12 ++++++------ .../Compose/Components/ComposeBlueprintContainer.cs | 9 ++++----- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 3e1d4f2f3a..053dcd0832 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -63,7 +63,14 @@ namespace osu.Game.Rulesets.Mania.Edit drawableRuleset.ScrollingInfo.TimeRange.Value, hoc.DrawHeight); - return new SnapResult(screenSpacePosition, targetTime); + targetTime = BeatSnapProvider.SnapTime(targetTime); + + screenSpacePosition.Y = hoc.ToScreenSpace( + new Vector2(0, drawableRuleset.ScrollingInfo.Algorithm.PositionAt(targetTime, EditorClock.CurrentTime, drawableRuleset.ScrollingInfo.TimeRange.Value, + hoc.DrawHeight)) + ).Y; + + return new SnapResult(screenSpacePosition, BeatSnapProvider.SnapTime(targetTime)); } protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 5018b7bcb6..b45cdea751 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Edit private IAdjustableClock adjustableClock { get; set; } [Resolved] - private IBeatSnapProvider beatSnapProvider { get; set; } + protected IBeatSnapProvider BeatSnapProvider { get; private set; } protected ComposeBlueprintContainer BlueprintContainer { get; private set; } @@ -278,27 +278,27 @@ 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.BeatmapInfo.BaseDifficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / BeatSnapProvider.BeatDivisor); } public override float DurationToDistance(double referenceTime, double duration) { - double beatLength = beatSnapProvider.GetBeatLengthAtTime(referenceTime); + double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceTime); return (float)(duration / beatLength * GetBeatSnapDistanceAt(referenceTime)); } public override double DistanceToDuration(double referenceTime, float distance) { - double beatLength = beatSnapProvider.GetBeatLengthAtTime(referenceTime); + double beatLength = BeatSnapProvider.GetBeatLengthAtTime(referenceTime); return distance / GetBeatSnapDistanceAt(referenceTime) * beatLength; } public override double GetSnappedDurationFromDistance(double referenceTime, float distance) - => beatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime) - referenceTime; + => BeatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime) - referenceTime; public override float GetSnappedDistanceFromDistance(double referenceTime, float distance) { - var snappedEndTime = beatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime); + var snappedEndTime = BeatSnapProvider.SnapTime(referenceTime + DistanceToDuration(referenceTime, distance), referenceTime); return DurationToDistance(referenceTime, snappedEndTime - referenceTime); } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 95e2f41f1f..7982cba4e3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -11,7 +11,6 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; -using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { @@ -65,9 +64,9 @@ namespace osu.Game.Screens.Edit.Compose.Components createPlacement(); } - private void updatePlacementPosition(Vector2 screenSpacePosition) + private void updatePlacementPosition() { - var snapResult = composer.SnapScreenSpacePositionToValidTime(screenSpacePosition); + var snapResult = composer.SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position); currentPlacement.UpdatePosition(snapResult.ScreenSpacePosition); } @@ -84,7 +83,7 @@ namespace osu.Game.Screens.Edit.Compose.Components removePlacement(); if (currentPlacement != null) - updatePlacementPosition(inputManager.CurrentState.Mouse.Position); + updatePlacementPosition(); } protected sealed override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) @@ -116,7 +115,7 @@ namespace osu.Game.Screens.Edit.Compose.Components placementBlueprintContainer.Child = currentPlacement = blueprint; // Fixes a 1-frame position discrepancy due to the first mouse move event happening in the next frame - updatePlacementPosition(inputManager.CurrentState.Mouse.Position); + updatePlacementPosition(); } } From 970bd86d2e1df8b109046ff0265d01b850b9f435 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 18:46:15 +0900 Subject: [PATCH 080/871] Remove local TimeAt usage in mania placement --- .../Edit/Blueprints/ManiaPlacementBlueprint.cs | 2 +- .../Edit/ManiaHitObjectComposer.cs | 11 +++++++---- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 3 +-- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 5 +---- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 3fb03d642f..4ebc4dae1a 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints return base.OnMouseDown(e); HitObject.Column = Column.Index; - BeginPlacement(TimeAt(e.ScreenSpaceMousePosition), true); + BeginPlacement(true); return true; } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 053dcd0832..724786a1c0 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -46,19 +46,22 @@ namespace osu.Game.Rulesets.Mania.Edit public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) { - var hoc = Playfield.GetColumn(0).HitObjectContainer; + var hoc = ColumnAt(screenSpacePosition)?.HitObjectContainer; - Vector2 targetPosition = hoc.ToLocalSpace(screenSpacePosition); + if (hoc == null) + return new SnapResult(screenSpacePosition, null); + + Vector2 localPosition = hoc.ToLocalSpace(screenSpacePosition); if (drawableRuleset.ScrollingInfo.Direction.Value == ScrollingDirection.Down) { // We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time. // The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position, // so when scrolling downwards the coordinates need to be flipped. - targetPosition.Y = hoc.DrawHeight - targetPosition.Y; + localPosition.Y = hoc.DrawHeight - localPosition.Y; } - double targetTime = drawableRuleset.ScrollingInfo.Algorithm.TimeAt(targetPosition.Y, + double targetTime = drawableRuleset.ScrollingInfo.Algorithm.TimeAt(localPosition.Y, EditorClock.CurrentTime, drawableRuleset.ScrollingInfo.TimeRange.Value, hoc.DrawHeight); diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index b45cdea751..1e328e6b6b 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -245,8 +245,7 @@ namespace osu.Game.Rulesets.Edit { EditorBeatmap.PlacementObject.Value = hitObject; - if (SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position).Time is double time) - hitObject.StartTime = time; + hitObject.StartTime = SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position).Time ?? EditorClock.CurrentTime; } public void EndPlacement(HitObject hitObject, bool commit) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index c06e50950c..5c506926b8 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -61,12 +61,9 @@ namespace osu.Game.Rulesets.Edit /// /// Signals that the placement of has started. /// - /// The start time of at the placement point. If null, the current clock time is used. /// Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments. - protected void BeginPlacement(double? startTime = null, bool commitStart = false) + protected void BeginPlacement(bool commitStart = false) { - HitObject.StartTime = startTime ?? EditorClock.CurrentTime; - // applies snapping to above time placementHandler.BeginPlacement(HitObject); From 82d6549161052871355aab9327e7a676e728081c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 19:05:03 +0900 Subject: [PATCH 081/871] Pass down snap result and remove local TimeAt usage --- .../Blueprints/HoldNotePlacementBlueprint.cs | 17 +++++++------ .../Blueprints/ManiaPlacementBlueprint.cs | 25 +++---------------- .../HitCircles/HitCirclePlacementBlueprint.cs | 3 +-- .../Sliders/SliderPlacementBlueprint.cs | 4 +-- .../Spinners/SpinnerPlacementBlueprint.cs | 3 +-- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 4 +-- .../Components/ComposeBlueprintContainer.cs | 2 +- .../Visual/PlacementBlueprintTestScene.cs | 4 +-- 8 files changed, 22 insertions(+), 40 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index c63e30e98a..5dbd84e370 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; using osuTK; @@ -59,23 +60,25 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private double originalStartTime; - public override void UpdatePosition(Vector2 screenSpacePosition) + public override void UpdatePosition(SnapResult result) { - base.UpdatePosition(screenSpacePosition); + base.UpdatePosition(result); if (PlacementActive) { - var endTime = TimeAt(screenSpacePosition); - - HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime; - HitObject.Duration = Math.Abs(endTime - originalStartTime); + if (result.Time is double endTime) + { + HitObject.StartTime = endTime < originalStartTime ? endTime : originalStartTime; + HitObject.Duration = Math.Abs(endTime - originalStartTime); + } } else { headPiece.Width = tailPiece.Width = SnappedWidth; headPiece.X = tailPiece.X = SnappedMousePosition.X; - originalStartTime = HitObject.StartTime = TimeAt(screenSpacePosition); + if (result.Time is double startTime) + originalStartTime = HitObject.StartTime = startTime; } } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 4ebc4dae1a..4d10afd289 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -58,10 +58,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints return true; } - public override void UpdatePosition(Vector2 screenSpacePosition) + public override void UpdatePosition(SnapResult result) { if (!PlacementActive) - Column = ColumnAt(screenSpacePosition); + Column = ColumnAt(result.ScreenSpacePosition); if (Column == null) return; @@ -69,26 +69,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints // Snap to the column var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0))); - SnappedMousePosition = new Vector2(parentPos.X, Parent.ToLocalSpace(screenSpacePosition).Y); - } - - protected double TimeAt(Vector2 screenSpacePosition) - { - if (Column == null) - return 0; - - var hitObjectContainer = Column.HitObjectContainer; - - // If we're scrolling downwards, a position of 0 is actually further away from the hit target - // so we need to flip the vertical coordinate in the hitobject container's space - var hitObjectPos = mouseToHitObjectPosition(Column.HitObjectContainer.ToLocalSpace(screenSpacePosition)).Y; - if (scrollingInfo.Direction.Value == ScrollingDirection.Down) - hitObjectPos = hitObjectContainer.DrawHeight - hitObjectPos; - - return scrollingInfo.Algorithm.TimeAt(hitObjectPos, - EditorClock.CurrentTime, - scrollingInfo.TimeRange.Value, - hitObjectContainer.DrawHeight); + SnappedMousePosition = new Vector2(parentPos.X, Parent.ToLocalSpace(result.ScreenSpacePosition).Y); } protected float PositionAt(double time) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs index dad199715e..e12dec2668 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -5,7 +5,6 @@ using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components; using osu.Game.Rulesets.Osu.Objects; -using osuTK; using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles @@ -40,6 +39,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles return base.OnMouseDown(e); } - public override void UpdatePosition(Vector2 screenSpacePosition) => HitObject.Position = ToLocalSpace(screenSpacePosition); + public override void UpdatePosition(SnapResult result) => HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index ac30f5a762..59ec92c79e 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -67,13 +67,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders inputManager = GetContainingInputManager(); } - public override void UpdatePosition(Vector2 screenSpacePosition) + public override void UpdatePosition(SnapResult result) { switch (state) { case PlacementState.Initial: BeginPlacement(); - HitObject.Position = ToLocalSpace(screenSpacePosition); + HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); break; case PlacementState.Body: diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs index 74b563d922..546f0e5981 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs @@ -8,7 +8,6 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; -using osuTK; using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners @@ -61,7 +60,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners return true; } - public override void UpdatePosition(Vector2 screenSpacePosition) + public override void UpdatePosition(SnapResult result) { } } diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 5c506926b8..bab9bf71ef 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -86,8 +86,8 @@ namespace osu.Game.Rulesets.Edit /// /// Updates the position of this to a new screen-space position. /// - /// The screen-space position. - public abstract void UpdatePosition(Vector2 screenSpacePosition); + /// The snap result information. + public abstract void UpdatePosition(SnapResult snapResult); /// /// Invokes , diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 7982cba4e3..0b5d8262fd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -68,7 +68,7 @@ namespace osu.Game.Screens.Edit.Compose.Components { var snapResult = composer.SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position); - currentPlacement.UpdatePosition(snapResult.ScreenSpacePosition); + currentPlacement.UpdatePosition(snapResult); } #endregion diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index dc67d28f63..a4e629b6f5 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -71,7 +71,7 @@ namespace osu.Game.Tests.Visual { base.Update(); - currentBlueprint.UpdatePosition(InputManager.CurrentState.Mouse.Position); + currentBlueprint.UpdatePosition(new SnapResult(InputManager.CurrentState.Mouse.Position, null)); } public override void Add(Drawable drawable) @@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual if (drawable is PlacementBlueprint blueprint) { blueprint.Show(); - blueprint.UpdatePosition(InputManager.CurrentState.Mouse.Position); + blueprint.UpdatePosition(new SnapResult(InputManager.CurrentState.Mouse.Position, null)); } } From 62092e3f5b4c60b18505a502a311b5db3dc1aa6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 19:09:04 +0900 Subject: [PATCH 082/871] Propagate mania column in SnapResult --- .../Blueprints/ManiaPlacementBlueprint.cs | 8 +------- .../Edit/ManiaHitObjectComposer.cs | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 4d10afd289..2f1b38d564 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -33,9 +33,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints /// protected float SnappedWidth { get; private set; } - [Resolved] - private IManiaHitObjectComposer composer { get; set; } - [Resolved] private IScrollingInfo scrollingInfo { get; set; } @@ -61,7 +58,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints public override void UpdatePosition(SnapResult result) { if (!PlacementActive) - Column = ColumnAt(result.ScreenSpacePosition); + Column = (result as ManiaSnapResult)?.Column; if (Column == null) return; @@ -85,9 +82,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints return hitObjectToMousePosition(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent)).Y; } - protected Column ColumnAt(Vector2 screenSpacePosition) - => composer.ColumnAt(screenSpacePosition); - /// /// Converts a mouse position to a hitobject position. /// diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 724786a1c0..89ccf0019a 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -46,11 +46,13 @@ namespace osu.Game.Rulesets.Mania.Edit public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) { - var hoc = ColumnAt(screenSpacePosition)?.HitObjectContainer; + var column = ColumnAt(screenSpacePosition); - if (hoc == null) + if (column == null) return new SnapResult(screenSpacePosition, null); + var hoc = column.HitObjectContainer; + Vector2 localPosition = hoc.ToLocalSpace(screenSpacePosition); if (drawableRuleset.ScrollingInfo.Direction.Value == ScrollingDirection.Down) @@ -73,7 +75,7 @@ namespace osu.Game.Rulesets.Mania.Edit hoc.DrawHeight)) ).Y; - return new SnapResult(screenSpacePosition, BeatSnapProvider.SnapTime(targetTime)); + return new ManiaSnapResult(screenSpacePosition, BeatSnapProvider.SnapTime(targetTime), column); } protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) @@ -94,4 +96,15 @@ namespace osu.Game.Rulesets.Mania.Edit new HoldNoteCompositionTool() }; } + + public class ManiaSnapResult : SnapResult + { + public readonly Column Column; + + public ManiaSnapResult(Vector2 screenSpacePosition, double time, Column column) + : base(screenSpacePosition, time) + { + Column = column; + } + } } From 2f78866dfb452c091cca286ec1d7744a3c4deab3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 19:23:17 +0900 Subject: [PATCH 083/871] Move positioning out of mania blueprints --- .../Blueprints/HoldNotePlacementBlueprint.cs | 7 +++- .../Blueprints/ManiaPlacementBlueprint.cs | 42 ------------------- .../Edit/Blueprints/NotePlacementBlueprint.cs | 20 +++++---- .../Edit/ManiaHitObjectComposer.cs | 17 ++++---- 4 files changed, 26 insertions(+), 60 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index 5dbd84e370..055b92b39d 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -74,8 +74,11 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints } else { - headPiece.Width = tailPiece.Width = SnappedWidth; - headPiece.X = tailPiece.X = SnappedMousePosition.X; + if (result is ManiaSnapResult maniaResult) + { + headPiece.Width = tailPiece.Width = maniaResult.Column.DrawWidth; + headPiece.X = tailPiece.X = ToLocalSpace(result.ScreenSpacePosition).X; + } if (result.Time is double startTime) originalStartTime = HitObject.StartTime = startTime; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 2f1b38d564..e8c7aea814 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -23,16 +23,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints protected Column Column; - /// - /// The current mouse position, snapped to the closest column. - /// - protected Vector2 SnappedMousePosition { get; private set; } - - /// - /// The width of the closest column to the current mouse position. - /// - protected float SnappedWidth { get; private set; } - [Resolved] private IScrollingInfo scrollingInfo { get; set; } @@ -59,14 +49,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { if (!PlacementActive) Column = (result as ManiaSnapResult)?.Column; - - if (Column == null) return; - - SnappedWidth = Column.DrawWidth; - - // Snap to the column - var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0))); - SnappedMousePosition = new Vector2(parentPos.X, Parent.ToLocalSpace(result.ScreenSpacePosition).Y); } protected float PositionAt(double time) @@ -82,30 +64,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints return hitObjectToMousePosition(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent)).Y; } - /// - /// Converts a mouse position to a hitobject position. - /// - /// - /// Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction. - /// - /// The mouse position. - /// The resulting hitobject position, acnhored at the top or bottom of the blueprint depending on the scroll direction. - private Vector2 mouseToHitObjectPosition(Vector2 mousePosition) - { - switch (scrollingInfo.Direction.Value) - { - case ScrollingDirection.Up: - mousePosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2; - break; - - case ScrollingDirection.Down: - mousePosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2; - break; - } - - return mousePosition; - } - /// /// Converts a hitobject position to a mouse position. /// diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs index a4c0791253..5f6db2e6dd 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs @@ -3,6 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; using osuTK.Input; @@ -11,22 +12,25 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { public class NotePlacementBlueprint : ManiaPlacementBlueprint { + private readonly EditNotePiece piece; + public NotePlacementBlueprint() : base(new Note()) { - Origin = Anchor.Centre; + RelativeSizeAxes = Axes.Both; - AutoSizeAxes = Axes.Y; - - InternalChild = new EditNotePiece { RelativeSizeAxes = Axes.X }; + InternalChild = piece = new EditNotePiece { Origin = Anchor.Centre }; } - protected override void Update() + public override void UpdatePosition(SnapResult result) { - base.Update(); + base.UpdatePosition(result); - Width = SnappedWidth; - Position = SnappedMousePosition; + if (result is ManiaSnapResult maniaResult) + { + piece.Width = maniaResult.Column.DrawWidth; + piece.Position = ToLocalSpace(result.ScreenSpacePosition); + } } protected override bool OnMouseDown(MouseDownEvent e) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 89ccf0019a..c38952fd29 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -55,7 +55,9 @@ namespace osu.Game.Rulesets.Mania.Edit Vector2 localPosition = hoc.ToLocalSpace(screenSpacePosition); - if (drawableRuleset.ScrollingInfo.Direction.Value == ScrollingDirection.Down) + var scrollInfo = drawableRuleset.ScrollingInfo; + + if (scrollInfo.Direction.Value == ScrollingDirection.Down) { // We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time. // The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position, @@ -63,19 +65,18 @@ namespace osu.Game.Rulesets.Mania.Edit localPosition.Y = hoc.DrawHeight - localPosition.Y; } - double targetTime = drawableRuleset.ScrollingInfo.Algorithm.TimeAt(localPosition.Y, + double targetTime = scrollInfo.Algorithm.TimeAt(localPosition.Y, EditorClock.CurrentTime, - drawableRuleset.ScrollingInfo.TimeRange.Value, + scrollInfo.TimeRange.Value, hoc.DrawHeight); targetTime = BeatSnapProvider.SnapTime(targetTime); - screenSpacePosition.Y = hoc.ToScreenSpace( - new Vector2(0, drawableRuleset.ScrollingInfo.Algorithm.PositionAt(targetTime, EditorClock.CurrentTime, drawableRuleset.ScrollingInfo.TimeRange.Value, - hoc.DrawHeight)) - ).Y; + var localPos = new Vector2( + hoc.DrawWidth / 2, + scrollInfo.Algorithm.PositionAt(targetTime, EditorClock.CurrentTime, scrollInfo.TimeRange.Value, hoc.DrawHeight)); - return new ManiaSnapResult(screenSpacePosition, BeatSnapProvider.SnapTime(targetTime), column); + return new ManiaSnapResult(hoc.ToScreenSpace(localPos), BeatSnapProvider.SnapTime(targetTime), column); } protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) From 26fb779f4d7b6929d90ecc434070d88401046387 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 19:52:57 +0900 Subject: [PATCH 084/871] Move remaining positioning logic local to hold note blueprint --- .../Blueprints/HoldNotePlacementBlueprint.cs | 22 +++++++++- .../Blueprints/ManiaPlacementBlueprint.cs | 41 ------------------- 2 files changed, 20 insertions(+), 43 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index 055b92b39d..38a12ed2ed 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.UI.Scrolling; using osuTK; using osuTK.Input; @@ -37,8 +39,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints if (Column != null) { - headPiece.Y = PositionAt(HitObject.StartTime); - tailPiece.Y = PositionAt(HitObject.EndTime); + headPiece.Y = positionAt(HitObject.StartTime); + tailPiece.Y = positionAt(HitObject.EndTime); } var topPosition = new Vector2(headPiece.DrawPosition.X, Math.Min(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y)); @@ -84,5 +86,21 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints originalStartTime = HitObject.StartTime = startTime; } } + + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } + + private float positionAt(double time) + { + var pos = scrollingInfo.Algorithm.PositionAt(time, + EditorClock.CurrentTime, + scrollingInfo.TimeRange.Value, + Column.HitObjectContainer.DrawHeight); + + if (scrollingInfo.Direction.Value == ScrollingDirection.Down) + pos = Column.HitObjectContainer.DrawHeight - pos; + + return Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent).Y; + } } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index e8c7aea814..e83495cec4 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -1,16 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.UI.Scrolling; -using osuTK; using osuTK.Input; namespace osu.Game.Rulesets.Mania.Edit.Blueprints @@ -23,9 +19,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints protected Column Column; - [Resolved] - private IScrollingInfo scrollingInfo { get; set; } - protected ManiaPlacementBlueprint(T hitObject) : base(hitObject) { @@ -50,39 +43,5 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints if (!PlacementActive) Column = (result as ManiaSnapResult)?.Column; } - - protected float PositionAt(double time) - { - var pos = scrollingInfo.Algorithm.PositionAt(time, - EditorClock.CurrentTime, - scrollingInfo.TimeRange.Value, - Column.HitObjectContainer.DrawHeight); - - if (scrollingInfo.Direction.Value == ScrollingDirection.Down) - pos = Column.HitObjectContainer.DrawHeight - pos; - - return hitObjectToMousePosition(Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent)).Y; - } - - /// - /// Converts a hitobject position to a mouse position. - /// - /// The hitobject position. - /// The resulting mouse position, anchored at the centre of the hitobject. - private Vector2 hitObjectToMousePosition(Vector2 hitObjectPosition) - { - switch (scrollingInfo.Direction.Value) - { - case ScrollingDirection.Up: - hitObjectPosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2; - break; - - case ScrollingDirection.Down: - hitObjectPosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2; - break; - } - - return hitObjectPosition; - } } } From 19e2da9c73e03b674ad2525bc59bf8b6d8e16571 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 19:59:36 +0900 Subject: [PATCH 085/871] Fix down scrolling giving incorrect positioning data --- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index c38952fd29..7ad2ce4699 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -72,11 +72,17 @@ namespace osu.Game.Rulesets.Mania.Edit targetTime = BeatSnapProvider.SnapTime(targetTime); - var localPos = new Vector2( + localPosition = new Vector2( hoc.DrawWidth / 2, scrollInfo.Algorithm.PositionAt(targetTime, EditorClock.CurrentTime, scrollInfo.TimeRange.Value, hoc.DrawHeight)); - return new ManiaSnapResult(hoc.ToScreenSpace(localPos), BeatSnapProvider.SnapTime(targetTime), column); + if (scrollInfo.Direction.Value == ScrollingDirection.Down) + { + // reapply the above. + localPosition.Y = hoc.DrawHeight - localPosition.Y; + } + + return new ManiaSnapResult(hoc.ToScreenSpace(localPosition), BeatSnapProvider.SnapTime(targetTime), column); } protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) From 7c9fbb6fcfb6a6fdfe6706bb1b320080a76ffc1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 21:03:03 +0900 Subject: [PATCH 086/871] Split out classes --- .../Edit/ManiaHitObjectComposer.cs | 11 ------- .../Edit/ManiaSnapResult.cs | 20 +++++++++++++ .../Rulesets/Edit/IPositionSnapProvider.cs | 19 ------------ osu.Game/Rulesets/Edit/SnapResult.cs | 29 +++++++++++++++++++ 4 files changed, 49 insertions(+), 30 deletions(-) create mode 100644 osu.Game.Rulesets.Mania/Edit/ManiaSnapResult.cs create mode 100644 osu.Game/Rulesets/Edit/SnapResult.cs diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 7ad2ce4699..3287c10531 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -103,15 +103,4 @@ namespace osu.Game.Rulesets.Mania.Edit new HoldNoteCompositionTool() }; } - - public class ManiaSnapResult : SnapResult - { - public readonly Column Column; - - public ManiaSnapResult(Vector2 screenSpacePosition, double time, Column column) - : base(screenSpacePosition, time) - { - Column = column; - } - } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSnapResult.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSnapResult.cs new file mode 100644 index 0000000000..b94f5e51c4 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSnapResult.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.Rulesets.Edit; +using osu.Game.Rulesets.Mania.UI; +using osuTK; + +namespace osu.Game.Rulesets.Mania.Edit +{ + public class ManiaSnapResult : SnapResult + { + public readonly Column Column; + + public ManiaSnapResult(Vector2 screenSpacePosition, double time, Column column) + : base(screenSpacePosition, time) + { + Column = column; + } + } +} diff --git a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs index d95800f403..c854c06031 100644 --- a/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs +++ b/osu.Game/Rulesets/Edit/IPositionSnapProvider.cs @@ -53,23 +53,4 @@ namespace osu.Game.Rulesets.Edit /// A value that represents snapped to the closest beat of the timing point. float GetSnappedDistanceFromDistance(double referenceTime, float distance); } - - public class SnapResult - { - /// - /// The screen space position, potentially altered for snapping. - /// - public Vector2 ScreenSpacePosition; - - /// - /// The resultant time for snapping, if a value could be attained. - /// - public double? Time; - - public SnapResult(Vector2 screenSpacePosition, double? time) - { - ScreenSpacePosition = screenSpacePosition; - Time = time; - } - } } diff --git a/osu.Game/Rulesets/Edit/SnapResult.cs b/osu.Game/Rulesets/Edit/SnapResult.cs new file mode 100644 index 0000000000..5d07d7b233 --- /dev/null +++ b/osu.Game/Rulesets/Edit/SnapResult.cs @@ -0,0 +1,29 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osuTK; + +namespace osu.Game.Rulesets.Edit +{ + /// + /// The result of a position/time snapping process. + /// + public class SnapResult + { + /// + /// The screen space position, potentially altered for snapping. + /// + public Vector2 ScreenSpacePosition; + + /// + /// The resultant time for snapping, if a value could be attained. + /// + public double? Time; + + public SnapResult(Vector2 screenSpacePosition, double? time) + { + ScreenSpacePosition = screenSpacePosition; + Time = time; + } + } +} From e3cec9cf6c7251c16d075bf0b27a8546f7ca0692 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 21:13:08 +0900 Subject: [PATCH 087/871] Simplify column assignment --- .../Edit/Blueprints/ManiaPlacementBlueprint.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index e83495cec4..af57b4fa07 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -17,7 +17,20 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { protected new T HitObject => (T)base.HitObject; - protected Column Column; + private Column column; + + public Column Column + { + get => column; + set + { + if (value == column) + return; + + column = value; + HitObject.Column = column.Index; + } + } protected ManiaPlacementBlueprint(T hitObject) : base(hitObject) @@ -31,9 +44,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints return false; if (Column == null) - return base.OnMouseDown(e); + return false; - HitObject.Column = Column.Index; BeginPlacement(true); return true; } From 63b5f1a376c1f6d3dce6c6040653297839acf04c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 21:14:20 +0900 Subject: [PATCH 088/871] Remove unnecessary IRequireHighFrequencyMousePosition --- .../Edit/Blueprints/ManiaPlacementBlueprint.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index af57b4fa07..8d3b3ea583 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics; -using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Objects; @@ -11,8 +10,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public abstract class ManiaPlacementBlueprint : PlacementBlueprint, - IRequireHighFrequencyMousePosition // the playfield could be moving behind us + public abstract class ManiaPlacementBlueprint : PlacementBlueprint where T : ManiaHitObject { protected new T HitObject => (T)base.HitObject; From 69db62b78a0d996843a07afc152b8f2aeea1c7e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 May 2020 21:42:21 +0900 Subject: [PATCH 089/871] Combine implementation of time-to-position lookup --- .../ManiaPlacementBlueprintTestScene.cs | 2 ++ .../ManiaSelectionBlueprintTestScene.cs | 2 ++ .../Blueprints/HoldNotePlacementBlueprint.cs | 23 ++++----------- .../Edit/IManiaHitObjectComposer.cs | 2 ++ .../Edit/ManiaHitObjectComposer.cs | 28 ++++++++++++------- 5 files changed, 29 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs index aac77c9c1c..be3e205f36 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs @@ -49,6 +49,8 @@ namespace osu.Game.Rulesets.Mania.Tests public Column ColumnAt(Vector2 screenSpacePosition) => column; + public Vector2 ScreenSpacePositionAtTime(double time, Column column = null) => Vector2.Zero; + public int TotalColumns => 1; } } diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs index b598893e8c..3d654466ed 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs @@ -33,6 +33,8 @@ namespace osu.Game.Rulesets.Mania.Tests public Column ColumnAt(Vector2 screenSpacePosition) => column; + public Vector2 ScreenSpacePositionAtTime(double time, Column column = null) => Vector2.Zero; + public int TotalColumns => 1; } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index 38a12ed2ed..31bf76edd0 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -20,6 +20,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private readonly EditNotePiece headPiece; private readonly EditNotePiece tailPiece; + [Resolved] + private IManiaHitObjectComposer composer { get; set; } + public HoldNotePlacementBlueprint() : base(new HoldNote()) { @@ -39,8 +42,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints if (Column != null) { - headPiece.Y = positionAt(HitObject.StartTime); - tailPiece.Y = positionAt(HitObject.EndTime); + headPiece.Y = Parent.ToLocalSpace(composer.ScreenSpacePositionAtTime(HitObject.StartTime, Column)).Y; + tailPiece.Y = Parent.ToLocalSpace(composer.ScreenSpacePositionAtTime(HitObject.EndTime, Column)).Y; } var topPosition = new Vector2(headPiece.DrawPosition.X, Math.Min(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y)); @@ -86,21 +89,5 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints originalStartTime = HitObject.StartTime = startTime; } } - - [Resolved] - private IScrollingInfo scrollingInfo { get; set; } - - private float positionAt(double time) - { - var pos = scrollingInfo.Algorithm.PositionAt(time, - EditorClock.CurrentTime, - scrollingInfo.TimeRange.Value, - Column.HitObjectContainer.DrawHeight); - - if (scrollingInfo.Direction.Value == ScrollingDirection.Down) - pos = Column.HitObjectContainer.DrawHeight - pos; - - return Column.HitObjectContainer.ToSpaceOfOtherDrawable(new Vector2(0, pos), Parent).Y; - } } } diff --git a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs index f64bab1fae..f1915cd85a 100644 --- a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs @@ -10,6 +10,8 @@ namespace osu.Game.Rulesets.Mania.Edit { Column ColumnAt(Vector2 screenSpacePosition); + Vector2 ScreenSpacePositionAtTime(double time, Column column = null); + int TotalColumns { get; } } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 3287c10531..0cae26b51c 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -53,6 +53,7 @@ namespace osu.Game.Rulesets.Mania.Edit var hoc = column.HitObjectContainer; + // convert to local space of column so we can snap and fetch correct location. Vector2 localPosition = hoc.ToLocalSpace(screenSpacePosition); var scrollInfo = drawableRuleset.ScrollingInfo; @@ -65,24 +66,31 @@ namespace osu.Game.Rulesets.Mania.Edit localPosition.Y = hoc.DrawHeight - localPosition.Y; } - double targetTime = scrollInfo.Algorithm.TimeAt(localPosition.Y, - EditorClock.CurrentTime, - scrollInfo.TimeRange.Value, - hoc.DrawHeight); + double targetTime = scrollInfo.Algorithm.TimeAt(localPosition.Y, EditorClock.CurrentTime, scrollInfo.TimeRange.Value, hoc.DrawHeight); + // apply beat snapping targetTime = BeatSnapProvider.SnapTime(targetTime); - localPosition = new Vector2( - hoc.DrawWidth / 2, - scrollInfo.Algorithm.PositionAt(targetTime, EditorClock.CurrentTime, scrollInfo.TimeRange.Value, hoc.DrawHeight)); + // convert back to screen space + screenSpacePosition = ScreenSpacePositionAtTime(targetTime, column); + + return new ManiaSnapResult(screenSpacePosition, targetTime, column); + } + + public Vector2 ScreenSpacePositionAtTime(double time, Column column = null) + { + var hoc = (column ?? Playfield.GetColumn(0)).HitObjectContainer; + var scrollInfo = drawableRuleset.ScrollingInfo; + + var pos = scrollInfo.Algorithm.PositionAt(time, EditorClock.CurrentTime, scrollInfo.TimeRange.Value, hoc.DrawHeight); if (scrollInfo.Direction.Value == ScrollingDirection.Down) { - // reapply the above. - localPosition.Y = hoc.DrawHeight - localPosition.Y; + // as explained above + pos = hoc.DrawHeight - pos; } - return new ManiaSnapResult(hoc.ToScreenSpace(localPosition), BeatSnapProvider.SnapTime(targetTime), column); + return hoc.ToScreenSpace(new Vector2(hoc.DrawWidth / 2, pos)); } protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) From e018d0744152e92df3e4559b7ce98551c30a57f2 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 20 May 2020 16:30:38 +0200 Subject: [PATCH 090/871] Use one constant for STABLE_CONFIG location string --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 4 +--- osu.Game.Tournament/Models/StableInfo.cs | 4 ++++ osu.Game.Tournament/Screens/SetupScreen.cs | 1 - osu.Game.Tournament/Screens/StablePathSelectScreen.cs | 4 +--- osu.Game.Tournament/TournamentGameBase.cs | 8 ++++---- 5 files changed, 10 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 6d1cd7cc3c..d2d74e94b2 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -40,8 +40,6 @@ namespace osu.Game.Tournament.IPC [Resolved] private StableInfo stableInfo { get; set; } - private const string stable_config = "tournament/stable.json"; - public Storage IPCStorage { get; private set; } [Resolved] @@ -196,7 +194,7 @@ namespace osu.Game.Tournament.IPC private void saveStablePath() { - using (var stream = tournamentStorage.GetStream(stable_config, FileAccess.Write, FileMode.Create)) + using (var stream = tournamentStorage.GetStream(StableInfo.STABLE_CONFIG, FileAccess.Write, FileMode.Create)) using (var sw = new StreamWriter(stream)) { sw.Write(JsonConvert.SerializeObject(stableInfo, diff --git a/osu.Game.Tournament/Models/StableInfo.cs b/osu.Game.Tournament/Models/StableInfo.cs index 4818842151..63423ca6fa 100644 --- a/osu.Game.Tournament/Models/StableInfo.cs +++ b/osu.Game.Tournament/Models/StableInfo.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Newtonsoft.Json; using osu.Framework.Bindables; namespace osu.Game.Tournament.Models @@ -13,5 +14,8 @@ namespace osu.Game.Tournament.Models public class StableInfo { public Bindable StablePath = new Bindable(string.Empty); + + [JsonIgnore] + public const string STABLE_CONFIG = "tournament/stable.json"; } } diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 1c479bdec4..9f8f81aa80 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -28,7 +28,6 @@ namespace osu.Game.Tournament.Screens private LoginOverlay loginOverlay; private ActionableInfo resolution; - private const string stable_config = "tournament/stable.json"; [Resolved] private MatchIPCInfo ipc { get; set; } diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index 5c488ae352..a42a5dc0fc 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -26,8 +26,6 @@ namespace osu.Game.Tournament.Screens { private DirectorySelector directorySelector; - private const string stable_config = "tournament/stable.json"; - [Resolved] private StableInfo stableInfo { get; set; } @@ -150,7 +148,7 @@ namespace osu.Game.Tournament.Screens try { - using (var stream = storage.GetStream(stable_config, FileAccess.Write, FileMode.Create)) + using (var stream = storage.GetStream(StableInfo.STABLE_CONFIG, FileAccess.Write, FileMode.Create)) using (var sw = new StreamWriter(stream)) { sw.Write(JsonConvert.SerializeObject(stableInfo, diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 31c56c7fc4..00946399fb 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -147,7 +147,10 @@ namespace osu.Game.Tournament private void readStableConfig() { - if (storage.Exists(stable_config)) + if (stableInfo == null) + stableInfo = new StableInfo(); + + if (storage.Exists(StableInfo.STABLE_CONFIG)) { using (Stream stream = storage.GetStream(stable_config, FileAccess.Read, FileMode.Open)) using (var sr = new StreamReader(stream)) @@ -156,9 +159,6 @@ namespace osu.Game.Tournament } } - if (stableInfo == null) - stableInfo = new StableInfo(); - dependencies.Cache(stableInfo); } From 1b8d657eade8b02ace6f42d5d97d45aa55e320c1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 May 2020 23:46:47 +0900 Subject: [PATCH 091/871] Implement score panel list --- .../Visual/Ranking/TestSceneScorePanelList.cs | 69 ++++++++++++++++ osu.Game/Screens/Ranking/ScorePanel.cs | 20 +++-- osu.Game/Screens/Ranking/ScorePanelList.cs | 80 +++++++++++++++++++ 3 files changed, 163 insertions(+), 6 deletions(-) create mode 100644 osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs create mode 100644 osu.Game/Screens/Ranking/ScorePanelList.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs new file mode 100644 index 0000000000..4964af8784 --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -0,0 +1,69 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking; +using osu.Game.Tests.Beatmaps; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Ranking +{ + public class TestSceneScorePanelList : OsuTestScene + { + public TestSceneScorePanelList() + { + var list = new ScorePanelList + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + + Add(list); + + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + list.AddScore(createScore()); + } + + private ScoreInfo createScore() => new ScoreInfo + { + User = new User + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, + Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, + Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, + TotalScore = 2845370, + Accuracy = 0.95, + MaxCombo = 999, + Rank = ScoreRank.S, + Date = DateTimeOffset.Now, + Statistics = + { + { HitResult.Miss, 1 }, + { HitResult.Meh, 50 }, + { HitResult.Good, 100 }, + { HitResult.Great, 300 }, + } + }; + } +} diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index bf57cb4dd9..baca2fd9e1 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Contracted; using osu.Game.Screens.Ranking.Expanded; @@ -75,8 +76,7 @@ namespace osu.Game.Screens.Ranking private static readonly Color4 contracted_middle_layer_colour = Color4Extensions.FromHex("#353535"); public event Action StateChanged; - - private readonly ScoreInfo score; + public readonly ScoreInfo Score; private Container topLayerContainer; private Drawable topLayerBackground; @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Ranking public ScorePanel(ScoreInfo score) { - this.score = score; + Score = score; } [BackgroundDependencyLoader] @@ -189,8 +189,8 @@ namespace osu.Game.Screens.Ranking topLayerBackground.FadeColour(expanded_top_layer_colour, resize_duration, Easing.OutQuint); middleLayerBackground.FadeColour(expanded_middle_layer_colour, resize_duration, Easing.OutQuint); - topLayerContentContainer.Add(middleLayerContent = new ExpandedPanelTopContent(score.User).With(d => d.Alpha = 0)); - middleLayerContentContainer.Add(topLayerContent = new ExpandedPanelMiddleContent(score).With(d => d.Alpha = 0)); + topLayerContentContainer.Add(middleLayerContent = new ExpandedPanelTopContent(Score.User).With(d => d.Alpha = 0)); + middleLayerContentContainer.Add(topLayerContent = new ExpandedPanelMiddleContent(Score).With(d => d.Alpha = 0)); break; case PanelState.Contracted: @@ -199,7 +199,7 @@ namespace osu.Game.Screens.Ranking topLayerBackground.FadeColour(contracted_top_layer_colour, resize_duration, Easing.OutQuint); middleLayerBackground.FadeColour(contracted_middle_layer_colour, resize_duration, Easing.OutQuint); - middleLayerContentContainer.Add(topLayerContent = new ContractedPanelMiddleContent(score).With(d => d.Alpha = 0)); + middleLayerContentContainer.Add(topLayerContent = new ContractedPanelMiddleContent(Score).With(d => d.Alpha = 0)); break; } @@ -222,5 +222,13 @@ namespace osu.Game.Screens.Ranking middleLayerContent?.FadeIn(content_fade_duration); } } + + protected override bool OnClick(ClickEvent e) + { + if (State == PanelState.Contracted) + State = PanelState.Expanded; + + return true; + } } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs new file mode 100644 index 0000000000..cc6842b2dd --- /dev/null +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; +using osu.Game.Scoring; +using osuTK; + +namespace osu.Game.Screens.Ranking +{ + public class ScorePanelList : CompositeDrawable + { + private readonly Flow panels; + private ScorePanel expandedPanel; + + public ScorePanelList() + { + RelativeSizeAxes = Axes.Both; + + InternalChild = panels = new Flow + { + Anchor = Anchor.Centre, + Origin = Anchor.Custom, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + }; + } + + public void AddScore(ScoreInfo score) + { + var panel = new ScorePanel(score) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }; + + panel.StateChanged += s => onPanelStateChanged(panel, s); + + // Todo: Temporary + panel.State = expandedPanel == null ? PanelState.Expanded : PanelState.Contracted; + + panels.Add(panel); + } + + public void RemoveScore(ScoreInfo score) => panels.RemoveAll(p => p.Score == score); + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + if (expandedPanel != null) + { + var firstPanel = panels.FlowingChildren.First(); + var target = expandedPanel.DrawPosition.X - firstPanel.DrawPosition.X + expandedPanel.DrawSize.X / 2; + + panels.OriginPosition = new Vector2((float)Interpolation.Lerp(panels.OriginPosition.X, target, Math.Clamp(Math.Abs(Time.Elapsed) / 80, 0, 1)), panels.DrawHeight / 2); + } + } + + private void onPanelStateChanged(ScorePanel panel, PanelState state) + { + if (state == PanelState.Contracted) + return; + + if (expandedPanel != null) + expandedPanel.State = PanelState.Contracted; + expandedPanel = panel; + } + + private class Flow : FillFlowContainer + { + // Todo: Order is wrong. + public override IEnumerable FlowingChildren => AliveInternalChildren.OfType().OrderBy(s => s.Score.TotalScore); + } + } +} From acba1f3ad667fdeae03a1708329e4c49f2ce4006 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 20 May 2020 23:46:54 +0900 Subject: [PATCH 092/871] Integrate score panel list into results screen --- osu.Game/Screens/Ranking/ResultsScreen.cs | 38 +++++++---------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index f2458d9f1f..652d158fbb 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Ranking private readonly bool allowRetry; private Drawable bottomPanel; - private Container contractedPanels; + private ScorePanelList panels; public ResultsScreen(ScoreInfo score, bool allowRetry = true) { @@ -63,28 +63,9 @@ namespace osu.Game.Screens.Ranking { new ResultsScrollContainer { - Children = new Drawable[] + Child = panels = new ScorePanelList { - new ScorePanel(Score) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - State = PanelState.Expanded - }, - new OsuScrollContainer(Direction.Horizontal) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = contractedPanels = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both - } - } + RelativeSizeAxes = Axes.Both, } }, bottomPanel = new Container @@ -117,6 +98,8 @@ namespace osu.Game.Screens.Ranking } }; + panels.AddScore(Score); + if (player != null && allowRetry) { buttons.Add(new RetryButton { Width = 300 }); @@ -141,12 +124,13 @@ namespace osu.Game.Screens.Ranking req.Success += r => { - contractedPanels.ChildrenEnumerable = r.Scores.Select(s => s.CreateScoreInfo(rulesets)).Select(s => new ScorePanel(s) + foreach (var s in r.Scores.Select(s => s.CreateScoreInfo(rulesets))) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - State = PanelState.Contracted - }); + if (s.OnlineScoreID == Score.OnlineScoreID) + continue; + + panels.AddScore(s); + } }; api.Queue(req); From 15ebe38303307f7bd5b6a24bc10bf72c3b926690 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 20 May 2020 17:13:35 +0200 Subject: [PATCH 093/871] Return null if path is not found, for clarity --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index d2d74e94b2..875bc4b4cd 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -184,7 +184,7 @@ namespace osu.Game.Tournament.IPC } } - return stableInstallPath; + return null; } finally { From b1c957c5e1aec15b346cbbd61db973bdce1a1f76 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 20 May 2020 17:25:53 +0200 Subject: [PATCH 094/871] invert if-statement and early return + reuse of checkExists --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 2 +- .../Screens/StablePathSelectScreen.cs | 56 +++++++++---------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 875bc4b4cd..cc19c9eaba 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -157,7 +157,7 @@ namespace osu.Game.Tournament.IPC return IPCStorage; } - private static bool checkExists(string p) => File.Exists(Path.Combine(p, "ipc.txt")); + public bool checkExists(string p) => File.Exists(Path.Combine(p, "ipc.txt")); private string findStablePath() { diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index a42a5dc0fc..dbb7a3b900 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -140,43 +140,43 @@ namespace osu.Game.Tournament.Screens private void changePath(Storage storage) { var target = directorySelector.CurrentDirectory.Value.FullName; + var fileBasedIpc = ipc as FileBasedIPC; Logger.Log($"Changing Stable CE location to {target}"); - if (File.Exists(Path.Combine(target, "ipc.txt"))) - { - stableInfo.StablePath.Value = target; - - try - { - using (var stream = storage.GetStream(StableInfo.STABLE_CONFIG, FileAccess.Write, FileMode.Create)) - using (var sw = new StreamWriter(stream)) - { - sw.Write(JsonConvert.SerializeObject(stableInfo, - new JsonSerializerSettings - { - Formatting = Formatting.Indented, - NullValueHandling = NullValueHandling.Ignore, - DefaultValueHandling = DefaultValueHandling.Ignore, - })); - } - - var fileBasedIpc = ipc as FileBasedIPC; - fileBasedIpc?.LocateStableStorage(); - sceneManager?.SetScreen(typeof(SetupScreen)); - } - catch (Exception e) - { - Logger.Log($"Error during migration: {e.Message}", level: LogLevel.Error); - } - } - else + if (!fileBasedIpc.checkExists(target)) { overlay = new DialogOverlay(); overlay.Push(new IPCErrorDialog("This is an invalid IPC Directory", "Select a directory that contains an osu! stable cutting edge installation and make sure it has an empty ipc.txt file in it.")); AddInternal(overlay); Logger.Log("Folder is not an osu! stable CE directory"); + return; // Return an error in the picker that the directory does not contain ipc.txt } + + stableInfo.StablePath.Value = target; + + try + { + using (var stream = storage.GetStream(StableInfo.STABLE_CONFIG, FileAccess.Write, FileMode.Create)) + using (var sw = new StreamWriter(stream)) + { + sw.Write(JsonConvert.SerializeObject(stableInfo, + new JsonSerializerSettings + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + DefaultValueHandling = DefaultValueHandling.Ignore, + })); + } + + + fileBasedIpc?.LocateStableStorage(); + sceneManager?.SetScreen(typeof(SetupScreen)); + } + catch (Exception e) + { + Logger.Log($"Error during migration: {e.Message}", level: LogLevel.Error); + } } private void autoDetect() From a5c2f97a76d0700d3498a78041160354f7851da4 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 20 May 2020 22:15:51 +0200 Subject: [PATCH 095/871] use common const in TournamentGameBase --- osu.Game.Tournament/TournamentGameBase.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 00946399fb..7d7d4f84aa 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -33,8 +33,6 @@ namespace osu.Game.Tournament { private const string bracket_filename = "bracket.json"; - private const string stable_config = "tournament/stable.json"; - private LadderInfo ladder; private Storage storage; @@ -152,7 +150,7 @@ namespace osu.Game.Tournament if (storage.Exists(StableInfo.STABLE_CONFIG)) { - using (Stream stream = storage.GetStream(stable_config, FileAccess.Read, FileMode.Open)) + using (Stream stream = storage.GetStream(StableInfo.STABLE_CONFIG, FileAccess.Read, FileMode.Open)) using (var sr = new StreamReader(stream)) { stableInfo = JsonConvert.DeserializeObject(sr.ReadToEnd()); From d2416ce30d6e5a8925310a1fae04fc97ceedf6d3 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 20 May 2020 22:16:37 +0200 Subject: [PATCH 096/871] removed redundant code and use existing checkExists --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 25 +++-------- .../Screens/StablePathSelectScreen.cs | 44 +++++++------------ 2 files changed, 21 insertions(+), 48 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index cc19c9eaba..74de5904e8 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -167,7 +167,7 @@ namespace osu.Game.Tournament.IPC { List> stableFindMethods = new List> { - findFromJsonConfig, + readFromStableInfo, findFromEnvVar, findFromRegistry, findFromLocalAppData, @@ -180,6 +180,7 @@ namespace osu.Game.Tournament.IPC if (stableInstallPath != null) { + saveStablePath(stableInstallPath); return stableInstallPath; } } @@ -192,8 +193,10 @@ namespace osu.Game.Tournament.IPC } } - private void saveStablePath() + private void saveStablePath(string path) { + stableInfo.StablePath.Value = path; + using (var stream = tournamentStorage.GetStream(StableInfo.STABLE_CONFIG, FileAccess.Write, FileMode.Create)) using (var sw = new StreamWriter(stream)) { @@ -215,11 +218,7 @@ namespace osu.Game.Tournament.IPC string stableInstallPath = Environment.GetEnvironmentVariable("OSU_STABLE_PATH"); if (checkExists(stableInstallPath)) - { - stableInfo.StablePath.Value = stableInstallPath; - saveStablePath(); return stableInstallPath; - } } catch { @@ -228,7 +227,7 @@ namespace osu.Game.Tournament.IPC return null; } - private string findFromJsonConfig() + private string readFromStableInfo() { try { @@ -250,11 +249,7 @@ namespace osu.Game.Tournament.IPC string stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); if (checkExists(stableInstallPath)) - { - stableInfo.StablePath.Value = stableInstallPath; - saveStablePath(); return stableInstallPath; - } return null; } @@ -265,11 +260,7 @@ namespace osu.Game.Tournament.IPC string stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu"); if (checkExists(stableInstallPath)) - { - stableInfo.StablePath.Value = stableInstallPath; - saveStablePath(); return stableInstallPath; - } return null; } @@ -284,11 +275,7 @@ namespace osu.Game.Tournament.IPC stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); if (checkExists(stableInstallPath)) - { - stableInfo.StablePath.Value = stableInstallPath; - saveStablePath(); return stableInstallPath; - } return null; } diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index dbb7a3b900..68fdaa34f8 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -139,39 +139,26 @@ namespace osu.Game.Tournament.Screens private void changePath(Storage storage) { - var target = directorySelector.CurrentDirectory.Value.FullName; - var fileBasedIpc = ipc as FileBasedIPC; - Logger.Log($"Changing Stable CE location to {target}"); - - if (!fileBasedIpc.checkExists(target)) - { - overlay = new DialogOverlay(); - overlay.Push(new IPCErrorDialog("This is an invalid IPC Directory", "Select a directory that contains an osu! stable cutting edge installation and make sure it has an empty ipc.txt file in it.")); - AddInternal(overlay); - Logger.Log("Folder is not an osu! stable CE directory"); - return; - // Return an error in the picker that the directory does not contain ipc.txt - } - - stableInfo.StablePath.Value = target; - try { - using (var stream = storage.GetStream(StableInfo.STABLE_CONFIG, FileAccess.Write, FileMode.Create)) - using (var sw = new StreamWriter(stream)) + var target = directorySelector.CurrentDirectory.Value.FullName; + stableInfo.StablePath.Value = target; + var fileBasedIpc = ipc as FileBasedIPC; + Logger.Log($"Changing Stable CE location to {target}"); + + if (!fileBasedIpc.checkExists(target)) { - sw.Write(JsonConvert.SerializeObject(stableInfo, - new JsonSerializerSettings - { - Formatting = Formatting.Indented, - NullValueHandling = NullValueHandling.Ignore, - DefaultValueHandling = DefaultValueHandling.Ignore, - })); + overlay = new DialogOverlay(); + overlay.Push(new IPCErrorDialog("This is an invalid IPC Directory", "Select a directory that contains an osu! stable cutting edge installation and make sure it has an empty ipc.txt file in it.")); + AddInternal(overlay); + Logger.Log("Folder is not an osu! stable CE directory"); + return; + // Return an error in the picker that the directory does not contain ipc.txt } - - fileBasedIpc?.LocateStableStorage(); - sceneManager?.SetScreen(typeof(SetupScreen)); + + fileBasedIpc.LocateStableStorage(); + sceneManager.SetScreen(typeof(SetupScreen)); } catch (Exception e) { @@ -181,7 +168,6 @@ namespace osu.Game.Tournament.Screens private void autoDetect() { - stableInfo.StablePath.Value = string.Empty; // This forces findStablePath() to look elsewhere. var fileBasedIpc = ipc as FileBasedIPC; fileBasedIpc?.LocateStableStorage(); From 585100207c05cb0bd0ddd50db488bbba15b53c60 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 20 May 2020 22:30:31 +0200 Subject: [PATCH 097/871] make CheckExists static public and removed unnecessary code --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 10 ++--- .../Screens/StablePathSelectScreen.cs | 40 +++++++------------ 2 files changed, 20 insertions(+), 30 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 74de5904e8..d93bce8dfa 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -157,7 +157,7 @@ namespace osu.Game.Tournament.IPC return IPCStorage; } - public bool checkExists(string p) => File.Exists(Path.Combine(p, "ipc.txt")); + public static bool CheckExists(string p) => File.Exists(Path.Combine(p, "ipc.txt")); private string findStablePath() { @@ -217,7 +217,7 @@ namespace osu.Game.Tournament.IPC Logger.Log("Trying to find stable with environment variables"); string stableInstallPath = Environment.GetEnvironmentVariable("OSU_STABLE_PATH"); - if (checkExists(stableInstallPath)) + if (CheckExists(stableInstallPath)) return stableInstallPath; } catch @@ -248,7 +248,7 @@ namespace osu.Game.Tournament.IPC Logger.Log("Trying to find stable in %LOCALAPPDATA%"); string stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); - if (checkExists(stableInstallPath)) + if (CheckExists(stableInstallPath)) return stableInstallPath; return null; @@ -259,7 +259,7 @@ namespace osu.Game.Tournament.IPC Logger.Log("Trying to find stable in dotfolders"); string stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu"); - if (checkExists(stableInstallPath)) + if (CheckExists(stableInstallPath)) return stableInstallPath; return null; @@ -274,7 +274,7 @@ namespace osu.Game.Tournament.IPC using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); - if (checkExists(stableInstallPath)) + if (CheckExists(stableInstallPath)) return stableInstallPath; return null; diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index 68fdaa34f8..dcc26b8b1e 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -1,9 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.IO; -using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -139,31 +137,23 @@ namespace osu.Game.Tournament.Screens private void changePath(Storage storage) { - try + var target = directorySelector.CurrentDirectory.Value.FullName; + stableInfo.StablePath.Value = target; + Logger.Log($"Changing Stable CE location to {target}"); + + if (!FileBasedIPC.CheckExists(target)) { - var target = directorySelector.CurrentDirectory.Value.FullName; - stableInfo.StablePath.Value = target; - var fileBasedIpc = ipc as FileBasedIPC; - Logger.Log($"Changing Stable CE location to {target}"); - - if (!fileBasedIpc.checkExists(target)) - { - overlay = new DialogOverlay(); - overlay.Push(new IPCErrorDialog("This is an invalid IPC Directory", "Select a directory that contains an osu! stable cutting edge installation and make sure it has an empty ipc.txt file in it.")); - AddInternal(overlay); - Logger.Log("Folder is not an osu! stable CE directory"); - return; - // Return an error in the picker that the directory does not contain ipc.txt - } - - - fileBasedIpc.LocateStableStorage(); - sceneManager.SetScreen(typeof(SetupScreen)); - } - catch (Exception e) - { - Logger.Log($"Error during migration: {e.Message}", level: LogLevel.Error); + overlay = new DialogOverlay(); + overlay.Push(new IPCErrorDialog("This is an invalid IPC Directory", "Select a directory that contains an osu! stable cutting edge installation and make sure it has an empty ipc.txt file in it.")); + AddInternal(overlay); + Logger.Log("Folder is not an osu! stable CE directory"); + return; + // Return an error in the picker that the directory does not contain ipc.txt } + + var fileBasedIpc = ipc as FileBasedIPC; + fileBasedIpc?.LocateStableStorage(); + sceneManager?.SetScreen(typeof(SetupScreen)); } private void autoDetect() From 83a5913b8d7a4ddb3adc9610b39e3e5e28041933 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 May 2020 12:11:39 +0900 Subject: [PATCH 098/871] Undo beat snapping related changes --- .../Edit/Blueprints/ManiaPlacementBlueprint.cs | 16 ++-------------- .../Edit/ManiaHitObjectComposer.cs | 17 +++++------------ 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 184356b89c..3fb03d642f 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -24,15 +24,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints protected Column Column; /// - /// The current beat-snapped mouse position, snapped to the closest column. + /// The current mouse position, snapped to the closest column. /// protected Vector2 SnappedMousePosition { get; private set; } - /// - /// The gameplay time at the current beat-snapped mouse position (). - /// - protected double SnappedTime { get; private set; } - /// /// The width of the closest column to the current mouse position. /// @@ -44,9 +39,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IScrollingInfo scrollingInfo { get; set; } - [Resolved(CanBeNull = true)] - private IDistanceSnapProvider snapProvider { get; set; } - protected ManiaPlacementBlueprint(T hitObject) : base(hitObject) { @@ -62,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints return base.OnMouseDown(e); HitObject.Column = Column.Index; - BeginPlacement(SnappedTime, true); + BeginPlacement(TimeAt(e.ScreenSpaceMousePosition), true); return true; } @@ -78,10 +70,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints // Snap to the column var parentPos = Parent.ToLocalSpace(Column.ToScreenSpace(new Vector2(Column.DrawWidth / 2, 0))); SnappedMousePosition = new Vector2(parentPos.X, Parent.ToLocalSpace(screenSpacePosition).Y); - - SnappedTime = TimeAt(screenSpacePosition); - if (snapProvider != null) - (SnappedMousePosition, SnappedTime) = snapProvider.GetSnappedPosition(SnappedMousePosition, SnappedTime); } protected double TimeAt(Vector2 screenSpacePosition) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 475320ece3..7677ac6f07 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -82,18 +82,9 @@ namespace osu.Game.Rulesets.Mania.Edit public override (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) { - var beatSnapped = beatSnapGrid.GetSnappedPosition(position); + var hoc = Playfield.GetColumn(0).HitObjectContainer; - if (beatSnapped != null) - return beatSnapped.Value; - - return base.GetSnappedPosition(position, getTimeFromPosition(ToScreenSpace(position))); - } - - private double getTimeFromPosition(Vector2 screenSpacePosition) - { - var hoc = Playfield.Stages[0].HitObjectContainer; - float targetPosition = hoc.ToLocalSpace(screenSpacePosition).Y; + float targetPosition = hoc.ToLocalSpace(ToScreenSpace(position)).Y; if (drawableRuleset.ScrollingInfo.Direction.Value == ScrollingDirection.Down) { @@ -103,10 +94,12 @@ namespace osu.Game.Rulesets.Mania.Edit targetPosition = hoc.DrawHeight - targetPosition; } - return drawableRuleset.ScrollingInfo.Algorithm.TimeAt(targetPosition, + double targetTime = drawableRuleset.ScrollingInfo.Algorithm.TimeAt(targetPosition, EditorClock.CurrentTime, drawableRuleset.ScrollingInfo.TimeRange.Value, hoc.DrawHeight); + + return base.GetSnappedPosition(position, targetTime); } protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) From 6d29ff092869b2e58ae983c8919bec93b2b6cc9b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 12:13:02 +0900 Subject: [PATCH 099/871] Fix banana showers not using cancellation token --- osu.Game.Rulesets.Catch/Objects/BananaShower.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 96ab66048a..3a0b5ace53 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -18,10 +18,10 @@ namespace osu.Game.Rulesets.Catch.Objects protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { base.CreateNestedHitObjects(cancellationToken); - createBananas(); + createBananas(cancellationToken); } - private void createBananas() + private void createBananas(CancellationToken cancellationToken) { double spacing = Duration; while (spacing > 100) @@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Catch.Objects for (double i = StartTime; i <= EndTime; i += spacing) { + cancellationToken.ThrowIfCancellationRequested(); + AddNested(new Banana { Samples = Samples, From 922b793a5aed03ee2ed1db3ae668eafc90a6eda8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 May 2020 13:04:35 +0900 Subject: [PATCH 100/871] Update hit object composer tests --- .../TestSceneManiaHitObjectComposer.cs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs index 6274bb1005..bad3d7854e 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs @@ -42,6 +42,7 @@ namespace osu.Game.Rulesets.Mania.Tests public void TestDragOffscreenSelectionVerticallyUpScroll() { DrawableHitObject lastObject = null; + double originalTime = 0; Vector2 originalPosition = Vector2.Zero; setScrollStep(ScrollingDirection.Up); @@ -49,6 +50,7 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("seek to last object", () => { lastObject = this.ChildrenOfType().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last()); + originalTime = lastObject.HitObject.StartTime; Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime); }); @@ -64,19 +66,20 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("move mouse downwards", () => { - InputManager.MoveMouseTo(lastObject, new Vector2(0, 20)); + InputManager.MoveMouseTo(lastObject, new Vector2(0, lastObject.ScreenSpaceDrawQuad.Height * 2)); InputManager.ReleaseButton(MouseButton.Left); }); AddAssert("hitobjects not moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 0)); AddAssert("hitobjects moved downwards", () => lastObject.DrawPosition.Y - originalPosition.Y > 0); - AddAssert("hitobjects not moved too far", () => lastObject.DrawPosition.Y - originalPosition.Y < 50); + AddAssert("hitobject has moved time", () => lastObject.HitObject.StartTime == originalTime + 125); } [Test] public void TestDragOffscreenSelectionVerticallyDownScroll() { DrawableHitObject lastObject = null; + double originalTime = 0; Vector2 originalPosition = Vector2.Zero; setScrollStep(ScrollingDirection.Down); @@ -84,6 +87,7 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("seek to last object", () => { lastObject = this.ChildrenOfType().Single(d => d.HitObject == composer.EditorBeatmap.HitObjects.Last()); + originalTime = lastObject.HitObject.StartTime; Clock.Seek(composer.EditorBeatmap.HitObjects.Last().StartTime); }); @@ -99,13 +103,13 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("move mouse upwards", () => { - InputManager.MoveMouseTo(lastObject, new Vector2(0, -20)); + InputManager.MoveMouseTo(lastObject, new Vector2(0, -lastObject.ScreenSpaceDrawQuad.Height * 2)); InputManager.ReleaseButton(MouseButton.Left); }); AddAssert("hitobjects not moved columns", () => composer.EditorBeatmap.HitObjects.All(h => ((ManiaHitObject)h).Column == 0)); AddAssert("hitobjects moved upwards", () => originalPosition.Y - lastObject.DrawPosition.Y > 0); - AddAssert("hitobjects not moved too far", () => originalPosition.Y - lastObject.DrawPosition.Y < 50); + AddAssert("hitobject has moved time", () => lastObject.HitObject.StartTime == originalTime + 125); } [Test] @@ -207,7 +211,7 @@ namespace osu.Game.Rulesets.Mania.Tests }; for (int i = 0; i < 10; i++) - EditorBeatmap.Add(new Note { StartTime = 100 * i }); + EditorBeatmap.Add(new Note { StartTime = 125 * i }); } } } From 5ad7842b917e862d3d5ce722ff0b0169a7660d53 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 May 2020 13:33:02 +0900 Subject: [PATCH 101/871] Move ScreenSpacePositionAtTime to inside Column implementation --- .../ManiaPlacementBlueprintTestScene.cs | 2 -- .../ManiaSelectionBlueprintTestScene.cs | 2 -- .../Blueprints/HoldNotePlacementBlueprint.cs | 5 ++--- .../Edit/IManiaHitObjectComposer.cs | 2 -- .../Edit/ManiaHitObjectComposer.cs | 18 +----------------- osu.Game.Rulesets.Mania/UI/Column.cs | 13 +++++++++++++ .../UI/Scrolling/ScrollingPlayfield.cs | 4 ++-- 7 files changed, 18 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs index be3e205f36..aac77c9c1c 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs @@ -49,8 +49,6 @@ namespace osu.Game.Rulesets.Mania.Tests public Column ColumnAt(Vector2 screenSpacePosition) => column; - public Vector2 ScreenSpacePositionAtTime(double time, Column column = null) => Vector2.Zero; - public int TotalColumns => 1; } } diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs index 3d654466ed..b598893e8c 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs @@ -33,8 +33,6 @@ namespace osu.Game.Rulesets.Mania.Tests public Column ColumnAt(Vector2 screenSpacePosition) => column; - public Vector2 ScreenSpacePositionAtTime(double time, Column column = null) => Vector2.Zero; - public int TotalColumns => 1; } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index 31bf76edd0..8689d479b4 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -8,7 +8,6 @@ using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.UI.Scrolling; using osuTK; using osuTK.Input; @@ -42,8 +41,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints if (Column != null) { - headPiece.Y = Parent.ToLocalSpace(composer.ScreenSpacePositionAtTime(HitObject.StartTime, Column)).Y; - tailPiece.Y = Parent.ToLocalSpace(composer.ScreenSpacePositionAtTime(HitObject.EndTime, Column)).Y; + headPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.StartTime, Column)).Y; + tailPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.EndTime, Column)).Y; } var topPosition = new Vector2(headPiece.DrawPosition.X, Math.Min(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y)); diff --git a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs index f1915cd85a..f64bab1fae 100644 --- a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs @@ -10,8 +10,6 @@ namespace osu.Game.Rulesets.Mania.Edit { Column ColumnAt(Vector2 screenSpacePosition); - Vector2 ScreenSpacePositionAtTime(double time, Column column = null); - int TotalColumns { get; } } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 0cae26b51c..5eafaefe37 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -72,27 +72,11 @@ namespace osu.Game.Rulesets.Mania.Edit targetTime = BeatSnapProvider.SnapTime(targetTime); // convert back to screen space - screenSpacePosition = ScreenSpacePositionAtTime(targetTime, column); + screenSpacePosition = column.ScreenSpacePositionAtTime(targetTime, column); return new ManiaSnapResult(screenSpacePosition, targetTime, column); } - public Vector2 ScreenSpacePositionAtTime(double time, Column column = null) - { - var hoc = (column ?? Playfield.GetColumn(0)).HitObjectContainer; - var scrollInfo = drawableRuleset.ScrollingInfo; - - var pos = scrollInfo.Algorithm.PositionAt(time, EditorClock.CurrentTime, scrollInfo.TimeRange.Value, hoc.DrawHeight); - - if (scrollInfo.Direction.Value == ScrollingDirection.Down) - { - // as explained above - pos = hoc.DrawHeight - pos; - } - - return hoc.ToScreenSpace(new Vector2(hoc.DrawWidth / 2, pos)); - } - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) { drawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods); diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 506a07f26b..3f85f449ce 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -140,5 +140,18 @@ namespace osu.Game.Rulesets.Mania.UI public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border => DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); + + public Vector2 ScreenSpacePositionAtTime(double time, Column column = null) + { + var pos = ScrollingInfo.Algorithm.PositionAt(time, Time.Current, ScrollingInfo.TimeRange.Value, HitObjectContainer.DrawHeight); + + if (ScrollingInfo.Direction.Value == ScrollingDirection.Down) + { + // as explained above + pos = HitObjectContainer.DrawHeight - pos; + } + + return HitObjectContainer.ToScreenSpace(new Vector2(HitObjectContainer.DrawWidth / 2, pos)); + } } } diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs index bf2203e176..fd143a3687 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -15,12 +15,12 @@ namespace osu.Game.Rulesets.UI.Scrolling protected readonly IBindable Direction = new Bindable(); [Resolved] - private IScrollingInfo scrollingInfo { get; set; } + protected IScrollingInfo ScrollingInfo { get; private set; } [BackgroundDependencyLoader] private void load() { - Direction.BindTo(scrollingInfo.Direction); + Direction.BindTo(ScrollingInfo.Direction); } protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer(); From bac78707de161819947b5b26ec4d7ea830ee1699 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 May 2020 14:25:37 +0900 Subject: [PATCH 102/871] Move more logic to column to both clean things up and fix tests --- .../ManiaPlacementBlueprintTestScene.cs | 9 +++++++++ .../Edit/ManiaHitObjectComposer.cs | 17 +---------------- osu.Game.Rulesets.Mania/UI/Column.cs | 16 ++++++++++++++++ .../Tests/Visual/PlacementBlueprintTestScene.cs | 7 +++++-- 4 files changed, 31 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs index aac77c9c1c..547786847b 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; @@ -43,6 +44,14 @@ namespace osu.Game.Rulesets.Mania.Tests }); } + protected override SnapResult SnapForBlueprint(PlacementBlueprint blueprint) + { + var time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position); + var pos = column.ScreenSpacePositionAtTime(time); + + return new ManiaSnapResult(pos, time, column); + } + protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both }; protected override void AddHitObject(DrawableHitObject hitObject) => column.Add((DrawableManiaHitObject)hitObject); diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 5eafaefe37..9085033140 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -51,22 +51,7 @@ namespace osu.Game.Rulesets.Mania.Edit if (column == null) return new SnapResult(screenSpacePosition, null); - var hoc = column.HitObjectContainer; - - // convert to local space of column so we can snap and fetch correct location. - Vector2 localPosition = hoc.ToLocalSpace(screenSpacePosition); - - var scrollInfo = drawableRuleset.ScrollingInfo; - - if (scrollInfo.Direction.Value == ScrollingDirection.Down) - { - // We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time. - // The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position, - // so when scrolling downwards the coordinates need to be flipped. - localPosition.Y = hoc.DrawHeight - localPosition.Y; - } - - double targetTime = scrollInfo.Algorithm.TimeAt(localPosition.Y, EditorClock.CurrentTime, scrollInfo.TimeRange.Value, hoc.DrawHeight); + double targetTime = column.TimeAtScreenSpacePosition(screenSpacePosition); // apply beat snapping targetTime = BeatSnapProvider.SnapTime(targetTime); diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 3f85f449ce..c582eb1c75 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -153,5 +153,21 @@ namespace osu.Game.Rulesets.Mania.UI return HitObjectContainer.ToScreenSpace(new Vector2(HitObjectContainer.DrawWidth / 2, pos)); } + + public double TimeAtScreenSpacePosition(Vector2 screenSpacePosition) + { + // convert to local space of column so we can snap and fetch correct location. + Vector2 localPosition = HitObjectContainer.ToLocalSpace(screenSpacePosition); + + if (ScrollingInfo.Direction.Value == ScrollingDirection.Down) + { + // We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time. + // The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position, + // so when scrolling downwards the coordinates need to be flipped. + localPosition.Y = HitObjectContainer.DrawHeight - localPosition.Y; + } + + return ScrollingInfo.Algorithm.TimeAt(localPosition.Y, Time.Current, ScrollingInfo.TimeRange.Value, HitObjectContainer.DrawHeight); + } } } diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index a4e629b6f5..feecea473c 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -71,9 +71,12 @@ namespace osu.Game.Tests.Visual { base.Update(); - currentBlueprint.UpdatePosition(new SnapResult(InputManager.CurrentState.Mouse.Position, null)); + currentBlueprint.UpdatePosition(SnapForBlueprint(currentBlueprint)); } + protected virtual SnapResult SnapForBlueprint(PlacementBlueprint blueprint) => + new SnapResult(InputManager.CurrentState.Mouse.Position, null); + public override void Add(Drawable drawable) { base.Add(drawable); @@ -81,7 +84,7 @@ namespace osu.Game.Tests.Visual if (drawable is PlacementBlueprint blueprint) { blueprint.Show(); - blueprint.UpdatePosition(new SnapResult(InputManager.CurrentState.Mouse.Position, null)); + blueprint.UpdatePosition(SnapForBlueprint(blueprint)); } } From a9a1c00cf1d2b54a757bc6a69a70cee31a39ce04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 May 2020 14:38:40 +0900 Subject: [PATCH 103/871] Move responsibility placement blueprint's StartTime set to within --- .../Edit/Blueprints/ManiaPlacementBlueprint.cs | 2 ++ .../HitCircles/HitCirclePlacementBlueprint.cs | 6 +++++- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 ++ .../Blueprints/Spinners/SpinnerPlacementBlueprint.cs | 4 ---- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 -- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 10 +++++++++- 6 files changed, 18 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index 8d3b3ea583..d173da9d9a 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -50,6 +50,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints public override void UpdatePosition(SnapResult result) { + base.UpdatePosition(result); + if (!PlacementActive) Column = (result as ManiaSnapResult)?.Column; } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs index e12dec2668..3dbbdcc5d0 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/HitCircles/HitCirclePlacementBlueprint.cs @@ -39,6 +39,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles return base.OnMouseDown(e); } - public override void UpdatePosition(SnapResult result) => HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); + public override void UpdatePosition(SnapResult result) + { + base.UpdatePosition(result); + HitObject.Position = ToLocalSpace(result.ScreenSpacePosition); + } } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 59ec92c79e..4b99cc23ed 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -69,6 +69,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders public override void UpdatePosition(SnapResult result) { + base.UpdatePosition(result); + switch (state) { case PlacementState.Initial: diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs index 546f0e5981..cc4ed0eccf 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Spinners/SpinnerPlacementBlueprint.cs @@ -59,9 +59,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners return true; } - - public override void UpdatePosition(SnapResult result) - { - } } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 1e328e6b6b..6edd01cd15 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -244,8 +244,6 @@ namespace osu.Game.Rulesets.Edit public void BeginPlacement(HitObject hitObject) { EditorBeatmap.PlacementObject.Value = hitObject; - - hitObject.StartTime = SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position).Time ?? EditorClock.CurrentTime; } public void EndPlacement(HitObject hitObject, bool commit) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index bab9bf71ef..2fd8c4b9d9 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -10,6 +10,7 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose; using osuTK; @@ -83,11 +84,18 @@ namespace osu.Game.Rulesets.Edit PlacementActive = false; } + [Resolved(canBeNull: true)] + private IFrameBasedClock editorClock { get; set; } + /// /// Updates the position of this to a new screen-space position. /// /// The snap result information. - public abstract void UpdatePosition(SnapResult snapResult); + public virtual void UpdatePosition(SnapResult snapResult) + { + if (!PlacementActive) + HitObject.StartTime = snapResult.Time ?? editorClock?.CurrentTime ?? Time.Current; + } /// /// Invokes , From 776b842fdbbabff264fd6120833be30bacd57053 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 May 2020 14:53:36 +0900 Subject: [PATCH 104/871] Remove unused using --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 2fd8c4b9d9..f0b63f8ea5 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -10,7 +10,6 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects; -using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose; using osuTK; From ce8b6b7383d2c32d7518b9a1091c1af1825bd334 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 May 2020 15:15:24 +0900 Subject: [PATCH 105/871] Correctly account for blueprint origins --- osu.Game.Rulesets.Mania/UI/Column.cs | 55 ++++++++++++++++++++++++---- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index c582eb1c75..0fdefe6dc9 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; using osu.Game.Rulesets.Mania.Beatmaps; +using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Mania.UI { @@ -145,10 +146,21 @@ namespace osu.Game.Rulesets.Mania.UI { var pos = ScrollingInfo.Algorithm.PositionAt(time, Time.Current, ScrollingInfo.TimeRange.Value, HitObjectContainer.DrawHeight); - if (ScrollingInfo.Direction.Value == ScrollingDirection.Down) + switch (ScrollingInfo.Direction.Value) { - // as explained above - pos = HitObjectContainer.DrawHeight - pos; + case ScrollingDirection.Down: + // We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time. + // The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position, + // so when scrolling downwards the coordinates need to be flipped. + pos = HitObjectContainer.DrawHeight - pos; + + // Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction. + pos -= DefaultNotePiece.NOTE_HEIGHT / 2; + break; + + case ScrollingDirection.Up: + pos += DefaultNotePiece.NOTE_HEIGHT / 2; + break; } return HitObjectContainer.ToScreenSpace(new Vector2(HitObjectContainer.DrawWidth / 2, pos)); @@ -159,15 +171,42 @@ namespace osu.Game.Rulesets.Mania.UI // convert to local space of column so we can snap and fetch correct location. Vector2 localPosition = HitObjectContainer.ToLocalSpace(screenSpacePosition); - if (ScrollingInfo.Direction.Value == ScrollingDirection.Down) + switch (ScrollingInfo.Direction.Value) { - // We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time. - // The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position, - // so when scrolling downwards the coordinates need to be flipped. - localPosition.Y = HitObjectContainer.DrawHeight - localPosition.Y; + case ScrollingDirection.Down: + // as above + localPosition.Y = HitObjectContainer.DrawHeight - localPosition.Y; + break; } + // offset for the fact that blueprints are centered, as above. + localPosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2; + return ScrollingInfo.Algorithm.TimeAt(localPosition.Y, Time.Current, ScrollingInfo.TimeRange.Value, HitObjectContainer.DrawHeight); } + + /// + /// Converts a mouse position to a hitobject position. + /// + /// + /// Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction. + /// + /// The mouse position. + /// The resulting hitobject position, acnhored at the top or bottom of the blueprint depending on the scroll direction. + private Vector2 mouseToHitObjectPosition(Vector2 mousePosition) + { + switch (ScrollingInfo.Direction.Value) + { + case ScrollingDirection.Up: + mousePosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2; + break; + + case ScrollingDirection.Down: + mousePosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2; + break; + } + + return mousePosition; + } } } From a756e6d21241080204da38772a8e34ff9d9946a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 May 2020 15:16:30 +0900 Subject: [PATCH 106/871] Add xmldoc and remove unnecessary parameter --- .../Edit/Blueprints/HoldNotePlacementBlueprint.cs | 4 ++-- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 2 +- osu.Game.Rulesets.Mania/UI/Column.cs | 8 +++++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index 8689d479b4..b757c17a48 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -41,8 +41,8 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints if (Column != null) { - headPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.StartTime, Column)).Y; - tailPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.EndTime, Column)).Y; + headPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.StartTime)).Y; + tailPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.EndTime)).Y; } var topPosition = new Vector2(headPiece.DrawPosition.X, Math.Min(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y)); diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 9085033140..cfb04c8e50 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Mania.Edit targetTime = BeatSnapProvider.SnapTime(targetTime); // convert back to screen space - screenSpacePosition = column.ScreenSpacePositionAtTime(targetTime, column); + screenSpacePosition = column.ScreenSpacePositionAtTime(targetTime); return new ManiaSnapResult(screenSpacePosition, targetTime, column); } diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index 0fdefe6dc9..f7339fdacd 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -142,7 +142,10 @@ namespace osu.Game.Rulesets.Mania.UI // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border => DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); - public Vector2 ScreenSpacePositionAtTime(double time, Column column = null) + /// + /// Given a time, return the screen space position within this column. + /// + public Vector2 ScreenSpacePositionAtTime(double time) { var pos = ScrollingInfo.Algorithm.PositionAt(time, Time.Current, ScrollingInfo.TimeRange.Value, HitObjectContainer.DrawHeight); @@ -166,6 +169,9 @@ namespace osu.Game.Rulesets.Mania.UI return HitObjectContainer.ToScreenSpace(new Vector2(HitObjectContainer.DrawWidth / 2, pos)); } + /// + /// Given a position in screen space, return the time within this column. + /// public double TimeAtScreenSpacePosition(Vector2 screenSpacePosition) { // convert to local space of column so we can snap and fetch correct location. From 7dd3b3eeb56473e200d07af163204022126b5e11 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 May 2020 15:16:59 +0900 Subject: [PATCH 107/871] Remove unused method --- osu.Game.Rulesets.Mania/UI/Column.cs | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index f7339fdacd..2d88670d77 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -190,29 +190,5 @@ namespace osu.Game.Rulesets.Mania.UI return ScrollingInfo.Algorithm.TimeAt(localPosition.Y, Time.Current, ScrollingInfo.TimeRange.Value, HitObjectContainer.DrawHeight); } - - /// - /// Converts a mouse position to a hitobject position. - /// - /// - /// Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction. - /// - /// The mouse position. - /// The resulting hitobject position, acnhored at the top or bottom of the blueprint depending on the scroll direction. - private Vector2 mouseToHitObjectPosition(Vector2 mousePosition) - { - switch (ScrollingInfo.Direction.Value) - { - case ScrollingDirection.Up: - mousePosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2; - break; - - case ScrollingDirection.Down: - mousePosition.Y += DefaultNotePiece.NOTE_HEIGHT / 2; - break; - } - - return mousePosition; - } } } From 0db1ea6a9d460c208e5cf513625b34968f686aa2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 May 2020 15:47:12 +0900 Subject: [PATCH 108/871] Fix failing tests --- .../TestSceneManiaHitObjectComposer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs index bad3d7854e..1a3fa29d4a 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectComposer.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("move mouse downwards", () => { - InputManager.MoveMouseTo(lastObject, new Vector2(0, lastObject.ScreenSpaceDrawQuad.Height * 2)); + InputManager.MoveMouseTo(lastObject, new Vector2(0, lastObject.ScreenSpaceDrawQuad.Height * 4)); InputManager.ReleaseButton(MouseButton.Left); }); @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Mania.Tests AddStep("move mouse upwards", () => { - InputManager.MoveMouseTo(lastObject, new Vector2(0, -lastObject.ScreenSpaceDrawQuad.Height * 2)); + InputManager.MoveMouseTo(lastObject, new Vector2(0, -lastObject.ScreenSpaceDrawQuad.Height * 4)); InputManager.ReleaseButton(MouseButton.Left); }); From 8a47e2431bbe7c3207e75004d61fd5fcbce103f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 21 May 2020 17:13:22 +0900 Subject: [PATCH 109/871] Move distance snap grid implementation to OsuHitObjectComposer --- .../Edit/OsuHitObjectComposer.cs | 64 +++++++++++++++- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 73 +++---------------- .../Compose/Components/BlueprintContainer.cs | 5 -- 3 files changed, 74 insertions(+), 68 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index cdf78a5902..9ba3e30445 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -4,6 +4,10 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Caching; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; @@ -12,6 +16,7 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; +using osuTK; namespace osu.Game.Rulesets.Osu.Edit { @@ -32,9 +37,66 @@ namespace osu.Game.Rulesets.Osu.Edit new SpinnerCompositionTool() }; + [BackgroundDependencyLoader] + private void load() + { + EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid(); + EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid(); + + LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both }); + } + protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(HitObjects); - protected override DistanceSnapGrid CreateDistanceSnapGrid(IEnumerable selectedHitObjects) + private DistanceSnapGrid distanceSnapGrid; + private Container distanceSnapGridContainer; + + private readonly Cached distanceSnapGridCache = new Cached(); + private double? lastDistanceSnapGridTime; + + protected override void Update() + { + base.Update(); + + if (!(BlueprintContainer.CurrentTool is SelectTool)) + { + if (EditorClock.CurrentTime != lastDistanceSnapGridTime) + { + distanceSnapGridCache.Invalidate(); + lastDistanceSnapGridTime = EditorClock.CurrentTime; + } + + if (!distanceSnapGridCache.IsValid) + updateDistanceSnapGrid(); + } + } + + public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) + { + if (distanceSnapGrid == null) + return base.SnapScreenSpacePositionToValidTime(screenSpacePosition); + + (Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition)); + + return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time); + } + + private void updateDistanceSnapGrid() + { + distanceSnapGridContainer.Clear(); + distanceSnapGridCache.Invalidate(); + + if (BlueprintContainer.CurrentTool is SelectTool && !EditorBeatmap.SelectedHitObjects.Any()) + return; + + if ((distanceSnapGrid = createDistanceSnapGrid(EditorBeatmap.SelectedHitObjects)) != null) + { + distanceSnapGridContainer.Add(distanceSnapGrid); + distanceSnapGridCache.Validate(); + } + } + + private DistanceSnapGrid createDistanceSnapGrid(IEnumerable selectedHitObjects) { if (BlueprintContainer.CurrentTool is SpinnerCompositionTool) return null; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index b437d81054..fd8af0afd5 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; -using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -52,8 +52,9 @@ namespace osu.Game.Rulesets.Edit protected ComposeBlueprintContainer BlueprintContainer { get; private set; } private DrawableEditRulesetWrapper drawableRulesetWrapper; - private Container distanceSnapGridContainer; - private DistanceSnapGrid distanceSnapGrid; + + protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both }; + private readonly List layerContainers = new List(); private InputManager inputManager; @@ -87,7 +88,7 @@ namespace osu.Game.Rulesets.Edit var layerBelowRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChildren(new Drawable[] { - distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both }, + LayerBelowRuleset, new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both } }); @@ -139,7 +140,7 @@ namespace osu.Game.Rulesets.Edit setSelectTool(); - BlueprintContainer.SelectionChanged += selectionChanged; + EditorBeatmap.SelectedHitObjects.CollectionChanged += selectionChanged; } protected override bool OnKeyDown(KeyDownEvent e) @@ -165,16 +166,6 @@ namespace osu.Game.Rulesets.Edit inputManager = GetContainingInputManager(); } - private double lastGridUpdateTime; - - protected override void Update() - { - base.Update(); - - if (EditorClock.CurrentTime != lastGridUpdateTime && !(BlueprintContainer.CurrentTool is SelectTool)) - showGridFor(Enumerable.Empty()); - } - protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -188,19 +179,13 @@ namespace osu.Game.Rulesets.Edit }); } - private void selectionChanged(IEnumerable selectedHitObjects) + private void selectionChanged(object sender, NotifyCollectionChangedEventArgs changedArgs) { - var hitObjects = selectedHitObjects.ToArray(); - - if (hitObjects.Any()) + if (EditorBeatmap.SelectedHitObjects.Any()) { // ensure in selection mode if a selection is made. setSelectTool(); - - showGridFor(hitObjects); } - else - distanceSnapGridContainer.Hide(); } private void setSelectTool() => toolboxCollection.Items.First().Select(); @@ -209,30 +194,12 @@ namespace osu.Game.Rulesets.Edit { BlueprintContainer.CurrentTool = tool; - if (tool is SelectTool) - distanceSnapGridContainer.Hide(); - else - { + if (!(tool is SelectTool)) EditorBeatmap.SelectedHitObjects.Clear(); - showGridFor(Enumerable.Empty()); - } - } - - private void showGridFor(IEnumerable selectedHitObjects) - { - distanceSnapGridContainer.Clear(); - distanceSnapGrid = CreateDistanceSnapGrid(selectedHitObjects); - - if (distanceSnapGrid != null) - { - distanceSnapGridContainer.Child = distanceSnapGrid; - distanceSnapGridContainer.Show(); - } - - lastGridUpdateTime = EditorClock.CurrentTime; } public override IEnumerable HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects; + public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); protected abstract IReadOnlyList CompositionTools { get; } @@ -257,21 +224,11 @@ namespace osu.Game.Rulesets.Edit if (adjustableClock.CurrentTime < hitObject.StartTime) adjustableClock.Seek(hitObject.StartTime); } - - showGridFor(Enumerable.Empty()); } public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject); - public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) - { - if (distanceSnapGrid == null) return new SnapResult(screenSpacePosition, null); - - // TODO: move distance snap grid to OsuHitObjectComposer. - (Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition)); - - return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time); - } + public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, null); public override float GetBeatSnapDistanceAt(double referenceTime) { @@ -321,14 +278,6 @@ namespace osu.Game.Rulesets.Edit /// public abstract bool CursorInPlacementArea { get; } - /// - /// Creates the applicable for a selection. - /// - /// The selection. - /// The for . If empty, a grid is returned for the current point in time. - [CanBeNull] - protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable selectedHitObjects) => null; - public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); public abstract float GetBeatSnapDistanceAt(double referenceTime); diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index e38df3d812..1e8a35c047 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.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.Collections.Specialized; using System.Diagnostics; using System.Linq; @@ -29,8 +28,6 @@ namespace osu.Game.Screens.Edit.Compose.Components /// public abstract class BlueprintContainer : CompositeDrawable, IKeyBindingHandler { - public event Action> SelectionChanged; - protected DragBox DragBox { get; private set; } protected Container SelectionBlueprints { get; private set; } @@ -88,8 +85,6 @@ namespace osu.Game.Screens.Edit.Compose.Components SelectionBlueprints.FirstOrDefault(b => b.HitObject == o)?.Deselect(); break; } - - SelectionChanged?.Invoke(selectedHitObjects); }; } From 700b5e0c73c75566a9b73267977111c819e6737c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 17:47:14 +0900 Subject: [PATCH 110/871] Adjust design --- .../ContractedPanelMiddleContent.cs | 30 ++++++------------- osu.Game/Screens/Ranking/ScorePanel.cs | 6 ++-- 2 files changed, 12 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index 1d7d5c4130..a263a03a77 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -65,28 +65,16 @@ namespace osu.Game.Screens.Ranking.Contracted }, Children = new Drawable[] { - // Buffered container is used to prevent 1px bleed outside the masking region - new BufferedContainer + new Box { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("444") - }, - new UserCoverBackground - { - RelativeSizeAxes = Axes.Both, - User = score.User, - }, - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.5f), Color4Extensions.FromHex("#444")) - }, - } + Colour = Color4Extensions.FromHex("444") + }, + new UserCoverBackground + { + RelativeSizeAxes = Axes.Both, + User = score.User, + Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.5f), Color4Extensions.FromHex("#444").Opacity(0)) }, new FillFlowContainer { @@ -100,7 +88,7 @@ namespace osu.Game.Screens.Ranking.Contracted { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Size = new Vector2(140), + Size = new Vector2(110), Masking = true, CornerExponent = 2.5f, CornerRadius = 20, diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index baca2fd9e1..305d4ee921 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -23,12 +23,12 @@ namespace osu.Game.Screens.Ranking /// /// Width of the panel when contracted. /// - public const float CONTRACTED_WIDTH = 160; + public const float CONTRACTED_WIDTH = 130; /// /// Height of the panel when contracted. /// - private const float contracted_height = 385; + private const float contracted_height = 355; /// /// Width of the panel when expanded. @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Ranking /// /// Height of the top layer when the panel is contracted. /// - private const float contracted_top_layer_height = 40; + private const float contracted_top_layer_height = 30; /// /// Duration for the panel to resize into its expanded/contracted size. From b6a1d1a2fc5d9a1927b4994861b23c6f46f09fd4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 18:07:31 +0900 Subject: [PATCH 111/871] Improve transforms between state changes --- osu.Game/Screens/Ranking/ScorePanel.cs | 8 ++++---- osu.Game/Screens/Ranking/ScorePanelList.cs | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 305d4ee921..2f6146a5e7 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -175,9 +175,6 @@ namespace osu.Game.Screens.Ranking private void updateState() { - topLayerContainer.MoveToY(0, resize_duration, Easing.OutQuint); - middleLayerContainer.MoveToY(0, resize_duration, Easing.OutQuint); - topLayerContent?.FadeOut(content_fade_duration).Expire(); middleLayerContent?.FadeOut(content_fade_duration).Expire(); @@ -203,7 +200,10 @@ namespace osu.Game.Screens.Ranking break; } - using (BeginDelayedSequence(resize_duration + top_layer_expand_delay, true)) + bool topLayerExpanded = topLayerContainer.Y < 0; + + // If the top layer was already expanded, then we don't need to wait for the resize and can instead transform immediately. This looks better when changing the panel state. + using (BeginDelayedSequence(topLayerExpanded ? 0 : resize_duration + top_layer_expand_delay, true)) { switch (state) { diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index cc6842b2dd..894be7e775 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -68,6 +68,7 @@ namespace osu.Game.Screens.Ranking if (expandedPanel != null) expandedPanel.State = PanelState.Contracted; + expandedPanel = panel; } From 9f868be872f0d55d9209536489008606f5dc171d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 18:39:22 +0900 Subject: [PATCH 112/871] Create common TestScoreInfo type --- .../TestSceneContractedPanelMiddleContent.cs | 32 +-------- .../TestSceneExpandedPanelMiddleContent.cs | 31 +-------- .../TestSceneExpandedPanelTopContent.cs | 4 +- .../Visual/Ranking/TestSceneResultsScreen.cs | 26 +------- .../Visual/Ranking/TestSceneScorePanel.cs | 65 +++---------------- .../Visual/Ranking/TestSceneScorePanelList.cs | 59 ++++------------- osu.Game/Tests/TestScoreInfo.cs | 50 ++++++++++++++ 7 files changed, 80 insertions(+), 187 deletions(-) create mode 100644 osu.Game/Tests/TestScoreInfo.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs index f7694c10ec..e1e00e3c2b 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -14,10 +13,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Contracted; @@ -37,7 +33,7 @@ namespace osu.Game.Tests.Visual.Ranking { var author = new User { Username = "mapper_name" }; - AddStep("show example score", () => showPanel(createTestBeatmap(author), createTestScore())); + AddStep("show example score", () => showPanel(createTestBeatmap(author), new TestScoreInfo(new OsuRuleset().RulesetInfo))); AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Text == "mapper_name")); } @@ -45,7 +41,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestMapWithUnknownMapper() { - AddStep("show example score", () => showPanel(createTestBeatmap(null), createTestScore())); + AddStep("show example score", () => showPanel(createTestBeatmap(null), new TestScoreInfo(new OsuRuleset().RulesetInfo))); AddAssert("mapped by text not present", () => this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text, "mapped", "by"))); @@ -66,30 +62,6 @@ namespace osu.Game.Tests.Visual.Ranking return new TestWorkingBeatmap(beatmap); } - private ScoreInfo createTestScore() => new ScoreInfo - { - User = new User - { - Id = 2, - Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", - }, - Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, - Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, - TotalScore = 999999, - Accuracy = 0.95, - MaxCombo = 999, - Rank = ScoreRank.S, - Date = DateTimeOffset.Now, - Statistics = - { - { HitResult.Miss, 1 }, - { HitResult.Meh, 50 }, - { HitResult.Good, 100 }, - { HitResult.Great, 300 }, - } - }; - private bool containsAny(string text, params string[] stringsToMatch) => stringsToMatch.Any(text.Contains); private class ContractedPanelMiddleContentContainer : Container diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index 106b4187ee..69511b85c0 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -14,10 +13,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Expanded; @@ -37,7 +33,7 @@ namespace osu.Game.Tests.Visual.Ranking { var author = new User { Username = "mapper_name" }; - AddStep("show example score", () => showPanel(createTestBeatmap(author), createTestScore())); + AddStep("show example score", () => showPanel(createTestBeatmap(author), new TestScoreInfo(new OsuRuleset().RulesetInfo))); AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Text == "mapper_name")); } @@ -45,7 +41,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestMapWithUnknownMapper() { - AddStep("show example score", () => showPanel(createTestBeatmap(null), createTestScore())); + AddStep("show example score", () => showPanel(createTestBeatmap(null), new TestScoreInfo(new OsuRuleset().RulesetInfo))); AddAssert("mapped by text not present", () => this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text, "mapped", "by"))); @@ -66,29 +62,6 @@ namespace osu.Game.Tests.Visual.Ranking return new TestWorkingBeatmap(beatmap); } - private ScoreInfo createTestScore() => new ScoreInfo - { - User = new User - { - Id = 2, - Username = "peppy", - }, - Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, - Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, - TotalScore = 999999, - Accuracy = 0.95, - MaxCombo = 999, - Rank = ScoreRank.S, - Date = DateTimeOffset.Now, - Statistics = - { - { HitResult.Miss, 1 }, - { HitResult.Meh, 50 }, - { HitResult.Good, 100 }, - { HitResult.Great, 300 }, - } - }; - private bool containsAny(string text, params string[] stringsToMatch) => stringsToMatch.Any(text.Contains); private class ExpandedPanelMiddleContentContainer : Container diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs index afaa607099..a32bcbe7f0 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelTopContent.cs @@ -5,8 +5,8 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Ranking.Expanded; -using osu.Game.Users; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#444"), }, - new ExpandedPanelTopContent(new User { Id = 2, Username = "peppy" }), + new ExpandedPanelTopContent(new TestScoreInfo(new OsuRuleset().RulesetInfo).User), } }; } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index aa0ce89d93..242766ad4b 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.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; -using System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -11,13 +9,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; -using osu.Game.Tests.Beatmaps; -using osu.Game.Users; namespace osu.Game.Tests.Visual.Ranking { @@ -41,26 +36,7 @@ namespace osu.Game.Tests.Visual.Ranking Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); } - private TestSoloResults createResultsScreen() => new TestSoloResults(new ScoreInfo - { - TotalScore = 2845370, - Accuracy = 0.98, - MaxCombo = 123, - Rank = ScoreRank.A, - Date = DateTimeOffset.Now, - Statistics = new Dictionary - { - { HitResult.Great, 50 }, - { HitResult.Good, 20 }, - { HitResult.Meh, 50 }, - { HitResult.Miss, 1 } - }, - Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, - User = new User - { - Username = "peppy", - } - }); + private TestSoloResults createResultsScreen() => new TestSoloResults(new TestScoreInfo(new OsuRuleset().RulesetInfo)); [Test] public void ResultsWithoutPlayer() diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index 0dbafb18bc..fdb77c14a3 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -1,17 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using NUnit.Framework; using osu.Framework.Graphics; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking; -using osu.Game.Tests.Beatmaps; -using osu.Game.Users; namespace osu.Game.Tests.Visual.Ranking { @@ -20,9 +15,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestDRank() { - var score = createScore(); - score.Accuracy = 0.5; - score.Rank = ScoreRank.D; + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.5, Rank = ScoreRank.D }; addPanelStep(score); } @@ -30,9 +23,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestCRank() { - var score = createScore(); - score.Accuracy = 0.75; - score.Rank = ScoreRank.C; + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.75, Rank = ScoreRank.C }; addPanelStep(score); } @@ -40,9 +31,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestBRank() { - var score = createScore(); - score.Accuracy = 0.85; - score.Rank = ScoreRank.B; + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.85, Rank = ScoreRank.B }; addPanelStep(score); } @@ -50,9 +39,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestARank() { - var score = createScore(); - score.Accuracy = 0.925; - score.Rank = ScoreRank.A; + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; addPanelStep(score); } @@ -60,9 +47,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestSRank() { - var score = createScore(); - score.Accuracy = 0.975; - score.Rank = ScoreRank.S; + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.975, Rank = ScoreRank.S }; addPanelStep(score); } @@ -70,9 +55,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAlmostSSRank() { - var score = createScore(); - score.Accuracy = 0.9999; - score.Rank = ScoreRank.S; + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.9999, Rank = ScoreRank.S }; addPanelStep(score); } @@ -80,9 +63,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestSSRank() { - var score = createScore(); - score.Accuracy = 1; - score.Rank = ScoreRank.X; + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 1, Rank = ScoreRank.X }; addPanelStep(score); } @@ -90,9 +71,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestAllHitResults() { - var score = createScore(); - score.Statistics[HitResult.Perfect] = 350; - score.Statistics[HitResult.Ok] = 200; + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Statistics = { [HitResult.Perfect] = 350, [HitResult.Ok] = 200 } }; addPanelStep(score); } @@ -100,9 +79,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestContractedPanel() { - var score = createScore(); - score.Accuracy = 0.925; - score.Rank = ScoreRank.A; + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; addPanelStep(score, PanelState.Contracted); } @@ -116,29 +93,5 @@ namespace osu.Game.Tests.Visual.Ranking State = state }; }); - - private ScoreInfo createScore() => new ScoreInfo - { - User = new User - { - Id = 2, - Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", - }, - Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, - Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, - TotalScore = 2845370, - Accuracy = 0.95, - MaxCombo = 999, - Rank = ScoreRank.S, - Date = DateTimeOffset.Now, - Statistics = - { - { HitResult.Miss, 1 }, - { HitResult.Meh, 50 }, - { HitResult.Good, 100 }, - { HitResult.Great, 300 }, - } - }; } } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index 4964af8784..81a9b22992 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -1,16 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Graphics; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; using osu.Game.Screens.Ranking; -using osu.Game.Tests.Beatmaps; -using osu.Game.Users; namespace osu.Game.Tests.Visual.Ranking { @@ -26,44 +19,20 @@ namespace osu.Game.Tests.Visual.Ranking Add(list); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); - list.AddScore(createScore()); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); } - - private ScoreInfo createScore() => new ScoreInfo - { - User = new User - { - Id = 2, - Username = "peppy", - CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", - }, - Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, - Mods = new Mod[] { new OsuModHardRock(), new OsuModDoubleTime() }, - TotalScore = 2845370, - Accuracy = 0.95, - MaxCombo = 999, - Rank = ScoreRank.S, - Date = DateTimeOffset.Now, - Statistics = - { - { HitResult.Miss, 1 }, - { HitResult.Meh, 50 }, - { HitResult.Good, 100 }, - { HitResult.Great, 300 }, - } - }; } } diff --git a/osu.Game/Tests/TestScoreInfo.cs b/osu.Game/Tests/TestScoreInfo.cs new file mode 100644 index 0000000000..155129e181 --- /dev/null +++ b/osu.Game/Tests/TestScoreInfo.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Tests.Beatmaps; +using osu.Game.Users; + +namespace osu.Game.Tests +{ + public class TestScoreInfo : ScoreInfo + { + public TestScoreInfo(RulesetInfo ruleset) + { + User = new User + { + Id = 2, + Username = "peppy", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }; + + Beatmap = new TestBeatmap(ruleset).BeatmapInfo; + Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() }; + + TotalScore = 2845370; + Accuracy = 0.95; + MaxCombo = 999; + Rank = ScoreRank.S; + Date = DateTimeOffset.Now; + + Statistics[HitResult.Miss] = 1; + Statistics[HitResult.Meh] = 50; + Statistics[HitResult.Good] = 100; + Statistics[HitResult.Great] = 300; + } + + private class TestModHardRock : ModHardRock + { + public override double ScoreMultiplier => 1; + } + + private class TestModDoubleTime : ModDoubleTime + { + public override double ScoreMultiplier => 1; + } + } +} From 45b59f574dcd09ba389adc85ec2619a177f92e5e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 18:43:12 +0900 Subject: [PATCH 113/871] Fix TestSceneResultsScreen crashing --- osu.Game/Tests/TestScoreInfo.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Tests/TestScoreInfo.cs b/osu.Game/Tests/TestScoreInfo.cs index 155129e181..1193a29d70 100644 --- a/osu.Game/Tests/TestScoreInfo.cs +++ b/osu.Game/Tests/TestScoreInfo.cs @@ -23,6 +23,8 @@ namespace osu.Game.Tests }; Beatmap = new TestBeatmap(ruleset).BeatmapInfo; + Ruleset = ruleset; + RulesetID = ruleset.ID ?? 0; Mods = new Mod[] { new TestModHardRock(), new TestModDoubleTime() }; TotalScore = 2845370; From 717869225e55f0ff2a6cd27d9f797ffb9f62b868 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 19:51:36 +0900 Subject: [PATCH 114/871] Rework list to use a scroll container + add spacing --- osu.Game/Screens/Ranking/ScorePanelList.cs | 60 ++++++++++++++-------- 1 file changed, 38 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 894be7e775..52a9f27db8 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Utils; +using osu.Game.Graphics.Containers; using osu.Game.Scoring; using osuTK; @@ -14,19 +13,36 @@ namespace osu.Game.Screens.Ranking { public class ScorePanelList : CompositeDrawable { - private readonly Flow panels; + /// + /// Normal spacing between all panels. + /// + private const float panel_spacing = 5; + + /// + /// Spacing around both sides of the expanded panel. This is added on top of . + /// + private const float expanded_panel_spacing = 15; + + private readonly Flow flow; + private readonly ScrollContainer scroll; + private ScorePanel expandedPanel; public ScorePanelList() { RelativeSizeAxes = Axes.Both; - InternalChild = panels = new Flow + InternalChild = scroll = new OsuScrollContainer(Direction.Horizontal) { - Anchor = Anchor.Centre, - Origin = Anchor.Custom, - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.Both, + Child = flow = new Flow + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(panel_spacing, 0), + AutoSizeAxes = Axes.Both, + } }; } @@ -34,8 +50,8 @@ namespace osu.Game.Screens.Ranking { var panel = new ScorePanel(score) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, }; panel.StateChanged += s => onPanelStateChanged(panel, s); @@ -43,22 +59,14 @@ namespace osu.Game.Screens.Ranking // Todo: Temporary panel.State = expandedPanel == null ? PanelState.Expanded : PanelState.Contracted; - panels.Add(panel); + flow.Add(panel); } - public void RemoveScore(ScoreInfo score) => panels.RemoveAll(p => p.Score == score); - - protected override void UpdateAfterChildren() + protected override void Update() { - base.UpdateAfterChildren(); + base.Update(); - if (expandedPanel != null) - { - var firstPanel = panels.FlowingChildren.First(); - var target = expandedPanel.DrawPosition.X - firstPanel.DrawPosition.X + expandedPanel.DrawSize.X / 2; - - panels.OriginPosition = new Vector2((float)Interpolation.Lerp(panels.OriginPosition.X, target, Math.Clamp(Math.Abs(Time.Elapsed) / 80, 0, 1)), panels.DrawHeight / 2); - } + flow.Padding = new MarginPadding { Horizontal = DrawWidth / 2f - ScorePanel.EXPANDED_WIDTH / 2f - expanded_panel_spacing }; } private void onPanelStateChanged(ScorePanel panel, PanelState state) @@ -67,9 +75,17 @@ namespace osu.Game.Screens.Ranking return; if (expandedPanel != null) + { + expandedPanel.Margin = new MarginPadding(0); expandedPanel.State = PanelState.Contracted; + } expandedPanel = panel; + expandedPanel.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; + + float panelOffset = flow.IndexOf(expandedPanel) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing); + + scroll.ScrollTo(panelOffset); } private class Flow : FillFlowContainer From 7b82a5d792d4fae9103bff89de4db4e81a8e5063 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 20:48:08 +0900 Subject: [PATCH 115/871] Fix score order --- osu.Game/Screens/Ranking/ScorePanelList.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 52a9f27db8..0e0ed4f60d 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -90,8 +90,7 @@ namespace osu.Game.Screens.Ranking private class Flow : FillFlowContainer { - // Todo: Order is wrong. - public override IEnumerable FlowingChildren => AliveInternalChildren.OfType().OrderBy(s => s.Score.TotalScore); + public override IEnumerable FlowingChildren => AliveInternalChildren.OfType().OrderByDescending(s => s.Score.TotalScore).ThenByDescending(s => s.Score.OnlineScoreID); } } } From d0f74c2b683e949ba780c7d6e8011a184b5468d6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 20:48:25 +0900 Subject: [PATCH 116/871] Refactor initial state --- .../Visual/Ranking/TestSceneScorePanelList.cs | 47 ++++++++++------ osu.Game/Screens/Ranking/ResultsScreen.cs | 4 +- osu.Game/Screens/Ranking/ScorePanelList.cs | 54 +++++++++---------- 3 files changed, 58 insertions(+), 47 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index 81a9b22992..f00bf7e151 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -1,38 +1,51 @@ // 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.Graphics; +using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Ranking; +using osuTK.Graphics; namespace osu.Game.Tests.Visual.Ranking { public class TestSceneScorePanelList : OsuTestScene { - public TestSceneScorePanelList() + private ScorePanelList list; + + [SetUp] + public void Setup() => Schedule(() => { - var list = new ScorePanelList + Child = list = new ScorePanelList(new TestScoreInfo(new OsuRuleset().RulesetInfo)) { Anchor = Anchor.Centre, Origin = Anchor.Centre, }; - Add(list); + Add(new Box + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Y, + Width = 1, + Colour = Color4.Red + }); + }); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + [Test] + public void TestSingleScore() + { + } + + [Test] + public void TestManyScores() + { + AddStep("add many scores", () => + { + for (int i = 0; i < 20; i++) + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + }); } } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 652d158fbb..cdceaa939e 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -63,7 +63,7 @@ namespace osu.Game.Screens.Ranking { new ResultsScrollContainer { - Child = panels = new ScorePanelList + Child = panels = new ScorePanelList(Score) { RelativeSizeAxes = Axes.Both, } @@ -98,8 +98,6 @@ namespace osu.Game.Screens.Ranking } }; - panels.AddScore(Score); - if (player != null && allowRetry) { buttons.Add(new RetryButton { Width = 300 }); diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 0e0ed4f60d..c2fd487767 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Ranking private ScorePanel expandedPanel; - public ScorePanelList() + public ScorePanelList(ScoreInfo initialScore) { RelativeSizeAxes = Axes.Both; @@ -44,48 +44,48 @@ namespace osu.Game.Screens.Ranking AutoSizeAxes = Axes.Both, } }; + + AddScore(initialScore); + ShowScore(initialScore); } public void AddScore(ScoreInfo score) { - var panel = new ScorePanel(score) + flow.Add(new ScorePanel(score) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - }; + }.With(p => + { + p.StateChanged += s => + { + if (s == PanelState.Expanded) + ShowScore(score); + }; + })); + } - panel.StateChanged += s => onPanelStateChanged(panel, s); + public void ShowScore(ScoreInfo score) + { + foreach (var p in flow.Where(p => p.Score != score)) + p.State = PanelState.Contracted; - // Todo: Temporary - panel.State = expandedPanel == null ? PanelState.Expanded : PanelState.Contracted; + if (expandedPanel != null) + expandedPanel.Margin = new MarginPadding(0); - flow.Add(panel); + expandedPanel = flow.Single(p => p.Score == score); + expandedPanel.State = PanelState.Expanded; + expandedPanel.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; + + float scrollOffset = flow.IndexOf(expandedPanel) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing); + scroll.ScrollTo(scrollOffset); } protected override void Update() { base.Update(); - flow.Padding = new MarginPadding { Horizontal = DrawWidth / 2f - ScorePanel.EXPANDED_WIDTH / 2f - expanded_panel_spacing }; - } - - private void onPanelStateChanged(ScorePanel panel, PanelState state) - { - if (state == PanelState.Contracted) - return; - - if (expandedPanel != null) - { - expandedPanel.Margin = new MarginPadding(0); - expandedPanel.State = PanelState.Contracted; - } - - expandedPanel = panel; - expandedPanel.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; - - float panelOffset = flow.IndexOf(expandedPanel) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing); - - scroll.ScrollTo(panelOffset); + flow.Padding = new MarginPadding { Horizontal = DrawWidth / 2f - expandedPanel.DrawWidth / 2f - expanded_panel_spacing }; } private class Flow : FillFlowContainer From 45244683de150597b66234e5ee78b78c0f718189 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 22:07:06 +0900 Subject: [PATCH 117/871] Fix scrolling (1-frame + maintain scroll position) --- .../Visual/Ranking/TestSceneScorePanelList.cs | 49 +++++++++++- osu.Game/Screens/Ranking/ScorePanel.cs | 71 +++++++++------- osu.Game/Screens/Ranking/ScorePanelList.cs | 80 ++++++++++++++++--- 3 files changed, 154 insertions(+), 46 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index f00bf7e151..89aef377c8 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -1,10 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Rulesets.Osu; +using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osuTK.Graphics; @@ -12,12 +16,13 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneScorePanelList : OsuTestScene { + private ScoreInfo initialScore; private ScorePanelList list; [SetUp] public void Setup() => Schedule(() => { - Child = list = new ScorePanelList(new TestScoreInfo(new OsuRuleset().RulesetInfo)) + Child = list = new ScorePanelList(initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo)) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -36,16 +41,52 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestSingleScore() { + assertPanelCentred(); } [Test] - public void TestManyScores() + public void TestAddManyScoresAfter() { - AddStep("add many scores", () => + AddStep("add scores", () => { for (int i = 0; i < 20; i++) - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 }); }); + + assertPanelCentred(); } + + [Test] + public void TestAddManyScoresBefore() + { + AddStep("add scores", () => + { + for (int i = 0; i < 20; i++) + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 }); + }); + + assertPanelCentred(); + } + + [Test] + public void TestAddManyPanelsOnBothSides() + { + AddStep("add scores after", () => + { + for (int i = 0; i < 20; i++) + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 }); + + for (int i = 0; i < 20; i++) + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 }); + }); + + assertPanelCentred(); + } + + private void assertPanelCentred() => AddUntilStep("expanded panel centred", () => + { + var expandedPanel = list.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + return Precision.AlmostEquals(expandedPanel.ScreenSpaceDrawQuad.Centre.X, list.ScreenSpaceDrawQuad.Centre.X, 1); + }); } } diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 2f6146a5e7..2933bbddd1 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -78,6 +78,8 @@ namespace osu.Game.Screens.Ranking public event Action StateChanged; public readonly ScoreInfo Score; + private Container content; + private Container topLayerContainer; private Drawable topLayerBackground; private Container topLayerContentContainer; @@ -96,41 +98,46 @@ namespace osu.Game.Screens.Ranking [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] + InternalChild = content = new Container { - topLayerContainer = new Container + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] { - Name = "Top layer", - RelativeSizeAxes = Axes.X, - Height = 120, - Children = new Drawable[] + topLayerContainer = new Container { - new Container + Name = "Top layer", + RelativeSizeAxes = Axes.X, + Height = 120, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - CornerRadius = 20, - CornerExponent = 2.5f, - Masking = true, - Child = topLayerBackground = new Box { RelativeSizeAxes = Axes.Both } - }, - topLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both } - } - }, - middleLayerContainer = new Container - { - Name = "Middle layer", - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + new Container + { + RelativeSizeAxes = Axes.Both, + CornerRadius = 20, + CornerExponent = 2.5f, + Masking = true, + Child = topLayerBackground = new Box { RelativeSizeAxes = Axes.Both } + }, + topLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both } + } + }, + middleLayerContainer = new Container { - new Container + Name = "Middle layer", + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - CornerRadius = 20, - CornerExponent = 2.5f, - Masking = true, - Child = middleLayerBackground = new Box { RelativeSizeAxes = Axes.Both } - }, - middleLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both } + new Container + { + RelativeSizeAxes = Axes.Both, + CornerRadius = 20, + CornerExponent = 2.5f, + Masking = true, + Child = middleLayerBackground = new Box { RelativeSizeAxes = Axes.Both } + }, + middleLayerContentContainer = new Container { RelativeSizeAxes = Axes.Both } + } } } }; @@ -181,7 +188,7 @@ namespace osu.Game.Screens.Ranking switch (state) { case PanelState.Expanded: - this.ResizeTo(new Vector2(EXPANDED_WIDTH, expanded_height), resize_duration, Easing.OutQuint); + Size = new Vector2(EXPANDED_WIDTH, expanded_height); topLayerBackground.FadeColour(expanded_top_layer_colour, resize_duration, Easing.OutQuint); middleLayerBackground.FadeColour(expanded_middle_layer_colour, resize_duration, Easing.OutQuint); @@ -191,7 +198,7 @@ namespace osu.Game.Screens.Ranking break; case PanelState.Contracted: - this.ResizeTo(new Vector2(CONTRACTED_WIDTH, contracted_height), resize_duration, Easing.OutQuint); + Size = new Vector2(CONTRACTED_WIDTH, contracted_height); topLayerBackground.FadeColour(contracted_top_layer_colour, resize_duration, Easing.OutQuint); middleLayerBackground.FadeColour(contracted_middle_layer_colour, resize_duration, Easing.OutQuint); @@ -200,6 +207,8 @@ namespace osu.Game.Screens.Ranking break; } + content.ResizeTo(Size, resize_duration, Easing.OutQuint); + bool topLayerExpanded = topLayerContainer.Y < 0; // If the top layer was already expanded, then we don't need to wait for the resize and can instead transform immediately. This looks better when changing the panel state. diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index c2fd487767..6dd21ec49d 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Ranking private const float expanded_panel_spacing = 15; private readonly Flow flow; - private readonly ScrollContainer scroll; + private readonly Scroll scroll; private ScorePanel expandedPanel; @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Ranking { RelativeSizeAxes = Axes.Both; - InternalChild = scroll = new OsuScrollContainer(Direction.Horizontal) + InternalChild = scroll = new Scroll { RelativeSizeAxes = Axes.Both, Child = flow = new Flow @@ -46,9 +46,13 @@ namespace osu.Game.Screens.Ranking }; AddScore(initialScore); - ShowScore(initialScore); + presentScore(initialScore); } + /// + /// Adds a to this list. + /// + /// The to add. public void AddScore(ScoreInfo score) { flow.Add(new ScorePanel(score) @@ -60,24 +64,45 @@ namespace osu.Game.Screens.Ranking p.StateChanged += s => { if (s == PanelState.Expanded) - ShowScore(score); + presentScore(score); }; })); + + // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. + // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. + if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + { + // A somewhat hacky property is used here because we need to: + // 1) Scroll after the scroll container's visible range is updated. + // 2) Scroll before the scroll container's scroll position is updated. + // Without this, we would have a 1-frame positioning error which looks very jarring. + scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + } } - public void ShowScore(ScoreInfo score) + /// + /// Brings a to the centre of the screen and expands it. + /// + /// The to present. + private void presentScore(ScoreInfo score) { + // Contract the old panel. foreach (var p in flow.Where(p => p.Score != score)) + { p.State = PanelState.Contracted; + p.Margin = new MarginPadding(); + } - if (expandedPanel != null) - expandedPanel.Margin = new MarginPadding(0); - + // Expand the new panel. expandedPanel = flow.Single(p => p.Score == score); expandedPanel.State = PanelState.Expanded; expandedPanel.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; - float scrollOffset = flow.IndexOf(expandedPanel) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing); + // Scroll to the new panel. This is done manually since we need: + // 1) To scroll after the scroll container's visible range is updated. + // 2) To account for the centre anchor/origins of panels. + // In the end, it's easier to compute the scroll position manually. + float scrollOffset = flow.GetPanelIndex(expandedPanel.Score) * (ScorePanel.CONTRACTED_WIDTH + panel_spacing); scroll.ScrollTo(scrollOffset); } @@ -85,12 +110,45 @@ namespace osu.Game.Screens.Ranking { base.Update(); - flow.Padding = new MarginPadding { Horizontal = DrawWidth / 2f - expandedPanel.DrawWidth / 2f - expanded_panel_spacing }; + // Add padding to both sides such that the centre of an expanded panel on either side is in the middle of the screen. + flow.Padding = new MarginPadding { Horizontal = DrawWidth / 2f - ScorePanel.EXPANDED_WIDTH / 2f - expanded_panel_spacing }; } private class Flow : FillFlowContainer { - public override IEnumerable FlowingChildren => AliveInternalChildren.OfType().OrderByDescending(s => s.Score.TotalScore).ThenByDescending(s => s.Score.OnlineScoreID); + public override IEnumerable FlowingChildren => applySorting(AliveInternalChildren); + + public int GetPanelIndex(ScoreInfo score) => applySorting(Children).OfType().TakeWhile(s => s.Score != score).Count(); + + private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() + .OrderByDescending(s => s.Score.TotalScore) + .ThenByDescending(s => s.Score.OnlineScoreID); + } + + private class Scroll : OsuScrollContainer + { + public new float Target => base.Target; + + public Scroll() + : base(Direction.Horizontal) + { + } + + /// + /// The target that will be scrolled to instantaneously next frame. + /// + public float? InstantScrollTarget; + + protected override void UpdateAfterChildren() + { + if (InstantScrollTarget != null) + { + ScrollTo(InstantScrollTarget.Value, false); + InstantScrollTarget = null; + } + + base.UpdateAfterChildren(); + } } } } From f5c80ac2d5c654794219db95fb5e2c8d48b5a7ce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 22:07:24 +0900 Subject: [PATCH 118/871] Remove vertical line --- .../Visual/Ranking/TestSceneScorePanelList.cs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index 89aef377c8..b32b3afbda 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -4,13 +4,11 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Framework.Utils; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; -using osuTK.Graphics; namespace osu.Game.Tests.Visual.Ranking { @@ -27,15 +25,6 @@ namespace osu.Game.Tests.Visual.Ranking Anchor = Anchor.Centre, Origin = Anchor.Centre, }; - - Add(new Box - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.Y, - Width = 1, - Colour = Color4.Red - }); }); [Test] From 899b9f8060928a5ed60984bc5eb073314ab63692 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 21 May 2020 22:26:04 +0900 Subject: [PATCH 119/871] Fix incorrect sorting order --- osu.Game/Screens/Ranking/ScorePanelList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 6dd21ec49d..ed6d07d078 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -122,7 +122,7 @@ namespace osu.Game.Screens.Ranking private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() .OrderByDescending(s => s.Score.TotalScore) - .ThenByDescending(s => s.Score.OnlineScoreID); + .ThenBy(s => s.Score.OnlineScoreID); } private class Scroll : OsuScrollContainer From 8a105bdbcfc554f4bd62217782d01632722a047a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 May 2020 11:19:57 +0900 Subject: [PATCH 120/871] Remove unused ColumnAt method --- osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs | 2 -- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 9 +-------- osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs | 2 +- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 8 -------- 4 files changed, 2 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs index 48e6b63064..5d9ad21cb7 100644 --- a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs @@ -8,8 +8,6 @@ namespace osu.Game.Rulesets.Mania.Edit { public interface IManiaHitObjectComposer { - Column ColumnAt(Vector2 screenSpacePosition); - ManiaPlayfield Playfield { get; } Vector2 ScreenSpacePositionAtTime(double time, Column column = null); diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 4795bdd8e2..d4cfab840d 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -44,13 +44,6 @@ namespace osu.Game.Rulesets.Mania.Edit inputManager = GetContainingInputManager(); } - /// - /// Retrieves the column that intersects a screen-space position. - /// - /// The screen-space position. - /// The column which intersects with . - public Column ColumnAt(Vector2 screenSpacePosition) => drawableRuleset.GetColumnByPosition(screenSpacePosition); - private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -85,7 +78,7 @@ namespace osu.Game.Rulesets.Mania.Edit public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) { - var column = ColumnAt(screenSpacePosition); + var column = Playfield.GetColumnByPosition(screenSpacePosition); if (column == null) return new SnapResult(screenSpacePosition, null); diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 83049ff959..4ea71652bc 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Edit private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent) { - var currentColumn = composer.ColumnAt(moveEvent.ScreenSpacePosition); + var currentColumn = composer.Playfield.GetColumnByPosition(moveEvent.ScreenSpacePosition); if (currentColumn == null) return; diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index f3f843f366..94b5ee9486 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -23,7 +23,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; -using osuTK; namespace osu.Game.Rulesets.Mania.UI { @@ -108,13 +107,6 @@ namespace osu.Game.Rulesets.Mania.UI private void updateTimeRange() => TimeRange.Value = configTimeRange.Value * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value; - /// - /// Retrieves the column that intersects a screen-space position. - /// - /// The screen-space position. - /// The column which intersects with . - public Column GetColumnByPosition(Vector2 screenSpacePosition) => Playfield.GetColumnByPosition(screenSpacePosition); - public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer(); protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages); From 9a2889abc54d2c5217bb5adbbd591be7c8dc7d97 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 May 2020 11:32:35 +0900 Subject: [PATCH 121/871] Remove remaining left-over test implementations --- .../ManiaPlacementBlueprintTestScene.cs | 3 --- .../ManiaSelectionBlueprintTestScene.cs | 9 +-------- .../TestSceneManiaBeatSnapGrid.cs | 5 ----- 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs index 9a50802454..fd18907d96 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs @@ -15,7 +15,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests @@ -57,7 +56,5 @@ namespace osu.Game.Rulesets.Mania.Tests protected override void AddHitObject(DrawableHitObject hitObject) => column.Add((DrawableManiaHitObject)hitObject); public ManiaPlayfield Playfield => null; - - public Vector2 ScreenSpacePositionAtTime(double time, Column column = null) => Vector2.Zero; } } diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs index f7dffbbc1a..35fe596e98 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs @@ -7,7 +7,6 @@ using osu.Framework.Timing; using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests @@ -18,11 +17,9 @@ namespace osu.Game.Rulesets.Mania.Tests [Cached(Type = typeof(IAdjustableClock))] private readonly IAdjustableClock clock = new StopwatchClock(); - private readonly Column column; - protected ManiaSelectionBlueprintTestScene() { - Add(column = new Column(0) + Add(new Column(0) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -31,10 +28,6 @@ namespace osu.Game.Rulesets.Mania.Tests }); } - public Column ColumnAt(Vector2 screenSpacePosition) => column; - public ManiaPlayfield Playfield => null; - - public Vector2 ScreenSpacePositionAtTime(double time, Column column = null) => Vector2.Zero; } } diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs index feda3cfb81..ce9546415f 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs @@ -14,7 +14,6 @@ using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; using osu.Game.Tests.Visual; -using osuTK; namespace osu.Game.Rulesets.Mania.Tests { @@ -66,10 +65,6 @@ namespace osu.Game.Rulesets.Mania.Tests return true; } - public Column ColumnAt(Vector2 screenSpacePosition) => null; - public ManiaPlayfield Playfield { get; } - - public Vector2 ScreenSpacePositionAtTime(double time, Column column = null) => Vector2.Zero; } } From f364d0e8328070f77232472b1db3dcee7d9f4bdf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 May 2020 11:35:26 +0900 Subject: [PATCH 122/871] Reduce IManiaHitObjectComposer scope --- .../ManiaPlacementBlueprintTestScene.cs | 5 +---- .../ManiaSelectionBlueprintTestScene.cs | 9 ++------- .../Edit/IManiaHitObjectComposer.cs | 5 +---- .../Edit/ManiaHitObjectComposer.cs | 11 +---------- osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs | 4 ++-- osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs | 8 -------- 6 files changed, 7 insertions(+), 35 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs index 547786847b..fd18907d96 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs @@ -15,7 +15,6 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Tests.Visual; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests @@ -56,8 +55,6 @@ namespace osu.Game.Rulesets.Mania.Tests protected override void AddHitObject(DrawableHitObject hitObject) => column.Add((DrawableManiaHitObject)hitObject); - public Column ColumnAt(Vector2 screenSpacePosition) => column; - - public int TotalColumns => 1; + public ManiaPlayfield Playfield => null; } } diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs index b598893e8c..35fe596e98 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs @@ -7,7 +7,6 @@ using osu.Framework.Timing; using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests @@ -18,11 +17,9 @@ namespace osu.Game.Rulesets.Mania.Tests [Cached(Type = typeof(IAdjustableClock))] private readonly IAdjustableClock clock = new StopwatchClock(); - private readonly Column column; - protected ManiaSelectionBlueprintTestScene() { - Add(column = new Column(0) + Add(new Column(0) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -31,8 +28,6 @@ namespace osu.Game.Rulesets.Mania.Tests }); } - public Column ColumnAt(Vector2 screenSpacePosition) => column; - - public int TotalColumns => 1; + public ManiaPlayfield Playfield => null; } } diff --git a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs index f64bab1fae..3818d0e15d 100644 --- a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs @@ -2,14 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Rulesets.Mania.UI; -using osuTK; namespace osu.Game.Rulesets.Mania.Edit { public interface IManiaHitObjectComposer { - Column ColumnAt(Vector2 screenSpacePosition); - - int TotalColumns { get; } + ManiaPlayfield Playfield { get; } } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index cfb04c8e50..8367b4f5e9 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -26,13 +26,6 @@ namespace osu.Game.Rulesets.Mania.Edit { } - /// - /// Retrieves the column that intersects a screen-space position. - /// - /// The screen-space position. - /// The column which intersects with . - public Column ColumnAt(Vector2 screenSpacePosition) => drawableRuleset.GetColumnByPosition(screenSpacePosition); - private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -42,11 +35,9 @@ namespace osu.Game.Rulesets.Mania.Edit public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo; - public int TotalColumns => Playfield.TotalColumns; - public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) { - var column = ColumnAt(screenSpacePosition); + var column = Playfield.GetColumnByPosition(screenSpacePosition); if (column == null) return new SnapResult(screenSpacePosition, null); diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 55245198c8..4ea71652bc 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Mania.Edit private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent) { - var currentColumn = composer.ColumnAt(moveEvent.ScreenSpacePosition); + var currentColumn = composer.Playfield.GetColumnByPosition(moveEvent.ScreenSpacePosition); if (currentColumn == null) return; @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Mania.Edit maxColumn = obj.Column; } - columnDelta = Math.Clamp(columnDelta, -minColumn, composer.TotalColumns - 1 - maxColumn); + columnDelta = Math.Clamp(columnDelta, -minColumn, composer.Playfield.TotalColumns - 1 - maxColumn); foreach (var obj in SelectedHitObjects.OfType()) obj.Column += columnDelta; diff --git a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs index f3f843f366..94b5ee9486 100644 --- a/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/UI/DrawableManiaRuleset.cs @@ -23,7 +23,6 @@ using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; -using osuTK; namespace osu.Game.Rulesets.Mania.UI { @@ -108,13 +107,6 @@ namespace osu.Game.Rulesets.Mania.UI private void updateTimeRange() => TimeRange.Value = configTimeRange.Value * speedAdjustmentTrack.AggregateTempo.Value * speedAdjustmentTrack.AggregateFrequency.Value; - /// - /// Retrieves the column that intersects a screen-space position. - /// - /// The screen-space position. - /// The column which intersects with . - public Column GetColumnByPosition(Vector2 screenSpacePosition) => Playfield.GetColumnByPosition(screenSpacePosition); - public override PlayfieldAdjustmentContainer CreatePlayfieldAdjustmentContainer() => new ManiaPlayfieldAdjustmentContainer(); protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages); From b2667bbb0210d00b9f6502362fc632f93bb9fa92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 May 2020 11:45:58 +0900 Subject: [PATCH 123/871] Move protected implementation down --- .../Edit/ManiaHitObjectComposer.cs | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 82a55b4965..683e921cbf 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -53,29 +53,6 @@ namespace osu.Game.Rulesets.Mania.Edit public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo; - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - if (BlueprintContainer.CurrentTool is SelectTool) - { - if (EditorBeatmap.SelectedHitObjects.Any()) - { - beatSnapGrid.SelectionTimeRange = (EditorBeatmap.SelectedHitObjects.Min(h => h.StartTime), EditorBeatmap.SelectedHitObjects.Max(h => h.GetEndTime())); - } - else - beatSnapGrid.SelectionTimeRange = null; - } - else - { - var result = SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position); - if (result.Time is double time) - beatSnapGrid.SelectionTimeRange = (time, time); - else - beatSnapGrid.SelectionTimeRange = null; - } - } - public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) { var column = Playfield.GetColumnByPosition(screenSpacePosition); @@ -111,5 +88,28 @@ namespace osu.Game.Rulesets.Mania.Edit new NoteCompositionTool(), new HoldNoteCompositionTool() }; + + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + if (BlueprintContainer.CurrentTool is SelectTool) + { + if (EditorBeatmap.SelectedHitObjects.Any()) + { + beatSnapGrid.SelectionTimeRange = (EditorBeatmap.SelectedHitObjects.Min(h => h.StartTime), EditorBeatmap.SelectedHitObjects.Max(h => h.GetEndTime())); + } + else + beatSnapGrid.SelectionTimeRange = null; + } + else + { + var result = SnapScreenSpacePositionToValidTime(inputManager.CurrentState.Mouse.Position); + if (result.Time is double time) + beatSnapGrid.SelectionTimeRange = (time, time); + else + beatSnapGrid.SelectionTimeRange = null; + } + } } } From d529a2aefac633bde0bcdf32e215e0d89af7bff6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 May 2020 12:28:01 +0900 Subject: [PATCH 124/871] Remove left-over function --- .../Edit/ManiaBeatSnapGrid.cs | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index fa8f8a755a..e52cd5774c 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -11,12 +11,10 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; -using osuTK; using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Edit @@ -171,36 +169,6 @@ namespace osu.Game.Rulesets.Mania.Edit } } - public (Vector2 position, double time)? GetSnappedPosition(Vector2 position) - { - float minDist = float.PositiveInfinity; - DrawableGridLine minDistLine = null; - - Vector2 minDistLinePosition = Vector2.Zero; - - foreach (var grid in grids) - { - foreach (var line in grid.Objects.OfType()) - { - Vector2 linePos = line.ToSpaceOfOtherDrawable(line.OriginPosition, this); - float d = Vector2.Distance(position, linePos); - - if (d < minDist) - { - minDist = d; - minDistLine = line; - minDistLinePosition = linePos; - } - } - } - - if (minDistLine == null) - return null; - - float noteOffset = (scrollingInfo.Direction.Value == ScrollingDirection.Up ? 1 : -1) * DefaultNotePiece.NOTE_HEIGHT / 2; - return (new Vector2(position.X, minDistLinePosition.Y + noteOffset), minDistLine.HitObject.StartTime); - } - private class DrawableGridLine : DrawableHitObject { [Resolved] From ce35d09e7dac2987f3cb439366eba75e0db0d7a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 May 2020 12:45:37 +0900 Subject: [PATCH 125/871] Fix incorrect alpha application to lines on rewinding --- osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs | 8 ++++++-- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index e52cd5774c..b5b6c08fca 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -199,10 +199,14 @@ namespace osu.Game.Rulesets.Mania.Edit : Anchor.BottomLeft; } + protected override void UpdateInitialTransforms() + { + // don't perform any fading – we are handling that ourselves. + } + protected override void UpdateStateTransforms(ArmedState state) { - using (BeginAbsoluteSequence(HitObject.StartTime + 1000)) - this.FadeOut(); + LifetimeEnd = HitObject.StartTime + visible_range; } } } diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index d594909cda..44afb7a227 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -257,7 +257,7 @@ namespace osu.Game.Rulesets.Objects.Drawables } } - if (state.Value != ArmedState.Idle && LifetimeEnd == double.MaxValue || HitObject.HitWindows == null) + if (LifetimeEnd == double.MaxValue && (state.Value != ArmedState.Idle || HitObject.HitWindows == null)) Expire(); // apply any custom state overrides From dd09d7830d108ff1de968035507f66199fc2ab57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 May 2020 16:37:28 +0900 Subject: [PATCH 126/871] Cache and resolve editor clock as EditorClock in all cases --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 14 +++++-------- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 11 +++++----- .../Edit/Components/PlaybackControl.cs | 11 +++++----- .../Edit/Components/TimeInfoContainer.cs | 5 ++--- .../Timelines/Summary/Parts/MarkerPart.cs | 15 +++++++------ .../Timelines/Summary/SummaryTimeline.cs | 5 ++--- .../Compose/Components/BlueprintContainer.cs | 5 ++--- .../Compose/Components/Timeline/Timeline.cs | 21 +++++++++---------- osu.Game/Screens/Edit/Editor.cs | 3 +-- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 2 +- osu.Game/Tests/Visual/EditorClockTestScene.cs | 3 +-- 11 files changed, 41 insertions(+), 54 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index fd8af0afd5..1987148aed 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Logging; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Configuration; @@ -38,14 +37,11 @@ namespace osu.Game.Rulesets.Edit protected readonly Ruleset Ruleset; [Resolved] - protected IFrameBasedClock EditorClock { get; private set; } + protected EditorClock EditorClock { get; private set; } [Resolved] protected EditorBeatmap EditorBeatmap { get; private set; } - [Resolved] - private IAdjustableClock adjustableClock { get; set; } - [Resolved] protected IBeatSnapProvider BeatSnapProvider { get; private set; } @@ -68,7 +64,7 @@ namespace osu.Game.Rulesets.Edit } [BackgroundDependencyLoader] - private void load(IFrameBasedClock framedClock) + private void load() { Config = Dependencies.Get().GetConfigFor(Ruleset); @@ -76,7 +72,7 @@ namespace osu.Game.Rulesets.Edit { drawableRulesetWrapper = new DrawableEditRulesetWrapper(CreateDrawableRuleset(Ruleset, EditorBeatmap.PlayableBeatmap)) { - Clock = framedClock, + Clock = EditorClock, ProcessCustomClock = false }; } @@ -221,8 +217,8 @@ namespace osu.Game.Rulesets.Edit { EditorBeatmap.Add(hitObject); - if (adjustableClock.CurrentTime < hitObject.StartTime) - adjustableClock.Seek(hitObject.StartTime); + if (EditorClock.CurrentTime < hitObject.StartTime) + EditorClock.Seek(hitObject.StartTime); } } diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index f0b63f8ea5..20584c66e5 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -6,10 +6,10 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose; using osuTK; @@ -30,7 +30,8 @@ namespace osu.Game.Rulesets.Edit /// protected readonly HitObject HitObject; - protected IClock EditorClock { get; private set; } + [Resolved] + protected EditorClock EditorClock { get; private set; } private readonly IBindable beatmap = new Bindable(); @@ -49,12 +50,10 @@ namespace osu.Game.Rulesets.Edit } [BackgroundDependencyLoader] - private void load(IBindable beatmap, IAdjustableClock clock) + private void load(IBindable beatmap) { this.beatmap.BindTo(beatmap); - EditorClock = clock; - ApplyDefaultsToHitObject(); } @@ -84,7 +83,7 @@ namespace osu.Game.Rulesets.Edit } [Resolved(canBeNull: true)] - private IFrameBasedClock editorClock { get; set; } + private EditorClock editorClock { get; set; } /// /// Updates the position of this to a new screen-space position. diff --git a/osu.Game/Screens/Edit/Components/PlaybackControl.cs b/osu.Game/Screens/Edit/Components/PlaybackControl.cs index 897c6ec531..59b3d1c565 100644 --- a/osu.Game/Screens/Edit/Components/PlaybackControl.cs +++ b/osu.Game/Screens/Edit/Components/PlaybackControl.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -26,7 +25,7 @@ namespace osu.Game.Screens.Edit.Components private IconButton playButton; [Resolved] - private IAdjustableClock adjustableClock { get; set; } + private EditorClock editorClock { get; set; } private readonly BindableNumber tempo = new BindableDouble(1); @@ -87,17 +86,17 @@ namespace osu.Game.Screens.Edit.Components private void togglePause() { - if (adjustableClock.IsRunning) - adjustableClock.Stop(); + if (editorClock.IsRunning) + editorClock.Stop(); else - adjustableClock.Start(); + editorClock.Start(); } protected override void Update() { base.Update(); - playButton.Icon = adjustableClock.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; + playButton.Icon = editorClock.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle; } private class PlaybackTabControl : OsuTabControl diff --git a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs index 4bf21d240a..c1f54d7938 100644 --- a/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs +++ b/osu.Game/Screens/Edit/Components/TimeInfoContainer.cs @@ -5,7 +5,6 @@ using osu.Framework.Graphics; using osu.Game.Graphics.Sprites; using System; using osu.Framework.Allocation; -using osu.Framework.Timing; using osu.Game.Graphics; namespace osu.Game.Screens.Edit.Components @@ -15,7 +14,7 @@ namespace osu.Game.Screens.Edit.Components private readonly OsuSpriteText trackTimer; [Resolved] - private IAdjustableClock adjustableClock { get; set; } + private EditorClock editorClock { get; set; } public TimeInfoContainer() { @@ -35,7 +34,7 @@ namespace osu.Game.Screens.Edit.Components { base.Update(); - trackTimer.Text = TimeSpan.FromMilliseconds(adjustableClock.CurrentTime).ToString(@"mm\:ss\:fff"); + trackTimer.Text = TimeSpan.FromMilliseconds(editorClock.CurrentTime).ToString(@"mm\:ss\:fff"); } } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index 5d638d7919..82581dfc56 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Threading; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -20,14 +19,14 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// public class MarkerPart : TimelinePart { - private readonly Drawable marker; + private Drawable marker; - private readonly IAdjustableClock adjustableClock; + [Resolved] + private EditorClock editorClock { get; set; } - public MarkerPart(IAdjustableClock adjustableClock) + [BackgroundDependencyLoader] + private void load() { - this.adjustableClock = adjustableClock; - Add(marker = new MarkerVisualisation()); } @@ -59,14 +58,14 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts return; float markerPos = Math.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); - adjustableClock.Seek(markerPos / DrawWidth * Beatmap.Value.Track.Length); + editorClock.Seek(markerPos / DrawWidth * editorClock.TrackLength); }); } protected override void Update() { base.Update(); - marker.X = (float)adjustableClock.CurrentTime; + marker.X = (float)editorClock.CurrentTime; } protected override void LoadBeatmap(WorkingBeatmap beatmap) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs index 20db2cac21..02cd4bccb4 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Timing; using osu.Game.Graphics; using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; @@ -18,11 +17,11 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary public class SummaryTimeline : BottomBarContainer { [BackgroundDependencyLoader] - private void load(OsuColour colours, IAdjustableClock adjustableClock) + private void load(OsuColour colours) { Children = new Drawable[] { - new MarkerPart(adjustableClock) { RelativeSizeAxes = Axes.Both }, + new MarkerPart { RelativeSizeAxes = Axes.Both }, new ControlPointPart { Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 1e8a35c047..fba7671fca 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -13,7 +13,6 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; -using osu.Framework.Timing; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -38,7 +37,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private IEditorChangeHandler changeHandler { get; set; } [Resolved] - private IAdjustableClock adjustableClock { get; set; } + private EditorClock editorClock { get; set; } [Resolved] private EditorBeatmap beatmap { get; set; } @@ -144,7 +143,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (clickedBlueprint == null) return false; - adjustableClock?.Seek(clickedBlueprint.HitObject.StartTime); + editorClock?.Seek(clickedBlueprint.HitObject.StartTime); return true; } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index ec2b11c0cf..121f3dc213 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -9,7 +9,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Input.Events; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; @@ -25,7 +24,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public readonly IBindable Beatmap = new Bindable(); [Resolved] - private IAdjustableClock adjustableClock { get; set; } + private EditorClock editorClock { get; set; } public Timeline() { @@ -101,7 +100,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Content.Margin = new MarginPadding { Horizontal = DrawWidth / 2 }; // This needs to happen after transforms are updated, but before the scroll position is updated in base.UpdateAfterChildren - if (adjustableClock.IsRunning) + if (editorClock.IsRunning) scrollToTrackTime(); } @@ -111,21 +110,21 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (handlingDragInput) seekTrackToCurrent(); - else if (!adjustableClock.IsRunning) + else if (!editorClock.IsRunning) { // The track isn't running. There are two cases we have to be wary of: // 1) The user flick-drags on this timeline: We want the track to follow us // 2) The user changes the track time through some other means (scrolling in the editor or overview timeline): We want to follow the track time // The simplest way to cover both cases is by checking whether the scroll position has changed and the audio hasn't been changed externally - if (Current != lastScrollPosition && adjustableClock.CurrentTime == lastTrackTime) + if (Current != lastScrollPosition && editorClock.CurrentTime == lastTrackTime) seekTrackToCurrent(); else scrollToTrackTime(); } lastScrollPosition = Current; - lastTrackTime = adjustableClock.CurrentTime; + lastTrackTime = editorClock.CurrentTime; } private void seekTrackToCurrent() @@ -133,7 +132,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (!track.IsLoaded) return; - adjustableClock.Seek(Current / Content.DrawWidth * track.Length); + editorClock.Seek(Current / Content.DrawWidth * track.Length); } private void scrollToTrackTime() @@ -141,7 +140,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (!track.IsLoaded || track.Length == 0) return; - ScrollTo((float)(adjustableClock.CurrentTime / track.Length) * Content.DrawWidth, false); + ScrollTo((float)(editorClock.CurrentTime / track.Length) * Content.DrawWidth, false); } protected override bool OnMouseDown(MouseDownEvent e) @@ -164,15 +163,15 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void beginUserDrag() { handlingDragInput = true; - trackWasPlaying = adjustableClock.IsRunning; - adjustableClock.Stop(); + trackWasPlaying = editorClock.IsRunning; + editorClock.Stop(); } private void endUserDrag() { handlingDragInput = false; if (trackWasPlaying) - adjustableClock.Start(); + editorClock.Start(); } [Resolved] diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 54e4af94a4..54c5a23c3e 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -83,8 +83,7 @@ namespace osu.Game.Screens.Edit clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; clock.ChangeSource(sourceClock); - dependencies.CacheAs(clock); - dependencies.CacheAs(clock); + dependencies.CacheAs(clock); // todo: remove caching of this and consume via editorBeatmap? dependencies.Cache(beatDivisor); diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index d9da3ff92d..f3d1ec2cbb 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -23,7 +23,7 @@ namespace osu.Game.Screens.Edit.Timing private Bindable selectedGroup = new Bindable(); [Resolved] - private IAdjustableClock clock { get; set; } + private EditorClock clock { get; set; } protected override Drawable CreateMainContent() => new GridContainer { diff --git a/osu.Game/Tests/Visual/EditorClockTestScene.cs b/osu.Game/Tests/Visual/EditorClockTestScene.cs index 830e6ed363..f0ec638fc9 100644 --- a/osu.Game/Tests/Visual/EditorClockTestScene.cs +++ b/osu.Game/Tests/Visual/EditorClockTestScene.cs @@ -30,8 +30,7 @@ namespace osu.Game.Tests.Visual var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); dependencies.Cache(BeatDivisor); - dependencies.CacheAs(Clock); - dependencies.CacheAs(Clock); + dependencies.CacheAs(Clock); return dependencies; } From d18eb663b157d63a2c46816d86f5c3194604bc40 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 May 2020 16:40:52 +0900 Subject: [PATCH 127/871] Add tweening seek support to EditorClock --- .../Edit/OsuHitObjectComposer.cs | 3 +- .../Visual/Editing/TimelineTestScene.cs | 13 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- .../Timelines/Summary/Parts/MarkerPart.cs | 2 +- .../Compose/Components/BlueprintContainer.cs | 2 +- osu.Game/Screens/Edit/Editor.cs | 1 + osu.Game/Screens/Edit/EditorClock.cs | 129 ++++++++++++++++-- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 2 +- 8 files changed, 129 insertions(+), 25 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 9ba3e30445..df14b11e8a 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -104,7 +104,8 @@ namespace osu.Game.Rulesets.Osu.Edit var objects = selectedHitObjects.ToList(); if (objects.Count == 0) - return createGrid(h => h.StartTime <= EditorClock.CurrentTime); + // use accurate time value to give more instantaneous feedback to the user. + return createGrid(h => h.StartTime <= EditorClock.CurrentTimeAccurate); double minTime = objects.Min(h => h.StartTime); return createGrid(h => h.StartTime < minTime, objects.Count + 1); diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 01ef7e6170..3652d41e67 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -7,7 +7,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; @@ -69,7 +68,7 @@ namespace osu.Game.Tests.Visual.Editing private IBindable beatmap { get; set; } [Resolved] - private IAdjustableClock adjustableClock { get; set; } + private EditorClock adjustableClock { get; set; } public AudioVisualiser() { @@ -102,7 +101,7 @@ namespace osu.Game.Tests.Visual.Editing private class StartStopButton : OsuButton { - private IAdjustableClock adjustableClock; + private EditorClock editorClock; private bool started; public StartStopButton() @@ -115,21 +114,21 @@ namespace osu.Game.Tests.Visual.Editing } [BackgroundDependencyLoader] - private void load(IAdjustableClock adjustableClock) + private void load(EditorClock editorClock) { - this.adjustableClock = adjustableClock; + this.editorClock = editorClock; } private void onClick() { if (started) { - adjustableClock.Stop(); + editorClock.Stop(); Text = "Start"; } else { - adjustableClock.Start(); + editorClock.Start(); Text = "Stop"; } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 1987148aed..edbdd41d81 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -218,7 +218,7 @@ namespace osu.Game.Rulesets.Edit EditorBeatmap.Add(hitObject); if (EditorClock.CurrentTime < hitObject.StartTime) - EditorClock.Seek(hitObject.StartTime); + EditorClock.SeekTo(hitObject.StartTime); } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs index 82581dfc56..9e9ac93d23 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts return; float markerPos = Math.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); - editorClock.Seek(markerPos / DrawWidth * editorClock.TrackLength); + editorClock.SeekTo(markerPos / DrawWidth * editorClock.TrackLength); }); } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index fba7671fca..cc417bbb10 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (clickedBlueprint == null) return false; - editorClock?.Seek(clickedBlueprint.HitObject.StartTime); + editorClock?.SeekTo(clickedBlueprint.HitObject.StartTime); return true; } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 54c5a23c3e..9f61589c36 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -84,6 +84,7 @@ namespace osu.Game.Screens.Edit clock.ChangeSource(sourceClock); dependencies.CacheAs(clock); + AddInternal(clock); // todo: remove caching of this and consume via editorBeatmap? dependencies.Cache(beatDivisor); diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index e5e47507f3..321a25170a 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -3,6 +3,8 @@ using System; using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Transforms; using osu.Framework.Utils; using osu.Framework.Timing; using osu.Game.Beatmaps; @@ -13,7 +15,7 @@ namespace osu.Game.Screens.Edit /// /// A decoupled clock which adds editor-specific functionality, such as snapping to a user-defined beat divisor. /// - public class EditorClock : DecoupleableInterpolatingFramedClock + public class EditorClock : Component, IFrameBasedClock, IAdjustableClock, ISourceChangeableClock { public readonly double TrackLength; @@ -21,12 +23,11 @@ namespace osu.Game.Screens.Edit private readonly BindableBeatDivisor beatDivisor; - public EditorClock(WorkingBeatmap beatmap, BindableBeatDivisor beatDivisor) - { - this.beatDivisor = beatDivisor; + private readonly DecoupleableInterpolatingFramedClock underlyingClock; - ControlPointInfo = beatmap.Beatmap.ControlPointInfo; - TrackLength = beatmap.Track.Length; + public EditorClock(WorkingBeatmap beatmap, BindableBeatDivisor beatDivisor) + : this(beatmap.Beatmap.ControlPointInfo, beatmap.Track.Length, beatDivisor) + { } public EditorClock(ControlPointInfo controlPointInfo, double trackLength, BindableBeatDivisor beatDivisor) @@ -35,6 +36,8 @@ namespace osu.Game.Screens.Edit ControlPointInfo = controlPointInfo; TrackLength = trackLength; + + underlyingClock = new DecoupleableInterpolatingFramedClock(); } /// @@ -79,20 +82,30 @@ namespace osu.Game.Screens.Edit private void seek(int direction, bool snapped, double amount = 1) { + double current = CurrentTime; + + // if a seek transform is active, use its end time instead of the reported current time. + var existingTransform = Transforms.OfType().FirstOrDefault(); + + // but only if the requested direction is in the same direction as the transform. + // this allows quick pivoting rather than resetting the transform for the first opposite direction movement. + if (existingTransform != null && Math.Sign(existingTransform.EndValue - current) == Math.Sign(direction)) + current = existingTransform.EndValue; + if (amount <= 0) throw new ArgumentException("Value should be greater than zero", nameof(amount)); - var timingPoint = ControlPointInfo.TimingPointAt(CurrentTime); + var timingPoint = ControlPointInfo.TimingPointAt(current); - if (direction < 0 && timingPoint.Time == CurrentTime) + if (direction < 0 && timingPoint.Time == current) // When going backwards and we're at the boundary of two timing points, we compute the seek distance with the timing point which we are seeking into - timingPoint = ControlPointInfo.TimingPointAt(CurrentTime - 1); + timingPoint = ControlPointInfo.TimingPointAt(current - 1); double seekAmount = timingPoint.BeatLength / beatDivisor.Value * amount; - double seekTime = CurrentTime + seekAmount * direction; + double seekTime = current + seekAmount * direction; if (!snapped || ControlPointInfo.TimingPoints.Count == 0) { - Seek(seekTime); + SeekTo(seekTime); return; } @@ -110,7 +123,7 @@ namespace osu.Game.Screens.Edit // Due to the rounding above, we may end up on the current beat. This will effectively cause 0 seeking to happen, but we don't want this. // Instead, we'll go to the next beat in the direction when this is the case - if (Precision.AlmostEquals(CurrentTime, seekTime)) + if (Precision.AlmostEquals(current, seekTime)) { closestBeat += direction > 0 ? 1 : -1; seekTime = timingPoint.Time + closestBeat * seekAmount; @@ -125,7 +138,97 @@ namespace osu.Game.Screens.Edit // Ensure the sought point is within the boundaries seekTime = Math.Clamp(seekTime, 0, TrackLength); - Seek(seekTime); + SeekTo(seekTime); + } + + /// + /// The current time of this clock, include any active transform seeks performed via . + /// + public double CurrentTimeAccurate => + Transforms.OfType().FirstOrDefault()?.EndValue ?? CurrentTime; + + public double CurrentTime => underlyingClock.CurrentTime; + + public void Reset() + { + ClearTransforms(); + underlyingClock.Reset(); + } + + public void Start() + { + ClearTransforms(); + underlyingClock.Start(); + } + + public void Stop() + { + underlyingClock.Stop(); + } + + public bool Seek(double position) + { + ClearTransforms(); + return underlyingClock.Seek(position); + } + + public void ResetSpeedAdjustments() => underlyingClock.ResetSpeedAdjustments(); + + double IAdjustableClock.Rate + { + get => underlyingClock.Rate; + set => underlyingClock.Rate = value; + } + + double IClock.Rate => underlyingClock.Rate; + + public bool IsRunning => underlyingClock.IsRunning; + + public void ProcessFrame() => underlyingClock.ProcessFrame(); + + public double ElapsedFrameTime => underlyingClock.ElapsedFrameTime; + + public double FramesPerSecond => underlyingClock.FramesPerSecond; + + public FrameTimeInfo TimeInfo => underlyingClock.TimeInfo; + + public void ChangeSource(IClock source) => underlyingClock.ChangeSource(source); + + public IClock Source => underlyingClock.Source; + + public bool IsCoupled + { + get => underlyingClock.IsCoupled; + set => underlyingClock.IsCoupled = value; + } + + private const double transform_time = 300; + + public void SeekTo(double seekDestination) + { + if (IsRunning) + Seek(seekDestination); + else + transformSeekTo(seekDestination, transform_time, Easing.OutQuint); + } + + private void transformSeekTo(double seek, double duration = 0, Easing easing = Easing.None) + => this.TransformTo(this.PopulateTransform(new TransformSeek(), seek, duration, easing)); + + private double currentTime + { + get => underlyingClock.CurrentTime; + set => underlyingClock.Seek(value); + } + + private class TransformSeek : Transform + { + public override string TargetMember => nameof(currentTime); + + protected override void Apply(EditorClock clock, double time) => + clock.currentTime = Interpolation.ValueAt(time, StartValue, EndValue, StartTime, EndTime, Easing); + + protected override void ReadIntoStartValue(EditorClock clock) => StartValue = clock.currentTime; } } } diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index f3d1ec2cbb..59af88a049 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.BindValueChanged(selected => { if (selected.NewValue != null) - clock.Seek(selected.NewValue.Time); + clock.SeekTo(selected.NewValue.Time); }); } From 866db629d6ce60b534e9082652c37c888c3cb42b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 May 2020 18:23:24 +0900 Subject: [PATCH 128/871] Fix remaining test failures --- .../Visual/Editing/TestScenePlaybackControl.cs | 7 +++---- osu.Game.Tests/Visual/Editing/TimelineTestScene.cs | 14 +++++--------- osu.Game/Screens/Edit/EditorClock.cs | 5 +++++ .../Tests/Visual/PlacementBlueprintTestScene.cs | 3 ++- 4 files changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs b/osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs index 3af976cae0..6aa884a197 100644 --- a/osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs +++ b/osu.Game.Tests/Visual/Editing/TestScenePlaybackControl.cs @@ -4,8 +4,8 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components; using osuTK; @@ -17,9 +17,8 @@ namespace osu.Game.Tests.Visual.Editing [BackgroundDependencyLoader] private void load() { - var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; - Dependencies.CacheAs(clock); - Dependencies.CacheAs(clock); + var clock = new EditorClock { IsCoupled = false }; + Dependencies.CacheAs(clock); var playback = new PlaybackControl { diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 01ef7e6170..2e7ccc8ad3 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -102,7 +102,9 @@ namespace osu.Game.Tests.Visual.Editing private class StartStopButton : OsuButton { - private IAdjustableClock adjustableClock; + [Resolved] + private EditorClock editorClock { get; set; } + private bool started; public StartStopButton() @@ -114,22 +116,16 @@ namespace osu.Game.Tests.Visual.Editing Action = onClick; } - [BackgroundDependencyLoader] - private void load(IAdjustableClock adjustableClock) - { - this.adjustableClock = adjustableClock; - } - private void onClick() { if (started) { - adjustableClock.Stop(); + editorClock.Stop(); Text = "Start"; } else { - adjustableClock.Start(); + editorClock.Start(); Text = "Stop"; } diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index e5e47507f3..d2bb1c8984 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -37,6 +37,11 @@ namespace osu.Game.Screens.Edit TrackLength = trackLength; } + public EditorClock() + : this(new ControlPointInfo(), 1000, new BindableBeatDivisor()) + { + } + /// /// Seek to the closest snappable beat from a time. /// diff --git a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs index feecea473c..c3d74f21aa 100644 --- a/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/PlacementBlueprintTestScene.cs @@ -8,6 +8,7 @@ using osu.Framework.Timing; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose; namespace osu.Game.Tests.Visual @@ -32,7 +33,7 @@ namespace osu.Game.Tests.Visual protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs(new StopwatchClock()); + dependencies.CacheAs(new EditorClock()); return dependencies; } From 3e0ee310d0e0d7dd529c1e7c8a3e59f4a4e8ec7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 May 2020 18:30:39 +0900 Subject: [PATCH 129/871] Remove now incorrect comment --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index f0b63f8ea5..e71ccc33a4 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -64,9 +64,7 @@ namespace osu.Game.Rulesets.Edit /// Whether this call is committing a value for HitObject.StartTime and continuing with further adjustments. protected void BeginPlacement(bool commitStart = false) { - // applies snapping to above time placementHandler.BeginPlacement(HitObject); - PlacementActive |= commitStart; } From af30d1201f0934fc49ee4857e0157611bdc4114c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 May 2020 18:57:28 +0900 Subject: [PATCH 130/871] Fix slider path control point blueprint not working correctly --- .../Blueprints/Sliders/Components/PathControlPointPiece.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 834bf1892f..c06904c0c2 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -162,8 +162,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (ControlPoint == slider.Path.ControlPoints[0]) { // Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account - var result = snapProvider?.SnapScreenSpacePositionToValidTime(e.MousePosition); - Vector2 movementDelta = (result?.ScreenSpacePosition ?? e.MousePosition) - slider.Position; + var result = snapProvider?.SnapScreenSpacePositionToValidTime(e.ScreenSpaceMousePosition); + + Vector2 movementDelta = Parent.ToLocalSpace(result?.ScreenSpacePosition ?? e.ScreenSpaceMousePosition) - slider.Position; slider.Position += movementDelta; slider.StartTime = result?.Time ?? slider.StartTime; From 5ea33f4c046ffdde090f21c67a9a26772083a24b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 May 2020 19:23:07 +0900 Subject: [PATCH 131/871] Fix incorrect rounding in DragBar --- osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs | 4 ++-- .../Compose/Components/Timeline/TimelineHitObjectBlueprint.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index ec2b11c0cf..61ed1743a9 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -181,8 +181,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private IBeatSnapProvider beatSnapProvider { get; set; } - public SnapResult SnapScreenSpacePositionToValidTime(Vector2 position) => - new SnapResult(position, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(position)))); + public SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => + new SnapResult(screenSpacePosition, beatSnapProvider.SnapTime(getTimeFromPosition(Content.ToLocalSpace(screenSpacePosition)))); private double getTimeFromPosition(Vector2 localPosition) => (localPosition.X / Content.DrawWidth) * track.Length; diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 03e05b75c5..dd2f7a833e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -282,7 +282,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline case IHasRepeats repeatHitObject: // find the number of repeats which can fit in the requested time. var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1); - var proposedCount = Math.Max(0, (int)((time - hitObject.StartTime) / lengthOfOneRepeat) - 1); + var proposedCount = Math.Max(0, (int)Math.Round((time - hitObject.StartTime) / lengthOfOneRepeat) - 1); if (proposedCount == repeatHitObject.RepeatCount) return; From 8b79e142257652c1914d5381f8942f4cf20c1ae6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 May 2020 19:49:49 +0900 Subject: [PATCH 132/871] Fix remaining test regressions --- osu.Game.Tests/Visual/Editing/TimelineTestScene.cs | 5 ++--- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs index 2e7ccc8ad3..fdb8781563 100644 --- a/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editing/TimelineTestScene.cs @@ -7,7 +7,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; @@ -69,7 +68,7 @@ namespace osu.Game.Tests.Visual.Editing private IBindable beatmap { get; set; } [Resolved] - private IAdjustableClock adjustableClock { get; set; } + private EditorClock editorClock { get; set; } public AudioVisualiser() { @@ -96,7 +95,7 @@ namespace osu.Game.Tests.Visual.Editing base.Update(); if (beatmap.Value.Track.IsLoaded) - marker.X = (float)(adjustableClock.CurrentTime / beatmap.Value.Track.Length); + marker.X = (float)(editorClock.CurrentTime / beatmap.Value.Track.Length); } } diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index f3d1ec2cbb..f22d7291d9 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -7,7 +7,6 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; @@ -62,7 +61,7 @@ namespace osu.Game.Screens.Edit.Timing private IBindableList controlGroups; [Resolved] - private IFrameBasedClock clock { get; set; } + private EditorClock clock { get; set; } [Resolved] protected IBindable Beatmap { get; private set; } From 50059860498d63d2dbe8cd77b5840f2bf628a9da Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 May 2020 20:18:47 +0900 Subject: [PATCH 133/871] Cleanup test --- .../TestSceneContractedPanelMiddleContent.cs | 25 +++---------------- 1 file changed, 4 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs index e1e00e3c2b..972ac26b84 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -9,16 +9,13 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Testing; using osu.Game.Beatmaps; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Contracted; using osu.Game.Tests.Beatmaps; -using osu.Game.Users; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -29,22 +26,9 @@ namespace osu.Game.Tests.Visual.Ranking private RulesetStore rulesetStore { get; set; } [Test] - public void TestMapWithKnownMapper() + public void TestShowPanel() { - var author = new User { Username = "mapper_name" }; - - AddStep("show example score", () => showPanel(createTestBeatmap(author), new TestScoreInfo(new OsuRuleset().RulesetInfo))); - - AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Text == "mapper_name")); - } - - [Test] - public void TestMapWithUnknownMapper() - { - AddStep("show example score", () => showPanel(createTestBeatmap(null), new TestScoreInfo(new OsuRuleset().RulesetInfo))); - - AddAssert("mapped by text not present", () => - this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text, "mapped", "by"))); + AddStep("show example score", () => showPanel(createTestBeatmap(), new TestScoreInfo(new OsuRuleset().RulesetInfo))); } private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score) @@ -52,10 +36,9 @@ namespace osu.Game.Tests.Visual.Ranking Child = new ContractedPanelMiddleContentContainer(workingBeatmap, score); } - private WorkingBeatmap createTestBeatmap(User author) + private WorkingBeatmap createTestBeatmap() { var beatmap = new TestBeatmap(rulesetStore.GetRuleset(0)); - beatmap.Metadata.Author = author; beatmap.Metadata.Title = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap title"; beatmap.Metadata.Artist = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap artist"; @@ -75,7 +58,7 @@ namespace osu.Game.Tests.Visual.Ranking Anchor = Anchor.Centre; Origin = Anchor.Centre; - Size = new Vector2(ScorePanel.CONTRACTED_WIDTH, 700); + Size = new Vector2(ScorePanel.CONTRACTED_WIDTH, 460); Children = new Drawable[] { new Box From 6bcc4c95cc0794260e82a7d70cfe06aa31e7c33f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 May 2020 20:19:23 +0900 Subject: [PATCH 134/871] Schedule api callback --- osu.Game/Screens/Ranking/ResultsScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index cdceaa939e..af748d8336 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -120,7 +120,7 @@ namespace osu.Game.Screens.Ranking var req = new GetScoresRequest(Score.Beatmap, Score.Ruleset); - req.Success += r => + req.Success += r => Schedule(() => { foreach (var s in r.Scores.Select(s => s.CreateScoreInfo(rulesets))) { @@ -129,7 +129,7 @@ namespace osu.Game.Screens.Ranking panels.AddScore(s); } - }; + }); api.Queue(req); } From 80388feac4f6bbc7f87b617476c142f55511fa78 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 22 May 2020 20:39:02 +0900 Subject: [PATCH 135/871] Disable scroll user scroll controls in list --- osu.Game/Screens/Ranking/ResultsScreen.cs | 69 +++++++++++++--------- osu.Game/Screens/Ranking/ScorePanelList.cs | 4 ++ 2 files changed, 46 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index af748d8336..133bdcca4a 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -59,42 +59,57 @@ namespace osu.Game.Screens.Ranking { FillFlowContainer buttons; - InternalChildren = new[] + InternalChild = new GridContainer { - new ResultsScrollContainer + RelativeSizeAxes = Axes.Both, + Content = new[] { - Child = panels = new ScorePanelList(Score) + new Drawable[] { - RelativeSizeAxes = Axes.Both, - } - }, - bottomPanel = new Container - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = TwoLayerButton.SIZE_EXTENDED.Y, - Alpha = 0, - Children = new Drawable[] + new ResultsScrollContainer + { + Child = panels = new ScorePanelList(Score) + { + RelativeSizeAxes = Axes.Both, + } + } + }, + new[] { - new Box + bottomPanel = new Container { - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#333") - }, - buttons = new FillFlowContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(5), - Direction = FillDirection.Horizontal, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = TwoLayerButton.SIZE_EXTENDED.Y, + Alpha = 0, Children = new Drawable[] { - new ReplayDownloadButton(Score) { Width = 300 }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#333") + }, + buttons = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5), + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new ReplayDownloadButton(Score) { Width = 300 }, + } + } } } } + }, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize) } }; @@ -171,7 +186,7 @@ namespace osu.Game.Screens.Ranking protected override void Update() { base.Update(); - content.Height = Math.Max(768, DrawHeight); + content.Height = Math.Max(768 - TwoLayerButton.SIZE_EXTENDED.Y, DrawHeight); } } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index ed6d07d078..97a132b9ff 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -149,6 +149,10 @@ namespace osu.Game.Screens.Ranking base.UpdateAfterChildren(); } + + public override bool HandlePositionalInput => false; + + public override bool HandleNonPositionalInput => false; } } } From 12d65f305f24cdfd8dc0792d94eb4c6c5fa2f020 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 May 2020 22:11:55 +0900 Subject: [PATCH 136/871] Simplify and fix incorrect seeking --- osu.Game/Screens/Edit/EditorClock.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 214b45f654..dd934c10cd 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -87,15 +87,7 @@ namespace osu.Game.Screens.Edit private void seek(int direction, bool snapped, double amount = 1) { - double current = CurrentTime; - - // if a seek transform is active, use its end time instead of the reported current time. - var existingTransform = Transforms.OfType().FirstOrDefault(); - - // but only if the requested direction is in the same direction as the transform. - // this allows quick pivoting rather than resetting the transform for the first opposite direction movement. - if (existingTransform != null && Math.Sign(existingTransform.EndValue - current) == Math.Sign(direction)) - current = existingTransform.EndValue; + double current = CurrentTimeAccurate; if (amount <= 0) throw new ArgumentException("Value should be greater than zero", nameof(amount)); From 83f4ba107f6b97185c0487bb75e45516e73aa52e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 22 May 2020 22:41:06 +0900 Subject: [PATCH 137/871] Fix defaults not being applied correctly to blueprints after StartTime is changed --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index e71ccc33a4..90a114bcdc 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -34,6 +34,8 @@ namespace osu.Game.Rulesets.Edit private readonly IBindable beatmap = new Bindable(); + private Bindable startTimeBindable; + [Resolved] private IPlacementHandler placementHandler { get; set; } @@ -55,7 +57,8 @@ namespace osu.Game.Rulesets.Edit EditorClock = clock; - ApplyDefaultsToHitObject(); + startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy(); + startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true); } /// From 3d3cc2c15efbf4ac33e1a70cea6ec9dd4ecb8f5b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 22 May 2020 17:26:37 +0300 Subject: [PATCH 138/871] Dispose BeatmapOnlineLookupQueue cache download request --- osu.Game/Beatmaps/BeatmapManager.cs | 7 ++++++- .../Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs | 7 ++++++- osu.Game/OsuGameBase.cs | 1 + 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 7aaf0ca08d..b286c054e9 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -34,7 +34,7 @@ namespace osu.Game.Beatmaps /// /// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps. /// - public partial class BeatmapManager : DownloadableArchiveModelManager + public partial class BeatmapManager : DownloadableArchiveModelManager, IDisposable { /// /// Fired when a single difficulty has been hidden. @@ -433,6 +433,11 @@ namespace osu.Game.Beatmaps return endTime - startTime; } + public void Dispose() + { + onlineLookupQueue?.Dispose(); + } + /// /// A dummy WorkingBeatmap for the purpose of retrieving a beatmap for star difficulty calculation. /// diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index 2c79a664c5..d47d37806e 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -23,7 +23,7 @@ namespace osu.Game.Beatmaps { public partial class BeatmapManager { - private class BeatmapOnlineLookupQueue + private class BeatmapOnlineLookupQueue : IDisposable { private readonly IAPIProvider api; private readonly Storage storage; @@ -180,6 +180,11 @@ namespace osu.Game.Beatmaps return false; } + public void Dispose() + { + cacheDownloadRequest?.Dispose(); + } + [Serializable] [SuppressMessage("ReSharper", "InconsistentNaming")] private class CachedOnlineBeatmapLookup diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index c367c3b636..453587df18 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -337,6 +337,7 @@ namespace osu.Game { base.Dispose(isDisposing); RulesetStore?.Dispose(); + BeatmapManager?.Dispose(); contextFactory.FlushConnections(); } From 554be1c4222eec67b7926c2a4a802b6130aeae35 Mon Sep 17 00:00:00 2001 From: Olle Kelderman Date: Fri, 22 May 2020 19:25:05 +0200 Subject: [PATCH 139/871] add the ability to set the size of the Tournament Client to an arbitrary value instead of a fixed 1080p option --- osu.Game.Tournament/Screens/SetupScreen.cs | 79 ++++++++++++++++++---- 1 file changed, 67 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index c91379b2d6..f9ec29d0c6 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tournament.Screens private FillFlowContainer fillFlow; private LoginOverlay loginOverlay; - private ActionableInfo resolution; + private ActionableInfoWithNumberBox resolution; [Resolved] private MatchIPCInfo ipc { get; set; } @@ -108,18 +108,22 @@ namespace osu.Game.Tournament.Screens Items = rulesets.AvailableRulesets, Current = LadderInfo.Ruleset, }, - resolution = new ActionableInfo + resolution = new ActionableInfoWithNumberBox { Label = "Stream area resolution", - ButtonText = "Set to 1080p", - Action = () => + ButtonText = "Set size", + Action = i => { - windowSize.Value = new Size((int)(1920 / TournamentSceneManager.STREAM_AREA_WIDTH * TournamentSceneManager.REQUIRED_WIDTH), 1080); + i = Math.Clamp(i, 480, 2160); + windowSize.Value = new Size((int)(i * aspect_ratio / TournamentSceneManager.STREAM_AREA_WIDTH * TournamentSceneManager.REQUIRED_WIDTH), i); + resolution.NumberValue = i; } }, }; } + private const float aspect_ratio = 16f / 9f; + protected override void Update() { base.Update(); @@ -149,7 +153,7 @@ namespace osu.Game.Tournament.Screens private class ActionableInfo : LabelledDrawable { - private OsuButton button; + protected OsuButton Button; public ActionableInfo() : base(true) @@ -158,22 +162,22 @@ namespace osu.Game.Tournament.Screens public string ButtonText { - set => button.Text = value; + set => Button.Text = value; } public string Value { - set => valueText.Text = value; + set => ValueText.Text = value; } public bool Failing { - set => valueText.Colour = value ? Color4.Red : Color4.White; + set => ValueText.Colour = value ? Color4.Red : Color4.White; } public Action Action; - private TournamentSpriteText valueText; + protected TournamentSpriteText ValueText; protected override Drawable CreateComponent() => new Container { @@ -181,12 +185,12 @@ namespace osu.Game.Tournament.Screens RelativeSizeAxes = Axes.X, Children = new Drawable[] { - valueText = new TournamentSpriteText + ValueText = new TournamentSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - button = new TriangleButton + Button = new TriangleButton { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, @@ -196,5 +200,56 @@ namespace osu.Game.Tournament.Screens } }; } + + private class ActionableInfoWithNumberBox : ActionableInfo + { + public new Action Action; + + private OsuNumberBox numberBox; + + public int NumberValue + { + get + { + int.TryParse(numberBox.Text, out var val); + return val; + } + set => numberBox.Text = value.ToString(); + } + + protected override Drawable CreateComponent() => new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Children = new Drawable[] + { + ValueText = new TournamentSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + numberBox = new OsuNumberBox + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Width = 100, + Margin = new MarginPadding + { + Right = 110 + } + }, + Button = new TriangleButton + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Size = new Vector2(100, 30), + Action = () => + { + if (numberBox.Text.Length > 0) Action?.Invoke(NumberValue); + } + }, + } + }; + } } } From 0717dab8e4b730fcb11ff5d422ddf2f418aad01d Mon Sep 17 00:00:00 2001 From: Shivam Date: Fri, 22 May 2020 19:51:08 +0200 Subject: [PATCH 140/871] Add StablePathSelectScreen visual test --- .../TestSceneStablePathSelectScreens.cs | 30 +++++++++++++++++++ .../Screens/StablePathSelectScreen.cs | 5 ++-- 2 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreens.cs diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreens.cs b/osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreens.cs new file mode 100644 index 0000000000..f0c89ba4ca --- /dev/null +++ b/osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreens.cs @@ -0,0 +1,30 @@ +// 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.Tournament.Screens; +using osu.Framework.Platform; + +namespace osu.Game.Tournament.Tests.Screens +{ + public class TestSceneStablePathSelectScreens : TournamentTestScene + { + + public TestSceneStablePathSelectScreens() + { + AddStep("Add screen", () => Add(new TestSceneStablePathSelectScreen())); + } + + private class TestSceneStablePathSelectScreen : StablePathSelectScreen + { + protected override void changePath(Storage storage) + { + Expire(); + } + + protected override void autoDetect() + { + Expire(); + } + } + } +} diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index dcc26b8b1e..f706c42e1d 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; +using osu.Game.Overlays.Dialog; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Components; using osuTK; @@ -135,7 +136,7 @@ namespace osu.Game.Tournament.Screens }); } - private void changePath(Storage storage) + protected virtual void changePath(Storage storage) { var target = directorySelector.CurrentDirectory.Value.FullName; stableInfo.StablePath.Value = target; @@ -156,7 +157,7 @@ namespace osu.Game.Tournament.Screens sceneManager?.SetScreen(typeof(SetupScreen)); } - private void autoDetect() + protected virtual void autoDetect() { var fileBasedIpc = ipc as FileBasedIPC; fileBasedIpc?.LocateStableStorage(); From c6345ba6c94c41e53108b317c3199a3c98ed2cc1 Mon Sep 17 00:00:00 2001 From: Shivam Date: Fri, 22 May 2020 20:01:26 +0200 Subject: [PATCH 141/871] corrected styling issues --- .../Screens/TestSceneStablePathSelectScreens.cs | 5 ++--- osu.Game.Tournament/Screens/StablePathSelectScreen.cs | 9 ++++----- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreens.cs b/osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreens.cs index f0c89ba4ca..4dfd4d35c8 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreens.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreens.cs @@ -8,7 +8,6 @@ namespace osu.Game.Tournament.Tests.Screens { public class TestSceneStablePathSelectScreens : TournamentTestScene { - public TestSceneStablePathSelectScreens() { AddStep("Add screen", () => Add(new TestSceneStablePathSelectScreen())); @@ -16,12 +15,12 @@ namespace osu.Game.Tournament.Tests.Screens private class TestSceneStablePathSelectScreen : StablePathSelectScreen { - protected override void changePath(Storage storage) + protected override void ChangePath(Storage storage) { Expire(); } - protected override void autoDetect() + protected override void AutoDetect() { Expire(); } diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index f706c42e1d..609c601106 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -14,7 +14,6 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; -using osu.Game.Overlays.Dialog; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Components; using osuTK; @@ -109,7 +108,7 @@ namespace osu.Game.Tournament.Screens Origin = Anchor.Centre, Width = 300, Text = "Select stable path", - Action = () => changePath(storage) + Action = () => ChangePath(storage) }, new TriangleButton { @@ -117,7 +116,7 @@ namespace osu.Game.Tournament.Screens Origin = Anchor.Centre, Width = 300, Text = "Auto detect", - Action = autoDetect + Action = AutoDetect }, } } @@ -136,7 +135,7 @@ namespace osu.Game.Tournament.Screens }); } - protected virtual void changePath(Storage storage) + protected virtual void ChangePath(Storage storage) { var target = directorySelector.CurrentDirectory.Value.FullName; stableInfo.StablePath.Value = target; @@ -157,7 +156,7 @@ namespace osu.Game.Tournament.Screens sceneManager?.SetScreen(typeof(SetupScreen)); } - protected virtual void autoDetect() + protected virtual void AutoDetect() { var fileBasedIpc = ipc as FileBasedIPC; fileBasedIpc?.LocateStableStorage(); From bc82c2d3b7b6995f77473ea9686f8404e16c3686 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 May 2020 10:44:53 +0900 Subject: [PATCH 142/871] Move drawable addition above event bindings --- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 9ba3e30445..10f0855e33 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -40,10 +40,10 @@ namespace osu.Game.Rulesets.Osu.Edit [BackgroundDependencyLoader] private void load() { + LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both }); + EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid(); EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid(); - - LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both }); } protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(HitObjects); From a8dbfe279159b3157b573f99a125c1fd4eda9060 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 May 2020 10:57:17 +0900 Subject: [PATCH 143/871] Fix distance snap grid not disappearing when exiting playfield --- .../Edit/OsuHitObjectComposer.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 10f0855e33..62287574ea 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -86,10 +86,24 @@ namespace osu.Game.Rulesets.Osu.Edit distanceSnapGridContainer.Clear(); distanceSnapGridCache.Invalidate(); - if (BlueprintContainer.CurrentTool is SelectTool && !EditorBeatmap.SelectedHitObjects.Any()) - return; + switch (BlueprintContainer.CurrentTool) + { + case SelectTool _: + if (!EditorBeatmap.SelectedHitObjects.Any()) + return; - if ((distanceSnapGrid = createDistanceSnapGrid(EditorBeatmap.SelectedHitObjects)) != null) + distanceSnapGrid = createDistanceSnapGrid(EditorBeatmap.SelectedHitObjects); + break; + + default: + if (!CursorInPlacementArea) + return; + + distanceSnapGrid = createDistanceSnapGrid(Enumerable.Empty()); + break; + } + + if (distanceSnapGrid != null) { distanceSnapGridContainer.Add(distanceSnapGrid); distanceSnapGridCache.Validate(); From 224a3ff462cbbaf45896578511f2769bc0134dda Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 May 2020 11:44:45 +0900 Subject: [PATCH 144/871] Add note about gameplay mechanics in osu!lazer --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 59d72247f5..336bf33f7e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ Rhythm is just a *click* away. The future of [osu!](https://osu.ppy.sh) and the This project is under heavy development, but is in a stable state. Users are encouraged to try it out and keep it installed alongside the stable *osu!* client. It will continue to evolve to the point of eventually replacing the existing stable client as an update. +**IMPORTANT:** Gameplay mechanics (and other features which you may have come to know and love) are in a constant state of flux. Game balance and final quality-of-life passses come at the end of development, preceeded by experimentation and changes which may potentially **reduce playability or usability**. This is done in order to allow us to move forward as developers and designers more efficiently. If this offends you, please consider sticking to the stable releases of osu! (found on the website). We are not yet open to heated discussion over game mechanics and will not be using github as a forum for such discussions just yet. + We are accepting bug reports (please report with as much detail as possible and follow the existing issue templates). Feature requests are also welcome, but understand that our focus is on completing the game to feature parity before adding new features. A few resources are available as starting points to getting involved and understanding the project: - Detailed release changelogs are available on the [official osu! site](https://osu.ppy.sh/home/changelog/lazer). From edc46d1dce0e8f82e924de8becdb30a787b71031 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Sat, 23 May 2020 16:18:55 +0200 Subject: [PATCH 145/871] Fix osu.Game.Benchmarks --- osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs | 6 +++--- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs index 394fd75488..a6b7c8fcdb 100644 --- a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs +++ b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs @@ -8,7 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Archives; -using osu.Game.Resources; +using osu.Game.Tests.Resources; namespace osu.Game.Benchmarks { @@ -18,8 +18,8 @@ namespace osu.Game.Benchmarks public override void SetUp() { - using (var resources = new DllResourceStore(OsuResources.ResourceAssembly)) - using (var archive = resources.GetStream("Beatmaps/241526 Soleily - Renatus.osz")) + using (var resources = new DllResourceStore(typeof(TestResources).Assembly)) + using (var archive = resources.GetStream($"Resources/Archives/241526 Soleily - Renatus.osz")) using (var reader = new ZipArchiveReader(archive)) reader.GetStream("Soleily - Renatus (Gamu) [Insane].osu").CopyTo(beatmapStream); } diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 88fe8f1150..41e726e05c 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -13,6 +13,7 @@ + From de60d509e8ac00b882d091b099f9171dfb4081f3 Mon Sep 17 00:00:00 2001 From: Berkan Diler Date: Sat, 23 May 2020 17:01:34 +0200 Subject: [PATCH 146/871] Remove redundant string interpolation to fix CI --- osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs index a6b7c8fcdb..1d207d04c7 100644 --- a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs +++ b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs @@ -19,7 +19,7 @@ namespace osu.Game.Benchmarks public override void SetUp() { using (var resources = new DllResourceStore(typeof(TestResources).Assembly)) - using (var archive = resources.GetStream($"Resources/Archives/241526 Soleily - Renatus.osz")) + using (var archive = resources.GetStream("Resources/Archives/241526 Soleily - Renatus.osz")) using (var reader = new ZipArchiveReader(archive)) reader.GetStream("Soleily - Renatus (Gamu) [Insane].osu").CopyTo(beatmapStream); } From c071fe61407440f6035d35f3fef2949036ad402e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 May 2020 13:44:11 +0900 Subject: [PATCH 147/871] Add the ability to export skins --- osu.Game/Beatmaps/BeatmapManager.cs | 23 --------------- osu.Game/Database/ArchiveModelManager.cs | 28 +++++++++++++++++++ .../Overlays/Settings/Sections/SkinSection.cs | 24 ++++++++++++++++ 3 files changed, 52 insertions(+), 23 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index b286c054e9..f626b45e42 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -27,7 +27,6 @@ using osu.Game.Online.API.Requests; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using Decoder = osu.Game.Beatmaps.Formats.Decoder; -using ZipArchive = SharpCompress.Archives.Zip.ZipArchive; namespace osu.Game.Beatmaps { @@ -66,7 +65,6 @@ namespace osu.Game.Beatmaps private readonly AudioManager audioManager; private readonly GameHost host; private readonly BeatmapOnlineLookupQueue onlineLookupQueue; - private readonly Storage exportStorage; public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null, WorkingBeatmap defaultBeatmap = null) @@ -83,7 +81,6 @@ namespace osu.Game.Beatmaps beatmaps.BeatmapRestored += b => beatmapRestored.Value = new WeakReference(b); onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); - exportStorage = storage.GetStorageForDirectory("exports"); } protected override ArchiveDownloadRequest CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) => @@ -214,26 +211,6 @@ namespace osu.Game.Beatmaps workingCache.Remove(working); } - /// - /// Exports a to an .osz package. - /// - /// The to export. - public void Export(BeatmapSetInfo set) - { - var localSet = QueryBeatmapSet(s => s.ID == set.ID); - - using (var archive = ZipArchive.Create()) - { - foreach (var file in localSet.Files) - archive.AddEntry(file.Filename, Files.Storage.GetStream(file.FileInfo.StoragePath)); - - using (var outputStream = exportStorage.GetStream($"{set}.osz", FileAccess.Write, FileMode.Create)) - archive.SaveTo(outputStream); - - exportStorage.OpenInNativeExplorer(); - } - } - private readonly WeakList workingCache = new WeakList(); /// diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 33b16cbaaf..3db367555f 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -22,6 +22,7 @@ using osu.Game.IO.Archives; using osu.Game.IPC; using osu.Game.Overlays.Notifications; using osu.Game.Utils; +using SharpCompress.Archives.Zip; using SharpCompress.Common; using FileInfo = osu.Game.IO.FileInfo; @@ -82,6 +83,8 @@ namespace osu.Game.Database // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) private ArchiveImportIPCChannel ipc; + private readonly Storage exportStorage; + protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStoreWithFileIncludes modelStore, IIpcHost importHost = null) { ContextFactory = contextFactory; @@ -90,6 +93,8 @@ namespace osu.Game.Database ModelStore.ItemAdded += item => handleEvent(() => itemAdded.Value = new WeakReference(item)); ModelStore.ItemRemoved += item => handleEvent(() => itemRemoved.Value = new WeakReference(item)); + exportStorage = storage.GetStorageForDirectory("exports"); + Files = new FileStore(contextFactory, storage); if (importHost != null) @@ -369,6 +374,29 @@ namespace osu.Game.Database return item; }, cancellationToken, TaskCreationOptions.HideScheduler, import_scheduler).Unwrap(); + /// + /// Exports an item to an legacy (.zip based) package. + /// + /// The item to export. + public void Export(TModel item) + { + var retrievedItem = ModelStore.ConsumableItems.FirstOrDefault(s => s.ID == item.ID); + + if (retrievedItem == null) + throw new ArgumentException("Specified model count not be found", nameof(item)); + + using (var archive = ZipArchive.Create()) + { + foreach (var file in retrievedItem.Files) + archive.AddEntry(file.Filename, Files.Storage.GetStream(file.FileInfo.StoragePath)); + + using (var outputStream = exportStorage.GetStream($"{item}{HandledExtensions.First()}", FileAccess.Write, FileMode.Create)) + archive.SaveTo(outputStream); + + exportStorage.OpenInNativeExplorer(); + } + } + public void UpdateFile(TModel model, TFileModel file, Stream contents) { using (var usage = ContextFactory.GetForWrite()) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 94080f5592..a89f2c26c8 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Skinning; @@ -34,13 +35,33 @@ namespace osu.Game.Overlays.Settings.Sections private IBindable> managerAdded; private IBindable> managerRemoved; + private SettingsButton exportButton; + + private Bindable currentSkin; + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { FlowContent.Spacing = new Vector2(0, 5); + Children = new Drawable[] { skinDropdown = new SkinSettingsDropdown(), + exportButton = new SettingsButton + { + Text = "Export selected skin", + Action = () => + { + try + { + skins.Export(skins.CurrentSkin.Value.SkinInfo); + } + catch (Exception) + { + Logger.Log("Could not export current skin", level: LogLevel.Error); + } + } + }, new SettingsSlider { LabelText = "Menu cursor size", @@ -81,6 +102,9 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Bindable = dropdownBindable; skinDropdown.Items = skins.GetAllUsableSkins().ToArray(); + currentSkin = skins.CurrentSkin.GetBoundCopy(); + currentSkin.BindValueChanged(skin => exportButton.Enabled.Value = skin.NewValue.SkinInfo.ID > 0); + // Todo: This should not be necessary when OsuConfigManager is databased if (skinDropdown.Items.All(s => s.ID != configBindable.Value)) configBindable.Value = 0; From f277b0c99f2a53f7b7bd92e0f748e1e206fe452c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 May 2020 22:30:56 +0900 Subject: [PATCH 148/871] Use better formatting for skin display (matching BeatmapMetadata) --- osu.Game/Skinning/SkinInfo.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 6b9627188e..b9fe44ef3b 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -24,8 +24,6 @@ namespace osu.Game.Skinning public bool DeletePending { get; set; } - public string FullName => $"\"{Name}\" by {Creator}"; - public static SkinInfo Default { get; } = new SkinInfo { Name = "osu!lazer", @@ -34,6 +32,10 @@ namespace osu.Game.Skinning public bool Equals(SkinInfo other) => other != null && ID == other.ID; - public override string ToString() => FullName; + public override string ToString() + { + string author = Creator == null ? string.Empty : $"({Creator})"; + return $"{Name} {author}".Trim(); + } } } From 234fa2844574e81ac520b90974af0350b750f070 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 May 2020 22:34:31 +0900 Subject: [PATCH 149/871] Ensure export filename is valid --- osu.Game/Database/ArchiveModelManager.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 3db367555f..9fc1bfceb5 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -390,7 +390,7 @@ namespace osu.Game.Database foreach (var file in retrievedItem.Files) archive.AddEntry(file.Filename, Files.Storage.GetStream(file.FileInfo.StoragePath)); - using (var outputStream = exportStorage.GetStream($"{item}{HandledExtensions.First()}", FileAccess.Write, FileMode.Create)) + using (var outputStream = exportStorage.GetStream($"{getValidFilename(item.ToString())}{HandledExtensions.First()}", FileAccess.Write, FileMode.Create)) archive.SaveTo(outputStream); exportStorage.OpenInNativeExplorer(); @@ -738,5 +738,12 @@ namespace osu.Game.Database } #endregion + + private string getValidFilename(string filename) + { + foreach (char c in Path.GetInvalidFileNameChars()) + filename = filename.Replace(c, '_'); + return filename; + } } } From 904d17224f75260783a416170ce6b53ee1edd8c5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 May 2020 23:09:38 +0900 Subject: [PATCH 150/871] Fix english --- osu.Game/Database/ArchiveModelManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 9fc1bfceb5..f21f708f95 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -375,7 +375,7 @@ namespace osu.Game.Database }, cancellationToken, TaskCreationOptions.HideScheduler, import_scheduler).Unwrap(); /// - /// Exports an item to an legacy (.zip based) package. + /// Exports an item to a legacy (.zip based) package. /// /// The item to export. public void Export(TModel item) @@ -383,7 +383,7 @@ namespace osu.Game.Database var retrievedItem = ModelStore.ConsumableItems.FirstOrDefault(s => s.ID == item.ID); if (retrievedItem == null) - throw new ArgumentException("Specified model count not be found", nameof(item)); + throw new ArgumentException("Specified model could not be found", nameof(item)); using (var archive = ZipArchive.Create()) { From 8ab65e4c5d3083682ea2fde406dbfcd229fca359 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 24 May 2020 23:15:24 +0900 Subject: [PATCH 151/871] Move implementation into own class --- .../Overlays/Settings/Sections/SkinSection.cs | 53 +++++++++++-------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index a89f2c26c8..b84b9fec37 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -35,10 +35,6 @@ namespace osu.Game.Overlays.Settings.Sections private IBindable> managerAdded; private IBindable> managerRemoved; - private SettingsButton exportButton; - - private Bindable currentSkin; - [BackgroundDependencyLoader] private void load(OsuConfigManager config) { @@ -47,21 +43,7 @@ namespace osu.Game.Overlays.Settings.Sections Children = new Drawable[] { skinDropdown = new SkinSettingsDropdown(), - exportButton = new SettingsButton - { - Text = "Export selected skin", - Action = () => - { - try - { - skins.Export(skins.CurrentSkin.Value.SkinInfo); - } - catch (Exception) - { - Logger.Log("Could not export current skin", level: LogLevel.Error); - } - } - }, + new ExportSkinButton(), new SettingsSlider { LabelText = "Menu cursor size", @@ -102,9 +84,6 @@ namespace osu.Game.Overlays.Settings.Sections skinDropdown.Bindable = dropdownBindable; skinDropdown.Items = skins.GetAllUsableSkins().ToArray(); - currentSkin = skins.CurrentSkin.GetBoundCopy(); - currentSkin.BindValueChanged(skin => exportButton.Enabled.Value = skin.NewValue.SkinInfo.ID > 0); - // Todo: This should not be necessary when OsuConfigManager is databased if (skinDropdown.Items.All(s => s.ID != configBindable.Value)) configBindable.Value = 0; @@ -141,5 +120,35 @@ namespace osu.Game.Overlays.Settings.Sections protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 200); } } + + private class ExportSkinButton : SettingsButton + { + [Resolved] + private SkinManager skins { get; set; } + + private Bindable currentSkin; + + [BackgroundDependencyLoader] + private void load() + { + Text = "Export selected skin"; + Action = export; + + currentSkin = skins.CurrentSkin.GetBoundCopy(); + currentSkin.BindValueChanged(skin => Enabled.Value = skin.NewValue.SkinInfo.ID > 0, true); + } + + private void export() + { + try + { + skins.Export(currentSkin.Value.SkinInfo); + } + catch (Exception e) + { + Logger.Log($"Could not export current skin: {e.Message}", level: LogLevel.Error); + } + } + } } } From 1062e07ec12f017da02a903f0ad1189468e8fe53 Mon Sep 17 00:00:00 2001 From: Olle Kelderman Date: Sun, 24 May 2020 22:24:46 +0200 Subject: [PATCH 152/871] refactor and implemented feedback: - button text change - renamed ActionableInfoWithNumberBox to ResolutionSelector and moved the clamping logic inside it - also removed the ugly right margin and added the FillFlowContainer --- osu.Game.Tournament/Screens/SetupScreen.cs | 99 +++++++++++----------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index f9ec29d0c6..33eefbe553 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -25,7 +25,7 @@ namespace osu.Game.Tournament.Screens private FillFlowContainer fillFlow; private LoginOverlay loginOverlay; - private ActionableInfoWithNumberBox resolution; + private ResolutionSelector resolution; [Resolved] private MatchIPCInfo ipc { get; set; } @@ -108,15 +108,13 @@ namespace osu.Game.Tournament.Screens Items = rulesets.AvailableRulesets, Current = LadderInfo.Ruleset, }, - resolution = new ActionableInfoWithNumberBox + resolution = new ResolutionSelector { Label = "Stream area resolution", - ButtonText = "Set size", + ButtonText = "Set height", Action = i => { - i = Math.Clamp(i, 480, 2160); windowSize.Value = new Size((int)(i * aspect_ratio / TournamentSceneManager.STREAM_AREA_WIDTH * TournamentSceneManager.REQUIRED_WIDTH), i); - resolution.NumberValue = i; } }, }; @@ -167,17 +165,18 @@ namespace osu.Game.Tournament.Screens public string Value { - set => ValueText.Text = value; + set => valueText.Text = value; } public bool Failing { - set => ValueText.Colour = value ? Color4.Red : Color4.White; + set => valueText.Colour = value ? Color4.Red : Color4.White; } public Action Action; - protected TournamentSpriteText ValueText; + private TournamentSpriteText valueText; + protected FillFlowContainer FlowContainer; protected override Drawable CreateComponent() => new Container { @@ -185,71 +184,75 @@ namespace osu.Game.Tournament.Screens RelativeSizeAxes = Axes.X, Children = new Drawable[] { - ValueText = new TournamentSpriteText + valueText = new TournamentSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, }, - Button = new TriangleButton + FlowContainer = new FillFlowContainer { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Size = new Vector2(100, 30), - Action = () => Action?.Invoke() - }, + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + Button = new TriangleButton + { + Size = new Vector2(100, 30), + Action = () => Action?.Invoke() + } + } + } } }; } - private class ActionableInfoWithNumberBox : ActionableInfo + private class ResolutionSelector : ActionableInfo { + private const int height_min_allowed_value = 480; + private const int height_max_allowed_value = 2160; // 4k public new Action Action; private OsuNumberBox numberBox; - public int NumberValue + protected override Drawable CreateComponent() { - get + var drawable = base.CreateComponent(); + FlowContainer.Insert(0, numberBox = new OsuNumberBox { - int.TryParse(numberBox.Text, out var val); - return val; - } - set => numberBox.Text = value.ToString(); - } + Width = 100 + }); + FlowContainer.SetLayoutPosition(Button, 1); - protected override Drawable CreateComponent() => new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - Children = new Drawable[] + base.Action = () => { - ValueText = new TournamentSpriteText + if (numberBox.Text.Length > 0) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - }, - numberBox = new OsuNumberBox - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Width = 100, - Margin = new MarginPadding + // box contains text + if (!int.TryParse(numberBox.Text, out var number)) { - Right = 110 + // at this point, the only reason we can arrive here is if the input number was too big to parse into an int + // so clamp to max allowed value + number = height_max_allowed_value; } - }, - Button = new TriangleButton - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Size = new Vector2(100, 30), - Action = () => + else { - if (numberBox.Text.Length > 0) Action?.Invoke(NumberValue); + number = Math.Clamp(number, height_min_allowed_value, height_max_allowed_value); } - }, - } - }; + + // in case number got clamped, reset number in numberBox + numberBox.Text = number.ToString(); + + Action?.Invoke(number); + } + else + { + // TODO: input box was empty, give user feedback? do nothing? + } + }; + return drawable; + } } } } From a174117880874df70d5cd07d210db4117a1d7cee Mon Sep 17 00:00:00 2001 From: Olle Kelderman Date: Mon, 25 May 2020 00:55:10 +0200 Subject: [PATCH 153/871] fix flowcontainer order properly and removed todo as its decided to do nothing there for now --- osu.Game.Tournament/Screens/SetupScreen.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 33eefbe553..bf328987fe 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -151,7 +151,7 @@ namespace osu.Game.Tournament.Screens private class ActionableInfo : LabelledDrawable { - protected OsuButton Button; + private OsuButton button; public ActionableInfo() : base(true) @@ -160,7 +160,7 @@ namespace osu.Game.Tournament.Screens public string ButtonText { - set => Button.Text = value; + set => button.Text = value; } public string Value @@ -197,7 +197,7 @@ namespace osu.Game.Tournament.Screens Spacing = new Vector2(10, 0), Children = new Drawable[] { - Button = new TriangleButton + button = new TriangleButton { Size = new Vector2(100, 30), Action = () => Action?.Invoke() @@ -219,11 +219,10 @@ namespace osu.Game.Tournament.Screens protected override Drawable CreateComponent() { var drawable = base.CreateComponent(); - FlowContainer.Insert(0, numberBox = new OsuNumberBox + FlowContainer.Insert(-1, numberBox = new OsuNumberBox { Width = 100 }); - FlowContainer.SetLayoutPosition(Button, 1); base.Action = () => { @@ -246,10 +245,6 @@ namespace osu.Game.Tournament.Screens Action?.Invoke(number); } - else - { - // TODO: input box was empty, give user feedback? do nothing? - } }; return drawable; } From 1977affe7e6fac5e263011ba20768d1401dcafe3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 May 2020 09:27:11 +0900 Subject: [PATCH 154/871] Fix OpenInNativeExplorer not working correctly for wrapped storages --- osu.Game/IO/WrappedStorage.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/IO/WrappedStorage.cs b/osu.Game/IO/WrappedStorage.cs index 646faba9eb..cd775df0fd 100644 --- a/osu.Game/IO/WrappedStorage.cs +++ b/osu.Game/IO/WrappedStorage.cs @@ -69,7 +69,9 @@ namespace osu.Game.IO public override void DeleteDatabase(string name) => UnderlyingStorage.DeleteDatabase(MutatePath(name)); - public override void OpenInNativeExplorer() => UnderlyingStorage.OpenInNativeExplorer(); + public override void OpenInNativeExplorer() => UnderlyingStorage.OpenPathInNativeExplorer(subPath); + + public override void OpenPathInNativeExplorer(string path) => UnderlyingStorage.OpenPathInNativeExplorer(MutatePath(path)); public override Storage GetStorageForDirectory(string path) { From 6904d5d2470c9164f593527a1bdc9d59a6d3f3bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 May 2020 13:12:53 +0900 Subject: [PATCH 155/871] Remove unnecessary override --- osu.Game/IO/WrappedStorage.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/IO/WrappedStorage.cs b/osu.Game/IO/WrappedStorage.cs index cd775df0fd..1dd3afbfae 100644 --- a/osu.Game/IO/WrappedStorage.cs +++ b/osu.Game/IO/WrappedStorage.cs @@ -69,8 +69,6 @@ namespace osu.Game.IO public override void DeleteDatabase(string name) => UnderlyingStorage.DeleteDatabase(MutatePath(name)); - public override void OpenInNativeExplorer() => UnderlyingStorage.OpenPathInNativeExplorer(subPath); - public override void OpenPathInNativeExplorer(string path) => UnderlyingStorage.OpenPathInNativeExplorer(MutatePath(path)); public override Storage GetStorageForDirectory(string path) From b44beb413729bb40dc5294c194454ea0707ddf3e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 May 2020 15:40:25 +0900 Subject: [PATCH 156/871] Remove double resolution of EditorClock --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index 1e0507fc0a..e2bb8b5995 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Edit /// protected readonly HitObject HitObject; - [Resolved] + [Resolved(canBeNull: true)] protected EditorClock EditorClock { get; private set; } private readonly IBindable beatmap = new Bindable(); @@ -85,9 +85,6 @@ namespace osu.Game.Rulesets.Edit PlacementActive = false; } - [Resolved(canBeNull: true)] - private EditorClock editorClock { get; set; } - /// /// Updates the position of this to a new screen-space position. /// @@ -95,7 +92,7 @@ namespace osu.Game.Rulesets.Edit public virtual void UpdatePosition(SnapResult snapResult) { if (!PlacementActive) - HitObject.StartTime = snapResult.Time ?? editorClock?.CurrentTime ?? Time.Current; + HitObject.StartTime = snapResult.Time ?? EditorClock?.CurrentTime ?? Time.Current; } /// From cd65fc860b56f7fb2132e547e93ab068e49b308b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 25 May 2020 16:15:55 +0900 Subject: [PATCH 157/871] Remove extra default application --- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index e2bb8b5995..3541a78faa 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -56,8 +56,6 @@ namespace osu.Game.Rulesets.Edit { this.beatmap.BindTo(beatmap); - ApplyDefaultsToHitObject(); - startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy(); startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true); } From 6f4cd6111cbdeb04d4e22f88f1693bd9f05e9dad Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 25 May 2020 16:50:49 +0900 Subject: [PATCH 158/871] Drop obsoletion status for now --- osu.Game/Rulesets/Objects/HitObject.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 8ff2bdefb3..6f9053d7cb 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -146,7 +146,6 @@ namespace osu.Game.Rulesets.Objects #pragma warning restore 618 } - [Obsolete("Use the overload with cancellation support instead.")] // can be removed 20201115 protected virtual void CreateNestedHitObjects() { } From d09b579aeee7ca9ca75f78e3bc67754882f1800e Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 25 May 2020 09:32:24 +0000 Subject: [PATCH 159/871] Bump DiffPlex from 1.6.1 to 1.6.2 Bumps [DiffPlex](https://github.com/mmanela/diffplex) from 1.6.1 to 1.6.2. - [Release notes](https://github.com/mmanela/diffplex/releases) - [Commits](https://github.com/mmanela/diffplex/commits) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 010ef8578a..dbaf0697a0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -19,7 +19,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 88b0c7dd8a..664a629369 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -75,7 +75,7 @@ - + From c4665048dbd036ee6f492eb45f00bde4a38eedd7 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 25 May 2020 09:34:26 +0000 Subject: [PATCH 160/871] Bump SharpCompress from 0.25.0 to 0.25.1 Bumps [SharpCompress](https://github.com/adamhathcock/sharpcompress) from 0.25.0 to 0.25.1. - [Release notes](https://github.com/adamhathcock/sharpcompress/releases) - [Commits](https://github.com/adamhathcock/sharpcompress/compare/0.25...0.25.1) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index dbaf0697a0..d8feb4df24 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -27,7 +27,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 664a629369..6d74be5f85 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -81,7 +81,7 @@ - + From af5fac471e56a2b819983ac49067e094e6ea8850 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 May 2020 13:54:30 +0900 Subject: [PATCH 161/871] Remove unnecessary size propagation in HitObjectComposer --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 37 +++++---------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 1987148aed..e5479251b5 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -51,8 +51,6 @@ namespace osu.Game.Rulesets.Edit protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both }; - private readonly List layerContainers = new List(); - private InputManager inputManager; private RadioButtonCollection toolboxCollection; @@ -82,17 +80,6 @@ namespace osu.Game.Rulesets.Edit return; } - var layerBelowRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChildren(new Drawable[] - { - LayerBelowRuleset, - new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both } - }); - - var layerAboveRuleset = drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChild(BlueprintContainer = CreateBlueprintContainer()); - - layerContainers.Add(layerBelowRuleset); - layerContainers.Add(layerAboveRuleset); - InternalChild = new GridContainer { RelativeSizeAxes = Axes.Both, @@ -116,9 +103,16 @@ namespace osu.Game.Rulesets.Edit RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - layerBelowRuleset, + // layers below playfield + drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChildren(new Drawable[] + { + LayerBelowRuleset, + new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both } + }), drawableRulesetWrapper, - layerAboveRuleset + // layers above playfield + drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer() + .WithChild(BlueprintContainer = CreateBlueprintContainer()) } } }, @@ -162,19 +156,6 @@ namespace osu.Game.Rulesets.Edit inputManager = GetContainingInputManager(); } - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - layerContainers.ForEach(l => - { - l.Anchor = drawableRulesetWrapper.Playfield.Anchor; - l.Origin = drawableRulesetWrapper.Playfield.Origin; - l.Position = drawableRulesetWrapper.Playfield.Position; - l.Size = drawableRulesetWrapper.Playfield.Size; - }); - } - private void selectionChanged(object sender, NotifyCollectionChangedEventArgs changedArgs) { if (EditorBeatmap.SelectedHitObjects.Any()) From b8130bd3669f447a8179c13ba889207f261c262c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 May 2020 18:26:21 +0900 Subject: [PATCH 162/871] Make mania selection blueprint abstract --- .../Edit/Blueprints/ManiaSelectionBlueprint.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index b8574b804e..0089a9fbee 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -11,7 +11,7 @@ using osuTK; namespace osu.Game.Rulesets.Mania.Edit.Blueprints { - public class ManiaSelectionBlueprint : OverlaySelectionBlueprint + public abstract class ManiaSelectionBlueprint : OverlaySelectionBlueprint { public new DrawableManiaHitObject DrawableObject => (DrawableManiaHitObject)base.DrawableObject; @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IManiaHitObjectComposer composer { get; set; } - public ManiaSelectionBlueprint(DrawableHitObject drawableObject) + protected ManiaSelectionBlueprint(DrawableHitObject drawableObject) : base(drawableObject) { RelativeSizeAxes = Axes.None; From 2c16619ecd9efa009601eefd0fdc3068fc4e4838 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 May 2020 18:26:28 +0900 Subject: [PATCH 163/871] Move time to position conversion to ScrollingHitObjectContainer --- osu.Game.Rulesets.Mania/UI/Column.cs | 50 ----------- .../Scrolling/ScrollingHitObjectContainer.cs | 87 +++++++++++++++++++ .../UI/Scrolling/ScrollingPlayfield.cs | 13 +++ 3 files changed, 100 insertions(+), 50 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/Column.cs b/osu.Game.Rulesets.Mania/UI/Column.cs index be31954099..511d6c8623 100644 --- a/osu.Game.Rulesets.Mania/UI/Column.cs +++ b/osu.Game.Rulesets.Mania/UI/Column.cs @@ -17,7 +17,6 @@ using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; using osu.Game.Rulesets.Mania.Beatmaps; -using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Mania.UI { @@ -143,54 +142,5 @@ namespace osu.Game.Rulesets.Mania.UI public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) // This probably shouldn't exist as is, but the columns in the stage are separated by a 1px border => DrawRectangle.Inflate(new Vector2(Stage.COLUMN_SPACING / 2, 0)).Contains(ToLocalSpace(screenSpacePos)); - - /// - /// Given a time, return the screen space position within this column. - /// - public Vector2 ScreenSpacePositionAtTime(double time) - { - var pos = ScrollingInfo.Algorithm.PositionAt(time, Time.Current, ScrollingInfo.TimeRange.Value, HitObjectContainer.DrawHeight); - - switch (ScrollingInfo.Direction.Value) - { - case ScrollingDirection.Down: - // We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time. - // The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position, - // so when scrolling downwards the coordinates need to be flipped. - pos = HitObjectContainer.DrawHeight - pos; - - // Blueprints are centred on the mouse position, such that the hitobject position is anchored at the top or bottom of the blueprint depending on the scroll direction. - pos -= DefaultNotePiece.NOTE_HEIGHT / 2; - break; - - case ScrollingDirection.Up: - pos += DefaultNotePiece.NOTE_HEIGHT / 2; - break; - } - - return HitObjectContainer.ToScreenSpace(new Vector2(HitObjectContainer.DrawWidth / 2, pos)); - } - - /// - /// Given a position in screen space, return the time within this column. - /// - public double TimeAtScreenSpacePosition(Vector2 screenSpacePosition) - { - // convert to local space of column so we can snap and fetch correct location. - Vector2 localPosition = HitObjectContainer.ToLocalSpace(screenSpacePosition); - - switch (ScrollingInfo.Direction.Value) - { - case ScrollingDirection.Down: - // as above - localPosition.Y = HitObjectContainer.DrawHeight - localPosition.Y; - break; - } - - // offset for the fact that blueprints are centered, as above. - localPosition.Y -= DefaultNotePiece.NOTE_HEIGHT / 2; - - return ScrollingInfo.Algorithm.TimeAt(localPosition.Y, Time.Current, ScrollingInfo.TimeRange.Value, HitObjectContainer.DrawHeight); - } } } diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 15e625872d..4ef2c04f23 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Layout; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; +using osuTK; namespace osu.Game.Rulesets.UI.Scrolling { @@ -78,6 +79,92 @@ namespace osu.Game.Rulesets.UI.Scrolling hitObjectInitialStateCache.Clear(); } + public double TimeAtScreenSpace(Vector2 screenSpacePosition) + { + // convert to local space of column so we can snap and fetch correct location. + Vector2 localPosition = ToLocalSpace(screenSpacePosition); + + float position = 0; + + switch (scrollingInfo.Direction.Value) + { + case ScrollingDirection.Up: + case ScrollingDirection.Down: + position = localPosition.Y; + break; + + case ScrollingDirection.Right: + case ScrollingDirection.Left: + position = localPosition.X; + break; + } + + flipPositionIfRequired(ref position); + + return scrollingInfo.Algorithm.TimeAt(position, Time.Current, scrollingInfo.TimeRange.Value, getLength()); + } + + public Vector2 ScreenSpacePositionAtTime(double time) + { + var pos = scrollingInfo.Algorithm.PositionAt(time, Time.Current, scrollingInfo.TimeRange.Value, getLength()); + + flipPositionIfRequired(ref pos); + + switch (scrollingInfo.Direction.Value) + { + case ScrollingDirection.Up: + case ScrollingDirection.Down: + return ToScreenSpace(new Vector2(getBredth() / 2, pos)); + + default: + return ToScreenSpace(new Vector2(pos, getBredth() / 2)); + } + } + + private float getLength() + { + switch (scrollingInfo.Direction.Value) + { + case ScrollingDirection.Left: + case ScrollingDirection.Right: + return DrawWidth; + + default: + return DrawHeight; + } + } + + private float getBredth() + { + switch (scrollingInfo.Direction.Value) + { + case ScrollingDirection.Up: + case ScrollingDirection.Down: + return DrawWidth; + + default: + return DrawHeight; + } + } + + private void flipPositionIfRequired(ref float position) + { + // We're dealing with screen coordinates in which the position decreases towards the centre of the screen resulting in an increase in start time. + // The scrolling algorithm instead assumes a top anchor meaning an increase in time corresponds to an increase in position, + // so when scrolling downwards the coordinates need to be flipped. + + switch (scrollingInfo.Direction.Value) + { + case ScrollingDirection.Down: + position = DrawHeight - position; + break; + + case ScrollingDirection.Right: + position = DrawWidth - position; + break; + } + } + private void onDefaultsApplied(DrawableHitObject drawableObject) { // The cache may not exist if the hitobject state hasn't been computed yet (e.g. if the hitobject was added + defaults applied in the same frame). diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs index fd143a3687..1ccde9b1e3 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Rulesets.Objects.Drawables; +using osuTK; namespace osu.Game.Rulesets.UI.Scrolling { @@ -23,6 +24,18 @@ namespace osu.Game.Rulesets.UI.Scrolling Direction.BindTo(ScrollingInfo.Direction); } + /// + /// Given a position in screen space, return the time within this column. + /// + public virtual double TimeAtScreenSpacePosition(Vector2 screenSpacePosition) => + ((ScrollingHitObjectContainer)HitObjectContainer).TimeAtScreenSpace(screenSpacePosition); + + /// + /// Given a time, return the screen space position within this column. + /// + public virtual Vector2 ScreenSpacePositionAtTime(double time) + => ((ScrollingHitObjectContainer)HitObjectContainer).ScreenSpacePositionAtTime(time); + protected sealed override HitObjectContainer CreateHitObjectContainer() => new ScrollingHitObjectContainer(); } } From e7442ec3a287d7887747bb392e5502bb8fcca387 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 May 2020 19:21:53 +0900 Subject: [PATCH 164/871] Remove need for ManiaSnapResult --- .../ManiaPlacementBlueprintTestScene.cs | 2 +- .../Blueprints/HoldNotePlacementBlueprint.cs | 4 ++-- .../Blueprints/ManiaPlacementBlueprint.cs | 2 +- .../Edit/Blueprints/NotePlacementBlueprint.cs | 4 ++-- .../Edit/ManiaHitObjectComposer.cs | 19 ++-------------- .../Edit/ManiaSnapResult.cs | 20 ----------------- .../Edit/OsuHitObjectComposer.cs | 2 +- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 22 ++++++++++++++++++- osu.Game/Rulesets/Edit/SnapResult.cs | 6 ++++- 9 files changed, 35 insertions(+), 46 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania/Edit/ManiaSnapResult.cs diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs index fd18907d96..1119a66f63 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mania.Tests var time = column.TimeAtScreenSpacePosition(InputManager.CurrentState.Mouse.Position); var pos = column.ScreenSpacePositionAtTime(time); - return new ManiaSnapResult(pos, time, column); + return new SnapResult(pos, time, column); } protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index b757c17a48..cd549ab6aa 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -78,9 +78,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints } else { - if (result is ManiaSnapResult maniaResult) + if (result.Playfield != null) { - headPiece.Width = tailPiece.Width = maniaResult.Column.DrawWidth; + headPiece.Width = tailPiece.Width = result.Playfield.DrawWidth; headPiece.X = tailPiece.X = ToLocalSpace(result.ScreenSpacePosition).X; } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs index d173da9d9a..27a279e044 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaPlacementBlueprint.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints base.UpdatePosition(result); if (!PlacementActive) - Column = (result as ManiaSnapResult)?.Column; + Column = result.Playfield as Column; } } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs index 5f6db2e6dd..684004b558 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/NotePlacementBlueprint.cs @@ -26,9 +26,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { base.UpdatePosition(result); - if (result is ManiaSnapResult maniaResult) + if (result.Playfield != null) { - piece.Width = maniaResult.Column.DrawWidth; + piece.Width = result.Playfield.DrawWidth; piece.Position = ToLocalSpace(result.ScreenSpacePosition); } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 683e921cbf..83bf202674 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -53,23 +53,8 @@ namespace osu.Game.Rulesets.Mania.Edit public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo; - public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) - { - var column = Playfield.GetColumnByPosition(screenSpacePosition); - - if (column == null) - return new SnapResult(screenSpacePosition, null); - - double targetTime = column.TimeAtScreenSpacePosition(screenSpacePosition); - - // apply beat snapping - targetTime = BeatSnapProvider.SnapTime(targetTime); - - // convert back to screen space - screenSpacePosition = column.ScreenSpacePositionAtTime(targetTime); - - return new ManiaSnapResult(screenSpacePosition, targetTime, column); - } + protected override Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) => + Playfield.GetColumnByPosition(screenSpacePosition); protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) { diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSnapResult.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSnapResult.cs deleted file mode 100644 index b94f5e51c4..0000000000 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSnapResult.cs +++ /dev/null @@ -1,20 +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 osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Mania.UI; -using osuTK; - -namespace osu.Game.Rulesets.Mania.Edit -{ - public class ManiaSnapResult : SnapResult - { - public readonly Column Column; - - public ManiaSnapResult(Vector2 screenSpacePosition, double time, Column column) - : base(screenSpacePosition, time) - { - Column = column; - } - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 62287574ea..9e7dc11fdd 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Osu.Edit (Vector2 pos, double time) = distanceSnapGrid.GetSnappedPosition(distanceSnapGrid.ToLocalSpace(screenSpacePosition)); - return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time); + return new SnapResult(distanceSnapGrid.ToScreenSpace(pos), time, PlayfieldAtScreenSpacePosition(screenSpacePosition)); } private void updateDistanceSnapGrid() diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 1987148aed..b26284c060 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -19,6 +19,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; +using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Components.RadioButtons; using osu.Game.Screens.Edit.Compose; @@ -224,7 +225,26 @@ namespace osu.Game.Rulesets.Edit public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject); - public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) => new SnapResult(screenSpacePosition, null); + protected virtual Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) => drawableRulesetWrapper.Playfield; + + public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) + { + var playfield = PlayfieldAtScreenSpacePosition(screenSpacePosition); + double? targetTime = null; + + if (playfield is ScrollingPlayfield scrollingPlayfield) + { + targetTime = scrollingPlayfield.TimeAtScreenSpacePosition(screenSpacePosition); + + // apply beat snapping + targetTime = BeatSnapProvider.SnapTime(targetTime.Value); + + // convert back to screen space + screenSpacePosition = scrollingPlayfield.ScreenSpacePositionAtTime(targetTime.Value); + } + + return new SnapResult(screenSpacePosition, targetTime, playfield); + } public override float GetBeatSnapDistanceAt(double referenceTime) { diff --git a/osu.Game/Rulesets/Edit/SnapResult.cs b/osu.Game/Rulesets/Edit/SnapResult.cs index 5d07d7b233..31dd2b9496 100644 --- a/osu.Game/Rulesets/Edit/SnapResult.cs +++ b/osu.Game/Rulesets/Edit/SnapResult.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 osu.Game.Rulesets.UI; using osuTK; namespace osu.Game.Rulesets.Edit @@ -20,10 +21,13 @@ namespace osu.Game.Rulesets.Edit /// public double? Time; - public SnapResult(Vector2 screenSpacePosition, double? time) + public readonly Playfield Playfield; + + public SnapResult(Vector2 screenSpacePosition, double? time, Playfield playfield = null) { ScreenSpacePosition = screenSpacePosition; Time = time; + Playfield = playfield; } } } From 827345ed88f6dd6792ebd1cfc4a16989cd163723 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 May 2020 20:21:06 +0900 Subject: [PATCH 165/871] Fix mania offsets --- .../Blueprints/HoldNotePlacementBlueprint.cs | 17 ++++++++++++++ .../Edit/ManiaHitObjectComposer.cs | 23 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index cd549ab6aa..500b26917d 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -8,6 +8,7 @@ using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.UI.Scrolling; using osuTK; using osuTK.Input; @@ -22,6 +23,9 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IManiaHitObjectComposer composer { get; set; } + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } + public HoldNotePlacementBlueprint() : base(new HoldNote()) { @@ -43,6 +47,19 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints { headPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.StartTime)).Y; tailPiece.Y = Parent.ToLocalSpace(Column.ScreenSpacePositionAtTime(HitObject.EndTime)).Y; + + switch (scrollingInfo.Direction.Value) + { + case ScrollingDirection.Down: + headPiece.Y -= headPiece.DrawHeight / 2; + tailPiece.Y -= tailPiece.DrawHeight / 2; + break; + + case ScrollingDirection.Up: + headPiece.Y += headPiece.DrawHeight / 2; + tailPiece.Y += tailPiece.DrawHeight / 2; + break; + } } var topPosition = new Vector2(headPiece.DrawPosition.X, Math.Min(headPiece.DrawPosition.Y, tailPiece.DrawPosition.Y)); diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 83bf202674..73cbadc97c 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -9,6 +9,7 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Input; +using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -56,6 +57,28 @@ namespace osu.Game.Rulesets.Mania.Edit protected override Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) => Playfield.GetColumnByPosition(screenSpacePosition); + public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) + { + var result = base.SnapScreenSpacePositionToValidTime(screenSpacePosition); + + switch (ScrollingInfo.Direction.Value) + { + case ScrollingDirection.Down: + result.ScreenSpacePosition -= new Vector2(0, getNoteHeight() / 2); + break; + + case ScrollingDirection.Up: + result.ScreenSpacePosition += new Vector2(0, getNoteHeight() / 2); + break; + } + + return result; + } + + private float getNoteHeight() => + Playfield.GetColumn(0).ToScreenSpace(new Vector2(DefaultNotePiece.NOTE_HEIGHT)).Y - + Playfield.GetColumn(0).ToScreenSpace(Vector2.Zero).Y; + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) { drawableRuleset = new DrawableManiaEditRuleset(ruleset, beatmap, mods); From 8fc60d12c910dabdb765a93a403ab928d488a506 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 May 2020 22:09:09 +0900 Subject: [PATCH 166/871] Add back comments --- .../Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 4ef2c04f23..544468fd47 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -79,6 +79,9 @@ namespace osu.Game.Rulesets.UI.Scrolling hitObjectInitialStateCache.Clear(); } + /// + /// Given a position in screen space, return the time within this column. + /// public double TimeAtScreenSpace(Vector2 screenSpacePosition) { // convert to local space of column so we can snap and fetch correct location. @@ -104,6 +107,9 @@ namespace osu.Game.Rulesets.UI.Scrolling return scrollingInfo.Algorithm.TimeAt(position, Time.Current, scrollingInfo.TimeRange.Value, getLength()); } + /// + /// Given a time, return the screen space position within this column. + /// public Vector2 ScreenSpacePositionAtTime(double time) { var pos = scrollingInfo.Algorithm.PositionAt(time, Time.Current, scrollingInfo.TimeRange.Value, getLength()); From cf341998c360fb3389dea953344c8bbc49cd11b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 May 2020 22:55:21 +0900 Subject: [PATCH 167/871] 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 f0f16d3763..b7d08fb120 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d8feb4df24..d5017a436f 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 6d74be5f85..19a36f1e1f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 4c3900cfc8a390f61b0252abec07cb61f6309c87 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 25 May 2020 17:16:40 +0200 Subject: [PATCH 168/871] Remove unnecessary comments, simplify initialPath and clarified TestScene name --- .../Screens/TestSceneStablePathSelectScreens.cs | 8 ++++---- osu.Game.Tournament/Screens/StablePathSelectScreen.cs | 11 +---------- 2 files changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreens.cs b/osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreens.cs index 4dfd4d35c8..ce0626dd0f 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreens.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreens.cs @@ -6,14 +6,14 @@ using osu.Framework.Platform; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneStablePathSelectScreens : TournamentTestScene + public class TestSceneStablePathSelectScreen : TournamentTestScene { - public TestSceneStablePathSelectScreens() + public TestSceneStablePathSelectScreen() { - AddStep("Add screen", () => Add(new TestSceneStablePathSelectScreen())); + AddStep("Add screen", () => Add(new StablePathSelectTestScreen())); } - private class TestSceneStablePathSelectScreen : StablePathSelectScreen + private class StablePathSelectTestScreen : StablePathSelectScreen { protected override void ChangePath(Storage storage) { diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index 609c601106..d2c7225909 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -38,14 +38,7 @@ namespace osu.Game.Tournament.Screens [BackgroundDependencyLoader(true)] private void load(Storage storage, OsuColour colours) { - // begin selection in the parent directory of the current storage location - var initialPath = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent?.FullName; - - if (!string.IsNullOrEmpty(stableInfo.StablePath.Value)) - { - // If the original path info for osu! stable is not empty, set it to the parent directory of that location - initialPath = new DirectoryInfo(stableInfo.StablePath.Value).Parent?.FullName; - } + var initialPath = new DirectoryInfo(storage.GetFullPath(stableInfo.StablePath.Value ?? string.Empty)).Parent?.FullName; AddRangeInternal(new Drawable[] { @@ -148,7 +141,6 @@ namespace osu.Game.Tournament.Screens AddInternal(overlay); Logger.Log("Folder is not an osu! stable CE directory"); return; - // Return an error in the picker that the directory does not contain ipc.txt } var fileBasedIpc = ipc as FileBasedIPC; @@ -163,7 +155,6 @@ namespace osu.Game.Tournament.Screens if (fileBasedIpc?.IPCStorage == null) { - // Could not auto detect overlay = new DialogOverlay(); overlay.Push(new IPCErrorDialog("Failed to auto detect", "An osu! stable cutting-edge installation could not be auto detected.\nPlease try and manually point to the directory.")); AddInternal(overlay); From 719da489221850d009c7a87389e3c25edf9a9bf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 May 2020 20:11:00 +0200 Subject: [PATCH 169/871] Rename delegate argument --- osu.Game.Tournament/Screens/SetupScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index bf328987fe..0ecab449ec 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -112,9 +112,9 @@ namespace osu.Game.Tournament.Screens { Label = "Stream area resolution", ButtonText = "Set height", - Action = i => + Action = height => { - windowSize.Value = new Size((int)(i * aspect_ratio / TournamentSceneManager.STREAM_AREA_WIDTH * TournamentSceneManager.REQUIRED_WIDTH), i); + windowSize.Value = new Size((int)(height * aspect_ratio / TournamentSceneManager.STREAM_AREA_WIDTH * TournamentSceneManager.REQUIRED_WIDTH), height); } }, }; From ca68d94cf7aa7acec4d638f61934836bd3c2b3c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 May 2020 20:18:17 +0200 Subject: [PATCH 170/871] Invert if to reduce nesting --- osu.Game.Tournament/Screens/SetupScreen.cs | 34 +++++++++++----------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 0ecab449ec..f8895f4703 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -226,25 +226,25 @@ namespace osu.Game.Tournament.Screens base.Action = () => { - if (numberBox.Text.Length > 0) + if (string.IsNullOrEmpty(numberBox.Text)) + return; + + // box contains text + if (!int.TryParse(numberBox.Text, out var number)) { - // box contains text - if (!int.TryParse(numberBox.Text, out var number)) - { - // at this point, the only reason we can arrive here is if the input number was too big to parse into an int - // so clamp to max allowed value - number = height_max_allowed_value; - } - else - { - number = Math.Clamp(number, height_min_allowed_value, height_max_allowed_value); - } - - // in case number got clamped, reset number in numberBox - numberBox.Text = number.ToString(); - - Action?.Invoke(number); + // at this point, the only reason we can arrive here is if the input number was too big to parse into an int + // so clamp to max allowed value + number = height_max_allowed_value; } + else + { + number = Math.Clamp(number, height_min_allowed_value, height_max_allowed_value); + } + + // in case number got clamped, reset number in numberBox + numberBox.Text = number.ToString(); + + Action?.Invoke(number); }; return drawable; } From 748f7fcd8b914ff7e84d0f29463142b18a6b244d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 25 May 2020 20:20:26 +0200 Subject: [PATCH 171/871] Rename constants --- osu.Game.Tournament/Screens/SetupScreen.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index f8895f4703..e1594de69e 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -210,8 +210,8 @@ namespace osu.Game.Tournament.Screens private class ResolutionSelector : ActionableInfo { - private const int height_min_allowed_value = 480; - private const int height_max_allowed_value = 2160; // 4k + private const int minimum_window_height = 480; + private const int maximum_window_height = 2160; // 4k public new Action Action; private OsuNumberBox numberBox; @@ -234,11 +234,11 @@ namespace osu.Game.Tournament.Screens { // at this point, the only reason we can arrive here is if the input number was too big to parse into an int // so clamp to max allowed value - number = height_max_allowed_value; + number = maximum_window_height; } else { - number = Math.Clamp(number, height_min_allowed_value, height_max_allowed_value); + number = Math.Clamp(number, minimum_window_height, maximum_window_height); } // in case number got clamped, reset number in numberBox From d69111c665079d495906232e24a49c640c7fe6e0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 10:17:34 +0900 Subject: [PATCH 172/871] Fix spelling of breadth --- .../Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 544468fd47..9b84b67241 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -120,10 +120,10 @@ namespace osu.Game.Rulesets.UI.Scrolling { case ScrollingDirection.Up: case ScrollingDirection.Down: - return ToScreenSpace(new Vector2(getBredth() / 2, pos)); + return ToScreenSpace(new Vector2(getBreadth() / 2, pos)); default: - return ToScreenSpace(new Vector2(pos, getBredth() / 2)); + return ToScreenSpace(new Vector2(pos, getBreadth() / 2)); } } @@ -140,7 +140,7 @@ namespace osu.Game.Rulesets.UI.Scrolling } } - private float getBredth() + private float getBreadth() { switch (scrollingInfo.Direction.Value) { From 417e31d77f87b66087e00c8918c83bef0fbfcbf3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 10:17:56 +0900 Subject: [PATCH 173/871] Rename function for consistency --- osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs | 2 +- osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 9b84b67241..c817d84d5c 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -82,7 +82,7 @@ namespace osu.Game.Rulesets.UI.Scrolling /// /// Given a position in screen space, return the time within this column. /// - public double TimeAtScreenSpace(Vector2 screenSpacePosition) + public double TimeAtScreenSpacePosition(Vector2 screenSpacePosition) { // convert to local space of column so we can snap and fetch correct location. Vector2 localPosition = ToLocalSpace(screenSpacePosition); diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs index 1ccde9b1e3..9dac3f4de1 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.UI.Scrolling /// Given a position in screen space, return the time within this column. /// public virtual double TimeAtScreenSpacePosition(Vector2 screenSpacePosition) => - ((ScrollingHitObjectContainer)HitObjectContainer).TimeAtScreenSpace(screenSpacePosition); + ((ScrollingHitObjectContainer)HitObjectContainer).TimeAtScreenSpacePosition(screenSpacePosition); /// /// Given a time, return the screen space position within this column. From 13bd6be8a3e2bf97ab9fb0bf19c7cf732ddcd5cb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 11:29:56 +0900 Subject: [PATCH 174/871] Convert wait steps into until steps --- .../Background/TestSceneUserDimBackgrounds.cs | 53 ++++++------------- 1 file changed, 17 insertions(+), 36 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index 76d0c7a50f..edba462bc2 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -79,11 +79,9 @@ namespace osu.Game.Tests.Visual.Background InputManager.MoveMouseTo(playerLoader.ScreenPos); InputManager.MoveMouseTo(playerLoader.VisualSettingsPos); }); - waitForDim(); - AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); + AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); AddStep("Stop background preview", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); - waitForDim(); - AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect()); + AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect()); } /// @@ -97,8 +95,7 @@ namespace osu.Game.Tests.Visual.Background performFullSetup(); AddStep("Trigger hover event", () => playerLoader.TriggerOnHover()); AddAssert("Background retained from song select", () => songSelect.IsBackgroundCurrent()); - waitForDim(); - AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); + AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } /// @@ -114,15 +111,13 @@ namespace osu.Game.Tests.Visual.Background player.ReplacesBackground.Value = true; player.StoryboardEnabled.Value = true; }); - waitForDim(); - AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible); + AddUntilStep("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible); AddStep("Disable Storyboard", () => { player.ReplacesBackground.Value = false; player.StoryboardEnabled.Value = false; }); - waitForDim(); - AddAssert("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && !player.IsStoryboardVisible); + AddUntilStep("Background is visible, storyboard is invisible", () => songSelect.IsBackgroundVisible() && !player.IsStoryboardVisible); } /// @@ -134,8 +129,7 @@ namespace osu.Game.Tests.Visual.Background performFullSetup(); createFakeStoryboard(); AddStep("Exit to song select", () => player.Exit()); - waitForDim(); - AddAssert("Background is visible", () => songSelect.IsBackgroundVisible()); + AddUntilStep("Background is visible", () => songSelect.IsBackgroundVisible()); } /// @@ -145,14 +139,11 @@ namespace osu.Game.Tests.Visual.Background public void DisableUserDimBackgroundTest() { performFullSetup(); - waitForDim(); - AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); + AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false); - waitForDim(); - AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled()); + AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled()); AddStep("Disable user dim", () => songSelect.DimEnabled.Value = true); - waitForDim(); - AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); + AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } /// @@ -170,11 +161,9 @@ namespace osu.Game.Tests.Visual.Background }); AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true); AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f); - waitForDim(); - AddAssert("Storyboard is invisible", () => !player.IsStoryboardVisible); + AddUntilStep("Storyboard is invisible", () => !player.IsStoryboardVisible); AddStep("Disable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = false); - waitForDim(); - AddAssert("Storyboard is visible", () => player.IsStoryboardVisible); + AddUntilStep("Storyboard is visible", () => player.IsStoryboardVisible); } /// @@ -185,11 +174,9 @@ namespace osu.Game.Tests.Visual.Background { performFullSetup(true); AddStep("Pause", () => player.Pause()); - waitForDim(); - AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); + AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); AddStep("Unpause", () => player.Resume()); - waitForDim(); - AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); + AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); } /// @@ -203,8 +190,7 @@ namespace osu.Game.Tests.Visual.Background AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } }))); AddUntilStep("Wait for results is current", () => results.IsCurrentScreen()); - waitForDim(); - AddAssert("Screen is undimmed, original background retained", () => + AddUntilStep("Screen is undimmed, original background retained", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect()); } @@ -216,8 +202,7 @@ namespace osu.Game.Tests.Visual.Background { performFullSetup(); AddStep("Exit to song select", () => player.Exit()); - waitForDim(); - AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect()); + AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBlurCorrect()); } /// @@ -229,15 +214,11 @@ namespace osu.Game.Tests.Visual.Background performFullSetup(); AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos)); AddStep("Resume PlayerLoader", () => player.Restart()); - waitForDim(); - AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); + AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); AddStep("Move mouse to center of screen", () => InputManager.MoveMouseTo(playerLoader.ScreenPos)); - waitForDim(); - AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect()); + AddUntilStep("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && playerLoader.IsBlurCorrect()); } - private void waitForDim() => AddWaitStep("Wait for dim", 5); - private void createFakeStoryboard() => AddStep("Create storyboard", () => { player.StoryboardEnabled.Value = false; From 2bf066d72c6099c47bd967a11424b35c13004500 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 11:30:36 +0900 Subject: [PATCH 175/871] Rename tests to match convention --- .../Background/TestSceneUserDimBackgrounds.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index edba462bc2..d601f40afe 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual.Background /// Check if properly triggers the visual settings preview when a user hovers over the visual settings panel. /// [Test] - public void PlayerLoaderSettingsHoverTest() + public void TestPlayerLoaderSettingsHover() { setupUserSettings(); AddStep("Start player loader", () => songSelect.Push(playerLoader = new TestPlayerLoader(player = new LoadBlockingTestPlayer { BlockLoad = true }))); @@ -90,7 +90,7 @@ namespace osu.Game.Tests.Visual.Background /// We need to check that in this scenario, the dim and blur is still properly applied after entering player. /// [Test] - public void PlayerLoaderTransitionTest() + public void TestPlayerLoaderTransition() { performFullSetup(); AddStep("Trigger hover event", () => playerLoader.TriggerOnHover()); @@ -102,7 +102,7 @@ namespace osu.Game.Tests.Visual.Background /// Make sure the background is fully invisible (Alpha == 0) when the background should be disabled by the storyboard. /// [Test] - public void StoryboardBackgroundVisibilityTest() + public void TestStoryboardBackgroundVisibility() { performFullSetup(); createFakeStoryboard(); @@ -124,7 +124,7 @@ namespace osu.Game.Tests.Visual.Background /// When exiting player, the screen that it suspends/exits to needs to have a fully visible (Alpha == 1) background. /// [Test] - public void StoryboardTransitionTest() + public void TestStoryboardTransition() { performFullSetup(); createFakeStoryboard(); @@ -136,7 +136,7 @@ namespace osu.Game.Tests.Visual.Background /// Ensure is properly accepting user-defined visual changes for a background. /// [Test] - public void DisableUserDimBackgroundTest() + public void TestDisableUserDimBackground() { performFullSetup(); AddUntilStep("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied()); @@ -150,7 +150,7 @@ namespace osu.Game.Tests.Visual.Background /// Ensure is properly accepting user-defined visual changes for a storyboard. /// [Test] - public void DisableUserDimStoryboardTest() + public void TestDisableUserDimStoryboard() { performFullSetup(); createFakeStoryboard(); @@ -170,7 +170,7 @@ namespace osu.Game.Tests.Visual.Background /// Check if the visual settings container retains dim and blur when pausing /// [Test] - public void PauseTest() + public void TestPause() { performFullSetup(true); AddStep("Pause", () => player.Pause()); @@ -183,7 +183,7 @@ namespace osu.Game.Tests.Visual.Background /// Check if the visual settings container removes user dim when suspending for /// [Test] - public void TransitionTest() + public void TestTransition() { performFullSetup(); FadeAccessibleResults results = null; @@ -198,7 +198,7 @@ namespace osu.Game.Tests.Visual.Background /// Check if background gets undimmed and unblurred when leaving for /// [Test] - public void TransitionOutTest() + public void TestTransitionOut() { performFullSetup(); AddStep("Exit to song select", () => player.Exit()); @@ -209,7 +209,7 @@ namespace osu.Game.Tests.Visual.Background /// Check if hovering on the visual settings dialogue after resuming from player still previews the background dim. /// [Test] - public void ResumeFromPlayerTest() + public void TestResumeFromPlayer() { performFullSetup(); AddStep("Move mouse to Visual Settings", () => InputManager.MoveMouseTo(playerLoader.VisualSettingsPos)); From d041de63ce3f312e22a1cb7a23cdc5a292c33da8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 13:00:32 +0900 Subject: [PATCH 176/871] Allow SelectionHandler to provide custom context menu items without local hover check --- .../Compose/Components/SelectionHandler.cs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 764eae1056..9ecda9fdb8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -244,14 +244,21 @@ namespace osu.Game.Screens.Edit.Compose.Components #region Context Menu - public virtual MenuItem[] ContextMenuItems + public MenuItem[] ContextMenuItems { get { if (!selectedBlueprints.Any(b => b.IsHovered)) return Array.Empty(); - var items = new List + var items = new List(); + + items.AddRange(GetContextMenuItemsForSelection(selectedBlueprints)); + + if (selectedBlueprints.Count == 1) + items.AddRange(selectedBlueprints[0].ContextMenuItems); + + items.AddRange(new[] { new OsuMenuItem("Sound") { @@ -263,15 +270,20 @@ namespace osu.Game.Screens.Edit.Compose.Components } }, new OsuMenuItem("Delete", MenuItemType.Destructive, deleteSelected), - }; - - if (selectedBlueprints.Count == 1) - items.AddRange(selectedBlueprints[0].ContextMenuItems); + }); return items.ToArray(); } } + /// + /// Provide context menu items relevant to current selection. Calling base is not required. + /// + /// The current selection. + /// The relevant menu items. + protected virtual IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) + => Enumerable.Empty(); + private MenuItem createHitSampleMenuItem(string name, string sampleName) { return new TernaryStateMenuItem(name, MenuItemType.Standard, setHitSampleState) From aaf5596f9c0f619fc9eb63ab046f21a03885bb45 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 May 2020 15:54:07 +0900 Subject: [PATCH 177/871] Cleanup test --- .../TestSceneContractedPanelMiddleContent.cs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs index 972ac26b84..76cfe75b59 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneContractedPanelMiddleContent.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -15,7 +14,6 @@ using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Contracted; -using osu.Game.Tests.Beatmaps; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -28,7 +26,7 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestShowPanel() { - AddStep("show example score", () => showPanel(createTestBeatmap(), new TestScoreInfo(new OsuRuleset().RulesetInfo))); + AddStep("show example score", () => showPanel(CreateWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)), new TestScoreInfo(new OsuRuleset().RulesetInfo))); } private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score) @@ -36,17 +34,6 @@ namespace osu.Game.Tests.Visual.Ranking Child = new ContractedPanelMiddleContentContainer(workingBeatmap, score); } - private WorkingBeatmap createTestBeatmap() - { - var beatmap = new TestBeatmap(rulesetStore.GetRuleset(0)); - beatmap.Metadata.Title = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap title"; - beatmap.Metadata.Artist = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap artist"; - - return new TestWorkingBeatmap(beatmap); - } - - private bool containsAny(string text, params string[] stringsToMatch) => stringsToMatch.Any(text.Contains); - private class ContractedPanelMiddleContentContainer : Container { [Cached] From 1768cbd1319ff8a6ab1da72846c2ec4c0a2ab65d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 May 2020 15:56:56 +0900 Subject: [PATCH 178/871] Format score same as expanded panel --- .../Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs index a263a03a77..8cd0e7025e 100644 --- a/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Contracted/ContractedPanelMiddleContent.cs @@ -160,7 +160,7 @@ namespace osu.Game.Screens.Ranking.Contracted { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Text = score.TotalScore.ToString(), + Text = score.TotalScore.ToString("N0"), Font = OsuFont.GetFont(size: 20, weight: FontWeight.Medium, fixedWidth: true), Spacing = new Vector2(-1, 0) }, From 906a317a3d35151baae2339a77671fbd82f34231 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 May 2020 16:26:53 +0900 Subject: [PATCH 179/871] Reduce casting --- osu.Game/Screens/Ranking/ScorePanelList.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 97a132b9ff..df2c66203b 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -118,11 +118,11 @@ namespace osu.Game.Screens.Ranking { public override IEnumerable FlowingChildren => applySorting(AliveInternalChildren); - public int GetPanelIndex(ScoreInfo score) => applySorting(Children).OfType().TakeWhile(s => s.Score != score).Count(); + public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Score != score).Count(); - private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() - .OrderByDescending(s => s.Score.TotalScore) - .ThenBy(s => s.Score.OnlineScoreID); + private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() + .OrderByDescending(s => s.Score.TotalScore) + .ThenBy(s => s.Score.OnlineScoreID); } private class Scroll : OsuScrollContainer From a1ece4f308d51317b5d2c6d25df1555a434f9f00 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 May 2020 16:26:58 +0900 Subject: [PATCH 180/871] Add expansion/contraction test --- .../Visual/Ranking/TestSceneScorePanel.cs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index fdb77c14a3..250fdc5ebd 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -12,6 +12,8 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneScorePanel : OsuTestScene { + private ScorePanel panel; + [Test] public void TestDRank() { @@ -84,9 +86,24 @@ namespace osu.Game.Tests.Visual.Ranking addPanelStep(score, PanelState.Contracted); } + [Test] + public void TestExpandAndContract() + { + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; + + addPanelStep(score, PanelState.Contracted); + AddWaitStep("wait for transition", 10); + + AddStep("expand panel", () => panel.State = PanelState.Expanded); + AddWaitStep("wait for transition", 10); + + AddStep("contract panel", () => panel.State = PanelState.Contracted); + AddWaitStep("wait for transition", 10); + } + private void addPanelStep(ScoreInfo score, PanelState state = PanelState.Expanded) => AddStep("add panel", () => { - Child = new ScorePanel(score) + Child = panel = new ScorePanel(score) { Anchor = Anchor.Centre, Origin = Anchor.Centre, From c86a003ef94eccc990c6e13f7d0ea7159de03ec6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 May 2020 16:27:41 +0900 Subject: [PATCH 181/871] Adjust transition for smaller sizes --- osu.Game/Screens/Ranking/ScorePanel.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 2933bbddd1..a99b48e8f0 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -102,12 +102,14 @@ namespace osu.Game.Screens.Ranking { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Size = new Vector2(40), Children = new Drawable[] { topLayerContainer = new Container { Name = "Top layer", RelativeSizeAxes = Axes.X, + Alpha = 0, Height = 120, Children = new Drawable[] { @@ -214,6 +216,8 @@ namespace osu.Game.Screens.Ranking // If the top layer was already expanded, then we don't need to wait for the resize and can instead transform immediately. This looks better when changing the panel state. using (BeginDelayedSequence(topLayerExpanded ? 0 : resize_duration + top_layer_expand_delay, true)) { + topLayerContainer.FadeIn(); + switch (state) { case PanelState.Expanded: From de0b6ec9f1941c64e19ab486748484351871074b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 May 2020 17:00:41 +0900 Subject: [PATCH 182/871] Create abstract implementation --- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Play/ReplayPlayer.cs | 2 +- osu.Game/Screens/Ranking/ResultsScreen.cs | 27 ++++++++-------- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 32 +++++++++++++++++++ osu.Game/Screens/Select/PlaySongSelect.cs | 2 +- 5 files changed, 49 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Screens/Ranking/SoloResultsScreen.cs diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 3d4b20bec4..36198bcc65 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -479,7 +479,7 @@ namespace osu.Game.Screens.Play protected override bool OnScroll(ScrollEvent e) => mouseWheelDisabled.Value && !GameplayClockContainer.IsPaused.Value; - protected virtual ResultsScreen CreateResults(ScoreInfo score) => new ResultsScreen(score); + protected virtual ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score); #region Fail Logic diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index f0c76163f1..b443603128 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -30,7 +30,7 @@ namespace osu.Game.Screens.Play this.Push(CreateResults(DrawableRuleset.ReplayScore.ScoreInfo)); } - protected override ResultsScreen CreateResults(ScoreInfo score) => new ResultsScreen(score, false); + protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, false); protected override ScoreInfo CreateScore() => score.ScoreInfo; } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 133bdcca4a..8db9cdc547 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -2,7 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -12,8 +12,6 @@ using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Rulesets; using osu.Game.Scoring; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Play; @@ -21,7 +19,7 @@ using osuTK; namespace osu.Game.Screens.Ranking { - public class ResultsScreen : OsuScreen + public abstract class ResultsScreen : OsuScreen { protected const float BACKGROUND_BLUR = 20; @@ -38,9 +36,6 @@ namespace osu.Game.Screens.Ranking [Resolved] private IAPIProvider api { get; set; } - [Resolved] - private RulesetStore rulesets { get; set; } - public readonly ScoreInfo Score; private readonly bool allowRetry; @@ -133,22 +128,28 @@ namespace osu.Game.Screens.Ranking { base.LoadComplete(); - var req = new GetScoresRequest(Score.Beatmap, Score.Ruleset); - - req.Success += r => Schedule(() => + var req = FetchScores(scores => Schedule(() => { - foreach (var s in r.Scores.Select(s => s.CreateScoreInfo(rulesets))) + foreach (var s in scores) { if (s.OnlineScoreID == Score.OnlineScoreID) continue; panels.AddScore(s); } - }); + })); - api.Queue(req); + if (req != null) + api.Queue(req); } + /// + /// Performs a fetch/refresh of scores to be displayed. + /// + /// A callback which should be called when fetching is completed. Scheduling is not required. + /// An responsible for the fetch operation. This will be queued and performed automatically. + protected virtual APIRequest FetchScores(Action> scoresCallback) => null; + public override void OnEntering(IScreen last) { base.OnEntering(last); diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs new file mode 100644 index 0000000000..2b00748ed8 --- /dev/null +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -0,0 +1,32 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Rulesets; +using osu.Game.Scoring; + +namespace osu.Game.Screens.Ranking +{ + public class SoloResultsScreen : ResultsScreen + { + [Resolved] + private RulesetStore rulesets { get; set; } + + public SoloResultsScreen(ScoreInfo score, bool allowRetry = true) + : base(score, allowRetry) + { + } + + protected override APIRequest FetchScores(Action> scoresCallback) + { + var req = new GetScoresRequest(Score.Beatmap, Score.Ruleset); + req.Success += r => scoresCallback?.Invoke(r.Scores.Select(s => s.CreateScoreInfo(rulesets))); + return req; + } + } +} diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 21ddc5685d..0a4c0e2085 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -37,7 +37,7 @@ namespace osu.Game.Screens.Select } protected void PresentScore(ScoreInfo score) => - FinaliseSelection(score.Beatmap, score.Ruleset, () => this.Push(new ResultsScreen(score))); + FinaliseSelection(score.Beatmap, score.Ruleset, () => this.Push(new SoloResultsScreen(score))); protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); From 7e1e26de2a3b3bc8e249ba75b417e4d5955d7c42 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 17:00:55 +0900 Subject: [PATCH 183/871] Allow HandleMovement by default --- .../Edit/Compose/Components/SelectionHandler.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 9ecda9fdb8..7ab6340e07 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -74,9 +74,16 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// Handles the selected s being moved. /// + /// + /// Just returning true is enough to allow updates to take place. + /// Custom implementation is only required if other attributes are to be considered, like changing columns. + /// /// The move event. - /// Whether any s were moved. - public virtual bool HandleMovement(MoveSelectionEvent moveEvent) => false; + /// + /// Whether any s could be moved. + /// Returning true will also propagate StartTime changes provided by the closest . + /// + public virtual bool HandleMovement(MoveSelectionEvent moveEvent) => true; public bool OnPressed(PlatformAction action) { From c07a33b24fc02d33a6dd15da10605d16bb958005 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 May 2020 17:31:50 +0900 Subject: [PATCH 184/871] Fix ctor accessibility --- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 8db9cdc547..97b56d22eb 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -43,7 +43,7 @@ namespace osu.Game.Screens.Ranking private Drawable bottomPanel; private ScorePanelList panels; - public ResultsScreen(ScoreInfo score, bool allowRetry = true) + protected ResultsScreen(ScoreInfo score, bool allowRetry = true) { Score = score; this.allowRetry = allowRetry; From 6b5b2152991f3ba617b42f274fee646f37753ab7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 17:44:47 +0900 Subject: [PATCH 185/871] Split out IHasPath from IHasCurve to better define hitobjects --- .../Beatmaps/CatchBeatmapConverter.cs | 2 +- .../Objects/JuiceStream.cs | 2 +- .../Legacy/DistanceObjectPatternGenerator.cs | 2 +- .../Beatmaps/OsuBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- .../Beatmaps/TaikoBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 10 +---- .../Formats/LegacyBeatmapDecoderTest.cs | 2 +- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 39 +++++++++++-------- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 2 +- osu.Game/Rulesets/Objects/Types/IHasPath.cs | 13 +++++++ .../{IHasCurve.cs => IHasPathWithRepeats.cs} | 20 +++++----- 13 files changed, 57 insertions(+), 43 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Types/IHasPath.cs rename osu.Game/Rulesets/Objects/Types/{IHasCurve.cs => IHasPathWithRepeats.cs} (77%) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 90a6e609f0..27a9b63e9a 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps switch (obj) { - case IHasCurve curveData: + case IHasPathWithRepeats curveData: return new JuiceStream { StartTime = obj.StartTime, diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index d32595c2e1..24090e233a 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -14,7 +14,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Catch.Objects { - public class JuiceStream : CatchHitObject, IHasCurve + public class JuiceStream : CatchHitObject, IHasPathWithRepeats { /// /// Positional distance that results in a duration of one second, before any speed adjustments. diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index d8d5b67c0e..1bd796511b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -474,7 +474,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// private IList sampleInfoListAt(double time) { - if (!(HitObject is IHasCurve curveData)) + if (!(HitObject is IHasPathWithRepeats curveData)) return HitObject.Samples; double segmentTime = (EndTime - HitObject.StartTime) / spanCount; diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index 147d74c929..060a3919bd 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps switch (original) { - case IHasCurve curveData: + case IHasPathWithRepeats curveData: return new Slider { StartTime = original.StartTime, diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 6ba0e1c6aa..713d1a61f8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -17,7 +17,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { - public class Slider : OsuHitObject, IHasCurve + public class Slider : OsuHitObject, IHasPathWithRepeats { public double EndTime { diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index d324441285..1a47be2282 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { - List> allSamples = obj is IHasCurve curveData ? curveData.NodeSamples : new List>(new[] { samples }); + List> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List>(new[] { samples }); int i = 0; diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 7b11bce520..5f52160be1 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -3,9 +3,7 @@ using osu.Game.Rulesets.Objects.Types; using System; -using System.Collections.Generic; using System.Threading; -using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; @@ -17,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Taiko.Objects { - public class DrumRoll : TaikoHitObject, IHasCurve + public class DrumRoll : TaikoHitObject, IHasPath { /// /// Drum roll distance that results in a duration of 1 speed-adjusted beat length. @@ -115,11 +113,7 @@ namespace osu.Game.Rulesets.Taiko.Objects double IHasDistance.Distance => Duration * Velocity; - int IHasRepeats.RepeatCount { get => 0; set { } } - - List> IHasRepeats.NodeSamples => new List>(); - - SliderPath IHasCurve.Path + SliderPath IHasPath.Path => new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER); #endregion diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index acb30a6277..dab923d75b 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -365,7 +365,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { var hitObjects = decoder.Decode(stream).HitObjects; - var curveData = hitObjects[0] as IHasCurve; + var curveData = hitObjects[0] as IHasPathWithRepeats; var positionData = hitObjects[0] as IHasPosition; Assert.IsNotNull(positionData); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index b034e66616..b4c78ce273 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { var beatmap = decodeAsJson(normal); - var curveData = beatmap.HitObjects[0] as IHasCurve; + var curveData = beatmap.HitObjects[0] as IHasPathWithRepeats; var positionData = beatmap.HitObjects[0] as IHasPosition; Assert.IsNotNull(positionData); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 7727f25967..d7e83fa471 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -233,9 +233,9 @@ namespace osu.Game.Beatmaps.Formats writer.Write(FormattableString.Invariant($"{(int)getObjectType(hitObject)},")); writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(hitObject.Samples)},")); - if (hitObject is IHasCurve curveData) + if (hitObject is IHasPathWithRepeats curveData) { - addCurveData(writer, curveData, position); + addPathData(writer, curveData, position); writer.Write(getSampleBank(hitObject.Samples, zeroBanks: true)); } else @@ -263,7 +263,7 @@ namespace osu.Game.Beatmaps.Formats switch (hitObject) { - case IHasCurve _: + case IHasPath _: type |= LegacyHitObjectType.Slider; break; @@ -282,13 +282,13 @@ namespace osu.Game.Beatmaps.Formats return type; } - private void addCurveData(TextWriter writer, IHasCurve curveData, Vector2 position) + private void addPathData(TextWriter writer, IHasPath pathData, Vector2 position) { PathType? lastType = null; - for (int i = 0; i < curveData.Path.ControlPoints.Count; i++) + for (int i = 0; i < pathData.Path.ControlPoints.Count; i++) { - PathControlPoint point = curveData.Path.ControlPoints[i]; + PathControlPoint point = pathData.Path.ControlPoints[i]; if (point.Type.Value != null) { @@ -325,23 +325,28 @@ namespace osu.Game.Beatmaps.Formats if (i != 0) { writer.Write(FormattableString.Invariant($"{position.X + point.Position.Value.X}:{position.Y + point.Position.Value.Y}")); - writer.Write(i != curveData.Path.ControlPoints.Count - 1 ? "|" : ","); + writer.Write(i != pathData.Path.ControlPoints.Count - 1 ? "|" : ","); } } - writer.Write(FormattableString.Invariant($"{curveData.RepeatCount + 1},")); - writer.Write(FormattableString.Invariant($"{curveData.Path.Distance},")); + var curveData = pathData as IHasPathWithRepeats; - for (int i = 0; i < curveData.NodeSamples.Count; i++) - { - writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(curveData.NodeSamples[i])}")); - writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); - } + writer.Write(FormattableString.Invariant($"{(curveData?.RepeatCount ?? 0) + 1},")); + writer.Write(FormattableString.Invariant($"{pathData.Path.Distance},")); - for (int i = 0; i < curveData.NodeSamples.Count; i++) + if (curveData != null) { - writer.Write(getSampleBank(curveData.NodeSamples[i], true)); - writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); + for (int i = 0; i < curveData.NodeSamples.Count; i++) + { + writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(curveData.NodeSamples[i])}")); + writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); + } + + for (int i = 0; i < curveData.NodeSamples.Count; i++) + { + writer.Write(getSampleBank(curveData.NodeSamples[i], true)); + writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); + } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 924182b265..73192dc42e 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -9,7 +9,7 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Objects.Legacy { - internal abstract class ConvertSlider : ConvertHitObject, IHasCurve, IHasLegacyLastTickOffset + internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset { /// /// Scoring distance with a speed-adjusted beat length of 1 second. diff --git a/osu.Game/Rulesets/Objects/Types/IHasPath.cs b/osu.Game/Rulesets/Objects/Types/IHasPath.cs new file mode 100644 index 0000000000..567c24a4a2 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasPath.cs @@ -0,0 +1,13 @@ +// 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.Rulesets.Objects.Types +{ + public interface IHasPath : IHasDistance + { + /// + /// The curve. + /// + SliderPath Path { get; } + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs b/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs similarity index 77% rename from osu.Game/Rulesets/Objects/Types/IHasCurve.cs rename to osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs index e98a888bd7..fba0fd7aff 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.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 osuTK; namespace osu.Game.Rulesets.Objects.Types @@ -8,15 +9,16 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that has a curve. /// - public interface IHasCurve : IHasDistance, IHasRepeats + public interface IHasPathWithRepeats : IHasPath, IHasRepeats { - /// - /// The curve. - /// - SliderPath Path { get; } } - public static class HasCurveExtensions + [Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126 + public interface IHasCurve : IHasPathWithRepeats + { + } + + public static class HasPathWithRepeatsExtensions { /// /// Computes the position on the curve relative to how much of the has been completed. @@ -24,7 +26,7 @@ namespace osu.Game.Rulesets.Objects.Types /// The curve. /// [0, 1] where 0 is the start time of the and 1 is the end time of the . /// The position on the curve. - public static Vector2 CurvePositionAt(this IHasCurve obj, double progress) + public static Vector2 CurvePositionAt(this IHasPathWithRepeats obj, double progress) => obj.Path.PositionAt(obj.ProgressAt(progress)); /// @@ -33,7 +35,7 @@ namespace osu.Game.Rulesets.Objects.Types /// The curve. /// [0, 1] where 0 is the start time of the and 1 is the end time of the . /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. - public static double ProgressAt(this IHasCurve obj, double progress) + public static double ProgressAt(this IHasPathWithRepeats obj, double progress) { double p = progress * obj.SpanCount() % 1; if (obj.SpanAt(progress) % 2 == 1) @@ -47,7 +49,7 @@ namespace osu.Game.Rulesets.Objects.Types /// The curve. /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. /// [0, SpanCount) where 0 is the first run. - public static int SpanAt(this IHasCurve obj, double progress) + public static int SpanAt(this IHasPathWithRepeats obj, double progress) => (int)(progress * obj.SpanCount()); } } From b8e0a6f12725e464f179f03e53dd9f20625cd635 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 12:37:44 +0900 Subject: [PATCH 186/871] Move sett from EndTime to Duration --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 10 +++++----- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 ++++---- .../Objects/Drawables/DrawableSwell.cs | 2 +- .../Gameplay/TestSceneDrawableScrollingRuleset.cs | 6 +++--- .../Objects/Legacy/Catch/ConvertHitObjectParser.cs | 6 +++--- .../Objects/Legacy/Catch/ConvertSpinner.cs | 4 ++-- .../Objects/Legacy/ConvertHitObjectParser.cs | 14 +++++++------- osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs | 6 +++--- .../Objects/Legacy/Mania/ConvertHitObjectParser.cs | 8 ++++---- .../Rulesets/Objects/Legacy/Mania/ConvertHold.cs | 4 ++-- .../Objects/Legacy/Mania/ConvertSpinner.cs | 4 ++-- .../Objects/Legacy/Osu/ConvertHitObjectParser.cs | 6 +++--- .../Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs | 4 ++-- .../Objects/Legacy/Taiko/ConvertHitObjectParser.cs | 6 +++--- .../Objects/Legacy/Taiko/ConvertSpinner.cs | 4 ++-- osu.Game/Rulesets/Objects/Types/IHasEndTime.cs | 6 +++--- .../Timeline/TimelineHitObjectBlueprint.cs | 2 +- 17 files changed, 50 insertions(+), 50 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index d32595c2e1..7c8d57e689 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -115,15 +115,15 @@ namespace osu.Game.Rulesets.Catch.Objects } } - public double EndTime + public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; + + public double Duration { - get => StartTime + this.SpanCount() * Path.Distance / Velocity; + get => this.SpanCount() * Path.Distance / Velocity; set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } - public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; - - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; private readonly SliderPath path = new SliderPath(); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 6ba0e1c6aa..984a9bf956 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -19,14 +19,14 @@ namespace osu.Game.Rulesets.Osu.Objects { public class Slider : OsuHitObject, IHasCurve { - public double EndTime + public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; + + public double Duration { - get => StartTime + this.SpanCount() * Path.Distance / Velocity; + get => EndTime - StartTime; set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } - public double Duration => EndTime - StartTime; - private readonly Cached endPositionCache = new Cached(); public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 32f7acadc8..7294587b10 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -237,7 +237,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables case ArmedState.Miss: case ArmedState.Hit: - using (BeginAbsoluteSequence(Time.Current, true)) + using (BeginDelayedSequence(HitObject.Duration, true)) { this.FadeOut(transition_duration, Easing.Out); bodyContainer.ScaleTo(1.4f, transition_duration); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index b25b81c9af..08fd849fa6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -281,7 +281,7 @@ namespace osu.Game.Tests.Visual.Gameplay yield return new TestHitObject { StartTime = original.StartTime, - EndTime = (original as IHasEndTime)?.EndTime ?? (original.StartTime + 100) + Duration = (original as IHasEndTime)?.Duration ?? 100 }; } } @@ -292,9 +292,9 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestHitObject : ConvertHitObject, IHasEndTime { - public double EndTime { get; set; } + public double EndTime => StartTime + Duration; - public double Duration => EndTime - StartTime; + public double Duration { get; set; } } private class DrawableTestHitObject : DrawableHitObject diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index 43e8d01297..c10c8dc30f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch }; } - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { // Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo // Their combo offset is still added to that next hitobject's combo index @@ -65,11 +65,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch return new ConvertSpinner { - EndTime = endTime + Duration = duration }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) { return null; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs index 9de311c9d7..4b0270064a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -10,9 +10,9 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition, IHasCombo { - public double EndTime { get; set; } + public double EndTime => StartTime + Duration; - public double Duration => EndTime - StartTime; + public double Duration { get; set; } public float X => 256; // Required for CatchBeatmapConverter diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 9a60a0a75c..d8d90fddfa 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -189,9 +189,9 @@ namespace osu.Game.Rulesets.Objects.Legacy } else if (type.HasFlag(LegacyHitObjectType.Spinner)) { - double endTime = Math.Max(startTime, Parsing.ParseDouble(split[5]) + Offset); + double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + Offset - startTime); - result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, endTime); + result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, duration); if (split.Length > 6) readCustomSampleBanks(split[6], bankInfo); @@ -209,7 +209,7 @@ namespace osu.Game.Rulesets.Objects.Legacy readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); } - result = CreateHold(pos, combo, comboOffset, endTime + Offset); + result = CreateHold(pos, combo, comboOffset, endTime + Offset - startTime); } if (result == null) @@ -321,9 +321,9 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The position of the hit object. /// Whether the hit object creates a new combo. /// When starting a new combo, the offset of the new combo relative to the current one. - /// The spinner end time. + /// The spinner duration. /// The hit object. - protected abstract HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime); + protected abstract HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration); /// /// Creates a legacy Hold-type hit object. @@ -331,8 +331,8 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The position of the hit object. /// Whether the hit object creates a new combo. /// When starting a new combo, the offset of the new combo relative to the current one. - /// The hold end time. - protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime); + /// The hold end time. + protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration); private List convertSoundType(LegacyHitSoundType type, SampleBankInfo bankInfo) { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 924182b265..0a3c1b40fd 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -26,13 +26,13 @@ namespace osu.Game.Rulesets.Objects.Legacy public List> NodeSamples { get; set; } public int RepeatCount { get; set; } - public double EndTime + public double Duration { - get => StartTime + this.SpanCount() * Distance / Velocity; + get => this.SpanCount() * Distance / Velocity; set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; public double Velocity = 1; diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index f94c4aaa75..bc64518f40 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -37,21 +37,21 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania }; } - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { return new ConvertSpinner { X = position.X, - EndTime = endTime + Duration = duration }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) { return new ConvertHold { X = position.X, - EndTime = endTime + Duration = duration }; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs index 1d92d638dd..dcb66163e4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs @@ -9,8 +9,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania { public float X { get; set; } - public double EndTime { get; set; } + public double Duration { get; set; } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs index 7dc13e27cd..b731f7c8d8 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs @@ -10,9 +10,9 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania /// internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition { - public double EndTime { get; set; } + public double Duration { get; set; } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; public float X { get; set; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index b95ec703b6..75ecab0b8f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu }; } - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { // Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo // Their combo offset is still added to that next hitobject's combo index @@ -66,11 +66,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu return new ConvertSpinner { Position = position, - EndTime = endTime + Duration = duration }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) { return null; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index 8b21aab411..a231237077 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -11,9 +11,9 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasPosition, IHasCombo { - public double EndTime { get; set; } + public double Duration { get; set; } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; public Vector2 Position { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index db65a61c90..13e3e84c6a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -33,15 +33,15 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko }; } - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { return new ConvertSpinner { - EndTime = endTime + Duration = duration }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) { return null; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs index 8e28487f2f..0976106ec4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs @@ -10,8 +10,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko /// internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime { - public double EndTime { get; set; } + public double Duration { get; set; } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs index bc7103c60d..5eb551e15c 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs @@ -13,12 +13,12 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The time at which the HitObject ends. /// - [JsonIgnore] - double EndTime { get; set; } + double EndTime { get; } /// /// The duration of the HitObject. /// - double Duration { get; } + [JsonIgnore] + double Duration { get; set; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index dd2f7a833e..d6fc17f358 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -296,7 +296,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (endTimeHitObject.EndTime == snappedTime) return; - endTimeHitObject.EndTime = snappedTime; + endTimeHitObject.Duration = snappedTime - hitObject.StartTime; break; } From cbd563e80b3b82aecf9b84e474197d9c12993b16 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 12:38:39 +0900 Subject: [PATCH 187/871] Rename to IHasDuration --- .../Beatmaps/CatchBeatmapConverter.cs | 2 +- .../Objects/BananaShower.cs | 2 +- .../TestSceneNotes.cs | 2 +- .../Beatmaps/ManiaBeatmapConverter.cs | 6 ++--- .../Legacy/EndTimeObjectPatternGenerator.cs | 2 +- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 2 +- .../Beatmaps/OsuBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 2 +- .../Beatmaps/TaikoBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Swell.cs | 2 +- .../TestSceneDrawableScrollingRuleset.cs | 4 ++-- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 6 ++--- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- osu.Game/Rulesets/Objects/HitObject.cs | 4 ++-- .../Objects/Legacy/Catch/ConvertSpinner.cs | 2 +- .../Objects/Legacy/Mania/ConvertHold.cs | 2 +- .../Objects/Legacy/Mania/ConvertSpinner.cs | 2 +- .../Objects/Legacy/Osu/ConvertSpinner.cs | 2 +- .../Objects/Legacy/Taiko/ConvertSpinner.cs | 2 +- .../Rulesets/Objects/Types/IHasDistance.cs | 2 +- .../Rulesets/Objects/Types/IHasDuration.cs | 24 +++++++++++++++++++ .../Rulesets/Objects/Types/IHasEndTime.cs | 20 ++++------------ .../Rulesets/Objects/Types/IHasRepeats.cs | 2 +- .../Scrolling/ScrollingHitObjectContainer.cs | 2 +- .../Timeline/TimelineHitObjectBlueprint.cs | 4 ++-- 27 files changed, 60 insertions(+), 48 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Types/IHasDuration.cs diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 90a6e609f0..0b370cf911 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0 }.Yield(); - case IHasEndTime endTime: + case IHasDuration endTime: return new BananaShower { StartTime = obj.StartTime, diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 3a0b5ace53..04a995c77e 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -7,7 +7,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Catch.Objects { - public class BananaShower : CatchHitObject, IHasEndTime + public class BananaShower : CatchHitObject, IHasDuration { public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index ea6a1e2e6a..dd5fd93710 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Mania.Tests foreach (var obj in content.OfType()) { - if (!(obj.HitObject is IHasEndTime endTime)) + if (!(obj.HitObject is IHasDuration endTime)) continue; foreach (var nested in obj.NestedHitObjects) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 1c8116754f..32abf5e7f9 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps } else { - float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count; + float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasDuration) / beatmap.HitObjects.Count; if (percentSliderOrSpinner < 0.2) TargetColumns = 7; else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5) @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps break; } - case IHasEndTime endTimeData: + case IHasDuration endTimeData: { conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap); @@ -231,7 +231,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps var pattern = new Pattern(); - if (HitObject is IHasEndTime endTimeData) + if (HitObject is IHasDuration endTimeData) { pattern.Add(new HoldNote { diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index 907bed0d65..d5286a3779 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap) : base(random, hitObject, beatmap, new Pattern(), originalBeatmap) { - endTime = (HitObject as IHasEndTime)?.EndTime ?? 0; + endTime = (HitObject as IHasDuration)?.EndTime ?? 0; } public override IEnumerable Generate() diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index e6f722a8a9..a100c9a58e 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Objects /// /// Represents a hit object which requires pressing, holding, and releasing a key. /// - public class HoldNote : ManiaHitObject, IHasEndTime + public class HoldNote : ManiaHitObject, IHasDuration { public double EndTime { diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index 147d74c929..3490d96b61 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / beatmap.ControlPointInfo.DifficultyPointAt(original.StartTime).SpeedMultiplier : 1 }.Yield(); - case IHasEndTime endTimeData: + case IHasDuration endTimeData: return new Spinner { StartTime = original.StartTime, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 7b1941b7f9..5d191119b9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Mods break; // already hit or beyond the hittable end time. - if (h.IsHit || (h.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime)) + if (h.IsHit || (h.HitObject is IHasDuration hasEnd && time > hasEnd.EndTime)) continue; switch (h) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 297a0fea79..3cad52faeb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Mods } // Keep wiggling sliders and spinners for their duration - if (!(osuObject is IHasEndTime endTime)) + if (!(osuObject is IHasDuration endTime)) return; amountWiggles = (int)(endTime.Duration / wiggle_duration); diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 0b8d03d118..418375c090 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { - public class Spinner : OsuHitObject, IHasEndTime + public class Spinner : OsuHitObject, IHasDuration { public double EndTime { diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index d324441285..a4ec7211ca 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps break; } - case IHasEndTime endTimeData: + case IHasDuration endTimeData: { double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index 390f8d1f3b..8a63a89951 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -10,7 +10,7 @@ using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects { - public class Swell : TaikoHitObject, IHasEndTime + public class Swell : TaikoHitObject, IHasDuration { public double EndTime { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 08fd849fa6..bd7e894cf8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -281,7 +281,7 @@ namespace osu.Game.Tests.Visual.Gameplay yield return new TestHitObject { StartTime = original.StartTime, - Duration = (original as IHasEndTime)?.Duration ?? 100 + Duration = (original as IHasDuration)?.Duration ?? 100 }; } } @@ -290,7 +290,7 @@ namespace osu.Game.Tests.Visual.Gameplay #region HitObject - private class TestHitObject : ConvertHitObject, IHasEndTime + private class TestHitObject : ConvertHitObject, IHasDuration { public double EndTime => StartTime + Duration; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 7727f25967..a8982238cf 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -240,7 +240,7 @@ namespace osu.Game.Beatmaps.Formats } else { - if (hitObject is IHasEndTime) + if (hitObject is IHasDuration) addEndTimeData(writer, hitObject); writer.Write(getSampleBank(hitObject.Samples)); @@ -267,7 +267,7 @@ namespace osu.Game.Beatmaps.Formats type |= LegacyHitObjectType.Slider; break; - case IHasEndTime _: + case IHasDuration _: if (beatmap.BeatmapInfo.RulesetID == 3) type |= LegacyHitObjectType.Hold; else @@ -347,7 +347,7 @@ namespace osu.Game.Beatmaps.Formats private void addEndTimeData(TextWriter writer, HitObject hitObject) { - var endTimeData = (IHasEndTime)hitObject; + var endTimeData = (IHasDuration)hitObject; var type = getObjectType(hitObject); char suffix = ','; diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 8126311cbd..ac399e37c4 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -63,7 +63,7 @@ namespace osu.Game.Beatmaps length = emptyLength; break; - case IHasEndTime endTime: + case IHasDuration endTime: length = endTime.EndTime + excess_length; break; diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 6f9053d7cb..e2cc98813a 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -175,10 +175,10 @@ namespace osu.Game.Rulesets.Objects /// Returns the end time of this object. /// /// - /// This returns the where available, falling back to otherwise. + /// This returns the where available, falling back to otherwise. /// /// The object. /// The end time of this object. - public static double GetEndTime(this HitObject hitObject) => (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime; + public static double GetEndTime(this HitObject hitObject) => (hitObject as IHasDuration)?.EndTime ?? hitObject.StartTime; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs index 4b0270064a..014494ec54 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// /// Legacy osu!catch Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition, IHasCombo + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition, IHasCombo { public double EndTime => StartTime + Duration; diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs index dcb66163e4..2fa4766c1d 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs @@ -5,7 +5,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Objects.Legacy.Mania { - internal sealed class ConvertHold : ConvertHitObject, IHasXPosition, IHasEndTime + internal sealed class ConvertHold : ConvertHitObject, IHasXPosition, IHasDuration { public float X { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs index b731f7c8d8..c05aaceb9c 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania /// /// Legacy osu!mania Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition { public double Duration { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index a231237077..e9e5ca8c94 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasPosition, IHasCombo + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasPosition, IHasCombo { public double Duration { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs index 0976106ec4..1d5ecb1ef3 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko /// /// Legacy osu!taiko Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration { public double Duration { get; set; } diff --git a/osu.Game/Rulesets/Objects/Types/IHasDistance.cs b/osu.Game/Rulesets/Objects/Types/IHasDistance.cs index e7f552115e..b497ca5da3 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasDistance.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasDistance.cs @@ -6,7 +6,7 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that has a positional length. /// - public interface IHasDistance : IHasEndTime + public interface IHasDistance : IHasDuration { /// /// The positional length of the HitObject. diff --git a/osu.Game/Rulesets/Objects/Types/IHasDuration.cs b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs new file mode 100644 index 0000000000..2433f9597e --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A HitObject that ends at a different time than its start time. + /// + public interface IHasDuration + { + /// + /// The time at which the HitObject ends. + /// + double EndTime { get; } + + /// + /// The duration of the HitObject. + /// + [JsonIgnore] + double Duration { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs index 5eb551e15c..7395223c7e 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs @@ -1,24 +1,12 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using Newtonsoft.Json; +using System; namespace osu.Game.Rulesets.Objects.Types { - /// - /// A HitObject that ends at a different time than its start time. - /// - public interface IHasEndTime + [Obsolete("Use IHasDuration instead.")] // can be removed 20201126 + public interface IHasEndTime : IHasDuration { - /// - /// The time at which the HitObject ends. - /// - double EndTime { get; } - - /// - /// The duration of the HitObject. - /// - [JsonIgnore] - double Duration { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index 256b1f3963..7a3fb16196 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that spans some length. /// - public interface IHasRepeats : IHasEndTime + public interface IHasRepeats : IHasDuration { /// /// The amount of times the HitObject repeats. diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index c817d84d5c..0dc3324559 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -270,7 +270,7 @@ namespace osu.Game.Rulesets.UI.Scrolling // Cant use AddOnce() since the delegate is re-constructed every invocation private void computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() => { - if (hitObject.HitObject is IHasEndTime e) + if (hitObject.HitObject is IHasDuration e) { switch (direction.Value) { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index d6fc17f358..b95b3842b3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline shadowComponents.Add(circle); - if (hitObject is IHasEndTime) + if (hitObject is IHasDuration) { DragBar dragBarUnderlay; Container extensionBar; @@ -290,7 +290,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline repeatHitObject.RepeatCount = proposedCount; break; - case IHasEndTime endTimeHitObject: + case IHasDuration endTimeHitObject: var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time)); if (endTimeHitObject.EndTime == snappedTime) From f5c974dd897b0a7006b944e0103e4158d3d7667a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 13:40:16 +0900 Subject: [PATCH 188/871] Hide non-alive selection blueprints by default --- .../Edit/Blueprints/OsuSelectionBlueprint.cs | 2 ++ osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs index b0e13808a5..8dd550bb96 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/OsuSelectionBlueprint.cs @@ -12,6 +12,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints { protected new T HitObject => (T)DrawableObject.HitObject; + protected override bool AlwaysShowWhenSelected => true; + protected OsuSelectionBlueprint(DrawableHitObject drawableObject) : base(drawableObject) { diff --git a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs index 8202d3a1d1..75200e3027 100644 --- a/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/OverlaySelectionBlueprint.cs @@ -15,7 +15,12 @@ namespace osu.Game.Rulesets.Edit /// public readonly DrawableHitObject DrawableObject; - protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || State == SelectionState.Selected; + /// + /// Whether the blueprint should be shown even when the is not alive. + /// + protected virtual bool AlwaysShowWhenSelected => false; + + protected override bool ShouldBeAlive => (DrawableObject.IsAlive && DrawableObject.IsPresent) || (AlwaysShowWhenSelected && State == SelectionState.Selected); protected OverlaySelectionBlueprint(DrawableHitObject drawableObject) : base(drawableObject.HitObject) From f989f1aa0034d6283ee3d49169316a80713c0a55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 16:08:47 +0900 Subject: [PATCH 189/871] Change event flow to avoid firing store delete events on update --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 4 ++-- osu.Game/Database/ArchiveModelManager.cs | 8 ++++---- osu.Game/Database/IModelManager.cs | 2 +- osu.Game/Database/MutableDatabaseBackedStore.cs | 16 +++++++++++----- osu.Game/Online/DownloadTrackingComposite.cs | 8 ++++---- osu.Game/OsuGameBase.cs | 2 +- osu.Game/Overlays/MusicController.cs | 12 ++++++------ .../Overlays/Settings/Sections/SkinSection.cs | 10 +++++----- .../Multi/Match/Components/ReadyButton.cs | 8 ++++---- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 8 ++++---- osu.Game/Screens/Select/BeatmapCarousel.cs | 8 ++++---- osu.Game/Screens/Select/Carousel/TopLocalRank.cs | 6 +++--- 12 files changed, 49 insertions(+), 43 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 43fab186aa..5eb11a3264 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -156,7 +156,7 @@ namespace osu.Game.Tests.Beatmaps.IO var manager = osu.Dependencies.Get(); // ReSharper disable once AccessToModifiedClosure - manager.ItemAdded.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount)); + manager.ItemUpdated.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount)); manager.ItemRemoved.BindValueChanged(_ => Interlocked.Increment(ref itemAddRemoveFireCount)); var imported = await LoadOszIntoOsu(osu); @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Beatmaps.IO imported.Hash += "-changed"; manager.Update(imported); - Assert.AreEqual(0, itemAddRemoveFireCount -= 2); + Assert.AreEqual(0, itemAddRemoveFireCount -= 1); checkBeatmapSetCount(osu, 1); checkBeatmapCount(osu, 12); diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index f21f708f95..ae55a7b14a 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -55,12 +55,12 @@ namespace osu.Game.Database public Action PostNotification { protected get; set; } /// - /// Fired when a new becomes available in the database. + /// Fired when a new or updated becomes available in the database. /// This is not guaranteed to run on the update thread. /// - public IBindable> ItemAdded => itemAdded; + public IBindable> ItemUpdated => itemUpdated; - private readonly Bindable> itemAdded = new Bindable>(); + private readonly Bindable> itemUpdated = new Bindable>(); /// /// Fired when a is removed from the database. @@ -90,7 +90,7 @@ namespace osu.Game.Database ContextFactory = contextFactory; ModelStore = modelStore; - ModelStore.ItemAdded += item => handleEvent(() => itemAdded.Value = new WeakReference(item)); + ModelStore.ItemUpdated += item => handleEvent(() => itemUpdated.Value = new WeakReference(item)); ModelStore.ItemRemoved += item => handleEvent(() => itemRemoved.Value = new WeakReference(item)); exportStorage = storage.GetStorageForDirectory("exports"); diff --git a/osu.Game/Database/IModelManager.cs b/osu.Game/Database/IModelManager.cs index 852b385798..7f7e5565f1 100644 --- a/osu.Game/Database/IModelManager.cs +++ b/osu.Game/Database/IModelManager.cs @@ -13,7 +13,7 @@ namespace osu.Game.Database public interface IModelManager where TModel : class { - IBindable> ItemAdded { get; } + IBindable> ItemUpdated { get; } IBindable> ItemRemoved { get; } } diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs index 4ca1eef989..c9d0c4bc41 100644 --- a/osu.Game/Database/MutableDatabaseBackedStore.cs +++ b/osu.Game/Database/MutableDatabaseBackedStore.cs @@ -16,7 +16,14 @@ namespace osu.Game.Database public abstract class MutableDatabaseBackedStore : DatabaseBackedStore where T : class, IHasPrimaryKey, ISoftDelete { - public event Action ItemAdded; + /// + /// Fired when an item was added or updated. + /// + public event Action ItemUpdated; + + /// + /// Fired when an item was removed. + /// public event Action ItemRemoved; protected MutableDatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null) @@ -41,7 +48,7 @@ namespace osu.Game.Database context.Attach(item); } - ItemAdded?.Invoke(item); + ItemUpdated?.Invoke(item); } /// @@ -53,8 +60,7 @@ namespace osu.Game.Database using (var usage = ContextFactory.GetForWrite()) usage.Context.Update(item); - ItemRemoved?.Invoke(item); - ItemAdded?.Invoke(item); + ItemUpdated?.Invoke(item); } /// @@ -91,7 +97,7 @@ namespace osu.Game.Database item.DeletePending = false; } - ItemAdded?.Invoke(item); + ItemUpdated?.Invoke(item); return true; } diff --git a/osu.Game/Online/DownloadTrackingComposite.cs b/osu.Game/Online/DownloadTrackingComposite.cs index 47de7d75ed..5d9cf612bb 100644 --- a/osu.Game/Online/DownloadTrackingComposite.cs +++ b/osu.Game/Online/DownloadTrackingComposite.cs @@ -34,7 +34,7 @@ namespace osu.Game.Online Model.Value = model; } - private IBindable> managerAdded; + private IBindable> managedUpdated; private IBindable> managerRemoved; private IBindable>> managerDownloadBegan; private IBindable>> managerDownloadFailed; @@ -56,8 +56,8 @@ namespace osu.Game.Online managerDownloadBegan.BindValueChanged(downloadBegan); managerDownloadFailed = manager.DownloadFailed.GetBoundCopy(); managerDownloadFailed.BindValueChanged(downloadFailed); - managerAdded = manager.ItemAdded.GetBoundCopy(); - managerAdded.BindValueChanged(itemAdded); + managedUpdated = manager.ItemUpdated.GetBoundCopy(); + managedUpdated.BindValueChanged(itemUpdated); managerRemoved = manager.ItemRemoved.GetBoundCopy(); managerRemoved.BindValueChanged(itemRemoved); } @@ -128,7 +128,7 @@ namespace osu.Game.Online private void onRequestFailure(Exception e) => Schedule(() => attachDownload(null)); - private void itemAdded(ValueChangedEvent> weakItem) + private void itemUpdated(ValueChangedEvent> weakItem) { if (weakItem.NewValue.TryGetTarget(out var item)) setDownloadStateFromManager(item, DownloadState.LocallyAvailable); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 453587df18..e7446975b9 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -192,7 +192,7 @@ namespace osu.Game ScoreManager.Delete(getBeatmapScores(item), true); }); - BeatmapManager.ItemAdded.BindValueChanged(i => + BeatmapManager.ItemUpdated.BindValueChanged(i => { if (i.NewValue.TryGetTarget(out var item)) ScoreManager.Undelete(getBeatmapScores(item), true); diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 35f3cb0e25..92cf490be2 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -60,14 +60,14 @@ namespace osu.Game.Overlays [Resolved(canBeNull: true)] private OnScreenDisplay onScreenDisplay { get; set; } - private IBindable> managerAdded; + private IBindable> managerUpdated; private IBindable> managerRemoved; [BackgroundDependencyLoader] private void load() { - managerAdded = beatmaps.ItemAdded.GetBoundCopy(); - managerAdded.BindValueChanged(beatmapAdded); + managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); + managerUpdated.BindValueChanged(beatmapUpdated); managerRemoved = beatmaps.ItemRemoved.GetBoundCopy(); managerRemoved.BindValueChanged(beatmapRemoved); @@ -98,14 +98,14 @@ namespace osu.Game.Overlays /// public bool IsPlaying => current?.Track.IsRunning ?? false; - private void beatmapAdded(ValueChangedEvent> weakSet) + private void beatmapUpdated(ValueChangedEvent> weakSet) { if (weakSet.NewValue.TryGetTarget(out var set)) { Schedule(() => { - if (!beatmapSets.Contains(set)) - beatmapSets.Add(set); + beatmapSets.Remove(set); + beatmapSets.Add(set); }); } } diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index b84b9fec37..04390a1193 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Settings.Sections [Resolved] private SkinManager skins { get; set; } - private IBindable> managerAdded; + private IBindable> managerUpdated; private IBindable> managerRemoved; [BackgroundDependencyLoader] @@ -73,8 +73,8 @@ namespace osu.Game.Overlays.Settings.Sections }, }; - managerAdded = skins.ItemAdded.GetBoundCopy(); - managerAdded.BindValueChanged(itemAdded); + managerUpdated = skins.ItemUpdated.GetBoundCopy(); + managerUpdated.BindValueChanged(itemUpdated); managerRemoved = skins.ItemRemoved.GetBoundCopy(); managerRemoved.BindValueChanged(itemRemoved); @@ -92,10 +92,10 @@ namespace osu.Game.Overlays.Settings.Sections dropdownBindable.BindValueChanged(skin => configBindable.Value = skin.NewValue.ID); } - private void itemAdded(ValueChangedEvent> weakItem) + private void itemUpdated(ValueChangedEvent> weakItem) { if (weakItem.NewValue.TryGetTarget(out var item)) - Schedule(() => skinDropdown.Items = skinDropdown.Items.Append(item).ToArray()); + Schedule(() => skinDropdown.Items = skinDropdown.Items.Where(i => !i.Equals(item)).Append(item).ToArray()); } private void itemRemoved(ValueChangedEvent> weakItem) diff --git a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs index 4420b2d58a..e1f86fcc97 100644 --- a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs +++ b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs @@ -32,14 +32,14 @@ namespace osu.Game.Screens.Multi.Match.Components Text = "Start"; } - private IBindable> managerAdded; + private IBindable> managerUpdated; private IBindable> managerRemoved; [BackgroundDependencyLoader] private void load(OsuColour colours) { - managerAdded = beatmaps.ItemAdded.GetBoundCopy(); - managerAdded.BindValueChanged(beatmapAdded); + managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); + managerUpdated.BindValueChanged(beatmapUpdated); managerRemoved = beatmaps.ItemRemoved.GetBoundCopy(); managerRemoved.BindValueChanged(beatmapRemoved); @@ -61,7 +61,7 @@ namespace osu.Game.Screens.Multi.Match.Components hasBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId) != null; } - private void beatmapAdded(ValueChangedEvent> weakSet) + private void beatmapUpdated(ValueChangedEvent> weakSet) { if (weakSet.NewValue.TryGetTarget(out var set)) { diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index caa547ac72..e1d72d9600 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -50,7 +50,7 @@ namespace osu.Game.Screens.Multi.Match private LeaderboardChatDisplay leaderboardChatDisplay; private MatchSettingsOverlay settingsOverlay; - private IBindable> managerAdded; + private IBindable> managerUpdated; public MatchSubScreen(Room room) { @@ -183,8 +183,8 @@ namespace osu.Game.Screens.Multi.Match SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged)); SelectedItem.Value = playlist.FirstOrDefault(); - managerAdded = beatmapManager.ItemAdded.GetBoundCopy(); - managerAdded.BindValueChanged(beatmapAdded); + managerUpdated = beatmapManager.ItemUpdated.GetBoundCopy(); + managerUpdated.BindValueChanged(beatmapUpdated); } public override bool OnExiting(IScreen next) @@ -217,7 +217,7 @@ namespace osu.Game.Screens.Multi.Match Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); } - private void beatmapAdded(ValueChangedEvent> weakSet) + private void beatmapUpdated(ValueChangedEvent> weakSet) { Schedule(() => { diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index f23e1b1ef2..2d714d1794 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -131,7 +131,7 @@ namespace osu.Game.Screens.Select private CarouselRoot root; - private IBindable> itemAdded; + private IBindable> itemUpdated; private IBindable> itemRemoved; private IBindable> itemHidden; private IBindable> itemRestored; @@ -166,8 +166,8 @@ namespace osu.Game.Screens.Select RightClickScrollingEnabled.ValueChanged += enabled => scroll.RightMouseScrollbar = enabled.NewValue; RightClickScrollingEnabled.TriggerChange(); - itemAdded = beatmaps.ItemAdded.GetBoundCopy(); - itemAdded.BindValueChanged(beatmapAdded); + itemUpdated = beatmaps.ItemUpdated.GetBoundCopy(); + itemUpdated.BindValueChanged(beatmapUpdated); itemRemoved = beatmaps.ItemRemoved.GetBoundCopy(); itemRemoved.BindValueChanged(beatmapRemoved); itemHidden = beatmaps.BeatmapHidden.GetBoundCopy(); @@ -582,7 +582,7 @@ namespace osu.Game.Screens.Select RemoveBeatmapSet(item); } - private void beatmapAdded(ValueChangedEvent> weakItem) + private void beatmapUpdated(ValueChangedEvent> weakItem) { if (weakItem.NewValue.TryGetTarget(out var item)) UpdateBeatmapSet(item); diff --git a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs index aed25787b0..3ad57c1cb0 100644 --- a/osu.Game/Screens/Select/Carousel/TopLocalRank.cs +++ b/osu.Game/Screens/Select/Carousel/TopLocalRank.cs @@ -28,7 +28,7 @@ namespace osu.Game.Screens.Select.Carousel [Resolved] private IAPIProvider api { get; set; } - private IBindable> itemAdded; + private IBindable> itemUpdated; private IBindable> itemRemoved; public TopLocalRank(BeatmapInfo beatmap) @@ -40,8 +40,8 @@ namespace osu.Game.Screens.Select.Carousel [BackgroundDependencyLoader] private void load() { - itemAdded = scores.ItemAdded.GetBoundCopy(); - itemAdded.BindValueChanged(scoreChanged); + itemUpdated = scores.ItemUpdated.GetBoundCopy(); + itemUpdated.BindValueChanged(scoreChanged); itemRemoved = scores.ItemRemoved.GetBoundCopy(); itemRemoved.BindValueChanged(scoreChanged); From 9a060cfb3acfac28816eb68f245e365153a9af46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 20:44:15 +0900 Subject: [PATCH 190/871] Allow drag selections to occur from outside the playfield --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 1 + osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 3453bfbf63..99759bde3d 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -102,6 +102,7 @@ namespace osu.Game.Rulesets.Edit { Name = "Content", RelativeSizeAxes = Axes.Both, + Masking = true, Children = new Drawable[] { // layers below playfield diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index cc417bbb10..d07cffff0c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -44,6 +44,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly BindableList selectedHitObjects = new BindableList(); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + [Resolved(canBeNull: true)] private IPositionSnapProvider snapProvider { get; set; } From 919ff92d15775e63bbb74083a3ce05b61ac53707 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 22:56:12 +0900 Subject: [PATCH 191/871] Remove unused resolved composer --- .../Edit/Blueprints/HoldNotePlacementBlueprint.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs index 500b26917d..b5ec1e1a2a 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/HoldNotePlacementBlueprint.cs @@ -20,9 +20,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints private readonly EditNotePiece headPiece; private readonly EditNotePiece tailPiece; - [Resolved] - private IManiaHitObjectComposer composer { get; set; } - [Resolved] private IScrollingInfo scrollingInfo { get; set; } From 6be5917eb0c232d5ad34843736fc7c4781c36d02 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 23:15:16 +0900 Subject: [PATCH 192/871] Remove necessity for custom mania interface caching --- .../ManiaPlacementBlueprintTestScene.cs | 4 +- .../ManiaSelectionBlueprintTestScene.cs | 4 +- .../TestSceneManiaBeatSnapGrid.cs | 60 ++++++++++++++++++- .../Blueprints/ManiaSelectionBlueprint.cs | 3 - .../Edit/IManiaHitObjectComposer.cs | 12 ---- .../Edit/ManiaBeatSnapGrid.cs | 6 +- .../Edit/ManiaHitObjectComposer.cs | 5 +- .../Edit/ManiaSelectionHandler.cs | 9 ++- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 6 +- 9 files changed, 76 insertions(+), 33 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs index 1119a66f63..0fe4a3c669 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaPlacementBlueprintTestScene.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; @@ -19,8 +18,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests { - [Cached(Type = typeof(IManiaHitObjectComposer))] - public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene, IManiaHitObjectComposer + public abstract class ManiaPlacementBlueprintTestScene : PlacementBlueprintTestScene { private readonly Column column; diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs index 35fe596e98..149f6582ab 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaSelectionBlueprintTestScene.cs @@ -4,15 +4,13 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Timing; -using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.UI; using osu.Game.Tests.Visual; using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Tests { - [Cached(Type = typeof(IManiaHitObjectComposer))] - public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene, IManiaHitObjectComposer + public abstract class ManiaSelectionBlueprintTestScene : SelectionBlueprintTestScene { [Cached(Type = typeof(IAdjustableClock))] private readonly IAdjustableClock clock = new StopwatchClock(); diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs index ce9546415f..639be0bc11 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaBeatSnapGrid.cs @@ -2,23 +2,27 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Input.Events; using osu.Framework.Timing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Configuration; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Edit; using osu.Game.Rulesets.Mania.UI; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit; using osu.Game.Tests.Visual; +using osuTK; namespace osu.Game.Rulesets.Mania.Tests { - [Cached(typeof(IManiaHitObjectComposer))] - public class TestSceneManiaBeatSnapGrid : EditorClockTestScene, IManiaHitObjectComposer + public class TestSceneManiaBeatSnapGrid : EditorClockTestScene { [Cached(typeof(IScrollingInfo))] private ScrollingTestContainer.TestScrollingInfo scrollingInfo = new ScrollingTestContainer.TestScrollingInfo(); @@ -50,7 +54,10 @@ namespace osu.Game.Rulesets.Mania.Tests { Clock = new FramedClock(new StopwatchClock()) }, - beatSnapGrid = new ManiaBeatSnapGrid() + new TestHitObjectComposer(Playfield) + { + Child = beatSnapGrid = new ManiaBeatSnapGrid() + } }; } @@ -67,4 +74,51 @@ namespace osu.Game.Rulesets.Mania.Tests public ManiaPlayfield Playfield { get; } } + + public class TestHitObjectComposer : HitObjectComposer + { + public override Playfield Playfield { get; } + public override IEnumerable HitObjects => Enumerable.Empty(); + public override bool CursorInPlacementArea => false; + + public TestHitObjectComposer(Playfield playfield) + { + Playfield = playfield; + } + + public Drawable Child + { + set => InternalChild = value; + } + + public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) + { + throw new System.NotImplementedException(); + } + + public override float GetBeatSnapDistanceAt(double referenceTime) + { + throw new System.NotImplementedException(); + } + + public override float DurationToDistance(double referenceTime, double duration) + { + throw new System.NotImplementedException(); + } + + public override double DistanceToDuration(double referenceTime, float distance) + { + throw new System.NotImplementedException(); + } + + public override double GetSnappedDurationFromDistance(double referenceTime, float distance) + { + throw new System.NotImplementedException(); + } + + public override float GetSnappedDistanceFromDistance(double referenceTime, float distance) + { + throw new System.NotImplementedException(); + } + } } diff --git a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs index 0089a9fbee..384f49d9b2 100644 --- a/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Mania/Edit/Blueprints/ManiaSelectionBlueprint.cs @@ -18,9 +18,6 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints [Resolved] private IScrollingInfo scrollingInfo { get; set; } - [Resolved] - private IManiaHitObjectComposer composer { get; set; } - protected ManiaSelectionBlueprint(DrawableHitObject drawableObject) : base(drawableObject) { diff --git a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs deleted file mode 100644 index 3818d0e15d..0000000000 --- a/osu.Game.Rulesets.Mania/Edit/IManiaHitObjectComposer.cs +++ /dev/null @@ -1,12 +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 osu.Game.Rulesets.Mania.UI; - -namespace osu.Game.Rulesets.Mania.Edit -{ - public interface IManiaHitObjectComposer - { - ManiaPlayfield Playfield { get; } - } -} diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index b5b6c08fca..2028cae9a5 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -11,6 +11,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI.Scrolling; @@ -63,9 +65,9 @@ namespace osu.Game.Rulesets.Mania.Edit private (double start, double end)? selectionTimeRange; [BackgroundDependencyLoader] - private void load(IManiaHitObjectComposer composer) + private void load(HitObjectComposer composer) { - foreach (var stage in composer.Playfield.Stages) + foreach (var stage in ((ManiaPlayfield)composer.Playfield).Stages) { foreach (var column in stage.Columns) { diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 73cbadc97c..10d344242c 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -20,8 +20,7 @@ using osuTK; namespace osu.Game.Rulesets.Mania.Edit { - [Cached(Type = typeof(IManiaHitObjectComposer))] - public class ManiaHitObjectComposer : HitObjectComposer, IManiaHitObjectComposer + public class ManiaHitObjectComposer : HitObjectComposer { private DrawableManiaEditRuleset drawableRuleset; private ManiaBeatSnapGrid beatSnapGrid; @@ -50,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Edit protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - public ManiaPlayfield Playfield => ((ManiaPlayfield)drawableRuleset.Playfield); + public new ManiaPlayfield Playfield => ((ManiaPlayfield)drawableRuleset.Playfield); public IScrollingInfo ScrollingInfo => drawableRuleset.ScrollingInfo; diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 4ea71652bc..65f40d7d0a 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using osu.Framework.Allocation; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.UI.Scrolling; @@ -17,7 +18,7 @@ namespace osu.Game.Rulesets.Mania.Edit private IScrollingInfo scrollingInfo { get; set; } [Resolved] - private IManiaHitObjectComposer composer { get; set; } + private HitObjectComposer composer { get; set; } public override bool HandleMovement(MoveSelectionEvent moveEvent) { @@ -31,7 +32,9 @@ namespace osu.Game.Rulesets.Mania.Edit private void performColumnMovement(int lastColumn, MoveSelectionEvent moveEvent) { - var currentColumn = composer.Playfield.GetColumnByPosition(moveEvent.ScreenSpacePosition); + var maniaPlayfield = ((ManiaHitObjectComposer)composer).Playfield; + + var currentColumn = maniaPlayfield.GetColumnByPosition(moveEvent.ScreenSpacePosition); if (currentColumn == null) return; @@ -50,7 +53,7 @@ namespace osu.Game.Rulesets.Mania.Edit maxColumn = obj.Column; } - columnDelta = Math.Clamp(columnDelta, -minColumn, composer.Playfield.TotalColumns - 1 - maxColumn); + columnDelta = Math.Clamp(columnDelta, -minColumn, maniaPlayfield.TotalColumns - 1 - maxColumn); foreach (var obj in SelectedHitObjects.OfType()) obj.Column += columnDelta; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 3453bfbf63..0a2df64dde 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -48,6 +48,8 @@ namespace osu.Game.Rulesets.Edit protected ComposeBlueprintContainer BlueprintContainer { get; private set; } + public override Playfield Playfield => drawableRulesetWrapper.Playfield; + private DrawableEditRulesetWrapper drawableRulesetWrapper; protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both }; @@ -260,11 +262,13 @@ namespace osu.Game.Rulesets.Edit [Cached(typeof(IPositionSnapProvider))] public abstract class HitObjectComposer : CompositeDrawable, IPositionSnapProvider { - internal HitObjectComposer() + protected HitObjectComposer() { RelativeSizeAxes = Axes.Both; } + public abstract Playfield Playfield { get; } + /// /// All the s. /// From e4de20f0a5b6721519d6fa1baa4b2b2daf9222e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 May 2020 11:54:27 +0900 Subject: [PATCH 193/871] 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 b7d08fb120..7ea1f3140b 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d5017a436f..3d2a4f3081 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 19a36f1e1f..8a7f75b515 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 912c999f4051a259c58c2dc8ec265193bb8f8bd6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 May 2020 19:05:35 +0900 Subject: [PATCH 194/871] Fix minor typo in OsuGameBase --- osu.Game/OsuGameBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index e7446975b9..5e44562144 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -229,8 +229,8 @@ namespace osu.Game FileStore.Cleanup(); - if (API is APIAccess apiAcces) - AddInternal(apiAcces); + if (API is APIAccess apiAccess) + AddInternal(apiAccess); AddInternal(RulesetConfigCache); GlobalActionContainer globalBinding; From a55ce261307b560ab303db9cbcf183e6c50bca43 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 May 2020 20:46:17 +0900 Subject: [PATCH 195/871] Allow null score --- .../Visual/Ranking/TestSceneScorePanelList.cs | 38 ++++++++++++++++++- osu.Game/Screens/Ranking/ScorePanelList.cs | 7 +++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index b32b3afbda..a204d2bcbc 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -9,10 +9,11 @@ using osu.Framework.Utils; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; +using osuTK.Input; namespace osu.Game.Tests.Visual.Ranking { - public class TestSceneScorePanelList : OsuTestScene + public class TestSceneScorePanelList : OsuManualInputManagerTestScene { private ScoreInfo initialScore; private ScorePanelList list; @@ -72,6 +73,41 @@ namespace osu.Game.Tests.Visual.Ranking assertPanelCentred(); } + [Test] + public void TestNullScore() + { + AddStep("create panel with null score", () => + { + Child = list = new ScorePanelList(null) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + }); + + AddStep("add many panels", () => + { + for (int i = 0; i < 20; i++) + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 }); + + for (int i = 0; i < 20; i++) + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 }); + }); + + AddWaitStep("wait for panel animation", 5); + + AddAssert("no panel selected", () => list.ChildrenOfType().All(p => p.State != PanelState.Expanded)); + + AddStep("expand second panel", () => + { + var expandedPanel = list.ChildrenOfType().OrderBy(p => p.DrawPosition.X).ElementAt(1); + InputManager.MoveMouseTo(expandedPanel); + InputManager.Click(MouseButton.Left); + }); + + assertPanelCentred(); + } + private void assertPanelCentred() => AddUntilStep("expanded panel centred", () => { var expandedPanel = list.ChildrenOfType().Single(p => p.State == PanelState.Expanded); diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index df2c66203b..a30d911f04 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -45,8 +45,11 @@ namespace osu.Game.Screens.Ranking } }; - AddScore(initialScore); - presentScore(initialScore); + if (initialScore != null) + { + AddScore(initialScore); + presentScore(initialScore); + } } /// From 666cbd0f40f2cf59a78d644caa3c1cb6a53b9588 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 May 2020 21:08:47 +0900 Subject: [PATCH 196/871] Allow selected score to be programmatically changed --- .../Visual/Ranking/TestSceneScorePanelList.cs | 128 ++++++++++++------ osu.Game/Screens/Ranking/ResultsScreen.cs | 3 +- osu.Game/Screens/Ranking/ScorePanelList.cs | 51 ++++--- 3 files changed, 120 insertions(+), 62 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index a204d2bcbc..588cddea7d 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -9,58 +10,121 @@ using osu.Framework.Utils; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; -using osuTK.Input; namespace osu.Game.Tests.Visual.Ranking { public class TestSceneScorePanelList : OsuManualInputManagerTestScene { - private ScoreInfo initialScore; private ScorePanelList list; - [SetUp] - public void Setup() => Schedule(() => + [Test] + public void TestEmptyList() { - Child = list = new ScorePanelList(initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo)) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; - }); + createListStep(() => new ScorePanelList()); + } [Test] - public void TestSingleScore() + public void TestEmptyListWithSelectedScore() { + createListStep(() => new ScorePanelList + { + SelectedScore = { Value = new TestScoreInfo(new OsuRuleset().RulesetInfo) } + }); + } + + [Test] + public void TestAddPanelAfterSelectingScore() + { + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + + createListStep(() => new ScorePanelList + { + SelectedScore = { Value = score } + }); + + AddStep("add panel", () => list.AddScore(score)); + + assertScoreState(score, true); + assertPanelCentred(); + } + + [Test] + public void TestAddPanelBeforeSelectingScore() + { + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + + createListStep(() => new ScorePanelList()); + + AddStep("add panel", () => list.AddScore(score)); + + assertScoreState(score, false); + assertPanelCentred(); + + AddStep("select score", () => list.SelectedScore.Value = score); + + assertScoreState(score, true); assertPanelCentred(); } [Test] public void TestAddManyScoresAfter() { - AddStep("add scores", () => + var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + + createListStep(() => new ScorePanelList()); + + AddStep("add initial panel and select", () => + { + list.AddScore(initialScore); + list.SelectedScore.Value = initialScore; + }); + + AddStep("add many scores", () => { for (int i = 0; i < 20; i++) list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 }); }); + assertScoreState(initialScore, true); assertPanelCentred(); } [Test] public void TestAddManyScoresBefore() { + var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + + createListStep(() => new ScorePanelList()); + + AddStep("add initial panel and select", () => + { + list.AddScore(initialScore); + list.SelectedScore.Value = initialScore; + }); + AddStep("add scores", () => { for (int i = 0; i < 20; i++) list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 }); }); + assertScoreState(initialScore, true); assertPanelCentred(); } [Test] public void TestAddManyPanelsOnBothSides() { + var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + + createListStep(() => new ScorePanelList()); + + AddStep("add initial panel and select", () => + { + list.AddScore(initialScore); + list.SelectedScore.Value = initialScore; + }); + AddStep("add scores after", () => { for (int i = 0; i < 20; i++) @@ -70,42 +134,19 @@ namespace osu.Game.Tests.Visual.Ranking list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 }); }); + assertScoreState(initialScore, true); assertPanelCentred(); } - [Test] - public void TestNullScore() + private void createListStep(Func creationFunc) { - AddStep("create panel with null score", () => + AddStep("create list", () => Child = list = creationFunc().With(d => { - Child = list = new ScorePanelList(null) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }; - }); + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + })); - AddStep("add many panels", () => - { - for (int i = 0; i < 20; i++) - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore - i - 1 }); - - for (int i = 0; i < 20; i++) - list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo) { TotalScore = initialScore.TotalScore + i + 1 }); - }); - - AddWaitStep("wait for panel animation", 5); - - AddAssert("no panel selected", () => list.ChildrenOfType().All(p => p.State != PanelState.Expanded)); - - AddStep("expand second panel", () => - { - var expandedPanel = list.ChildrenOfType().OrderBy(p => p.DrawPosition.X).ElementAt(1); - InputManager.MoveMouseTo(expandedPanel); - InputManager.Click(MouseButton.Left); - }); - - assertPanelCentred(); + AddUntilStep("wait for load", () => list.IsLoaded); } private void assertPanelCentred() => AddUntilStep("expanded panel centred", () => @@ -113,5 +154,8 @@ namespace osu.Game.Tests.Visual.Ranking var expandedPanel = list.ChildrenOfType().Single(p => p.State == PanelState.Expanded); return Precision.AlmostEquals(expandedPanel.ScreenSpaceDrawQuad.Centre.X, list.ScreenSpaceDrawQuad.Centre.X, 1); }); + + private void assertScoreState(ScoreInfo score, bool expanded) + => AddUntilStep($"correct score expanded = {expanded}", () => (list.ChildrenOfType().Single(p => p.Score == score).State == PanelState.Expanded) == expanded); } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 97b56d22eb..a4d1a3c26b 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -63,9 +63,10 @@ namespace osu.Game.Screens.Ranking { new ResultsScrollContainer { - Child = panels = new ScorePanelList(Score) + Child = panels = new ScorePanelList { RelativeSizeAxes = Axes.Both, + SelectedScore = { Value = Score } } } }, diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index a30d911f04..89f5a47ee8 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; @@ -23,12 +24,13 @@ namespace osu.Game.Screens.Ranking /// private const float expanded_panel_spacing = 15; + public readonly Bindable SelectedScore = new Bindable(); + private readonly Flow flow; private readonly Scroll scroll; - private ScorePanel expandedPanel; - public ScorePanelList(ScoreInfo initialScore) + public ScorePanelList() { RelativeSizeAxes = Axes.Both; @@ -44,12 +46,13 @@ namespace osu.Game.Screens.Ranking AutoSizeAxes = Axes.Both, } }; + } - if (initialScore != null) - { - AddScore(initialScore); - presentScore(initialScore); - } + protected override void LoadComplete() + { + base.LoadComplete(); + + SelectedScore.BindValueChanged(selectedScoreChanged, true); } /// @@ -67,19 +70,24 @@ namespace osu.Game.Screens.Ranking p.StateChanged += s => { if (s == PanelState.Expanded) - presentScore(score); + SelectedScore.Value = p.Score; }; })); - // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. - // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. - if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + if (SelectedScore.Value == score) + selectedScoreChanged(new ValueChangedEvent(SelectedScore.Value, SelectedScore.Value)); + else { - // A somewhat hacky property is used here because we need to: - // 1) Scroll after the scroll container's visible range is updated. - // 2) Scroll before the scroll container's scroll position is updated. - // Without this, we would have a 1-frame positioning error which looks very jarring. - scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + // We want the scroll position to remain relative to the expanded panel. When a new panel is added after the expanded panel, nothing needs to be done. + // But when a panel is added before the expanded panel, we need to offset the scroll position by the width of the new panel. + if (expandedPanel != null && flow.GetPanelIndex(score) < flow.GetPanelIndex(expandedPanel.Score)) + { + // A somewhat hacky property is used here because we need to: + // 1) Scroll after the scroll container's visible range is updated. + // 2) Scroll before the scroll container's scroll position is updated. + // Without this, we would have a 1-frame positioning error which looks very jarring. + scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; + } } } @@ -87,17 +95,22 @@ namespace osu.Game.Screens.Ranking /// Brings a to the centre of the screen and expands it. /// /// The to present. - private void presentScore(ScoreInfo score) + private void selectedScoreChanged(ValueChangedEvent score) { // Contract the old panel. - foreach (var p in flow.Where(p => p.Score != score)) + foreach (var p in flow.Where(p => p.Score != score.OldValue)) { p.State = PanelState.Contracted; p.Margin = new MarginPadding(); } + // Find the panel corresponding to the new score. + expandedPanel = flow.SingleOrDefault(p => p.Score == score.NewValue); + + if (expandedPanel == null) + return; + // Expand the new panel. - expandedPanel = flow.Single(p => p.Score == score); expandedPanel.State = PanelState.Expanded; expandedPanel.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; From ad99d854682af9e1404ff4a4e6f1d38e41a10ab1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 May 2020 21:29:16 +0900 Subject: [PATCH 197/871] Resolve several positioning errors --- .../Visual/Ranking/TestSceneScorePanelList.cs | 69 ++++++++++++++++--- osu.Game/Screens/Ranking/ScorePanelList.cs | 15 +++- 2 files changed, 71 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs index 588cddea7d..e65dcb19b1 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanelList.cs @@ -10,6 +10,7 @@ using osu.Framework.Utils; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking; +using osuTK.Input; namespace osu.Game.Tests.Visual.Ranking { @@ -45,7 +46,7 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("add panel", () => list.AddScore(score)); assertScoreState(score, true); - assertPanelCentred(); + assertExpandedPanelCentred(); } [Test] @@ -58,16 +59,30 @@ namespace osu.Game.Tests.Visual.Ranking AddStep("add panel", () => list.AddScore(score)); assertScoreState(score, false); - assertPanelCentred(); + assertFirstPanelCentred(); AddStep("select score", () => list.SelectedScore.Value = score); assertScoreState(score, true); - assertPanelCentred(); + assertExpandedPanelCentred(); } [Test] - public void TestAddManyScoresAfter() + public void TestAddManyNonExpandedPanels() + { + createListStep(() => new ScorePanelList()); + + AddStep("add many scores", () => + { + for (int i = 0; i < 20; i++) + list.AddScore(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + }); + + assertFirstPanelCentred(); + } + + [Test] + public void TestAddManyScoresAfterExpandedPanel() { var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); @@ -86,11 +101,11 @@ namespace osu.Game.Tests.Visual.Ranking }); assertScoreState(initialScore, true); - assertPanelCentred(); + assertExpandedPanelCentred(); } [Test] - public void TestAddManyScoresBefore() + public void TestAddManyScoresBeforeExpandedPanel() { var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); @@ -109,11 +124,11 @@ namespace osu.Game.Tests.Visual.Ranking }); assertScoreState(initialScore, true); - assertPanelCentred(); + assertExpandedPanelCentred(); } [Test] - public void TestAddManyPanelsOnBothSides() + public void TestAddManyPanelsOnBothSidesOfExpandedPanel() { var initialScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); @@ -135,7 +150,36 @@ namespace osu.Game.Tests.Visual.Ranking }); assertScoreState(initialScore, true); - assertPanelCentred(); + assertExpandedPanelCentred(); + } + + [Test] + public void TestSelectMultipleScores() + { + var firstScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var secondScore = new TestScoreInfo(new OsuRuleset().RulesetInfo); + + createListStep(() => new ScorePanelList()); + + AddStep("add scores and select first", () => + { + list.AddScore(firstScore); + list.AddScore(secondScore); + list.SelectedScore.Value = firstScore; + }); + + assertScoreState(firstScore, true); + assertScoreState(secondScore, false); + + AddStep("select second score", () => + { + InputManager.MoveMouseTo(list.ChildrenOfType().Single(p => p.Score == secondScore)); + InputManager.Click(MouseButton.Left); + }); + + assertScoreState(firstScore, false); + assertScoreState(secondScore, true); + assertExpandedPanelCentred(); } private void createListStep(Func creationFunc) @@ -149,13 +193,16 @@ namespace osu.Game.Tests.Visual.Ranking AddUntilStep("wait for load", () => list.IsLoaded); } - private void assertPanelCentred() => AddUntilStep("expanded panel centred", () => + private void assertExpandedPanelCentred() => AddUntilStep("expanded panel centred", () => { var expandedPanel = list.ChildrenOfType().Single(p => p.State == PanelState.Expanded); return Precision.AlmostEquals(expandedPanel.ScreenSpaceDrawQuad.Centre.X, list.ScreenSpaceDrawQuad.Centre.X, 1); }); + private void assertFirstPanelCentred() + => AddUntilStep("first panel centred", () => Precision.AlmostEquals(list.ChildrenOfType().First().ScreenSpaceDrawQuad.Centre.X, list.ScreenSpaceDrawQuad.Centre.X, 1)); + private void assertScoreState(ScoreInfo score, bool expanded) - => AddUntilStep($"correct score expanded = {expanded}", () => (list.ChildrenOfType().Single(p => p.Score == score).State == PanelState.Expanded) == expanded); + => AddUntilStep($"score expanded = {expanded}", () => (list.ChildrenOfType().Single(p => p.Score == score).State == PanelState.Expanded) == expanded); } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 89f5a47ee8..18db3f2af4 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -98,7 +98,7 @@ namespace osu.Game.Screens.Ranking private void selectedScoreChanged(ValueChangedEvent score) { // Contract the old panel. - foreach (var p in flow.Where(p => p.Score != score.OldValue)) + foreach (var p in flow.Where(p => p.Score == score.OldValue)) { p.State = PanelState.Contracted; p.Margin = new MarginPadding(); @@ -126,8 +126,19 @@ namespace osu.Game.Screens.Ranking { base.Update(); + float offset = DrawWidth / 2f; + // Add padding to both sides such that the centre of an expanded panel on either side is in the middle of the screen. - flow.Padding = new MarginPadding { Horizontal = DrawWidth / 2f - ScorePanel.EXPANDED_WIDTH / 2f - expanded_panel_spacing }; + + if (SelectedScore.Value != null) + { + // The expanded panel has extra padding applied to it, so it needs to be included into the offset. + offset -= ScorePanel.EXPANDED_WIDTH / 2f + expanded_panel_spacing; + } + else + offset -= ScorePanel.CONTRACTED_WIDTH / 2f; + + flow.Padding = new MarginPadding { Horizontal = offset }; } private class Flow : FillFlowContainer From 47d5974f04c77d1a4e1b59beeb2bb85892fbba33 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 May 2020 21:40:01 +0900 Subject: [PATCH 198/871] Improve results screen behaviour when changing selected score --- .../Screens/Ranking/ReplayDownloadButton.cs | 3 ++ osu.Game/Screens/Ranking/ResultsScreen.cs | 28 +++++++++++-------- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 2 +- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index a36c86eafc..347fcb5f6e 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; @@ -13,6 +14,8 @@ namespace osu.Game.Screens.Ranking { public class ReplayDownloadButton : DownloadTrackingComposite { + public Bindable Score => Model; + private DownloadButton button; private ShakeContainer shakeContainer; diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index a4d1a3c26b..fbb9b95478 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -30,16 +31,17 @@ namespace osu.Game.Screens.Ranking protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap.Value); + public readonly Bindable SelectedScore = new Bindable(); + + public readonly ScoreInfo Score; + private readonly bool allowRetry; + [Resolved(CanBeNull = true)] private Player player { get; set; } [Resolved] private IAPIProvider api { get; set; } - public readonly ScoreInfo Score; - - private readonly bool allowRetry; - private Drawable bottomPanel; private ScorePanelList panels; @@ -47,6 +49,8 @@ namespace osu.Game.Screens.Ranking { Score = score; this.allowRetry = allowRetry; + + SelectedScore.Value = score; } [BackgroundDependencyLoader] @@ -66,7 +70,7 @@ namespace osu.Game.Screens.Ranking Child = panels = new ScorePanelList { RelativeSizeAxes = Axes.Both, - SelectedScore = { Value = Score } + SelectedScore = { BindTarget = SelectedScore } } } }, @@ -95,7 +99,11 @@ namespace osu.Game.Screens.Ranking Direction = FillDirection.Horizontal, Children = new Drawable[] { - new ReplayDownloadButton(Score) { Width = 300 }, + new ReplayDownloadButton(null) + { + Score = { BindTarget = SelectedScore }, + Width = 300 + }, } } } @@ -109,6 +117,9 @@ namespace osu.Game.Screens.Ranking } }; + if (Score != null) + panels.AddScore(Score); + if (player != null && allowRetry) { buttons.Add(new RetryButton { Width = 300 }); @@ -132,12 +143,7 @@ namespace osu.Game.Screens.Ranking var req = FetchScores(scores => Schedule(() => { foreach (var s in scores) - { - if (s.OnlineScoreID == Score.OnlineScoreID) - continue; - panels.AddScore(s); - } })); if (req != null) diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 2b00748ed8..3ae723683a 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Ranking protected override APIRequest FetchScores(Action> scoresCallback) { var req = new GetScoresRequest(Score.Beatmap, Score.Ruleset); - req.Success += r => scoresCallback?.Invoke(r.Scores.Select(s => s.CreateScoreInfo(rulesets))); + req.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets))); return req; } } From 013461377e89730ee381499d816c3e9bd3c4887d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 May 2020 21:46:02 +0900 Subject: [PATCH 199/871] Fix potential nullref --- .../Gameplay/TestSceneReplayDownloadButton.cs | 17 +++++++++++++++++ .../Screens/Ranking/ReplayDownloadButton.cs | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index a35437a286..1809332bce 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -28,6 +28,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep(@"locally available state", () => downloadButton.SetDownloadState(DownloadState.LocallyAvailable)); AddStep(@"not downloaded state", () => downloadButton.SetDownloadState(DownloadState.NotDownloaded)); createButton(false); + createButtonNoScore(); } private void createButton(bool withReplay) @@ -40,6 +41,22 @@ namespace osu.Game.Tests.Visual.Gameplay Origin = Anchor.Centre, }; }); + + AddUntilStep("wait for load", () => downloadButton.IsLoaded); + } + + private void createButtonNoScore() + { + AddStep("create button with null score", () => + { + Child = downloadButton = new TestReplayDownloadButton(null) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }; + }); + + AddUntilStep("wait for load", () => downloadButton.IsLoaded); } private ScoreInfo getScoreInfo(bool replayAvailable) diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index 347fcb5f6e..9d4e3af230 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens.Ranking if (State.Value == DownloadState.LocallyAvailable) return ReplayAvailability.Local; - if (!string.IsNullOrEmpty(Model.Value.Hash)) + if (!string.IsNullOrEmpty(Model.Value?.Hash)) return ReplayAvailability.Online; return ReplayAvailability.NotAvailable; From 7ae2383288109693324d02cb6c39805a4df0a3f4 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 28 May 2020 15:03:49 +0200 Subject: [PATCH 200/871] move stable config declaration and initial reading --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 29 +++++++----------------- osu.Game.Tournament/Models/StableInfo.cs | 2 -- 2 files changed, 8 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index d93bce8dfa..6a403c5a6a 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -39,6 +39,7 @@ namespace osu.Game.Tournament.IPC [Resolved] private StableInfo stableInfo { get; set; } + private const string STABLE_CONFIG = "tournament/stable.json"; public Storage IPCStorage { get; private set; } @@ -161,13 +162,14 @@ namespace osu.Game.Tournament.IPC private string findStablePath() { - string stableInstallPath = string.Empty; + if (!string.IsNullOrEmpty(stableInfo.StablePath.Value)) + return stableInfo.StablePath.Value; + string stableInstallPath = string.Empty; try { List> stableFindMethods = new List> { - readFromStableInfo, findFromEnvVar, findFromRegistry, findFromLocalAppData, @@ -180,7 +182,7 @@ namespace osu.Game.Tournament.IPC if (stableInstallPath != null) { - saveStablePath(stableInstallPath); + saveStableConfig(stableInstallPath); return stableInstallPath; } } @@ -193,11 +195,12 @@ namespace osu.Game.Tournament.IPC } } - private void saveStablePath(string path) + + private void saveStableConfig(string path) { stableInfo.StablePath.Value = path; - using (var stream = tournamentStorage.GetStream(StableInfo.STABLE_CONFIG, FileAccess.Write, FileMode.Create)) + using (var stream = tournamentStorage.GetStream(STABLE_CONFIG, FileAccess.Write, FileMode.Create)) using (var sw = new StreamWriter(stream)) { sw.Write(JsonConvert.SerializeObject(stableInfo, @@ -227,22 +230,6 @@ namespace osu.Game.Tournament.IPC return null; } - private string readFromStableInfo() - { - try - { - Logger.Log("Trying to find stable through the json config"); - - if (!string.IsNullOrEmpty(stableInfo.StablePath.Value)) - return stableInfo.StablePath.Value; - } - catch - { - } - - return null; - } - private string findFromLocalAppData() { Logger.Log("Trying to find stable in %LOCALAPPDATA%"); diff --git a/osu.Game.Tournament/Models/StableInfo.cs b/osu.Game.Tournament/Models/StableInfo.cs index 63423ca6fa..873e1c5e25 100644 --- a/osu.Game.Tournament/Models/StableInfo.cs +++ b/osu.Game.Tournament/Models/StableInfo.cs @@ -15,7 +15,5 @@ namespace osu.Game.Tournament.Models { public Bindable StablePath = new Bindable(string.Empty); - [JsonIgnore] - public const string STABLE_CONFIG = "tournament/stable.json"; } } From ee591829899d2fd64938bd9ac406e88ad72b9082 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 26 May 2020 18:12:19 +0900 Subject: [PATCH 201/871] Implement initial structure for room scores --- .../Requests/GetRoomPlaylistScoresRequest.cs | 21 +++++ osu.Game/Online/API/RoomScore.cs | 79 +++++++++++++++++++ .../Screens/Multi/Match/MatchSubScreen.cs | 16 ++++ .../Screens/Multi/Play/TimeshiftPlayer.cs | 8 ++ .../Multi/Ranking/TimeshiftResultsScreen.cs | 34 ++++++++ 5 files changed, 158 insertions(+) create mode 100644 osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs create mode 100644 osu.Game/Online/API/RoomScore.cs create mode 100644 osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs diff --git a/osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs b/osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs new file mode 100644 index 0000000000..dd7f80fd46 --- /dev/null +++ b/osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs @@ -0,0 +1,21 @@ +// 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; + +namespace osu.Game.Online.API.Requests +{ + public class GetRoomPlaylistScoresRequest : APIRequest> + { + private readonly int roomId; + private readonly int playlistItemId; + + public GetRoomPlaylistScoresRequest(int roomId, int playlistItemId) + { + this.roomId = roomId; + this.playlistItemId = playlistItemId; + } + + protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores"; + } +} diff --git a/osu.Game/Online/API/RoomScore.cs b/osu.Game/Online/API/RoomScore.cs new file mode 100644 index 0000000000..5d0a9539aa --- /dev/null +++ b/osu.Game/Online/API/RoomScore.cs @@ -0,0 +1,79 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; + +namespace osu.Game.Online.API +{ + public class RoomScore + { + [JsonProperty("id")] + public int ID { get; set; } + + [JsonProperty("user_id")] + public int UserID { get; set; } + + [JsonProperty("room_id")] + public int RoomID { get; set; } + + [JsonProperty("playlist_item_id")] + public int PlaylistItemID { get; set; } + + [JsonProperty("beatmap_id")] + public int BeatmapID { get; set; } + + [JsonProperty("rank")] + [JsonConverter(typeof(StringEnumConverter))] + public ScoreRank Rank { get; set; } + + [JsonProperty("total_score")] + public long TotalScore { get; set; } + + [JsonProperty("accuracy")] + public double Accuracy { get; set; } + + [JsonProperty("max_combo")] + public int MaxCombo { get; set; } + + [JsonProperty("mods")] + public APIMod[] Mods { get; set; } + + [JsonProperty("statistics")] + public Dictionary Statistics = new Dictionary(); + + [JsonProperty("passed")] + public bool Passed { get; set; } + + [JsonProperty("ended_at")] + public DateTimeOffset EndedAt { get; set; } + + public ScoreInfo CreateScoreInfo(PlaylistItem playlistItem) + { + var scoreInfo = new ScoreInfo + { + OnlineScoreID = ID, + TotalScore = TotalScore, + MaxCombo = MaxCombo, + Beatmap = playlistItem.Beatmap.Value, + BeatmapInfoID = playlistItem.BeatmapID, + Ruleset = playlistItem.Ruleset.Value, + RulesetID = playlistItem.RulesetID, + User = null, // todo: do we have a user object? + Accuracy = Accuracy, + Date = EndedAt, + Hash = string.Empty, // todo: temporary? + Rank = Rank, + Mods = Array.Empty(), // todo: how? + }; + + return scoreInfo; + } + } +} diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index caa547ac72..c37f51bcb4 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -10,6 +10,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.GameTypes; using osu.Game.Rulesets.Mods; @@ -162,6 +164,9 @@ namespace osu.Game.Screens.Multi.Match }; } + [Resolved] + private IAPIProvider api { get; set; } + protected override void LoadComplete() { base.LoadComplete(); @@ -185,6 +190,17 @@ namespace osu.Game.Screens.Multi.Match managerAdded = beatmapManager.ItemAdded.GetBoundCopy(); managerAdded.BindValueChanged(beatmapAdded); + + if (roomId.Value != null) + { + var req = new GetRoomPlaylistScoresRequest(roomId.Value.Value, playlist[0].ID); + + req.Success += scores => + { + }; + + api.Queue(req); + } } public override bool OnExiting(IScreen next) diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index 7f58de29fb..fbe9e3480f 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -14,7 +14,9 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; using osu.Game.Rulesets; using osu.Game.Scoring; +using osu.Game.Screens.Multi.Ranking; using osu.Game.Screens.Play; +using osu.Game.Screens.Ranking; namespace osu.Game.Screens.Multi.Play { @@ -88,6 +90,12 @@ namespace osu.Game.Screens.Multi.Play return false; } + protected override ResultsScreen CreateResults(ScoreInfo score) + { + Debug.Assert(roomId.Value != null); + return new TimeshiftResultsScreen(score, roomId.Value.Value, playlistItem); + } + protected override ScoreInfo CreateScore() { submitScore(); diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs new file mode 100644 index 0000000000..60cffc06df --- /dev/null +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.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; +using System.Collections.Generic; +using System.Linq; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Multiplayer; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking; + +namespace osu.Game.Screens.Multi.Ranking +{ + public class TimeshiftResultsScreen : ResultsScreen + { + private readonly int roomId; + private readonly PlaylistItem playlistItem; + + public TimeshiftResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem, bool allowRetry = true) + : base(score, allowRetry) + { + this.roomId = roomId; + this.playlistItem = playlistItem; + } + + protected override APIRequest FetchScores(Action> scoresCallback) + { + var req = new GetRoomPlaylistScoresRequest(roomId, playlistItem.ID); + req.Success += r => scoresCallback?.Invoke(r.Select(s => s.CreateScoreInfo(playlistItem))); + return req; + } + } +} From 38502ba88c29615642e986c7dce99bbe16b92e87 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 May 2020 14:54:51 +0900 Subject: [PATCH 202/871] Remove some unnecessary members --- osu.Game/Online/API/RoomScore.cs | 9 --------- 1 file changed, 9 deletions(-) diff --git a/osu.Game/Online/API/RoomScore.cs b/osu.Game/Online/API/RoomScore.cs index 5d0a9539aa..3ad6169833 100644 --- a/osu.Game/Online/API/RoomScore.cs +++ b/osu.Game/Online/API/RoomScore.cs @@ -20,15 +20,6 @@ namespace osu.Game.Online.API [JsonProperty("user_id")] public int UserID { get; set; } - [JsonProperty("room_id")] - public int RoomID { get; set; } - - [JsonProperty("playlist_item_id")] - public int PlaylistItemID { get; set; } - - [JsonProperty("beatmap_id")] - public int BeatmapID { get; set; } - [JsonProperty("rank")] [JsonConverter(typeof(StringEnumConverter))] public ScoreRank Rank { get; set; } From f9c64d7be38558d5b737ade5053622cbff276906 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 May 2020 19:57:58 +0900 Subject: [PATCH 203/871] Implement creation of mods --- osu.Game/Online/API/RoomScore.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/RoomScore.cs b/osu.Game/Online/API/RoomScore.cs index 3ad6169833..cb4f47c812 100644 --- a/osu.Game/Online/API/RoomScore.cs +++ b/osu.Game/Online/API/RoomScore.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using osu.Game.Online.Multiplayer; @@ -61,7 +62,7 @@ namespace osu.Game.Online.API Date = EndedAt, Hash = string.Empty, // todo: temporary? Rank = Rank, - Mods = Array.Empty(), // todo: how? + Mods = Mods.Select(m => m.ToMod(playlistItem.Ruleset.Value.CreateInstance())).ToArray() }; return scoreInfo; From 7ac08620b80e335f747be668dfe5eaeae2a78703 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 May 2020 19:58:07 +0900 Subject: [PATCH 204/871] Add a user object for now --- osu.Game/Online/API/RoomScore.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/RoomScore.cs b/osu.Game/Online/API/RoomScore.cs index cb4f47c812..00907eaa38 100644 --- a/osu.Game/Online/API/RoomScore.cs +++ b/osu.Game/Online/API/RoomScore.cs @@ -7,9 +7,9 @@ using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using osu.Game.Online.Multiplayer; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Users; namespace osu.Game.Online.API { @@ -18,8 +18,8 @@ namespace osu.Game.Online.API [JsonProperty("id")] public int ID { get; set; } - [JsonProperty("user_id")] - public int UserID { get; set; } + [JsonProperty("user")] + public User User { get; set; } [JsonProperty("rank")] [JsonConverter(typeof(StringEnumConverter))] @@ -57,7 +57,7 @@ namespace osu.Game.Online.API BeatmapInfoID = playlistItem.BeatmapID, Ruleset = playlistItem.Ruleset.Value, RulesetID = playlistItem.RulesetID, - User = null, // todo: do we have a user object? + User = User, Accuracy = Accuracy, Date = EndedAt, Hash = string.Empty, // todo: temporary? From d88bfa2080f7c1dcf852dfa278fd7f068dd3d8dc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 May 2020 20:07:51 +0900 Subject: [PATCH 205/871] Cache ruleset + fix possible nullrefs --- osu.Game/Online/API/RoomScore.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/RoomScore.cs b/osu.Game/Online/API/RoomScore.cs index 00907eaa38..a1b08fe40e 100644 --- a/osu.Game/Online/API/RoomScore.cs +++ b/osu.Game/Online/API/RoomScore.cs @@ -7,6 +7,7 @@ using System.Linq; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Users; @@ -48,6 +49,8 @@ namespace osu.Game.Online.API public ScoreInfo CreateScoreInfo(PlaylistItem playlistItem) { + var rulesetInstance = playlistItem.Ruleset.Value.CreateInstance(); + var scoreInfo = new ScoreInfo { OnlineScoreID = ID, @@ -62,7 +65,7 @@ namespace osu.Game.Online.API Date = EndedAt, Hash = string.Empty, // todo: temporary? Rank = Rank, - Mods = Mods.Select(m => m.ToMod(playlistItem.Ruleset.Value.CreateInstance())).ToArray() + Mods = Mods?.Select(m => m.ToMod(rulesetInstance)).ToArray() ?? Array.Empty() }; return scoreInfo; From 0e28ded80fc4b078b10c9666f1399ce8fa994aa8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 May 2020 20:07:54 +0900 Subject: [PATCH 206/871] Forward statistics --- osu.Game/Online/API/RoomScore.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/API/RoomScore.cs b/osu.Game/Online/API/RoomScore.cs index a1b08fe40e..3c7f8c9833 100644 --- a/osu.Game/Online/API/RoomScore.cs +++ b/osu.Game/Online/API/RoomScore.cs @@ -60,6 +60,7 @@ namespace osu.Game.Online.API BeatmapInfoID = playlistItem.BeatmapID, Ruleset = playlistItem.Ruleset.Value, RulesetID = playlistItem.RulesetID, + Statistics = Statistics, User = User, Accuracy = Accuracy, Date = EndedAt, From 0f373acacb55be124e573144a8b112291ea82fdd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 May 2020 20:08:45 +0900 Subject: [PATCH 207/871] Add test scene --- .../TestSceneTimeshiftResultsScreen.cs | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs new file mode 100644 index 0000000000..7f43aea56e --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs @@ -0,0 +1,76 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; +using osu.Game.Screens.Multi.Ranking; +using osu.Game.Tests.Beatmaps; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneTimeshiftResultsScreen : ScreenTestScene + { + [Test] + public void TestShowResults() + { + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + var roomScores = new List(); + + for (int i = 0; i < 10; i++) + { + roomScores.Add(new RoomScore + { + ID = i, + Accuracy = 0.9 - 0.01 * i, + EndedAt = DateTimeOffset.Now.Subtract(TimeSpan.FromHours(i)), + Passed = true, + Rank = ScoreRank.B, + MaxCombo = 999, + TotalScore = 999999 - i * 1000, + User = new User + { + Id = 2, + Username = $"peppy{i}", + CoverUrl = "https://osu.ppy.sh/images/headers/profile-covers/c3.jpg", + }, + Statistics = + { + { HitResult.Miss, 1 }, + { HitResult.Meh, 50 }, + { HitResult.Good, 100 }, + { HitResult.Great, 300 }, + } + }); + } + + AddStep("bind request handler", () => ((DummyAPIAccess)API).HandleRequest = request => + { + switch (request) + { + case GetRoomPlaylistScoresRequest r: + r.TriggerSuccess(roomScores); + break; + } + }); + + AddStep("load results", () => + { + LoadScreen(new TimeshiftResultsScreen(score, 1, new PlaylistItem + { + Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo } + })); + }); + + AddWaitStep("wait for display", 10); + } + } +} From a606f41297931301e66225c6ada60cf5fa6b326b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 May 2020 22:25:00 +0900 Subject: [PATCH 208/871] Add button to open results --- .../TestSceneTimeshiftResultsScreen.cs | 14 ++++++-- .../Screens/Multi/Match/MatchSubScreen.cs | 33 +++++++++++++++++-- .../Multi/Ranking/TimeshiftResultsScreen.cs | 2 +- 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs index 7f43aea56e..d87a2e3408 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs @@ -19,9 +19,19 @@ namespace osu.Game.Tests.Visual.Multiplayer public class TestSceneTimeshiftResultsScreen : ScreenTestScene { [Test] - public void TestShowResults() + public void TestShowResultsWithScore() + { + createResults(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + } + + [Test] + public void TestShowResultsNullScore() + { + createResults(null); + } + + private void createResults(ScoreInfo score) { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); var roomScores = new List(); for (int i = 0; i < 10; i++) diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index c37f51bcb4..01a90139c3 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -10,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; @@ -18,6 +20,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Screens.Multi.Components; using osu.Game.Screens.Multi.Match.Components; using osu.Game.Screens.Multi.Play; +using osu.Game.Screens.Multi.Ranking; using osu.Game.Screens.Select; using Footer = osu.Game.Screens.Multi.Match.Components.Footer; @@ -114,10 +117,29 @@ namespace osu.Game.Screens.Multi.Match { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Horizontal = 5 }, - Child = new OverlinedPlaylist(true) // Temporarily always allow selection + Child = new GridContainer { RelativeSizeAxes = Axes.Both, - SelectedItem = { BindTarget = SelectedItem } + Content = new[] + { + new Drawable[] + { + new OverlinedPlaylist(true) // Temporarily always allow selection + { + RelativeSizeAxes = Axes.Both, + SelectedItem = { BindTarget = SelectedItem } + } + }, + new Drawable[] + { + new TriangleButton + { + RelativeSizeAxes = Axes.X, + Text = "Show beatmap results", + Action = showBeatmapResults + } + } + } } }, new Container @@ -257,5 +279,12 @@ namespace osu.Game.Screens.Multi.Match break; } } + + private void showBeatmapResults() + { + Debug.Assert(roomId.Value != null); + + this.Push(new TimeshiftResultsScreen(null, roomId.Value.Value, SelectedItem.Value, false)); + } } } diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index 60cffc06df..f2afe15d35 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Multi.Ranking protected override APIRequest FetchScores(Action> scoresCallback) { var req = new GetRoomPlaylistScoresRequest(roomId, playlistItem.ID); - req.Success += r => scoresCallback?.Invoke(r.Select(s => s.CreateScoreInfo(playlistItem))); + req.Success += r => scoresCallback?.Invoke(r.Where(s => s.ID != Score?.OnlineScoreID).Select(s => s.CreateScoreInfo(playlistItem))); return req; } } From 3731e76b10b99a1307e222355efe95df3a5efb2d Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 28 May 2020 15:28:27 +0200 Subject: [PATCH 209/871] Move stable_config declaration, rename testscene --- ...thSelectScreens.cs => TestSceneStablePathSelectScreen.cs} | 0 osu.Game.Tournament/IPC/FileBasedIPC.cs | 5 +++-- osu.Game.Tournament/Models/StableInfo.cs | 2 -- osu.Game.Tournament/Screens/StablePathSelectScreen.cs | 2 +- osu.Game.Tournament/TournamentGameBase.cs | 4 ++-- 5 files changed, 6 insertions(+), 7 deletions(-) rename osu.Game.Tournament.Tests/Screens/{TestSceneStablePathSelectScreens.cs => TestSceneStablePathSelectScreen.cs} (100%) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreens.cs b/osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreen.cs similarity index 100% rename from osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreens.cs rename to osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreen.cs diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 6a403c5a6a..8518b7f8da 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -39,7 +39,8 @@ namespace osu.Game.Tournament.IPC [Resolved] private StableInfo stableInfo { get; set; } - private const string STABLE_CONFIG = "tournament/stable.json"; + + public const string STABLE_CONFIG = "tournament/stable.json"; public Storage IPCStorage { get; private set; } @@ -166,6 +167,7 @@ namespace osu.Game.Tournament.IPC return stableInfo.StablePath.Value; string stableInstallPath = string.Empty; + try { List> stableFindMethods = new List> @@ -195,7 +197,6 @@ namespace osu.Game.Tournament.IPC } } - private void saveStableConfig(string path) { stableInfo.StablePath.Value = path; diff --git a/osu.Game.Tournament/Models/StableInfo.cs b/osu.Game.Tournament/Models/StableInfo.cs index 873e1c5e25..4818842151 100644 --- a/osu.Game.Tournament/Models/StableInfo.cs +++ b/osu.Game.Tournament/Models/StableInfo.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using Newtonsoft.Json; using osu.Framework.Bindables; namespace osu.Game.Tournament.Models @@ -14,6 +13,5 @@ namespace osu.Game.Tournament.Models public class StableInfo { public Bindable StablePath = new Bindable(string.Empty); - } } diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index d2c7225909..eace3c78d5 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -38,7 +38,7 @@ namespace osu.Game.Tournament.Screens [BackgroundDependencyLoader(true)] private void load(Storage storage, OsuColour colours) { - var initialPath = new DirectoryInfo(storage.GetFullPath(stableInfo.StablePath.Value ?? string.Empty)).Parent?.FullName; + var initialPath = new DirectoryInfo(storage.GetFullPath(stableInfo.StablePath.Value ?? string.Empty)).Parent?.FullName; AddRangeInternal(new Drawable[] { diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 7d7d4f84aa..dcfe646390 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -148,9 +148,9 @@ namespace osu.Game.Tournament if (stableInfo == null) stableInfo = new StableInfo(); - if (storage.Exists(StableInfo.STABLE_CONFIG)) + if (storage.Exists(FileBasedIPC.STABLE_CONFIG)) { - using (Stream stream = storage.GetStream(StableInfo.STABLE_CONFIG, FileAccess.Read, FileMode.Open)) + using (Stream stream = storage.GetStream(FileBasedIPC.STABLE_CONFIG, FileAccess.Read, FileMode.Open)) using (var sr = new StreamReader(stream)) { stableInfo = JsonConvert.DeserializeObject(sr.ReadToEnd()); From 46689a2fbc46281277feb6fe806b0f756e1bfddc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 11:46:08 +0900 Subject: [PATCH 210/871] Tidy up and complete xmldoc for HitObjectComposer --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 89 ++++++++++++++++----- osu.sln.DotSettings | 5 +- 2 files changed, 74 insertions(+), 20 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 38576e02a0..c956439eb2 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.Edit protected ComposeBlueprintContainer BlueprintContainer { get; private set; } - public override Playfield Playfield => drawableRulesetWrapper.Playfield; - private DrawableEditRulesetWrapper drawableRulesetWrapper; protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both }; @@ -61,7 +59,6 @@ namespace osu.Game.Rulesets.Edit protected HitObjectComposer(Ruleset ruleset) { Ruleset = ruleset; - RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] @@ -137,6 +134,49 @@ namespace osu.Game.Rulesets.Edit EditorBeatmap.SelectedHitObjects.CollectionChanged += selectionChanged; } + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + + public override Playfield Playfield => drawableRulesetWrapper.Playfield; + + public override IEnumerable HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects; + + public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); + + /// + /// Defines all available composition tools, listed on the left side of the editor screen as button controls. + /// This should usually define one tool for each type used in the target ruleset. + /// + /// + /// A "select" tool is automatically added as the first tool. + /// + protected abstract IReadOnlyList CompositionTools { get; } + + /// + /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. + /// + protected abstract ComposeBlueprintContainer CreateBlueprintContainer(); + + /// + /// Construct a drawable ruleset for the provided ruleset. + /// + /// + /// Can be overridden to add editor-specific logical changes to a 's standard . + /// For example, hit animations or judgement logic may be changed to give a better editor user experience. + /// + /// The ruleset used to construct its drawable counterpart. + /// The loaded beatmap. + /// The mods to be applied. + /// An editor-relevant . + protected virtual DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + => (DrawableRuleset)ruleset.CreateDrawableRulesetWith(beatmap, mods); + + #region Tool selection logic + protected override bool OnKeyDown(KeyDownEvent e) { if (e.Key >= Key.Number1 && e.Key <= Key.Number9) @@ -153,13 +193,6 @@ namespace osu.Game.Rulesets.Edit return base.OnKeyDown(e); } - protected override void LoadComplete() - { - base.LoadComplete(); - - inputManager = GetContainingInputManager(); - } - private void selectionChanged(object sender, NotifyCollectionChangedEventArgs changedArgs) { if (EditorBeatmap.SelectedHitObjects.Any()) @@ -179,15 +212,9 @@ namespace osu.Game.Rulesets.Edit EditorBeatmap.SelectedHitObjects.Clear(); } - public override IEnumerable HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects; + #endregion - public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); - - protected abstract IReadOnlyList CompositionTools { get; } - - protected abstract ComposeBlueprintContainer CreateBlueprintContainer(); - - protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null); + #region IPlacementHandler public void BeginPlacement(HitObject hitObject) { @@ -209,6 +236,17 @@ namespace osu.Game.Rulesets.Edit public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject); + #endregion + + #region IPositionSnapProvider + + /// + /// Retrieve the relevant at a specified screen-space position. + /// In cases where a ruleset doesn't require custom logic (due to nested playfields, for example) + /// this will return the ruleset's main playfield. + /// + /// The screen-space position to query. + /// The most relevant . protected virtual Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) => drawableRulesetWrapper.Playfield; public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) @@ -257,8 +295,14 @@ namespace osu.Game.Rulesets.Edit return DurationToDistance(referenceTime, snappedEndTime - referenceTime); } + + #endregion } + /// + /// A non-generic definition of a HitObject composer class. + /// Generally used to access certain methods without requiring a generic type for . + /// [Cached(typeof(HitObjectComposer))] [Cached(typeof(IPositionSnapProvider))] public abstract class HitObjectComposer : CompositeDrawable, IPositionSnapProvider @@ -268,10 +312,13 @@ namespace osu.Game.Rulesets.Edit RelativeSizeAxes = Axes.Both; } + /// + /// The target ruleset's playfield. + /// public abstract Playfield Playfield { get; } /// - /// All the s. + /// All s in currently loaded beatmap. /// public abstract IEnumerable HitObjects { get; } @@ -280,6 +327,8 @@ namespace osu.Game.Rulesets.Edit /// public abstract bool CursorInPlacementArea { get; } + #region IPositionSnapProvider + public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); public abstract float GetBeatSnapDistanceAt(double referenceTime); @@ -291,5 +340,7 @@ namespace osu.Game.Rulesets.Edit public abstract double GetSnappedDurationFromDistance(double referenceTime, float distance); public abstract float GetSnappedDistanceFromDistance(double referenceTime, float distance); + + #endregion } } diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index e3b64c03b9..b9fc3de734 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -1,4 +1,4 @@ - + True True True @@ -905,14 +905,17 @@ private void load() True True True + True True True True True True True + True True True True + True True True From 8fa8c561e7bc438eb5c64473d70d50e6de4c4584 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 12:20:50 +0900 Subject: [PATCH 211/871] Pass hitobjects as a parameter to CreateBlueprintContainer --- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 4 +++- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 4 +++- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 10d344242c..7e2469a794 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; @@ -88,7 +89,8 @@ namespace osu.Game.Rulesets.Mania.Edit return drawableRuleset; } - protected override ComposeBlueprintContainer CreateBlueprintContainer() => new ManiaBlueprintContainer(drawableRuleset.Playfield.AllHitObjects); + protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) + => new ManiaBlueprintContainer(hitObjects); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index de5c1e54d7..37019a7a05 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; @@ -46,7 +47,8 @@ namespace osu.Game.Rulesets.Osu.Edit EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid(); } - protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(HitObjects); + protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) + => new OsuBlueprintContainer(hitObjects); private DistanceSnapGrid distanceSnapGrid; private Container distanceSnapGridContainer; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c956439eb2..8b9f531417 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Edit drawableRulesetWrapper, // layers above playfield drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer() - .WithChild(BlueprintContainer = CreateBlueprintContainer()) + .WithChild(BlueprintContainer = CreateBlueprintContainer(HitObjects)) } } }, @@ -159,7 +159,9 @@ namespace osu.Game.Rulesets.Edit /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. /// - protected abstract ComposeBlueprintContainer CreateBlueprintContainer(); + /// A live collection of all s in the editor beatmap. + protected virtual ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) + => new ComposeBlueprintContainer(hitObjects); /// /// Construct a drawable ruleset for the provided ruleset. From f9883373bbd86eb0513968d6dd487b95f4ce759f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 16:11:26 +0900 Subject: [PATCH 212/871] Flip direction to avoid breaking other usages --- osu.Game/Rulesets/Objects/Types/IHasDuration.cs | 16 +++++++++++++--- osu.Game/Rulesets/Objects/Types/IHasEndTime.cs | 16 +++++++++++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Types/IHasDuration.cs b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs index 2433f9597e..185fd5977b 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasDuration.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs @@ -8,17 +8,27 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that ends at a different time than its start time. /// - public interface IHasDuration +#pragma warning disable 618 + public interface IHasDuration : IHasEndTime +#pragma warning restore 618 { + double IHasEndTime.EndTime + { + get => EndTime; + set => Duration = (Duration - EndTime) + value; + } + + double IHasEndTime.Duration => Duration; + /// /// The time at which the HitObject ends. /// - double EndTime { get; } + new double EndTime { get; } /// /// The duration of the HitObject. /// [JsonIgnore] - double Duration { get; set; } + new double Duration { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs index 7395223c7e..c3769c5909 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs @@ -2,11 +2,25 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Newtonsoft.Json; namespace osu.Game.Rulesets.Objects.Types { + /// + /// A HitObject that ends at a different time than its start time. + /// [Obsolete("Use IHasDuration instead.")] // can be removed 20201126 - public interface IHasEndTime : IHasDuration + public interface IHasEndTime { + /// + /// The time at which the HitObject ends. + /// + [JsonIgnore] + double EndTime { get; set; } + + /// + /// The duration of the HitObject. + /// + double Duration { get; } } } From 7d4e60f05e01b77932ffac10ab5f5735a85a3015 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 23 May 2020 16:06:25 +0900 Subject: [PATCH 213/871] Add basic setup for TaikoHitObjectComposer --- .../TestSceneTaikoHitObjectComposer.cs | 58 +++++++++++++++++++ .../TaikoHitObjectComposer.cs | 34 +++++++++++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 3 + 3 files changed, 95 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs create mode 100644 osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs new file mode 100644 index 0000000000..0fff08fab7 --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs @@ -0,0 +1,58 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Screens.Edit; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + public class TestSceneTaikoHitObjectComposer : EditorClockTestScene + { + private TestComposer composer; + + [SetUp] + public void Setup() => Schedule(() => + { + BeatDivisor.Value = 8; + Clock.Seek(0); + + Child = composer = new TestComposer { RelativeSizeAxes = Axes.Both }; + }); + + [Test] + public void BasicTest() + { + } + + private class TestComposer : CompositeDrawable + { + [Cached(typeof(EditorBeatmap))] + [Cached(typeof(IBeatSnapProvider))] + public readonly EditorBeatmap EditorBeatmap; + + public readonly TaikoHitObjectComposer Composer; + + public TestComposer() + { + InternalChildren = new Drawable[] + { + EditorBeatmap = new EditorBeatmap(new TaikoBeatmap()) + { + BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo } + }, + Composer = new TaikoHitObjectComposer(new TaikoRuleset()) + }; + + for (int i = 0; i < 10; i++) + EditorBeatmap.Add(new Hit { StartTime = 125 * i }); + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs new file mode 100644 index 0000000000..069517fe46 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.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.Collections.Generic; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.UI; +using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Taiko +{ + public class TaikoHitObjectComposer : HitObjectComposer + { + private DrawableTaikoRuleset drawableRuleset; + + public TaikoHitObjectComposer(Ruleset ruleset) + : base(ruleset) + { + } + + protected override IReadOnlyList CompositionTools => new List(); + + protected override ComposeBlueprintContainer CreateBlueprintContainer() => new ComposeBlueprintContainer(drawableRuleset.Playfield.AllHitObjects); + + protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + { + return drawableRuleset = new DrawableTaikoRuleset(ruleset, beatmap, mods); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 74d9e68ad3..7be16471b4 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -21,6 +21,7 @@ using osu.Game.Rulesets.Taiko.Difficulty; using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Scoring; using System; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Taiko.Skinning; using osu.Game.Skinning; @@ -144,6 +145,8 @@ namespace osu.Game.Rulesets.Taiko public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetTaiko }; + public override HitObjectComposer CreateHitObjectComposer() => new TaikoHitObjectComposer(this); + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => new TaikoDifficultyCalculator(this, beatmap); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new TaikoPerformanceCalculator(this, beatmap, score); From 90acba8c36ded4f5d6c3873d46c99e27a5ff9517 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 May 2020 19:23:36 +0900 Subject: [PATCH 214/871] Introduce initial placement blueprint logic --- .../TestSceneTaikoHitObjectComposer.cs | 8 +- .../TaikoHitObjectComposer.cs | 119 +++++++++++++++++- 2 files changed, 119 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs index 0fff08fab7..b5ee33fa8e 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs @@ -15,15 +15,13 @@ namespace osu.Game.Rulesets.Taiko.Tests { public class TestSceneTaikoHitObjectComposer : EditorClockTestScene { - private TestComposer composer; - [SetUp] public void Setup() => Schedule(() => { BeatDivisor.Value = 8; Clock.Seek(0); - Child = composer = new TestComposer { RelativeSizeAxes = Axes.Both }; + Child = new TestComposer { RelativeSizeAxes = Axes.Both }; }); [Test] @@ -37,8 +35,6 @@ namespace osu.Game.Rulesets.Taiko.Tests [Cached(typeof(IBeatSnapProvider))] public readonly EditorBeatmap EditorBeatmap; - public readonly TaikoHitObjectComposer Composer; - public TestComposer() { InternalChildren = new Drawable[] @@ -47,7 +43,7 @@ namespace osu.Game.Rulesets.Taiko.Tests { BeatmapInfo = { Ruleset = new TaikoRuleset().RulesetInfo } }, - Composer = new TaikoHitObjectComposer(new TaikoRuleset()) + new TaikoHitObjectComposer(new TaikoRuleset()) }; for (int i = 0; i < 10; i++) diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index 069517fe46..dfdc91f7dc 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -2,14 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; +using osuTK; +using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Rulesets.Taiko { @@ -22,13 +30,120 @@ namespace osu.Game.Rulesets.Taiko { } - protected override IReadOnlyList CompositionTools => new List(); + protected override IReadOnlyList CompositionTools => new[] + { + new HitCompositionTool() + }; - protected override ComposeBlueprintContainer CreateBlueprintContainer() => new ComposeBlueprintContainer(drawableRuleset.Playfield.AllHitObjects); + protected override ComposeBlueprintContainer CreateBlueprintContainer() => new TaikoBlueprintContainer(drawableRuleset.Playfield.AllHitObjects); protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) { return drawableRuleset = new DrawableTaikoRuleset(ruleset, beatmap, mods); } } + + public class TaikoBlueprintContainer : ComposeBlueprintContainer + { + public TaikoBlueprintContainer(IEnumerable hitObjects) + : base(hitObjects) + { + } + + public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => + new TaikoSelectionBlueprint(hitObject); + } + + public class TaikoSelectionBlueprint : OverlaySelectionBlueprint + { + public TaikoSelectionBlueprint(DrawableHitObject hitObject) + : base(hitObject) + { + RelativeSizeAxes = Axes.None; + + AddInternal(new HitPiece { RelativeSizeAxes = Axes.Both }); + } + + protected override void Update() + { + base.Update(); + + // Move the rectangle to cover the hitobjects + var topLeft = new Vector2(float.MaxValue, float.MaxValue); + var bottomRight = new Vector2(float.MinValue, float.MinValue); + + topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.TopLeft)); + bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.BottomRight)); + + Size = bottomRight - topLeft; + Position = topLeft; + } + } + + public class HitCompositionTool : HitObjectCompositionTool + { + public HitCompositionTool() + : base(nameof(Hit)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint(); + } + + public class HitPlacementBlueprint : PlacementBlueprint + + { + private readonly HitPiece piece; + + public HitPlacementBlueprint() + : base(new Hit()) + { + InternalChild = piece = new HitPiece + { + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) + }; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.Button == MouseButton.Left) + { + EndPlacement(true); + return true; + } + + return base.OnMouseDown(e); + } + + public override void UpdatePosition(SnapResult snapResult) + { + piece.Position = ToLocalSpace(snapResult.ScreenSpacePosition); + base.UpdatePosition(snapResult); + } + } + + public class HitPiece : CompositeDrawable + { + public HitPiece() + { + Origin = Anchor.Centre; + + InternalChild = new CircularContainer + { + Masking = true, + BorderThickness = 10, + BorderColour = Color4.Yellow, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + AlwaysPresent = true, + Alpha = 0, + RelativeSizeAxes = Axes.Both + } + } + }; + } + } } From 4b1a2b5bc2d1481120d60700270c7b48436d853c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 25 May 2020 22:36:49 +0900 Subject: [PATCH 215/871] Fix offsets --- osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index dfdc91f7dc..c820b3eb92 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -61,7 +61,11 @@ namespace osu.Game.Rulesets.Taiko { RelativeSizeAxes = Axes.None; - AddInternal(new HitPiece { RelativeSizeAxes = Axes.Both }); + AddInternal(new HitPiece + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.TopLeft + }); } protected override void Update() From 3487c1fd1b9e1d53df72f60cece30f890c239637 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 13:51:53 +0900 Subject: [PATCH 216/871] Add menus to mark as rim and strong --- .../TestSceneEditor.cs | 17 +++++ osu.Game.Rulesets.Taiko/Objects/Hit.cs | 10 ++- .../Objects/TaikoHitObject.cs | 9 ++- .../TaikoHitObjectComposer.cs | 70 +++++++++++++++++++ .../UserInterface/TernaryStateMenuItem.cs | 12 +--- 5 files changed, 105 insertions(+), 13 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs new file mode 100644 index 0000000000..089a7ad00b --- /dev/null +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs @@ -0,0 +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 NUnit.Framework; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Taiko.Tests +{ + [TestFixture] + public class TestSceneEditor : EditorTestScene + { + public TestSceneEditor() + : base(new TaikoRuleset()) + { + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Objects/Hit.cs b/osu.Game.Rulesets.Taiko/Objects/Hit.cs index 2aca701515..68cc8d0ead 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Hit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Hit.cs @@ -1,13 +1,21 @@ // 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; + namespace osu.Game.Rulesets.Taiko.Objects { public class Hit : TaikoHitObject { + public readonly Bindable TypeBindable = new Bindable(); + /// /// The that actuates this . /// - public HitType Type { get; set; } + public HitType Type + { + get => TypeBindable.Value; + set => TypeBindable.Value = value; + } } } diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index 206bfcfdb2..4de762ce30 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Threading; +using osu.Framework.Bindables; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -27,11 +28,17 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public const float DEFAULT_STRONG_SIZE = DEFAULT_SIZE * STRONG_SCALE; + public readonly Bindable IsStrongBindable = new BindableBool(); + /// /// Whether this HitObject is a "strong" type. /// Strong hit objects give more points for hitting the hit object with both keys. /// - public virtual bool IsStrong { get; set; } + public virtual bool IsStrong + { + get => IsStrongBindable.Value; + set => IsStrongBindable.Value = value; + } protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index c820b3eb92..beef2b6155 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -1,12 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; @@ -50,10 +54,76 @@ namespace osu.Game.Rulesets.Taiko { } + protected override SelectionHandler CreateSelectionHandler() => new TaikoSelectionHandler(); + public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => new TaikoSelectionBlueprint(hitObject); } + public class TaikoSelectionHandler : SelectionHandler + { + protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) + { + if (selection.All(s => s.HitObject is Hit)) + { + var hits = selection.Select(s => s.HitObject).OfType(); + + yield return new TernaryStateMenuItem("Rim", action: state => + { + foreach (var h in hits) + { + switch (state) + { + case TernaryState.True: + h.Type = HitType.Rim; + break; + + case TernaryState.False: + h.Type = HitType.Centre; + break; + } + } + }) + { + State = { Value = getTernaryState(hits, h => h.Type == HitType.Rim) } + }; + } + + if (selection.All(s => s.HitObject is TaikoHitObject)) + { + var hits = selection.Select(s => s.HitObject).OfType(); + + yield return new TernaryStateMenuItem("Strong", action: state => + { + foreach (var h in hits) + { + switch (state) + { + case TernaryState.True: + h.IsStrong = true; + break; + + case TernaryState.False: + h.IsStrong = false; + break; + } + } + }) + { + State = { Value = getTernaryState(hits, h => h.IsStrong) } + }; + } + } + + private TernaryState getTernaryState(IEnumerable selection, Func func) + { + if (selection.Any(func)) + return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate; + + return TernaryState.False; + } + } + public class TaikoSelectionBlueprint : OverlaySelectionBlueprint { public TaikoSelectionBlueprint(DrawableHitObject hitObject) diff --git a/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs b/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs index 2d9e2106d4..acf4065f49 100644 --- a/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/TernaryStateMenuItem.cs @@ -11,23 +11,13 @@ namespace osu.Game.Graphics.UserInterface /// public class TernaryStateMenuItem : StatefulMenuItem { - /// - /// Creates a new . - /// - /// The text to display. - /// The type of action which this performs. - public TernaryStateMenuItem(string text, MenuItemType type = MenuItemType.Standard) - : this(text, type, null) - { - } - /// /// Creates a new . /// /// The text to display. /// The type of action which this performs. /// A delegate to be invoked when this is pressed. - public TernaryStateMenuItem(string text, MenuItemType type, Action action) + public TernaryStateMenuItem(string text, MenuItemType type = MenuItemType.Standard, Action action = null) : this(text, getNextState, type, action) { } From 4e9631b54631655ee6d92742b14fb3c730d9a463 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 14:43:38 +0900 Subject: [PATCH 217/871] Support HitType bindable changes --- .../Objects/Drawables/DrawableHit.cs | 24 ++++++++++++++++++- .../Drawables/DrawableTaikoHitObject.cs | 21 ++++++++++++---- 2 files changed, 39 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs index 81b969eaf3..92ae7e0fd3 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableHit.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Audio; using osu.Game.Rulesets.Objects.Drawables; @@ -19,7 +21,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables /// /// A list of keys which can result in hits for this HitObject. /// - public TaikoAction[] HitActions { get; } + public TaikoAction[] HitActions { get; private set; } /// /// The action that caused this to be hit. @@ -34,15 +36,35 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables private bool pressHandledThisFrame; + private Bindable type; + public DrawableHit(Hit hit) : base(hit) { FillMode = FillMode.Fit; + } + [BackgroundDependencyLoader] + private void load() + { + type = HitObject.TypeBindable.GetBoundCopy(); + type.BindValueChanged(_ => + { + updateType(); + RecreatePieces(); + }); + + updateType(); + } + + private void updateType() + { HitActions = HitObject.Type == HitType.Centre ? new[] { TaikoAction.LeftCentre, TaikoAction.RightCentre } : new[] { TaikoAction.LeftRim, TaikoAction.RightRim }; + + RecreatePieces(); } protected override SkinnableDrawable CreateMainPiece() => HitObject.Type == HitType.Centre diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index 3ab09d4cbe..a3dfc9acc0 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -8,6 +8,7 @@ using osuTK; using System.Linq; using osu.Game.Audio; using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Objects; @@ -115,10 +116,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables public new TObject HitObject; - protected readonly Vector2 BaseSize; - protected readonly SkinnableDrawable MainPiece; + protected Vector2 BaseSize; + protected SkinnableDrawable MainPiece; - private readonly Container strongHitContainer; + private Container strongHitContainer; protected DrawableTaikoHitObject(TObject hitObject) : base(hitObject) @@ -129,11 +130,21 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Origin = Anchor.Custom; RelativeSizeAxes = Axes.Both; + AddInternal(strongHitContainer = new Container()); + } + + [BackgroundDependencyLoader] + private void load() + { + RecreatePieces(); + } + + protected virtual void RecreatePieces() + { Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE); + Content.Clear(); Content.Add(MainPiece = CreateMainPiece()); - - AddInternal(strongHitContainer = new Container()); } protected override void AddNestedHitObject(DrawableHitObject hitObject) From 50fcd4149f42dae335f9e0eae0507c516a2c058e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 14:54:12 +0900 Subject: [PATCH 218/871] Support Strong bindable changes --- .../Objects/Drawables/DrawableTaikoHitObject.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs index a3dfc9acc0..929cf8a937 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableTaikoHitObject.cs @@ -9,6 +9,7 @@ using System.Linq; using osu.Game.Audio; using System.Collections.Generic; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Game.Rulesets.Objects; @@ -119,7 +120,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables protected Vector2 BaseSize; protected SkinnableDrawable MainPiece; - private Container strongHitContainer; + private Bindable isStrong; + + private readonly Container strongHitContainer; protected DrawableTaikoHitObject(TObject hitObject) : base(hitObject) @@ -130,20 +133,22 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables Origin = Anchor.Custom; RelativeSizeAxes = Axes.Both; + AddInternal(strongHitContainer = new Container()); } [BackgroundDependencyLoader] private void load() { - RecreatePieces(); + isStrong = HitObject.IsStrongBindable.GetBoundCopy(); + isStrong.BindValueChanged(_ => RecreatePieces(), true); } protected virtual void RecreatePieces() { Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE); - Content.Clear(); + MainPiece?.Expire(); Content.Add(MainPiece = CreateMainPiece()); } From 910326623c8e24658565e63d95e77b707c244a4a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 15:09:22 +0900 Subject: [PATCH 219/871] Place rim hits using right mosue for now --- .../TaikoHitObjectComposer.cs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index beef2b6155..5d7880278a 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -169,8 +169,10 @@ namespace osu.Game.Rulesets.Taiko { private readonly HitPiece piece; + private static Hit hit; + public HitPlacementBlueprint() - : base(new Hit()) + : base(hit = new Hit()) { InternalChild = piece = new HitPiece { @@ -180,13 +182,20 @@ namespace osu.Game.Rulesets.Taiko protected override bool OnMouseDown(MouseDownEvent e) { - if (e.Button == MouseButton.Left) + switch (e.Button) { - EndPlacement(true); - return true; + case MouseButton.Left: + hit.Type = HitType.Centre; + EndPlacement(true); + return true; + + case MouseButton.Right: + hit.Type = HitType.Rim; + EndPlacement(true); + return true; } - return base.OnMouseDown(e); + return false; } public override void UpdatePosition(SnapResult snapResult) From a2eec5d963dad4cbf4cda878eb7fefc36d3c5d91 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 16:58:28 +0900 Subject: [PATCH 220/871] Fix strong bindable changes for DrumRolls --- .../Objects/Drawables/DrawableDrumRoll.cs | 17 +++++++++++------ osu.Game.Rulesets.Taiko/Objects/Swell.cs | 6 ------ .../Objects/TaikoHitObject.cs | 2 +- .../TaikoHitObjectComposer.cs | 2 ++ .../Edit/Compose/Components/SelectionHandler.cs | 8 ++++---- 5 files changed, 18 insertions(+), 17 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs index 5e731e5ad6..2c1c2d2bc1 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableDrumRoll.cs @@ -48,12 +48,11 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables colourIdle = colours.YellowDark; colourEngaged = colours.YellowDarker; - updateColour(); - - Content.Add(tickContainer = new Container { RelativeSizeAxes = Axes.Both }); - - if (MainPiece.Drawable is IHasAccentColour accentMain) - accentMain.AccentColour = colourIdle; + Content.Add(tickContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Depth = float.MinValue + }); } protected override void LoadComplete() @@ -63,6 +62,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables OnNewResult += onNewResult; } + protected override void RecreatePieces() + { + base.RecreatePieces(); + updateColour(); + } + protected override void AddNestedHitObject(DrawableHitObject hitObject) { base.AddNestedHitObject(hitObject); diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index 390f8d1f3b..b4c09b884e 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Threading; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Judgements; @@ -25,11 +24,6 @@ namespace osu.Game.Rulesets.Taiko.Objects /// public int RequiredHits = 10; - public override bool IsStrong - { - set => throw new NotSupportedException($"{nameof(Swell)} cannot be a strong hitobject."); - } - protected override void CreateNestedHitObjects(CancellationToken cancellationToken) { base.CreateNestedHitObjects(cancellationToken); diff --git a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs index 4de762ce30..2922010001 100644 --- a/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs +++ b/osu.Game.Rulesets.Taiko/Objects/TaikoHitObject.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Taiko.Objects /// Whether this HitObject is a "strong" type. /// Strong hit objects give more points for hitting the hit object with both keys. /// - public virtual bool IsStrong + public bool IsStrong { get => IsStrongBindable.Value; set => IsStrongBindable.Value = value; diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index 5d7880278a..802bb80fa3 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -107,6 +107,8 @@ namespace osu.Game.Rulesets.Taiko h.IsStrong = false; break; } + + EditorBeatmap?.UpdateHitObject(h); } }) { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 7ab6340e07..38893f90a8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private Drawable outline; [Resolved(CanBeNull = true)] - private EditorBeatmap editorBeatmap { get; set; } + protected EditorBeatmap EditorBeatmap { get; private set; } [Resolved(CanBeNull = true)] private IEditorChangeHandler changeHandler { get; set; } @@ -117,7 +117,7 @@ namespace osu.Game.Screens.Edit.Compose.Components internal void HandleSelected(SelectionBlueprint blueprint) { selectedBlueprints.Add(blueprint); - editorBeatmap.SelectedHitObjects.Add(blueprint.HitObject); + EditorBeatmap.SelectedHitObjects.Add(blueprint.HitObject); UpdateVisibility(); } @@ -129,7 +129,7 @@ namespace osu.Game.Screens.Edit.Compose.Components internal void HandleDeselected(SelectionBlueprint blueprint) { selectedBlueprints.Remove(blueprint); - editorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject); + EditorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject); // We don't want to update visibility if > 0, since we may be deselecting blueprints during drag-selection if (selectedBlueprints.Count == 0) @@ -165,7 +165,7 @@ namespace osu.Game.Screens.Edit.Compose.Components changeHandler?.BeginChange(); foreach (var h in selectedBlueprints.ToList()) - editorBeatmap?.Remove(h.HitObject); + EditorBeatmap?.Remove(h.HitObject); changeHandler?.EndChange(); } From 280b0adb1dc532938d12e4fd966fb7be033030d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 26 May 2020 17:44:47 +0900 Subject: [PATCH 221/871] Split out IHasPath from IHasCurve to better define hitobjects --- .../Beatmaps/CatchBeatmapConverter.cs | 2 +- .../Objects/JuiceStream.cs | 2 +- .../Legacy/DistanceObjectPatternGenerator.cs | 2 +- .../Beatmaps/OsuBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- .../Beatmaps/TaikoBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 10 +---- .../Formats/LegacyBeatmapDecoderTest.cs | 2 +- .../Beatmaps/Formats/OsuJsonDecoderTest.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 39 +++++++++++-------- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 2 +- osu.Game/Rulesets/Objects/Types/IHasPath.cs | 13 +++++++ .../{IHasCurve.cs => IHasPathWithRepeats.cs} | 20 +++++----- 13 files changed, 57 insertions(+), 43 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Types/IHasPath.cs rename osu.Game/Rulesets/Objects/Types/{IHasCurve.cs => IHasPathWithRepeats.cs} (77%) diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 90a6e609f0..27a9b63e9a 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps switch (obj) { - case IHasCurve curveData: + case IHasPathWithRepeats curveData: return new JuiceStream { StartTime = obj.StartTime, diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index d32595c2e1..24090e233a 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -14,7 +14,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Catch.Objects { - public class JuiceStream : CatchHitObject, IHasCurve + public class JuiceStream : CatchHitObject, IHasPathWithRepeats { /// /// Positional distance that results in a duration of one second, before any speed adjustments. diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index d8d5b67c0e..1bd796511b 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -474,7 +474,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// private IList sampleInfoListAt(double time) { - if (!(HitObject is IHasCurve curveData)) + if (!(HitObject is IHasPathWithRepeats curveData)) return HitObject.Samples; double segmentTime = (EndTime - HitObject.StartTime) / spanCount; diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index 147d74c929..060a3919bd 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps switch (original) { - case IHasCurve curveData: + case IHasPathWithRepeats curveData: return new Slider { StartTime = original.StartTime, diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 6ba0e1c6aa..713d1a61f8 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -17,7 +17,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { - public class Slider : OsuHitObject, IHasCurve + public class Slider : OsuHitObject, IHasPathWithRepeats { public double EndTime { diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index d324441285..1a47be2282 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) { - List> allSamples = obj is IHasCurve curveData ? curveData.NodeSamples : new List>(new[] { samples }); + List> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List>(new[] { samples }); int i = 0; diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 7b11bce520..5f52160be1 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -3,9 +3,7 @@ using osu.Game.Rulesets.Objects.Types; using System; -using System.Collections.Generic; using System.Threading; -using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Judgements; @@ -17,7 +15,7 @@ using osuTK; namespace osu.Game.Rulesets.Taiko.Objects { - public class DrumRoll : TaikoHitObject, IHasCurve + public class DrumRoll : TaikoHitObject, IHasPath { /// /// Drum roll distance that results in a duration of 1 speed-adjusted beat length. @@ -115,11 +113,7 @@ namespace osu.Game.Rulesets.Taiko.Objects double IHasDistance.Distance => Duration * Velocity; - int IHasRepeats.RepeatCount { get => 0; set { } } - - List> IHasRepeats.NodeSamples => new List>(); - - SliderPath IHasCurve.Path + SliderPath IHasPath.Path => new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER); #endregion diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index acb30a6277..dab923d75b 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -365,7 +365,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { var hitObjects = decoder.Decode(stream).HitObjects; - var curveData = hitObjects[0] as IHasCurve; + var curveData = hitObjects[0] as IHasPathWithRepeats; var positionData = hitObjects[0] as IHasPosition; Assert.IsNotNull(positionData); diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs index b034e66616..b4c78ce273 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuJsonDecoderTest.cs @@ -95,7 +95,7 @@ namespace osu.Game.Tests.Beatmaps.Formats { var beatmap = decodeAsJson(normal); - var curveData = beatmap.HitObjects[0] as IHasCurve; + var curveData = beatmap.HitObjects[0] as IHasPathWithRepeats; var positionData = beatmap.HitObjects[0] as IHasPosition; Assert.IsNotNull(positionData); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index 7727f25967..d7e83fa471 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -233,9 +233,9 @@ namespace osu.Game.Beatmaps.Formats writer.Write(FormattableString.Invariant($"{(int)getObjectType(hitObject)},")); writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(hitObject.Samples)},")); - if (hitObject is IHasCurve curveData) + if (hitObject is IHasPathWithRepeats curveData) { - addCurveData(writer, curveData, position); + addPathData(writer, curveData, position); writer.Write(getSampleBank(hitObject.Samples, zeroBanks: true)); } else @@ -263,7 +263,7 @@ namespace osu.Game.Beatmaps.Formats switch (hitObject) { - case IHasCurve _: + case IHasPath _: type |= LegacyHitObjectType.Slider; break; @@ -282,13 +282,13 @@ namespace osu.Game.Beatmaps.Formats return type; } - private void addCurveData(TextWriter writer, IHasCurve curveData, Vector2 position) + private void addPathData(TextWriter writer, IHasPath pathData, Vector2 position) { PathType? lastType = null; - for (int i = 0; i < curveData.Path.ControlPoints.Count; i++) + for (int i = 0; i < pathData.Path.ControlPoints.Count; i++) { - PathControlPoint point = curveData.Path.ControlPoints[i]; + PathControlPoint point = pathData.Path.ControlPoints[i]; if (point.Type.Value != null) { @@ -325,23 +325,28 @@ namespace osu.Game.Beatmaps.Formats if (i != 0) { writer.Write(FormattableString.Invariant($"{position.X + point.Position.Value.X}:{position.Y + point.Position.Value.Y}")); - writer.Write(i != curveData.Path.ControlPoints.Count - 1 ? "|" : ","); + writer.Write(i != pathData.Path.ControlPoints.Count - 1 ? "|" : ","); } } - writer.Write(FormattableString.Invariant($"{curveData.RepeatCount + 1},")); - writer.Write(FormattableString.Invariant($"{curveData.Path.Distance},")); + var curveData = pathData as IHasPathWithRepeats; - for (int i = 0; i < curveData.NodeSamples.Count; i++) - { - writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(curveData.NodeSamples[i])}")); - writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); - } + writer.Write(FormattableString.Invariant($"{(curveData?.RepeatCount ?? 0) + 1},")); + writer.Write(FormattableString.Invariant($"{pathData.Path.Distance},")); - for (int i = 0; i < curveData.NodeSamples.Count; i++) + if (curveData != null) { - writer.Write(getSampleBank(curveData.NodeSamples[i], true)); - writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); + for (int i = 0; i < curveData.NodeSamples.Count; i++) + { + writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(curveData.NodeSamples[i])}")); + writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); + } + + for (int i = 0; i < curveData.NodeSamples.Count; i++) + { + writer.Write(getSampleBank(curveData.NodeSamples[i], true)); + writer.Write(i != curveData.NodeSamples.Count - 1 ? "|" : ","); + } } } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 924182b265..73192dc42e 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -9,7 +9,7 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Objects.Legacy { - internal abstract class ConvertSlider : ConvertHitObject, IHasCurve, IHasLegacyLastTickOffset + internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset { /// /// Scoring distance with a speed-adjusted beat length of 1 second. diff --git a/osu.Game/Rulesets/Objects/Types/IHasPath.cs b/osu.Game/Rulesets/Objects/Types/IHasPath.cs new file mode 100644 index 0000000000..567c24a4a2 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasPath.cs @@ -0,0 +1,13 @@ +// 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.Rulesets.Objects.Types +{ + public interface IHasPath : IHasDistance + { + /// + /// The curve. + /// + SliderPath Path { get; } + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs b/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs similarity index 77% rename from osu.Game/Rulesets/Objects/Types/IHasCurve.cs rename to osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs index e98a888bd7..fba0fd7aff 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.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 osuTK; namespace osu.Game.Rulesets.Objects.Types @@ -8,15 +9,16 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that has a curve. /// - public interface IHasCurve : IHasDistance, IHasRepeats + public interface IHasPathWithRepeats : IHasPath, IHasRepeats { - /// - /// The curve. - /// - SliderPath Path { get; } } - public static class HasCurveExtensions + [Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126 + public interface IHasCurve : IHasPathWithRepeats + { + } + + public static class HasPathWithRepeatsExtensions { /// /// Computes the position on the curve relative to how much of the has been completed. @@ -24,7 +26,7 @@ namespace osu.Game.Rulesets.Objects.Types /// The curve. /// [0, 1] where 0 is the start time of the and 1 is the end time of the . /// The position on the curve. - public static Vector2 CurvePositionAt(this IHasCurve obj, double progress) + public static Vector2 CurvePositionAt(this IHasPathWithRepeats obj, double progress) => obj.Path.PositionAt(obj.ProgressAt(progress)); /// @@ -33,7 +35,7 @@ namespace osu.Game.Rulesets.Objects.Types /// The curve. /// [0, 1] where 0 is the start time of the and 1 is the end time of the . /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. - public static double ProgressAt(this IHasCurve obj, double progress) + public static double ProgressAt(this IHasPathWithRepeats obj, double progress) { double p = progress * obj.SpanCount() % 1; if (obj.SpanAt(progress) % 2 == 1) @@ -47,7 +49,7 @@ namespace osu.Game.Rulesets.Objects.Types /// The curve. /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. /// [0, SpanCount) where 0 is the first run. - public static int SpanAt(this IHasCurve obj, double progress) + public static int SpanAt(this IHasPathWithRepeats obj, double progress) => (int)(progress * obj.SpanCount()); } } From a953f9e422810198b1d431b52f4b365c912c4e84 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 20:30:33 +0900 Subject: [PATCH 222/871] Add drum roll composition support --- .../TaikoHitObjectComposer.cs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index 802bb80fa3..d233cd5e7f 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -34,9 +34,10 @@ namespace osu.Game.Rulesets.Taiko { } - protected override IReadOnlyList CompositionTools => new[] + protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { - new HitCompositionTool() + new HitCompositionTool(), + new DrumRollCompositionTool() }; protected override ComposeBlueprintContainer CreateBlueprintContainer() => new TaikoBlueprintContainer(drawableRuleset.Playfield.AllHitObjects); @@ -156,6 +157,26 @@ namespace osu.Game.Rulesets.Taiko } } + public class DrumRollCompositionTool : HitObjectCompositionTool + { + public DrumRollCompositionTool() + : base(nameof(DrumRoll)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint(); + } + + public class DrumRollPlacementBlueprint : PlacementBlueprint + { + private static DrumRoll drumRoll; + + public DrumRollPlacementBlueprint() + : base(drumRoll = new DrumRoll()) + { + } + } + public class HitCompositionTool : HitObjectCompositionTool { public HitCompositionTool() From 534dccc0c384a0e33a6fbe49c1b3b2acc97d56b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 12:37:44 +0900 Subject: [PATCH 223/871] Move sett from EndTime to Duration --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 10 +++++----- osu.Game.Rulesets.Osu/Objects/Slider.cs | 8 ++++---- .../Objects/Drawables/DrawableSwell.cs | 2 +- .../Gameplay/TestSceneDrawableScrollingRuleset.cs | 6 +++--- .../Objects/Legacy/Catch/ConvertHitObjectParser.cs | 6 +++--- .../Objects/Legacy/Catch/ConvertSpinner.cs | 4 ++-- .../Objects/Legacy/ConvertHitObjectParser.cs | 14 +++++++------- osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs | 6 +++--- .../Objects/Legacy/Mania/ConvertHitObjectParser.cs | 8 ++++---- .../Rulesets/Objects/Legacy/Mania/ConvertHold.cs | 4 ++-- .../Objects/Legacy/Mania/ConvertSpinner.cs | 4 ++-- .../Objects/Legacy/Osu/ConvertHitObjectParser.cs | 6 +++--- .../Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs | 4 ++-- .../Objects/Legacy/Taiko/ConvertHitObjectParser.cs | 6 +++--- .../Objects/Legacy/Taiko/ConvertSpinner.cs | 4 ++-- osu.Game/Rulesets/Objects/Types/IHasEndTime.cs | 6 +++--- .../Timeline/TimelineHitObjectBlueprint.cs | 2 +- 17 files changed, 50 insertions(+), 50 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 24090e233a..2c96ee2b19 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -115,15 +115,15 @@ namespace osu.Game.Rulesets.Catch.Objects } } - public double EndTime + public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; + + public double Duration { - get => StartTime + this.SpanCount() * Path.Distance / Velocity; + get => this.SpanCount() * Path.Distance / Velocity; set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } - public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; - - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; private readonly SliderPath path = new SliderPath(); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 713d1a61f8..705e88040f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -19,14 +19,14 @@ namespace osu.Game.Rulesets.Osu.Objects { public class Slider : OsuHitObject, IHasPathWithRepeats { - public double EndTime + public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; + + public double Duration { - get => StartTime + this.SpanCount() * Path.Distance / Velocity; + get => EndTime - StartTime; set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } - public double Duration => EndTime - StartTime; - private readonly Cached endPositionCache = new Cached(); public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 32f7acadc8..7294587b10 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -237,7 +237,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables case ArmedState.Miss: case ArmedState.Hit: - using (BeginAbsoluteSequence(Time.Current, true)) + using (BeginDelayedSequence(HitObject.Duration, true)) { this.FadeOut(transition_duration, Easing.Out); bodyContainer.ScaleTo(1.4f, transition_duration); diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index b25b81c9af..08fd849fa6 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -281,7 +281,7 @@ namespace osu.Game.Tests.Visual.Gameplay yield return new TestHitObject { StartTime = original.StartTime, - EndTime = (original as IHasEndTime)?.EndTime ?? (original.StartTime + 100) + Duration = (original as IHasEndTime)?.Duration ?? 100 }; } } @@ -292,9 +292,9 @@ namespace osu.Game.Tests.Visual.Gameplay private class TestHitObject : ConvertHitObject, IHasEndTime { - public double EndTime { get; set; } + public double EndTime => StartTime + Duration; - public double Duration => EndTime - StartTime; + public double Duration { get; set; } } private class DrawableTestHitObject : DrawableHitObject diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs index 43e8d01297..c10c8dc30f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertHitObjectParser.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch }; } - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { // Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo // Their combo offset is still added to that next hitobject's combo index @@ -65,11 +65,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch return new ConvertSpinner { - EndTime = endTime + Duration = duration }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) { return null; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs index 9de311c9d7..4b0270064a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -10,9 +10,9 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition, IHasCombo { - public double EndTime { get; set; } + public double EndTime => StartTime + Duration; - public double Duration => EndTime - StartTime; + public double Duration { get; set; } public float X => 256; // Required for CatchBeatmapConverter diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 9a60a0a75c..d8d90fddfa 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -189,9 +189,9 @@ namespace osu.Game.Rulesets.Objects.Legacy } else if (type.HasFlag(LegacyHitObjectType.Spinner)) { - double endTime = Math.Max(startTime, Parsing.ParseDouble(split[5]) + Offset); + double duration = Math.Max(0, Parsing.ParseDouble(split[5]) + Offset - startTime); - result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, endTime); + result = CreateSpinner(new Vector2(512, 384) / 2, combo, comboOffset, duration); if (split.Length > 6) readCustomSampleBanks(split[6], bankInfo); @@ -209,7 +209,7 @@ namespace osu.Game.Rulesets.Objects.Legacy readCustomSampleBanks(string.Join(":", ss.Skip(1)), bankInfo); } - result = CreateHold(pos, combo, comboOffset, endTime + Offset); + result = CreateHold(pos, combo, comboOffset, endTime + Offset - startTime); } if (result == null) @@ -321,9 +321,9 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The position of the hit object. /// Whether the hit object creates a new combo. /// When starting a new combo, the offset of the new combo relative to the current one. - /// The spinner end time. + /// The spinner duration. /// The hit object. - protected abstract HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime); + protected abstract HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration); /// /// Creates a legacy Hold-type hit object. @@ -331,8 +331,8 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The position of the hit object. /// Whether the hit object creates a new combo. /// When starting a new combo, the offset of the new combo relative to the current one. - /// The hold end time. - protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime); + /// The hold end time. + protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration); private List convertSoundType(LegacyHitSoundType type, SampleBankInfo bankInfo) { diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 73192dc42e..cd2f9f88b8 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -26,13 +26,13 @@ namespace osu.Game.Rulesets.Objects.Legacy public List> NodeSamples { get; set; } public int RepeatCount { get; set; } - public double EndTime + public double Duration { - get => StartTime + this.SpanCount() * Distance / Velocity; + get => this.SpanCount() * Distance / Velocity; set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; public double Velocity = 1; diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs index f94c4aaa75..bc64518f40 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHitObjectParser.cs @@ -37,21 +37,21 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania }; } - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { return new ConvertSpinner { X = position.X, - EndTime = endTime + Duration = duration }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) { return new ConvertHold { X = position.X, - EndTime = endTime + Duration = duration }; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs index 1d92d638dd..dcb66163e4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs @@ -9,8 +9,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania { public float X { get; set; } - public double EndTime { get; set; } + public double Duration { get; set; } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs index 7dc13e27cd..b731f7c8d8 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs @@ -10,9 +10,9 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania /// internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition { - public double EndTime { get; set; } + public double Duration { get; set; } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; public float X { get; set; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs index b95ec703b6..75ecab0b8f 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertHitObjectParser.cs @@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu }; } - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { // Convert spinners don't create the new combo themselves, but force the next non-spinner hitobject to create a new combo // Their combo offset is still added to that next hitobject's combo index @@ -66,11 +66,11 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu return new ConvertSpinner { Position = position, - EndTime = endTime + Duration = duration }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) { return null; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index 8b21aab411..a231237077 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -11,9 +11,9 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasPosition, IHasCombo { - public double EndTime { get; set; } + public double Duration { get; set; } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; public Vector2 Position { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs index db65a61c90..13e3e84c6a 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertHitObjectParser.cs @@ -33,15 +33,15 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko }; } - protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateSpinner(Vector2 position, bool newCombo, int comboOffset, double duration) { return new ConvertSpinner { - EndTime = endTime + Duration = duration }; } - protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double endTime) + protected override HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration) { return null; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs index 8e28487f2f..0976106ec4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs @@ -10,8 +10,8 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko /// internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime { - public double EndTime { get; set; } + public double Duration { get; set; } - public double Duration => EndTime - StartTime; + public double EndTime => StartTime + Duration; } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs index bc7103c60d..5eb551e15c 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs @@ -13,12 +13,12 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The time at which the HitObject ends. /// - [JsonIgnore] - double EndTime { get; set; } + double EndTime { get; } /// /// The duration of the HitObject. /// - double Duration { get; } + [JsonIgnore] + double Duration { get; set; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index dd2f7a833e..d6fc17f358 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -296,7 +296,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (endTimeHitObject.EndTime == snappedTime) return; - endTimeHitObject.EndTime = snappedTime; + endTimeHitObject.Duration = snappedTime - hitObject.StartTime; break; } From dd7dbfd5488efddef6d56f897876ce6cba4eb32d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 27 May 2020 12:38:39 +0900 Subject: [PATCH 224/871] Rename to IHasDuration --- .../Beatmaps/CatchBeatmapConverter.cs | 2 +- .../Objects/BananaShower.cs | 2 +- .../TestSceneNotes.cs | 2 +- .../Beatmaps/ManiaBeatmapConverter.cs | 6 ++--- .../Legacy/EndTimeObjectPatternGenerator.cs | 2 +- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 2 +- .../Beatmaps/OsuBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs | 2 +- osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Spinner.cs | 2 +- .../Beatmaps/TaikoBeatmapConverter.cs | 2 +- osu.Game.Rulesets.Taiko/Objects/Swell.cs | 2 +- .../TestSceneDrawableScrollingRuleset.cs | 4 ++-- .../Beatmaps/Formats/LegacyBeatmapEncoder.cs | 6 ++--- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- osu.Game/Rulesets/Objects/HitObject.cs | 4 ++-- .../Objects/Legacy/Catch/ConvertSpinner.cs | 2 +- .../Objects/Legacy/Mania/ConvertHold.cs | 2 +- .../Objects/Legacy/Mania/ConvertSpinner.cs | 2 +- .../Objects/Legacy/Osu/ConvertSpinner.cs | 2 +- .../Objects/Legacy/Taiko/ConvertSpinner.cs | 2 +- .../Rulesets/Objects/Types/IHasDistance.cs | 2 +- .../Rulesets/Objects/Types/IHasDuration.cs | 24 +++++++++++++++++++ .../Rulesets/Objects/Types/IHasEndTime.cs | 20 ++++------------ .../Rulesets/Objects/Types/IHasRepeats.cs | 2 +- .../Scrolling/ScrollingHitObjectContainer.cs | 2 +- .../Timeline/TimelineHitObjectBlueprint.cs | 4 ++-- 27 files changed, 60 insertions(+), 48 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Types/IHasDuration.cs diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 27a9b63e9a..0de2060e2d 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0 }.Yield(); - case IHasEndTime endTime: + case IHasDuration endTime: return new BananaShower { StartTime = obj.StartTime, diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 3a0b5ace53..04a995c77e 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -7,7 +7,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Catch.Objects { - public class BananaShower : CatchHitObject, IHasEndTime + public class BananaShower : CatchHitObject, IHasDuration { public override FruitVisualRepresentation VisualRepresentation => FruitVisualRepresentation.Banana; diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs index ea6a1e2e6a..dd5fd93710 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneNotes.cs @@ -156,7 +156,7 @@ namespace osu.Game.Rulesets.Mania.Tests foreach (var obj in content.OfType()) { - if (!(obj.HitObject is IHasEndTime endTime)) + if (!(obj.HitObject is IHasDuration endTime)) continue; foreach (var nested in obj.NestedHitObjects) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 1c8116754f..32abf5e7f9 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -54,7 +54,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps } else { - float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasEndTime) / beatmap.HitObjects.Count; + float percentSliderOrSpinner = (float)beatmap.HitObjects.Count(h => h is IHasDuration) / beatmap.HitObjects.Count; if (percentSliderOrSpinner < 0.2) TargetColumns = 7; else if (percentSliderOrSpinner < 0.3 || roundedCircleSize >= 5) @@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps break; } - case IHasEndTime endTimeData: + case IHasDuration endTimeData: { conversion = new EndTimeObjectPatternGenerator(Random, original, beatmap, originalBeatmap); @@ -231,7 +231,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps var pattern = new Pattern(); - if (HitObject is IHasEndTime endTimeData) + if (HitObject is IHasDuration endTimeData) { pattern.Add(new HoldNote { diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs index 907bed0d65..d5286a3779 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/EndTimeObjectPatternGenerator.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, IBeatmap originalBeatmap) : base(random, hitObject, beatmap, new Pattern(), originalBeatmap) { - endTime = (HitObject as IHasEndTime)?.EndTime ?? 0; + endTime = (HitObject as IHasDuration)?.EndTime ?? 0; } public override IEnumerable Generate() diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index e6f722a8a9..a100c9a58e 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Mania.Objects /// /// Represents a hit object which requires pressing, holding, and releasing a key. /// - public class HoldNote : ManiaHitObject, IHasEndTime + public class HoldNote : ManiaHitObject, IHasDuration { public double EndTime { diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs index 060a3919bd..fcad356a1c 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapConverter.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps TickDistanceMultiplier = beatmap.BeatmapInfo.BeatmapVersion < 8 ? 1f / beatmap.ControlPointInfo.DifficultyPointAt(original.StartTime).SpeedMultiplier : 1 }.Yield(); - case IHasEndTime endTimeData: + case IHasDuration endTimeData: return new Spinner { StartTime = original.StartTime, diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs index 7b1941b7f9..5d191119b9 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModRelax.cs @@ -70,7 +70,7 @@ namespace osu.Game.Rulesets.Osu.Mods break; // already hit or beyond the hittable end time. - if (h.IsHit || (h.HitObject is IHasEndTime hasEnd && time > hasEnd.EndTime)) + if (h.IsHit || (h.HitObject is IHasDuration hasEnd && time > hasEnd.EndTime)) continue; switch (h) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs index 297a0fea79..3cad52faeb 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModWiggle.cs @@ -61,7 +61,7 @@ namespace osu.Game.Rulesets.Osu.Mods } // Keep wiggling sliders and spinners for their duration - if (!(osuObject is IHasEndTime endTime)) + if (!(osuObject is IHasDuration endTime)) return; amountWiggles = (int)(endTime.Duration / wiggle_duration); diff --git a/osu.Game.Rulesets.Osu/Objects/Spinner.cs b/osu.Game.Rulesets.Osu/Objects/Spinner.cs index 0b8d03d118..418375c090 100644 --- a/osu.Game.Rulesets.Osu/Objects/Spinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Spinner.cs @@ -11,7 +11,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects { - public class Spinner : OsuHitObject, IHasEndTime + public class Spinner : OsuHitObject, IHasDuration { public double EndTime { diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 1a47be2282..78550ed270 100644 --- a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs +++ b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs @@ -150,7 +150,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps break; } - case IHasEndTime endTimeData: + case IHasDuration endTimeData: { double hitMultiplier = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty, 3, 5, 7.5) * swell_hit_multiplier; diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index b4c09b884e..eeae6e79f8 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -9,7 +9,7 @@ using osu.Game.Rulesets.Taiko.Judgements; namespace osu.Game.Rulesets.Taiko.Objects { - public class Swell : TaikoHitObject, IHasEndTime + public class Swell : TaikoHitObject, IHasDuration { public double EndTime { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index 08fd849fa6..bd7e894cf8 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -281,7 +281,7 @@ namespace osu.Game.Tests.Visual.Gameplay yield return new TestHitObject { StartTime = original.StartTime, - Duration = (original as IHasEndTime)?.Duration ?? 100 + Duration = (original as IHasDuration)?.Duration ?? 100 }; } } @@ -290,7 +290,7 @@ namespace osu.Game.Tests.Visual.Gameplay #region HitObject - private class TestHitObject : ConvertHitObject, IHasEndTime + private class TestHitObject : ConvertHitObject, IHasDuration { public double EndTime => StartTime + Duration; diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index d7e83fa471..8c63ce6fcb 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -240,7 +240,7 @@ namespace osu.Game.Beatmaps.Formats } else { - if (hitObject is IHasEndTime) + if (hitObject is IHasDuration) addEndTimeData(writer, hitObject); writer.Write(getSampleBank(hitObject.Samples)); @@ -267,7 +267,7 @@ namespace osu.Game.Beatmaps.Formats type |= LegacyHitObjectType.Slider; break; - case IHasEndTime _: + case IHasDuration _: if (beatmap.BeatmapInfo.RulesetID == 3) type |= LegacyHitObjectType.Hold; else @@ -352,7 +352,7 @@ namespace osu.Game.Beatmaps.Formats private void addEndTimeData(TextWriter writer, HitObject hitObject) { - var endTimeData = (IHasEndTime)hitObject; + var endTimeData = (IHasDuration)hitObject; var type = getObjectType(hitObject); char suffix = ','; diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 8126311cbd..ac399e37c4 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -63,7 +63,7 @@ namespace osu.Game.Beatmaps length = emptyLength; break; - case IHasEndTime endTime: + case IHasDuration endTime: length = endTime.EndTime + excess_length; break; diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index 6f9053d7cb..e2cc98813a 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -175,10 +175,10 @@ namespace osu.Game.Rulesets.Objects /// Returns the end time of this object. /// /// - /// This returns the where available, falling back to otherwise. + /// This returns the where available, falling back to otherwise. /// /// The object. /// The end time of this object. - public static double GetEndTime(this HitObject hitObject) => (hitObject as IHasEndTime)?.EndTime ?? hitObject.StartTime; + public static double GetEndTime(this HitObject hitObject) => (hitObject as IHasDuration)?.EndTime ?? hitObject.StartTime; } } diff --git a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs index 4b0270064a..014494ec54 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Catch/ConvertSpinner.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Catch /// /// Legacy osu!catch Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition, IHasCombo + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition, IHasCombo { public double EndTime => StartTime + Duration; diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs index dcb66163e4..2fa4766c1d 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertHold.cs @@ -5,7 +5,7 @@ using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Objects.Legacy.Mania { - internal sealed class ConvertHold : ConvertHitObject, IHasXPosition, IHasEndTime + internal sealed class ConvertHold : ConvertHitObject, IHasXPosition, IHasDuration { public float X { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs index b731f7c8d8..c05aaceb9c 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Mania/ConvertSpinner.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Mania /// /// Legacy osu!mania Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasXPosition + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasXPosition { public double Duration { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs index a231237077..e9e5ca8c94 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Osu/ConvertSpinner.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Osu /// /// Legacy osu! Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime, IHasPosition, IHasCombo + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration, IHasPosition, IHasCombo { public double Duration { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs index 0976106ec4..1d5ecb1ef3 100644 --- a/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs +++ b/osu.Game/Rulesets/Objects/Legacy/Taiko/ConvertSpinner.cs @@ -8,7 +8,7 @@ namespace osu.Game.Rulesets.Objects.Legacy.Taiko /// /// Legacy osu!taiko Spinner-type, used for parsing Beatmaps. /// - internal sealed class ConvertSpinner : ConvertHitObject, IHasEndTime + internal sealed class ConvertSpinner : ConvertHitObject, IHasDuration { public double Duration { get; set; } diff --git a/osu.Game/Rulesets/Objects/Types/IHasDistance.cs b/osu.Game/Rulesets/Objects/Types/IHasDistance.cs index e7f552115e..b497ca5da3 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasDistance.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasDistance.cs @@ -6,7 +6,7 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that has a positional length. /// - public interface IHasDistance : IHasEndTime + public interface IHasDistance : IHasDuration { /// /// The positional length of the HitObject. diff --git a/osu.Game/Rulesets/Objects/Types/IHasDuration.cs b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs new file mode 100644 index 0000000000..2433f9597e --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; + +namespace osu.Game.Rulesets.Objects.Types +{ + /// + /// A HitObject that ends at a different time than its start time. + /// + public interface IHasDuration + { + /// + /// The time at which the HitObject ends. + /// + double EndTime { get; } + + /// + /// The duration of the HitObject. + /// + [JsonIgnore] + double Duration { get; set; } + } +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs index 5eb551e15c..7395223c7e 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs @@ -1,24 +1,12 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using Newtonsoft.Json; +using System; namespace osu.Game.Rulesets.Objects.Types { - /// - /// A HitObject that ends at a different time than its start time. - /// - public interface IHasEndTime + [Obsolete("Use IHasDuration instead.")] // can be removed 20201126 + public interface IHasEndTime : IHasDuration { - /// - /// The time at which the HitObject ends. - /// - double EndTime { get; } - - /// - /// The duration of the HitObject. - /// - [JsonIgnore] - double Duration { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index 256b1f3963..7a3fb16196 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -9,7 +9,7 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that spans some length. /// - public interface IHasRepeats : IHasEndTime + public interface IHasRepeats : IHasDuration { /// /// The amount of times the HitObject repeats. diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index c817d84d5c..0dc3324559 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -270,7 +270,7 @@ namespace osu.Game.Rulesets.UI.Scrolling // Cant use AddOnce() since the delegate is re-constructed every invocation private void computeInitialStateRecursive(DrawableHitObject hitObject) => hitObject.Schedule(() => { - if (hitObject.HitObject is IHasEndTime e) + if (hitObject.HitObject is IHasDuration e) { switch (direction.Value) { diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index d6fc17f358..b95b3842b3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline shadowComponents.Add(circle); - if (hitObject is IHasEndTime) + if (hitObject is IHasDuration) { DragBar dragBarUnderlay; Container extensionBar; @@ -290,7 +290,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline repeatHitObject.RepeatCount = proposedCount; break; - case IHasEndTime endTimeHitObject: + case IHasDuration endTimeHitObject: var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time)); if (endTimeHitObject.EndTime == snappedTime) From b2fad915898c445cfe8ef13888de52dc08d232d7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 28 May 2020 20:33:12 +0900 Subject: [PATCH 225/871] Add swell and drumroll blueprints --- .../TaikoHitObjectComposer.cs | 155 +++++++++++++++++- osu.Game/Rulesets/Edit/PlacementBlueprint.cs | 6 +- 2 files changed, 149 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index d233cd5e7f..2bb037815e 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -14,7 +14,9 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.UI; @@ -37,7 +39,8 @@ namespace osu.Game.Rulesets.Taiko protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { new HitCompositionTool(), - new DrumRollCompositionTool() + new DrumRollCompositionTool(), + new SwellCompositionTool() }; protected override ComposeBlueprintContainer CreateBlueprintContainer() => new TaikoBlueprintContainer(drawableRuleset.Playfield.AllHitObjects); @@ -157,6 +160,16 @@ namespace osu.Game.Rulesets.Taiko } } + public class SwellCompositionTool : HitObjectCompositionTool + { + public SwellCompositionTool() + : base(nameof(Swell)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint(); + } + public class DrumRollCompositionTool : HitObjectCompositionTool { public DrumRollCompositionTool() @@ -167,16 +180,110 @@ namespace osu.Game.Rulesets.Taiko public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint(); } - public class DrumRollPlacementBlueprint : PlacementBlueprint + public class SwellPlacementBlueprint : TaikoSpanPlacementBlueprint { - private static DrumRoll drumRoll; - - public DrumRollPlacementBlueprint() - : base(drumRoll = new DrumRoll()) + public SwellPlacementBlueprint() + : base(new Swell()) { } } + public class DrumRollPlacementBlueprint : TaikoSpanPlacementBlueprint + { + public DrumRollPlacementBlueprint() + : base(new DrumRoll()) + { + } + } + + public class TaikoSpanPlacementBlueprint : PlacementBlueprint + { + private readonly HitPiece headPiece; + private readonly HitPiece tailPiece; + + private readonly LengthPiece lengthPiece; + + private readonly IHasDuration spanPlacementObject; + + public TaikoSpanPlacementBlueprint(HitObject hitObject) + : base(hitObject) + + { + spanPlacementObject = hitObject as IHasDuration; + + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + headPiece = new HitPiece + { + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) + }, + lengthPiece = new LengthPiece + { + Height = TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT + }, + tailPiece = new HitPiece + { + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) + } + }; + } + + private double originalStartTime; + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.Button != MouseButton.Left) + return false; + + BeginPlacement(true); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + if (e.Button != MouseButton.Left) + return; + + base.OnMouseUp(e); + EndPlacement(true); + } + + public override void UpdatePosition(SnapResult result) + { + base.UpdatePosition(result); + + if (PlacementActive) + { + if (result.Time is double endTime) + { + if (endTime < originalStartTime) + { + HitObject.StartTime = endTime; + spanPlacementObject.Duration = Math.Abs(endTime - originalStartTime); + headPiece.Position = ToLocalSpace(result.ScreenSpacePosition); + lengthPiece.X = headPiece.X; + lengthPiece.Width = tailPiece.X - headPiece.X; + } + else + { + spanPlacementObject.Duration = Math.Abs(endTime - originalStartTime); + tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition); + lengthPiece.Width = tailPiece.X - headPiece.X; + } + } + } + else + { + lengthPiece.Position = headPiece.Position = tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition); + + if (result.Time is double startTime) + originalStartTime = HitObject.StartTime = startTime; + } + } + } + public class HitCompositionTool : HitObjectCompositionTool { public HitCompositionTool() @@ -221,10 +328,40 @@ namespace osu.Game.Rulesets.Taiko return false; } - public override void UpdatePosition(SnapResult snapResult) + public override void UpdatePosition(SnapResult result) { - piece.Position = ToLocalSpace(snapResult.ScreenSpacePosition); - base.UpdatePosition(snapResult); + piece.Position = ToLocalSpace(result.ScreenSpacePosition); + base.UpdatePosition(result); + } + } + + public class LengthPiece : CompositeDrawable + { + public LengthPiece() + { + Origin = Anchor.CentreLeft; + + InternalChild = new Container + { + Masking = true, + Colour = Color4.Yellow, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.X, + Height = 8, + }, + new Box + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = 8, + } + } + }; } } diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index bb89ba8311..02d5955ae6 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -87,11 +87,11 @@ namespace osu.Game.Rulesets.Edit /// /// Updates the position of this to a new screen-space position. /// - /// The snap result information. - public virtual void UpdatePosition(SnapResult snapResult) + /// The snap result information. + public virtual void UpdatePosition(SnapResult result) { if (!PlacementActive) - HitObject.StartTime = snapResult.Time ?? EditorClock?.CurrentTime ?? Time.Current; + HitObject.StartTime = result.Time ?? EditorClock?.CurrentTime ?? Time.Current; } /// From 597f2848054977d21c2af3a68dff5df82a18c516 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 11:46:08 +0900 Subject: [PATCH 226/871] Tidy up and complete xmldoc for HitObjectComposer --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 89 ++++++++++++++++----- osu.sln.DotSettings | 5 +- 2 files changed, 74 insertions(+), 20 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 38576e02a0..c956439eb2 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.Edit protected ComposeBlueprintContainer BlueprintContainer { get; private set; } - public override Playfield Playfield => drawableRulesetWrapper.Playfield; - private DrawableEditRulesetWrapper drawableRulesetWrapper; protected readonly Container LayerBelowRuleset = new Container { RelativeSizeAxes = Axes.Both }; @@ -61,7 +59,6 @@ namespace osu.Game.Rulesets.Edit protected HitObjectComposer(Ruleset ruleset) { Ruleset = ruleset; - RelativeSizeAxes = Axes.Both; } [BackgroundDependencyLoader] @@ -137,6 +134,49 @@ namespace osu.Game.Rulesets.Edit EditorBeatmap.SelectedHitObjects.CollectionChanged += selectionChanged; } + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + + public override Playfield Playfield => drawableRulesetWrapper.Playfield; + + public override IEnumerable HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects; + + public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); + + /// + /// Defines all available composition tools, listed on the left side of the editor screen as button controls. + /// This should usually define one tool for each type used in the target ruleset. + /// + /// + /// A "select" tool is automatically added as the first tool. + /// + protected abstract IReadOnlyList CompositionTools { get; } + + /// + /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. + /// + protected abstract ComposeBlueprintContainer CreateBlueprintContainer(); + + /// + /// Construct a drawable ruleset for the provided ruleset. + /// + /// + /// Can be overridden to add editor-specific logical changes to a 's standard . + /// For example, hit animations or judgement logic may be changed to give a better editor user experience. + /// + /// The ruleset used to construct its drawable counterpart. + /// The loaded beatmap. + /// The mods to be applied. + /// An editor-relevant . + protected virtual DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) + => (DrawableRuleset)ruleset.CreateDrawableRulesetWith(beatmap, mods); + + #region Tool selection logic + protected override bool OnKeyDown(KeyDownEvent e) { if (e.Key >= Key.Number1 && e.Key <= Key.Number9) @@ -153,13 +193,6 @@ namespace osu.Game.Rulesets.Edit return base.OnKeyDown(e); } - protected override void LoadComplete() - { - base.LoadComplete(); - - inputManager = GetContainingInputManager(); - } - private void selectionChanged(object sender, NotifyCollectionChangedEventArgs changedArgs) { if (EditorBeatmap.SelectedHitObjects.Any()) @@ -179,15 +212,9 @@ namespace osu.Game.Rulesets.Edit EditorBeatmap.SelectedHitObjects.Clear(); } - public override IEnumerable HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects; + #endregion - public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); - - protected abstract IReadOnlyList CompositionTools { get; } - - protected abstract ComposeBlueprintContainer CreateBlueprintContainer(); - - protected abstract DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null); + #region IPlacementHandler public void BeginPlacement(HitObject hitObject) { @@ -209,6 +236,17 @@ namespace osu.Game.Rulesets.Edit public void Delete(HitObject hitObject) => EditorBeatmap.Remove(hitObject); + #endregion + + #region IPositionSnapProvider + + /// + /// Retrieve the relevant at a specified screen-space position. + /// In cases where a ruleset doesn't require custom logic (due to nested playfields, for example) + /// this will return the ruleset's main playfield. + /// + /// The screen-space position to query. + /// The most relevant . protected virtual Playfield PlayfieldAtScreenSpacePosition(Vector2 screenSpacePosition) => drawableRulesetWrapper.Playfield; public override SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition) @@ -257,8 +295,14 @@ namespace osu.Game.Rulesets.Edit return DurationToDistance(referenceTime, snappedEndTime - referenceTime); } + + #endregion } + /// + /// A non-generic definition of a HitObject composer class. + /// Generally used to access certain methods without requiring a generic type for . + /// [Cached(typeof(HitObjectComposer))] [Cached(typeof(IPositionSnapProvider))] public abstract class HitObjectComposer : CompositeDrawable, IPositionSnapProvider @@ -268,10 +312,13 @@ namespace osu.Game.Rulesets.Edit RelativeSizeAxes = Axes.Both; } + /// + /// The target ruleset's playfield. + /// public abstract Playfield Playfield { get; } /// - /// All the s. + /// All s in currently loaded beatmap. /// public abstract IEnumerable HitObjects { get; } @@ -280,6 +327,8 @@ namespace osu.Game.Rulesets.Edit /// public abstract bool CursorInPlacementArea { get; } + #region IPositionSnapProvider + public abstract SnapResult SnapScreenSpacePositionToValidTime(Vector2 screenSpacePosition); public abstract float GetBeatSnapDistanceAt(double referenceTime); @@ -291,5 +340,7 @@ namespace osu.Game.Rulesets.Edit public abstract double GetSnappedDurationFromDistance(double referenceTime, float distance); public abstract float GetSnappedDistanceFromDistance(double referenceTime, float distance); + + #endregion } } diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index e3b64c03b9..b9fc3de734 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -1,4 +1,4 @@ - + True True True @@ -905,14 +905,17 @@ private void load() True True True + True True True True True True True + True True True True + True True True From 3e973c176f0ccf506b2b07c94c5bf317a94f0b34 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 11:59:21 +0900 Subject: [PATCH 227/871] Remove unnecessary overrides --- osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index 2bb037815e..16111f4abf 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -9,17 +9,14 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; -using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; -using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.UI; -using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; using osuTK; using osuTK.Graphics; @@ -29,8 +26,6 @@ namespace osu.Game.Rulesets.Taiko { public class TaikoHitObjectComposer : HitObjectComposer { - private DrawableTaikoRuleset drawableRuleset; - public TaikoHitObjectComposer(Ruleset ruleset) : base(ruleset) { @@ -43,12 +38,7 @@ namespace osu.Game.Rulesets.Taiko new SwellCompositionTool() }; - protected override ComposeBlueprintContainer CreateBlueprintContainer() => new TaikoBlueprintContainer(drawableRuleset.Playfield.AllHitObjects); - - protected override DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) - { - return drawableRuleset = new DrawableTaikoRuleset(ruleset, beatmap, mods); - } + protected override ComposeBlueprintContainer CreateBlueprintContainer() => new TaikoBlueprintContainer(Playfield.AllHitObjects); } public class TaikoBlueprintContainer : ComposeBlueprintContainer From 590931b17cfaccafd3bf8f0db168f4082a1dd8be Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 12:20:50 +0900 Subject: [PATCH 228/871] Pass hitobjects as a parameter to CreateBlueprintContainer --- osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs | 4 +++- osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs | 4 +++- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 6 ++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs index 10d344242c..7e2469a794 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Screens.Edit.Compose.Components; @@ -88,7 +89,8 @@ namespace osu.Game.Rulesets.Mania.Edit return drawableRuleset; } - protected override ComposeBlueprintContainer CreateBlueprintContainer() => new ManiaBlueprintContainer(drawableRuleset.Playfield.AllHitObjects); + protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) + => new ManiaBlueprintContainer(hitObjects); protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] { diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index de5c1e54d7..37019a7a05 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -13,6 +13,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.UI; using osu.Game.Screens.Edit.Compose.Components; @@ -46,7 +47,8 @@ namespace osu.Game.Rulesets.Osu.Edit EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid(); } - protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(HitObjects); + protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) + => new OsuBlueprintContainer(hitObjects); private DistanceSnapGrid distanceSnapGrid; private Container distanceSnapGridContainer; diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c956439eb2..8b9f531417 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Edit drawableRulesetWrapper, // layers above playfield drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer() - .WithChild(BlueprintContainer = CreateBlueprintContainer()) + .WithChild(BlueprintContainer = CreateBlueprintContainer(HitObjects)) } } }, @@ -159,7 +159,9 @@ namespace osu.Game.Rulesets.Edit /// /// Construct a relevant blueprint container. This will manage hitobject selection/placement input handling and display logic. /// - protected abstract ComposeBlueprintContainer CreateBlueprintContainer(); + /// A live collection of all s in the editor beatmap. + protected virtual ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) + => new ComposeBlueprintContainer(hitObjects); /// /// Construct a drawable ruleset for the provided ruleset. From 7b52faa76d086ce98dd455e1475448e09c825870 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 12:45:09 +0900 Subject: [PATCH 229/871] Update override --- osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index 16111f4abf..29e0e3a3c0 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -38,7 +38,8 @@ namespace osu.Game.Rulesets.Taiko new SwellCompositionTool() }; - protected override ComposeBlueprintContainer CreateBlueprintContainer() => new TaikoBlueprintContainer(Playfield.AllHitObjects); + protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) + => new TaikoBlueprintContainer(hitObjects); } public class TaikoBlueprintContainer : ComposeBlueprintContainer From 7f8f41715d31f74234e019b4de049c9e0acacc64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 13:15:43 +0900 Subject: [PATCH 230/871] Remove stray whitespace --- osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs index 29e0e3a3c0..b34a8e75cc 100644 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs @@ -286,7 +286,6 @@ namespace osu.Game.Rulesets.Taiko } public class HitPlacementBlueprint : PlacementBlueprint - { private readonly HitPiece piece; From 3b6619a3608e7d60c7c22b541478ec62c4442335 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 16:11:26 +0900 Subject: [PATCH 231/871] Flip direction to avoid breaking other usages --- osu.Game/Rulesets/Objects/Types/IHasDuration.cs | 16 +++++++++++++--- osu.Game/Rulesets/Objects/Types/IHasEndTime.cs | 16 +++++++++++++++- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Types/IHasDuration.cs b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs index 2433f9597e..185fd5977b 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasDuration.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasDuration.cs @@ -8,17 +8,27 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that ends at a different time than its start time. /// - public interface IHasDuration +#pragma warning disable 618 + public interface IHasDuration : IHasEndTime +#pragma warning restore 618 { + double IHasEndTime.EndTime + { + get => EndTime; + set => Duration = (Duration - EndTime) + value; + } + + double IHasEndTime.Duration => Duration; + /// /// The time at which the HitObject ends. /// - double EndTime { get; } + new double EndTime { get; } /// /// The duration of the HitObject. /// [JsonIgnore] - double Duration { get; set; } + new double Duration { get; set; } } } diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs index 7395223c7e..c3769c5909 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs @@ -2,11 +2,25 @@ // See the LICENCE file in the repository root for full licence text. using System; +using Newtonsoft.Json; namespace osu.Game.Rulesets.Objects.Types { + /// + /// A HitObject that ends at a different time than its start time. + /// [Obsolete("Use IHasDuration instead.")] // can be removed 20201126 - public interface IHasEndTime : IHasDuration + public interface IHasEndTime { + /// + /// The time at which the HitObject ends. + /// + [JsonIgnore] + double EndTime { get; set; } + + /// + /// The duration of the HitObject. + /// + double Duration { get; } } } From da289c474e2ceba450437a20572fa7d7d2b902f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 16:40:10 +0900 Subject: [PATCH 232/871] Split files out --- .../TestSceneTaikoHitObjectComposer.cs | 1 + .../Blueprints/DrumRollPlacementBlueprint.cs | 12 + .../Edit/Blueprints/HitPiece.cs | 32 ++ .../Edit/Blueprints/HitPlacementBlueprint.cs | 49 +++ .../Edit/Blueprints/LengthPiece.cs | 37 ++ .../Blueprints/SwellPlacementBlueprint.cs | 12 + .../Blueprints/TaikoSpanPlacementBlueprint.cs | 101 +++++ .../Edit/DrumRollCompositionTool.cs | 17 + .../Edit/HitCompositionTool.cs | 17 + .../Edit/SwellCompositionTool.cs | 17 + .../Edit/TaikoBlueprintContainer.cs | 20 + .../Edit/TaikoHitObjectComposer.cs | 30 ++ .../Edit/TaikoSelectionBlueprint.cs | 41 ++ .../Edit/TaikoSelectionHandler.cs | 80 ++++ .../TaikoHitObjectComposer.cs | 382 ------------------ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 1 + 16 files changed, 467 insertions(+), 382 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/TaikoSelectionBlueprint.cs create mode 100644 osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs delete mode 100644 osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs index b5ee33fa8e..34d5fdf857 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoHitObjectComposer.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Taiko.Beatmaps; +using osu.Game.Rulesets.Taiko.Edit; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Screens.Edit; using osu.Game.Tests.Visual; diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs new file mode 100644 index 0000000000..2f086891a9 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs @@ -0,0 +1,12 @@ +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Edit.Blueprints +{ + public class DrumRollPlacementBlueprint : TaikoSpanPlacementBlueprint + { + public DrumRollPlacementBlueprint() + : base(new DrumRoll()) + { + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs new file mode 100644 index 0000000000..992fba1e29 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs @@ -0,0 +1,32 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Taiko.Edit.Blueprints +{ + public class HitPiece : CompositeDrawable + { + public HitPiece() + { + Origin = Anchor.Centre; + + InternalChild = new CircularContainer + { + Masking = true, + BorderThickness = 10, + BorderColour = Color4.Yellow, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + AlwaysPresent = true, + Alpha = 0, + RelativeSizeAxes = Axes.Both + } + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs new file mode 100644 index 0000000000..c21aed32e8 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs @@ -0,0 +1,49 @@ +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.UI; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Taiko.Edit.Blueprints +{ + public class HitPlacementBlueprint : PlacementBlueprint + { + private readonly HitPiece piece; + + private static Hit hit; + + public HitPlacementBlueprint() + : base(hit = new Hit()) + { + InternalChild = piece = new HitPiece + { + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) + }; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + switch (e.Button) + { + case MouseButton.Left: + hit.Type = HitType.Centre; + EndPlacement(true); + return true; + + case MouseButton.Right: + hit.Type = HitType.Rim; + EndPlacement(true); + return true; + } + + return false; + } + + public override void UpdatePosition(SnapResult result) + { + piece.Position = ToLocalSpace(result.ScreenSpacePosition); + base.UpdatePosition(result); + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs new file mode 100644 index 0000000000..2139a852b2 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs @@ -0,0 +1,37 @@ +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Taiko.Edit.Blueprints +{ + public class LengthPiece : CompositeDrawable + { + public LengthPiece() + { + Origin = Anchor.CentreLeft; + + InternalChild = new Container + { + Masking = true, + Colour = Color4.Yellow, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.X, + Height = 8, + }, + new Box + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = 8, + } + } + }; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs new file mode 100644 index 0000000000..180bf26f34 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs @@ -0,0 +1,12 @@ +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Edit.Blueprints +{ + public class SwellPlacementBlueprint : TaikoSpanPlacementBlueprint + { + public SwellPlacementBlueprint() + : base(new Swell()) + { + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs new file mode 100644 index 0000000000..b08cc68225 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs @@ -0,0 +1,101 @@ +using System; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Rulesets.Taiko.UI; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Rulesets.Taiko.Edit.Blueprints +{ + public class TaikoSpanPlacementBlueprint : PlacementBlueprint + { + private readonly HitPiece headPiece; + private readonly HitPiece tailPiece; + + private readonly LengthPiece lengthPiece; + + private readonly IHasDuration spanPlacementObject; + + public TaikoSpanPlacementBlueprint(HitObject hitObject) + : base(hitObject) + + { + spanPlacementObject = hitObject as IHasDuration; + + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + headPiece = new HitPiece + { + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) + }, + lengthPiece = new LengthPiece + { + Height = TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT + }, + tailPiece = new HitPiece + { + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) + } + }; + } + + private double originalStartTime; + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (e.Button != MouseButton.Left) + return false; + + BeginPlacement(true); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + if (e.Button != MouseButton.Left) + return; + + base.OnMouseUp(e); + EndPlacement(true); + } + + public override void UpdatePosition(SnapResult result) + { + base.UpdatePosition(result); + + if (PlacementActive) + { + if (result.Time is double endTime) + { + if (endTime < originalStartTime) + { + HitObject.StartTime = endTime; + spanPlacementObject.Duration = Math.Abs(endTime - originalStartTime); + headPiece.Position = ToLocalSpace(result.ScreenSpacePosition); + lengthPiece.X = headPiece.X; + lengthPiece.Width = tailPiece.X - headPiece.X; + } + else + { + spanPlacementObject.Duration = Math.Abs(endTime - originalStartTime); + tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition); + lengthPiece.Width = tailPiece.X - headPiece.X; + } + } + } + else + { + lengthPiece.Position = headPiece.Position = tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition); + + if (result.Time is double startTime) + originalStartTime = HitObject.StartTime = startTime; + } + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs new file mode 100644 index 0000000000..c17e22180a --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs @@ -0,0 +1,17 @@ +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Taiko.Edit.Blueprints; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Edit +{ + public class DrumRollCompositionTool : HitObjectCompositionTool + { + public DrumRollCompositionTool() + : base(nameof(DrumRoll)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs new file mode 100644 index 0000000000..7e8f245613 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs @@ -0,0 +1,17 @@ +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Taiko.Edit.Blueprints; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Edit +{ + public class HitCompositionTool : HitObjectCompositionTool + { + public HitCompositionTool() + : base(nameof(Hit)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs new file mode 100644 index 0000000000..aa96a397d0 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs @@ -0,0 +1,17 @@ +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Taiko.Edit.Blueprints; +using osu.Game.Rulesets.Taiko.Objects; + +namespace osu.Game.Rulesets.Taiko.Edit +{ + public class SwellCompositionTool : HitObjectCompositionTool + { + public SwellCompositionTool() + : base(nameof(Swell)) + { + } + + public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint(); + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs new file mode 100644 index 0000000000..b7cda04705 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Taiko.Edit +{ + public class TaikoBlueprintContainer : ComposeBlueprintContainer + { + public TaikoBlueprintContainer(IEnumerable hitObjects) + : base(hitObjects) + { + } + + protected override SelectionHandler CreateSelectionHandler() => new TaikoSelectionHandler(); + + public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => + new TaikoSelectionBlueprint(hitObject); + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs new file mode 100644 index 0000000000..7ad40903d2 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs @@ -0,0 +1,30 @@ +// 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.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Taiko.Edit +{ + public class TaikoHitObjectComposer : HitObjectComposer + { + public TaikoHitObjectComposer(Ruleset ruleset) + : base(ruleset) + { + } + + protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] + { + new HitCompositionTool(), + new DrumRollCompositionTool(), + new SwellCompositionTool() + }; + + protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) + => new TaikoBlueprintContainer(hitObjects); + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionBlueprint.cs new file mode 100644 index 0000000000..e3797a5fa6 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionBlueprint.cs @@ -0,0 +1,41 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Edit.Blueprints; +using osuTK; + +namespace osu.Game.Rulesets.Taiko.Edit +{ + public class TaikoSelectionBlueprint : OverlaySelectionBlueprint + { + public TaikoSelectionBlueprint(DrawableHitObject hitObject) + : base(hitObject) + { + RelativeSizeAxes = Axes.None; + + AddInternal(new HitPiece + { + RelativeSizeAxes = Axes.Both, + Origin = Anchor.TopLeft + }); + } + + protected override void Update() + { + base.Update(); + + // Move the rectangle to cover the hitobjects + var topLeft = new Vector2(float.MaxValue, float.MaxValue); + var bottomRight = new Vector2(float.MinValue, float.MinValue); + + topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.TopLeft)); + bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.BottomRight)); + + Size = bottomRight - topLeft; + Position = topLeft; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs new file mode 100644 index 0000000000..eebf6980fe --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionHandler.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Screens.Edit.Compose.Components; + +namespace osu.Game.Rulesets.Taiko.Edit +{ + public class TaikoSelectionHandler : SelectionHandler + { + protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) + { + if (selection.All(s => s.HitObject is Hit)) + { + var hits = selection.Select(s => s.HitObject).OfType(); + + yield return new TernaryStateMenuItem("Rim", action: state => + { + foreach (var h in hits) + { + switch (state) + { + case TernaryState.True: + h.Type = HitType.Rim; + break; + + case TernaryState.False: + h.Type = HitType.Centre; + break; + } + } + }) + { + State = { Value = getTernaryState(hits, h => h.Type == HitType.Rim) } + }; + } + + if (selection.All(s => s.HitObject is TaikoHitObject)) + { + var hits = selection.Select(s => s.HitObject).OfType(); + + yield return new TernaryStateMenuItem("Strong", action: state => + { + foreach (var h in hits) + { + switch (state) + { + case TernaryState.True: + h.IsStrong = true; + break; + + case TernaryState.False: + h.IsStrong = false; + break; + } + + EditorBeatmap?.UpdateHitObject(h); + } + }) + { + State = { Value = getTernaryState(hits, h => h.IsStrong) } + }; + } + } + + private TernaryState getTernaryState(IEnumerable selection, Func func) + { + if (selection.Any(func)) + return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate; + + return TernaryState.False; + } + } +} diff --git a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs deleted file mode 100644 index b34a8e75cc..0000000000 --- a/osu.Game.Rulesets.Taiko/TaikoHitObjectComposer.cs +++ /dev/null @@ -1,382 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using System.Collections.Generic; -using System.Linq; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; -using osu.Game.Graphics.UserInterface; -using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Edit.Tools; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Objects.Types; -using osu.Game.Rulesets.Taiko.Objects; -using osu.Game.Rulesets.Taiko.UI; -using osu.Game.Screens.Edit.Compose.Components; -using osuTK; -using osuTK.Graphics; -using osuTK.Input; - -namespace osu.Game.Rulesets.Taiko -{ - public class TaikoHitObjectComposer : HitObjectComposer - { - public TaikoHitObjectComposer(Ruleset ruleset) - : base(ruleset) - { - } - - protected override IReadOnlyList CompositionTools => new HitObjectCompositionTool[] - { - new HitCompositionTool(), - new DrumRollCompositionTool(), - new SwellCompositionTool() - }; - - protected override ComposeBlueprintContainer CreateBlueprintContainer(IEnumerable hitObjects) - => new TaikoBlueprintContainer(hitObjects); - } - - public class TaikoBlueprintContainer : ComposeBlueprintContainer - { - public TaikoBlueprintContainer(IEnumerable hitObjects) - : base(hitObjects) - { - } - - protected override SelectionHandler CreateSelectionHandler() => new TaikoSelectionHandler(); - - public override OverlaySelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject) => - new TaikoSelectionBlueprint(hitObject); - } - - public class TaikoSelectionHandler : SelectionHandler - { - protected override IEnumerable GetContextMenuItemsForSelection(IEnumerable selection) - { - if (selection.All(s => s.HitObject is Hit)) - { - var hits = selection.Select(s => s.HitObject).OfType(); - - yield return new TernaryStateMenuItem("Rim", action: state => - { - foreach (var h in hits) - { - switch (state) - { - case TernaryState.True: - h.Type = HitType.Rim; - break; - - case TernaryState.False: - h.Type = HitType.Centre; - break; - } - } - }) - { - State = { Value = getTernaryState(hits, h => h.Type == HitType.Rim) } - }; - } - - if (selection.All(s => s.HitObject is TaikoHitObject)) - { - var hits = selection.Select(s => s.HitObject).OfType(); - - yield return new TernaryStateMenuItem("Strong", action: state => - { - foreach (var h in hits) - { - switch (state) - { - case TernaryState.True: - h.IsStrong = true; - break; - - case TernaryState.False: - h.IsStrong = false; - break; - } - - EditorBeatmap?.UpdateHitObject(h); - } - }) - { - State = { Value = getTernaryState(hits, h => h.IsStrong) } - }; - } - } - - private TernaryState getTernaryState(IEnumerable selection, Func func) - { - if (selection.Any(func)) - return selection.All(func) ? TernaryState.True : TernaryState.Indeterminate; - - return TernaryState.False; - } - } - - public class TaikoSelectionBlueprint : OverlaySelectionBlueprint - { - public TaikoSelectionBlueprint(DrawableHitObject hitObject) - : base(hitObject) - { - RelativeSizeAxes = Axes.None; - - AddInternal(new HitPiece - { - RelativeSizeAxes = Axes.Both, - Origin = Anchor.TopLeft - }); - } - - protected override void Update() - { - base.Update(); - - // Move the rectangle to cover the hitobjects - var topLeft = new Vector2(float.MaxValue, float.MaxValue); - var bottomRight = new Vector2(float.MinValue, float.MinValue); - - topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.TopLeft)); - bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(DrawableObject.ScreenSpaceDrawQuad.BottomRight)); - - Size = bottomRight - topLeft; - Position = topLeft; - } - } - - public class SwellCompositionTool : HitObjectCompositionTool - { - public SwellCompositionTool() - : base(nameof(Swell)) - { - } - - public override PlacementBlueprint CreatePlacementBlueprint() => new SwellPlacementBlueprint(); - } - - public class DrumRollCompositionTool : HitObjectCompositionTool - { - public DrumRollCompositionTool() - : base(nameof(DrumRoll)) - { - } - - public override PlacementBlueprint CreatePlacementBlueprint() => new DrumRollPlacementBlueprint(); - } - - public class SwellPlacementBlueprint : TaikoSpanPlacementBlueprint - { - public SwellPlacementBlueprint() - : base(new Swell()) - { - } - } - - public class DrumRollPlacementBlueprint : TaikoSpanPlacementBlueprint - { - public DrumRollPlacementBlueprint() - : base(new DrumRoll()) - { - } - } - - public class TaikoSpanPlacementBlueprint : PlacementBlueprint - { - private readonly HitPiece headPiece; - private readonly HitPiece tailPiece; - - private readonly LengthPiece lengthPiece; - - private readonly IHasDuration spanPlacementObject; - - public TaikoSpanPlacementBlueprint(HitObject hitObject) - : base(hitObject) - - { - spanPlacementObject = hitObject as IHasDuration; - - RelativeSizeAxes = Axes.Both; - - InternalChildren = new Drawable[] - { - headPiece = new HitPiece - { - Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) - }, - lengthPiece = new LengthPiece - { - Height = TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT - }, - tailPiece = new HitPiece - { - Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) - } - }; - } - - private double originalStartTime; - - protected override bool OnMouseDown(MouseDownEvent e) - { - if (e.Button != MouseButton.Left) - return false; - - BeginPlacement(true); - return true; - } - - protected override void OnMouseUp(MouseUpEvent e) - { - if (e.Button != MouseButton.Left) - return; - - base.OnMouseUp(e); - EndPlacement(true); - } - - public override void UpdatePosition(SnapResult result) - { - base.UpdatePosition(result); - - if (PlacementActive) - { - if (result.Time is double endTime) - { - if (endTime < originalStartTime) - { - HitObject.StartTime = endTime; - spanPlacementObject.Duration = Math.Abs(endTime - originalStartTime); - headPiece.Position = ToLocalSpace(result.ScreenSpacePosition); - lengthPiece.X = headPiece.X; - lengthPiece.Width = tailPiece.X - headPiece.X; - } - else - { - spanPlacementObject.Duration = Math.Abs(endTime - originalStartTime); - tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition); - lengthPiece.Width = tailPiece.X - headPiece.X; - } - } - } - else - { - lengthPiece.Position = headPiece.Position = tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition); - - if (result.Time is double startTime) - originalStartTime = HitObject.StartTime = startTime; - } - } - } - - public class HitCompositionTool : HitObjectCompositionTool - { - public HitCompositionTool() - : base(nameof(Hit)) - { - } - - public override PlacementBlueprint CreatePlacementBlueprint() => new HitPlacementBlueprint(); - } - - public class HitPlacementBlueprint : PlacementBlueprint - { - private readonly HitPiece piece; - - private static Hit hit; - - public HitPlacementBlueprint() - : base(hit = new Hit()) - { - InternalChild = piece = new HitPiece - { - Size = new Vector2(TaikoHitObject.DEFAULT_SIZE * TaikoPlayfield.DEFAULT_HEIGHT) - }; - } - - protected override bool OnMouseDown(MouseDownEvent e) - { - switch (e.Button) - { - case MouseButton.Left: - hit.Type = HitType.Centre; - EndPlacement(true); - return true; - - case MouseButton.Right: - hit.Type = HitType.Rim; - EndPlacement(true); - return true; - } - - return false; - } - - public override void UpdatePosition(SnapResult result) - { - piece.Position = ToLocalSpace(result.ScreenSpacePosition); - base.UpdatePosition(result); - } - } - - public class LengthPiece : CompositeDrawable - { - public LengthPiece() - { - Origin = Anchor.CentreLeft; - - InternalChild = new Container - { - Masking = true, - Colour = Color4.Yellow, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.X, - Height = 8, - }, - new Box - { - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Height = 8, - } - } - }; - } - } - - public class HitPiece : CompositeDrawable - { - public HitPiece() - { - Origin = Anchor.Centre; - - InternalChild = new CircularContainer - { - Masking = true, - BorderThickness = 10, - BorderColour = Color4.Yellow, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - AlwaysPresent = true, - Alpha = 0, - RelativeSizeAxes = Axes.Both - } - } - }; - } - } -} diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 7be16471b4..4cdd1fbc24 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -22,6 +22,7 @@ using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Scoring; using System; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Taiko.Edit; using osu.Game.Rulesets.Taiko.Skinning; using osu.Game.Skinning; From e0aae15c0a313549f29a610b0bc697a1fd2c9435 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 16:40:23 +0900 Subject: [PATCH 233/871] Hard type incoming ruleset --- osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs index 7ad40903d2..cdc9672a8e 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoHitObjectComposer.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Taiko.Edit { public class TaikoHitObjectComposer : HitObjectComposer { - public TaikoHitObjectComposer(Ruleset ruleset) + public TaikoHitObjectComposer(TaikoRuleset ruleset) : base(ruleset) { } From b068992a15f43237bfb3693e0fca4daa63922fd8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 29 May 2020 18:58:34 +0900 Subject: [PATCH 234/871] Add missing licence headers --- .../Edit/Blueprints/DrumRollPlacementBlueprint.cs | 3 +++ osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs | 3 +++ .../Edit/Blueprints/HitPlacementBlueprint.cs | 3 +++ osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs | 3 +++ .../Edit/Blueprints/SwellPlacementBlueprint.cs | 3 +++ .../Edit/Blueprints/TaikoSpanPlacementBlueprint.cs | 3 +++ osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs | 3 +++ osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs | 3 +++ osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs | 3 +++ osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs | 3 +++ 10 files changed, 30 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs index 2f086891a9..eb07ce7635 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/DrumRollPlacementBlueprint.cs @@ -1,3 +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 osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Edit.Blueprints diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs index 992fba1e29..b02e3aa9ba 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPiece.cs @@ -1,3 +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 osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs index c21aed32e8..c5191ab241 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/HitPlacementBlueprint.cs @@ -1,3 +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 osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Taiko.Objects; diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs index 2139a852b2..6b651fd739 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/LengthPiece.cs @@ -1,3 +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 osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs index 180bf26f34..95fa82a0f2 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/SwellPlacementBlueprint.cs @@ -1,3 +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 osu.Game.Rulesets.Taiko.Objects; namespace osu.Game.Rulesets.Taiko.Edit.Blueprints diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs index b08cc68225..7f96b5a46e 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using System; using osu.Framework.Graphics; using osu.Framework.Input.Events; diff --git a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs index c17e22180a..bf77c76670 100644 --- a/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/DrumRollCompositionTool.cs @@ -1,3 +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 osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Taiko.Edit.Blueprints; diff --git a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs index 7e8f245613..e877cf6240 100644 --- a/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/HitCompositionTool.cs @@ -1,3 +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 osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Taiko.Edit.Blueprints; diff --git a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs index aa96a397d0..a6191fcedc 100644 --- a/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs +++ b/osu.Game.Rulesets.Taiko/Edit/SwellCompositionTool.cs @@ -1,3 +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 osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Taiko.Edit.Blueprints; diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs index b7cda04705..36227b0798 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using System.Collections.Generic; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Drawables; From affad4724867f9cda7a5d707a1edca3943e0c0a0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 29 May 2020 19:44:53 +0300 Subject: [PATCH 235/871] Fix genre/language search doesn't work --- .../Online/API/Requests/SearchBeatmapSetsRequest.cs | 13 ++++++++++--- .../BeatmapListing/BeatmapListingFilterControl.cs | 4 +++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index 0c3272c7de..ce8b40aa30 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -27,7 +27,14 @@ namespace osu.Game.Online.API.Requests private string directionString => SortDirection == SortDirection.Descending ? @"desc" : @"asc"; - public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, Cursor cursor = null, SearchCategory searchCategory = SearchCategory.Any, SortCriteria sortCriteria = SortCriteria.Ranked, SortDirection sortDirection = SortDirection.Descending) + public SearchBeatmapSetsRequest(string query, + RulesetInfo ruleset, + Cursor cursor = null, + SearchCategory searchCategory = SearchCategory.Any, + SortCriteria sortCriteria = SortCriteria.Ranked, + SortDirection sortDirection = SortDirection.Descending, + SearchGenre genre = SearchGenre.Any, + SearchLanguage language = SearchLanguage.Any) { this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query); this.ruleset = ruleset; @@ -36,8 +43,8 @@ namespace osu.Game.Online.API.Requests SearchCategory = searchCategory; SortCriteria = sortCriteria; SortDirection = sortDirection; - Genre = SearchGenre.Any; - Language = SearchLanguage.Any; + Genre = genre; + Language = language; } protected override WebRequest CreateWebRequest() diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 41c99d5d03..0ead5cc226 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -177,7 +177,9 @@ namespace osu.Game.Overlays.BeatmapListing lastResponse?.Cursor, searchControl.Category.Value, sortControl.Current.Value, - sortControl.SortDirection.Value); + sortControl.SortDirection.Value, + searchControl.Genre.Value, + searchControl.Language.Value); getSetsRequest.Success += response => { From 9aa54ed89e059349ca257fbb5b27e8ceee5f835e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 29 May 2020 19:53:32 +0300 Subject: [PATCH 236/871] Fix serach control background never being updated --- .../Overlays/BeatmapListing/BeatmapListingFilterControl.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs index 0ead5cc226..494a0df8f8 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs @@ -188,6 +188,9 @@ namespace osu.Game.Overlays.BeatmapListing if (sets.Count == 0) noMoreResults = true; + if (CurrentPage == 0) + searchControl.BeatmapSet = sets.FirstOrDefault(); + lastResponse = response; getSetsRequest = null; From 11057cd6a83859744ee3057112eef5a8b3daefca Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Fri, 29 May 2020 21:43:31 +0300 Subject: [PATCH 237/871] CI fix --- osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs index ce8b40aa30..dde45b5aeb 100644 --- a/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs +++ b/osu.Game/Online/API/Requests/SearchBeatmapSetsRequest.cs @@ -27,7 +27,8 @@ namespace osu.Game.Online.API.Requests private string directionString => SortDirection == SortDirection.Descending ? @"desc" : @"asc"; - public SearchBeatmapSetsRequest(string query, + public SearchBeatmapSetsRequest( + string query, RulesetInfo ruleset, Cursor cursor = null, SearchCategory searchCategory = SearchCategory.Any, From 816f721f3dffb36ed0d6279237e9137bd791dba6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 30 May 2020 15:24:37 +0900 Subject: [PATCH 238/871] Move selection blueprint to correct namespace --- .../Edit/{ => Blueprints}/TaikoSelectionBlueprint.cs | 3 +-- osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) rename osu.Game.Rulesets.Taiko/Edit/{ => Blueprints}/TaikoSelectionBlueprint.cs (93%) diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs similarity index 93% rename from osu.Game.Rulesets.Taiko/Edit/TaikoSelectionBlueprint.cs rename to osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs index e3797a5fa6..62f69122cc 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSelectionBlueprint.cs @@ -4,10 +4,9 @@ using osu.Framework.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Taiko.Edit.Blueprints; using osuTK; -namespace osu.Game.Rulesets.Taiko.Edit +namespace osu.Game.Rulesets.Taiko.Edit.Blueprints { public class TaikoSelectionBlueprint : OverlaySelectionBlueprint { diff --git a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs index 36227b0798..35227b3c64 100644 --- a/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs +++ b/osu.Game.Rulesets.Taiko/Edit/TaikoBlueprintContainer.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Taiko.Edit.Blueprints; using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Taiko.Edit From 82fe99cf4a54b90a473d43855238f2aa5ae58d65 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 31 May 2020 02:18:07 +0300 Subject: [PATCH 239/871] Replace any potential usage of Environment.CurrentDirectory with a new RuntimeInfo.StartupDirectory Using `Environment.CurrentDirectory` for storing / reading files is dangerous as the current directory is mutable and can be changed when performing a certain operation (like opening solutions in roslyn type reference builder for example). --- osu.Desktop/Program.cs | 4 +--- .../NonVisual/CustomDataDirectoryTest.cs | 16 ++++++++-------- osu.Game.Tests/Resources/TestResources.cs | 5 +++-- osu.Game/Rulesets/RulesetStore.cs | 5 +++-- osu.Game/Tests/Visual/OsuTestScene.cs | 3 ++- 5 files changed, 17 insertions(+), 16 deletions(-) diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index bd91bcc933..285a813d97 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -33,13 +33,11 @@ namespace osu.Desktop if (args.Length > 0 && args[0].Contains('.')) // easy way to check for a file import in args { var importer = new ArchiveImportIPCChannel(host); - // Restore the cwd so relative paths given at the command line work correctly - Directory.SetCurrentDirectory(cwd); foreach (var file in args) { Console.WriteLine(@"Importing {0}", file); - if (!importer.ImportAsync(Path.GetFullPath(file)).Wait(3000)) + if (!importer.ImportAsync(Path.GetFullPath(file, cwd)).Wait(3000)) throw new TimeoutException(@"IPC took too long to send"); } diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 743c924bbd..1f6e92a535 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Platform; @@ -35,8 +36,7 @@ namespace osu.Game.Tests.NonVisual var osu = loadOsu(host); var storage = osu.Dependencies.Get(); - string defaultStorageLocation = Path.Combine(Environment.CurrentDirectory, "headless", nameof(TestDefaultDirectory)); - + string defaultStorageLocation = RuntimeInfo.StartupStorage.GetFullPath(Path.Combine("headless", nameof(TestDefaultDirectory))); Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation)); } finally @@ -46,17 +46,17 @@ namespace osu.Game.Tests.NonVisual } } - private string customPath => Path.Combine(Environment.CurrentDirectory, "custom-path"); + private string customPath { get; } = RuntimeInfo.StartupStorage.GetFullPath("custom-path"); [Test] public void TestCustomDirectory() { using (var host = new HeadlessGameHost(nameof(TestCustomDirectory))) { - string headlessPrefix = Path.Combine("headless", nameof(TestCustomDirectory)); + string defaultStorageLocation = RuntimeInfo.StartupStorage.GetFullPath(Path.Combine("headless", nameof(TestCustomDirectory))); // need access before the game has constructed its own storage yet. - Storage storage = new DesktopStorage(headlessPrefix, host); + Storage storage = new DesktopStorage(defaultStorageLocation, host); // manual cleaning so we can prepare a config file. storage.DeleteDirectory(string.Empty); @@ -84,10 +84,10 @@ namespace osu.Game.Tests.NonVisual { using (var host = new HeadlessGameHost(nameof(TestSubDirectoryLookup))) { - string headlessPrefix = Path.Combine("headless", nameof(TestSubDirectoryLookup)); + string defaultStorageLocation = RuntimeInfo.StartupStorage.GetFullPath(Path.Combine("headless", nameof(TestSubDirectoryLookup))); // need access before the game has constructed its own storage yet. - Storage storage = new DesktopStorage(headlessPrefix, host); + Storage storage = new DesktopStorage(defaultStorageLocation, host); // manual cleaning so we can prepare a config file. storage.DeleteDirectory(string.Empty); @@ -136,7 +136,7 @@ namespace osu.Game.Tests.NonVisual // for testing nested files are not ignored (only top level) host.Storage.GetStorageForDirectory("test-nested").GetStorageForDirectory("cache"); - string defaultStorageLocation = Path.Combine(Environment.CurrentDirectory, "headless", nameof(TestMigration)); + string defaultStorageLocation = RuntimeInfo.StartupStorage.GetFullPath(Path.Combine("headless", nameof(TestMigration))); Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation)); diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 8b892fbb2f..33b580f68f 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -3,6 +3,7 @@ using System.IO; using NUnit.Framework; +using osu.Framework; using osu.Framework.IO.Stores; namespace osu.Game.Tests.Resources @@ -20,10 +21,10 @@ namespace osu.Game.Tests.Resources var temp = Path.GetTempFileName() + ".osz"; using (var stream = GetTestBeatmapStream(virtualTrack)) - using (var newFile = File.Create(temp)) + using (var newFile = RuntimeInfo.StartupStorage.GetStream(temp, FileAccess.Write)) stream.CopyTo(newFile); - Assert.IsTrue(File.Exists(temp)); + Assert.IsTrue(RuntimeInfo.StartupStorage.Exists(temp)); return temp; } } diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index b3026bf2b7..5c49141064 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; +using osu.Framework; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Database; @@ -153,14 +154,14 @@ namespace osu.Game.Rulesets { try { - string[] files = Directory.GetFiles(Environment.CurrentDirectory, $"{ruleset_library_prefix}.*.dll"); + var files = RuntimeInfo.StartupStorage.GetFiles($"{ruleset_library_prefix}.*.dll"); foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests"))) loadRulesetFromFile(file); } catch (Exception e) { - Logger.Error(e, $"Could not load rulesets from directory {Environment.CurrentDirectory}"); + Logger.Error(e, $"Could not load rulesets from directory {RuntimeInfo.StartupDirectory}"); } } diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 5dc8714c07..2672022720 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Track; @@ -118,7 +119,7 @@ namespace osu.Game.Tests.Visual } } - localStorage = new Lazy(() => new NativeStorage($"{GetType().Name}-{Guid.NewGuid()}")); + localStorage = new Lazy(() => RuntimeInfo.StartupStorage.GetStorageForDirectory($"{GetType().Name}-{Guid.NewGuid()}")); } [Resolved] From b06017dbf16a868f353226c1bed6261bed79308c Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 31 May 2020 11:28:54 +0800 Subject: [PATCH 240/871] supress horizontal scaling of left-and-right stages --- osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs index 7680526ac4..f177284399 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs @@ -52,10 +52,10 @@ namespace osu.Game.Rulesets.Mania.Skinning base.Update(); if (leftSprite?.Height > 0) - leftSprite.Scale = new Vector2(DrawHeight / leftSprite.Height); + leftSprite.Scale = new Vector2(1, DrawHeight / leftSprite.Height); if (rightSprite?.Height > 0) - rightSprite.Scale = new Vector2(DrawHeight / rightSprite.Height); + rightSprite.Scale = new Vector2(1, DrawHeight / rightSprite.Height); } } } From f2dadeeeb5bbd96c2ff9e62f3b09464eb1c7ffc5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 May 2020 13:50:42 +0900 Subject: [PATCH 241/871] Allow horizontal scroll on results screen when not hovering expanded panel --- osu.Game/Screens/Ranking/ScorePanelList.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 18db3f2af4..1142297274 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.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 osu.Framework.Bindables; @@ -107,6 +108,9 @@ namespace osu.Game.Screens.Ranking // Find the panel corresponding to the new score. expandedPanel = flow.SingleOrDefault(p => p.Score == score.NewValue); + // handle horizontal scroll only when not hovering the expanded panel. + scroll.HandleScroll = () => expandedPanel?.IsHovered != true; + if (expandedPanel == null) return; @@ -166,6 +170,11 @@ namespace osu.Game.Screens.Ranking /// public float? InstantScrollTarget; + /// + /// Whether this container should handle scroll trigger events. + /// + public Func HandleScroll; + protected override void UpdateAfterChildren() { if (InstantScrollTarget != null) @@ -177,9 +186,9 @@ namespace osu.Game.Screens.Ranking base.UpdateAfterChildren(); } - public override bool HandlePositionalInput => false; + public override bool HandlePositionalInput => HandleScroll(); - public override bool HandleNonPositionalInput => false; + public override bool HandleNonPositionalInput => HandleScroll(); } } } From e43217f5797c4699511078fbe3e3550a85b5a7b1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 May 2020 20:01:13 +0900 Subject: [PATCH 242/871] Add test resources --- .../Resources/special-skin/mania-stage-left.png | Bin 0 -> 165 bytes .../Resources/special-skin/mania-stage-right.png | Bin 0 -> 899 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-left.png create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-right.png diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-left.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-left.png new file mode 100644 index 0000000000000000000000000000000000000000..03ca371c4e26972982544c5b8f7b02a85da9a823 GIT binary patch literal 165 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61SBU+%rFB|oCO|{#S9F3${@^GvDCf{D9B#o z>Fdh=gqe+pPssgM@hqT_WQl7;iF1B#Zfaf$gL6@8Vo7R>LV0FMhJw4NZ$Nk>pEyvF zyr+v}h{pNkHS5=>EAW|^m>2+o;RFsh)1E8|D=7xCTxMCBnie0RQU*^~KbLh*2~7Y- CL?;CR literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-right.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-right.png new file mode 100644 index 0000000000000000000000000000000000000000..45b7be025591b85e6849824f181672905d129933 GIT binary patch literal 899 zcmZ`%OK;Oa5FY1IO4N$d6BHyYw?YLq-XwrTEITw!ATg>-6jCWI9B`9OWAG!gH^fMs zI20jqK%6;5NL)DdA0UCaqe%P;#E~l!?AobQT54-|W@oDM2f3eKxn)?okJ#UJ$W5jWM2romOJPeYQMhd6`KAGifb20Fl9?n0#3 zx#ck?2Jq5=#2B+pB~w?}7RkihJvc=z=jnPg0!6zSQfU)AsPv0_c*2zg-rmt793JK;hyD?z&F8uXqz z>tP`)qhq#lFTr$&yxmMdzx*tmJN_4`a>W_2_g!Taje r`qIzU@0rIC|JptGdXHOvF!Njj=r1MTeKD0y_QurP8|D4AM=$;YKmXm5 literal 0 HcmV?d00001 From 81b8898272fa1bf325afb85236bb0020be65667c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 May 2020 22:30:55 +0900 Subject: [PATCH 243/871] Fix incorrect type cast in encoder --- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index d7e83fa471..ab1b8aecfd 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -233,9 +233,9 @@ namespace osu.Game.Beatmaps.Formats writer.Write(FormattableString.Invariant($"{(int)getObjectType(hitObject)},")); writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(hitObject.Samples)},")); - if (hitObject is IHasPathWithRepeats curveData) + if (hitObject is IHasPath path) { - addPathData(writer, curveData, position); + addPathData(writer, path, position); writer.Write(getSampleBank(hitObject.Samples, zeroBanks: true)); } else From 19be111da098a03227d645ee13f75b3b42b74814 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 May 2020 22:33:10 +0900 Subject: [PATCH 244/871] Move incorrect placed full stop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index c956439eb2..6dd6e6815b 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -171,7 +171,7 @@ namespace osu.Game.Rulesets.Edit /// The ruleset used to construct its drawable counterpart. /// The loaded beatmap. /// The mods to be applied. - /// An editor-relevant . + /// An editor-relevant . protected virtual DrawableRuleset CreateDrawableRuleset(Ruleset ruleset, IBeatmap beatmap, IReadOnlyList mods = null) => (DrawableRuleset)ruleset.CreateDrawableRulesetWith(beatmap, mods); From e688033967bbd5000f0f515d8e63ccafa1ded00d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 31 May 2020 22:39:03 +0900 Subject: [PATCH 245/871] Fix incorrect xmldoc --- osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index d8d90fddfa..9e936c7717 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -331,7 +331,7 @@ namespace osu.Game.Rulesets.Objects.Legacy /// The position of the hit object. /// Whether the hit object creates a new combo. /// When starting a new combo, the offset of the new combo relative to the current one. - /// The hold end time. + /// The hold duration. protected abstract HitObject CreateHold(Vector2 position, bool newCombo, int comboOffset, double duration); private List convertSoundType(LegacyHitSoundType type, SampleBankInfo bankInfo) From 0027f44bd0d0a099a4bd1ce1b5a053b3c771d1b3 Mon Sep 17 00:00:00 2001 From: Shivam Date: Sun, 31 May 2020 16:27:05 +0200 Subject: [PATCH 246/871] Moved stableInfo read to FileBasedIPC DI is also not needed anymore to access StableInfo, this goes through FileBasedIPC. Note: directory selector now always navigates to the osu! lazer base path. --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 30 +++++++++++++++---- osu.Game.Tournament/Screens/SetupScreen.cs | 12 ++------ .../Screens/StablePathSelectScreen.cs | 10 ++----- osu.Game.Tournament/TournamentGameBase.cs | 19 ------------ 4 files changed, 31 insertions(+), 40 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 8518b7f8da..4ec9d2012a 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -37,8 +37,7 @@ namespace osu.Game.Tournament.IPC private int lastBeatmapId; private ScheduledDelegate scheduled; - [Resolved] - private StableInfo stableInfo { get; set; } + private StableInfo stableInfo; public const string STABLE_CONFIG = "tournament/stable.json"; @@ -161,9 +160,11 @@ namespace osu.Game.Tournament.IPC public static bool CheckExists(string p) => File.Exists(Path.Combine(p, "ipc.txt")); + public StableInfo GetStableInfo() => stableInfo; + private string findStablePath() { - if (!string.IsNullOrEmpty(stableInfo.StablePath.Value)) + if (!string.IsNullOrEmpty(readStableConfig())) return stableInfo.StablePath.Value; string stableInstallPath = string.Empty; @@ -184,7 +185,7 @@ namespace osu.Game.Tournament.IPC if (stableInstallPath != null) { - saveStableConfig(stableInstallPath); + SaveStableConfig(stableInstallPath); return stableInstallPath; } } @@ -197,7 +198,7 @@ namespace osu.Game.Tournament.IPC } } - private void saveStableConfig(string path) + public void SaveStableConfig(string path) { stableInfo.StablePath.Value = path; @@ -214,6 +215,25 @@ namespace osu.Game.Tournament.IPC } } + private string readStableConfig() + { + if (stableInfo == null) + stableInfo = new StableInfo(); + + if (tournamentStorage.Exists(FileBasedIPC.STABLE_CONFIG)) + { + using (Stream stream = tournamentStorage.GetStream(FileBasedIPC.STABLE_CONFIG, FileAccess.Read, FileMode.Open)) + using (var sr = new StreamReader(stream)) + { + stableInfo = JsonConvert.DeserializeObject(sr.ReadToEnd()); + } + + return stableInfo.StablePath.Value; + } + + return null; + } + private string findFromEnvVar() { try diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 9f8f81aa80..da91fbba04 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -15,7 +15,6 @@ using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Tournament.IPC; -using osu.Framework.Platform; using osu.Game.Tournament.Models; using osuTK; using osuTK.Graphics; @@ -43,12 +42,6 @@ namespace osu.Game.Tournament.Screens private Bindable windowSize; - [Resolved] - private Storage storage { get; set; } - - [Resolved] - private StableInfo stableInfo { get; set; } - [BackgroundDependencyLoader] private void load(FrameworkConfigManager frameworkConfig) { @@ -73,6 +66,7 @@ namespace osu.Game.Tournament.Screens private void reload() { var fileBasedIpc = ipc as FileBasedIPC; + StableInfo stableInfo = fileBasedIpc?.GetStableInfo(); fillFlow.Children = new Drawable[] { new ActionableInfo @@ -81,13 +75,13 @@ namespace osu.Game.Tournament.Screens ButtonText = "Change source", Action = () => { - stableInfo.StablePath.BindValueChanged(_ => + stableInfo?.StablePath.BindValueChanged(_ => { Schedule(reload); }); sceneManager?.SetScreen(new StablePathSelectScreen()); }, - Value = fileBasedIpc?.IPCStorage?.GetFullPath(string.Empty) ?? "Not found", + Value = fileBasedIpc?.IPCStorage.GetFullPath(string.Empty) ?? "Not found", Failing = fileBasedIpc?.IPCStorage == null, Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation." }, diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index eace3c78d5..2e1f0180a9 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Game.Tournament.Models; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -24,9 +23,6 @@ namespace osu.Game.Tournament.Screens { private DirectorySelector directorySelector; - [Resolved] - private StableInfo stableInfo { get; set; } - [Resolved] private MatchIPCInfo ipc { get; set; } @@ -38,7 +34,7 @@ namespace osu.Game.Tournament.Screens [BackgroundDependencyLoader(true)] private void load(Storage storage, OsuColour colours) { - var initialPath = new DirectoryInfo(storage.GetFullPath(stableInfo.StablePath.Value ?? string.Empty)).Parent?.FullName; + var initialPath = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent?.FullName; AddRangeInternal(new Drawable[] { @@ -131,7 +127,7 @@ namespace osu.Game.Tournament.Screens protected virtual void ChangePath(Storage storage) { var target = directorySelector.CurrentDirectory.Value.FullName; - stableInfo.StablePath.Value = target; + var fileBasedIpc = ipc as FileBasedIPC; Logger.Log($"Changing Stable CE location to {target}"); if (!FileBasedIPC.CheckExists(target)) @@ -143,7 +139,7 @@ namespace osu.Game.Tournament.Screens return; } - var fileBasedIpc = ipc as FileBasedIPC; + fileBasedIpc?.SaveStableConfig(target); fileBasedIpc?.LocateStableStorage(); sceneManager?.SetScreen(typeof(SetupScreen)); } diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index dcfe646390..85db9e61fb 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -43,7 +43,6 @@ namespace osu.Game.Tournament private Bindable windowSize; private FileBasedIPC ipc; - private StableInfo stableInfo; private Drawable heightWarning; @@ -72,7 +71,6 @@ namespace osu.Game.Tournament }), true); readBracket(); - readStableConfig(); ladder.CurrentMatch.Value = ladder.Matches.FirstOrDefault(p => p.Current.Value); @@ -143,23 +141,6 @@ namespace osu.Game.Tournament }); } - private void readStableConfig() - { - if (stableInfo == null) - stableInfo = new StableInfo(); - - if (storage.Exists(FileBasedIPC.STABLE_CONFIG)) - { - using (Stream stream = storage.GetStream(FileBasedIPC.STABLE_CONFIG, FileAccess.Read, FileMode.Open)) - using (var sr = new StreamReader(stream)) - { - stableInfo = JsonConvert.DeserializeObject(sr.ReadToEnd()); - } - } - - dependencies.Cache(stableInfo); - } - private void readBracket() { if (storage.Exists(bracket_filename)) From ce360a960f2d0d876a7c424baac0cd202edc336c Mon Sep 17 00:00:00 2001 From: Shivam Date: Sun, 31 May 2020 16:50:13 +0200 Subject: [PATCH 247/871] use GameHost's GetStorage instead of local storage This will now get the IPC Path again as the default path if one is present, else it will fall back to osu! lazer's base path. --- osu.Game.Tournament/Screens/StablePathSelectScreen.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index 2e1f0180a9..50db0afa66 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -31,10 +31,14 @@ namespace osu.Game.Tournament.Screens [Resolved(canBeNull: true)] private TournamentSceneManager sceneManager { get; set; } + [Resolved] + private GameHost host { get; set; } + [BackgroundDependencyLoader(true)] private void load(Storage storage, OsuColour colours) { - var initialPath = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent?.FullName; + var fileBasedIpc = ipc as FileBasedIPC; + var initialPath = new DirectoryInfo(host.GetStorage(fileBasedIpc?.GetStableInfo().StablePath.Value).GetFullPath(string.Empty) ?? storage.GetFullPath(string.Empty)).Parent?.FullName; AddRangeInternal(new Drawable[] { From 33d731644c092f7164687b1ceeed6ec3145bae4b Mon Sep 17 00:00:00 2001 From: Shivam Date: Sun, 31 May 2020 17:35:53 +0200 Subject: [PATCH 248/871] Fix test crashing: NullReferenceException --- osu.Game.Tournament/Screens/SetupScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index da91fbba04..19ac84dea3 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -81,7 +81,7 @@ namespace osu.Game.Tournament.Screens }); sceneManager?.SetScreen(new StablePathSelectScreen()); }, - Value = fileBasedIpc?.IPCStorage.GetFullPath(string.Empty) ?? "Not found", + Value = fileBasedIpc?.IPCStorage?.GetFullPath(string.Empty) ?? "Not found", Failing = fileBasedIpc?.IPCStorage == null, Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation." }, From 2c6887e610dec2babe5f1436b2406b31547b7a15 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sun, 31 May 2020 19:49:03 +0300 Subject: [PATCH 249/871] Remove unnecessary use of and remove StartupStorage --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 10 +++++----- osu.Game/Rulesets/RulesetStore.cs | 2 +- osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 1f6e92a535..f3d54d876a 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -36,7 +36,7 @@ namespace osu.Game.Tests.NonVisual var osu = loadOsu(host); var storage = osu.Dependencies.Get(); - string defaultStorageLocation = RuntimeInfo.StartupStorage.GetFullPath(Path.Combine("headless", nameof(TestDefaultDirectory))); + string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestDefaultDirectory)); Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation)); } finally @@ -46,14 +46,14 @@ namespace osu.Game.Tests.NonVisual } } - private string customPath { get; } = RuntimeInfo.StartupStorage.GetFullPath("custom-path"); + private string customPath => Path.Combine(RuntimeInfo.StartupDirectory, "custom-path"); [Test] public void TestCustomDirectory() { using (var host = new HeadlessGameHost(nameof(TestCustomDirectory))) { - string defaultStorageLocation = RuntimeInfo.StartupStorage.GetFullPath(Path.Combine("headless", nameof(TestCustomDirectory))); + string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestCustomDirectory)); // need access before the game has constructed its own storage yet. Storage storage = new DesktopStorage(defaultStorageLocation, host); @@ -84,7 +84,7 @@ namespace osu.Game.Tests.NonVisual { using (var host = new HeadlessGameHost(nameof(TestSubDirectoryLookup))) { - string defaultStorageLocation = RuntimeInfo.StartupStorage.GetFullPath(Path.Combine("headless", nameof(TestSubDirectoryLookup))); + string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestSubDirectoryLookup)); // need access before the game has constructed its own storage yet. Storage storage = new DesktopStorage(defaultStorageLocation, host); @@ -136,7 +136,7 @@ namespace osu.Game.Tests.NonVisual // for testing nested files are not ignored (only top level) host.Storage.GetStorageForDirectory("test-nested").GetStorageForDirectory("cache"); - string defaultStorageLocation = RuntimeInfo.StartupStorage.GetFullPath(Path.Combine("headless", nameof(TestMigration))); + string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration)); Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation)); diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 5c49141064..10b6edca8c 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets { try { - var files = RuntimeInfo.StartupStorage.GetFiles($"{ruleset_library_prefix}.*.dll"); + var files = Directory.GetFiles(Path.Combine(RuntimeInfo.StartupDirectory, $"{ruleset_library_prefix}.*.dll")); foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests"))) loadRulesetFromFile(file); diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 2672022720..632d668a01 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual } } - localStorage = new Lazy(() => RuntimeInfo.StartupStorage.GetStorageForDirectory($"{GetType().Name}-{Guid.NewGuid()}")); + localStorage = new Lazy(() => new NativeStorage(Path.Combine(RuntimeInfo.StartupDirectory, $"{GetType().Name}-{Guid.NewGuid()}"))); } [Resolved] From 53b58910c3d0faacf969685c45ba5620e90d5917 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Jun 2020 14:27:39 +0900 Subject: [PATCH 250/871] Invert interface definition --- osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs index fba0fd7aff..342b0ec1f6 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs @@ -9,12 +9,14 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that has a curve. /// - public interface IHasPathWithRepeats : IHasPath, IHasRepeats +#pragma warning disable 618 + public interface IHasPathWithRepeats : IHasCurve +#pragma warning restore 618 { } [Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126 - public interface IHasCurve : IHasPathWithRepeats + public interface IHasCurve : IHasPath, IHasRepeats { } From cac6e93575890d31ea6e4276d93b3b4698ea440c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 1 Jun 2020 15:10:22 +0900 Subject: [PATCH 251/871] Restore original IHasCurve implementation --- .../Rulesets/Objects/Legacy/ConvertSlider.cs | 5 +- osu.Game/Rulesets/Objects/Types/IHasCurve.cs | 55 +++++++++++++++++++ .../Objects/Types/IHasPathWithRepeats.cs | 11 +--- 3 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 osu.Game/Rulesets/Objects/Types/IHasCurve.cs diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 73192dc42e..c946e43df3 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -9,7 +9,10 @@ using osu.Game.Beatmaps.ControlPoints; namespace osu.Game.Rulesets.Objects.Legacy { - internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset + internal abstract class ConvertSlider : ConvertHitObject, IHasPathWithRepeats, IHasLegacyLastTickOffset, +#pragma warning disable 618 + IHasCurve +#pragma warning restore 618 { /// /// Scoring distance with a speed-adjusted beat length of 1 second. diff --git a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs new file mode 100644 index 0000000000..26f50ffa31 --- /dev/null +++ b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs @@ -0,0 +1,55 @@ +// 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 osuTK; + +namespace osu.Game.Rulesets.Objects.Types +{ + [Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126 + public interface IHasCurve : IHasDistance, IHasRepeats + { + /// + /// The curve. + /// + SliderPath Path { get; } + } + +#pragma warning disable 618 + [Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126 + public static class HasCurveExtensions + { + /// + /// Computes the position on the curve relative to how much of the has been completed. + /// + /// The curve. + /// [0, 1] where 0 is the start time of the and 1 is the end time of the . + /// The position on the curve. + public static Vector2 CurvePositionAt(this IHasCurve obj, double progress) + => obj.Path.PositionAt(obj.ProgressAt(progress)); + + /// + /// Computes the progress along the curve relative to how much of the has been completed. + /// + /// The curve. + /// [0, 1] where 0 is the start time of the and 1 is the end time of the . + /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. + public static double ProgressAt(this IHasCurve obj, double progress) + { + double p = progress * obj.SpanCount() % 1; + if (obj.SpanAt(progress) % 2 == 1) + p = 1 - p; + return p; + } + + /// + /// Determines which span of the curve the progress point is on. + /// + /// The curve. + /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. + /// [0, SpanCount) where 0 is the first run. + public static int SpanAt(this IHasCurve obj, double progress) + => (int)(progress * obj.SpanCount()); + } +#pragma warning restore 618 +} diff --git a/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs index 342b0ec1f6..279946b44e 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasPathWithRepeats.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osuTK; namespace osu.Game.Rulesets.Objects.Types @@ -9,14 +8,8 @@ namespace osu.Game.Rulesets.Objects.Types /// /// A HitObject that has a curve. /// -#pragma warning disable 618 - public interface IHasPathWithRepeats : IHasCurve -#pragma warning restore 618 - { - } - - [Obsolete("Use IHasPathWithRepeats instead.")] // can be removed 20201126 - public interface IHasCurve : IHasPath, IHasRepeats + // ReSharper disable once RedundantExtendsListEntry + public interface IHasPathWithRepeats : IHasPath, IHasRepeats { } From 7a9ed78527d1f9c02d7e3509011cc6be5a8ec60b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 1 Jun 2020 11:57:32 +0300 Subject: [PATCH 252/871] Remove missed leftover usages --- osu.Game.Tests/Resources/TestResources.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 33b580f68f..1c264f66e0 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -18,14 +18,14 @@ namespace osu.Game.Tests.Resources public static string GetTestBeatmapForImport(bool virtualTrack = false) { - var temp = Path.GetTempFileName() + ".osz"; + var tempPath = Path.Combine(RuntimeInfo.StartupDirectory, Path.GetTempFileName() + ".osz"); using (var stream = GetTestBeatmapStream(virtualTrack)) - using (var newFile = RuntimeInfo.StartupStorage.GetStream(temp, FileAccess.Write)) + using (var newFile = File.Create(tempPath)) stream.CopyTo(newFile); - Assert.IsTrue(RuntimeInfo.StartupStorage.Exists(temp)); - return temp; + Assert.IsTrue(File.Exists(tempPath)); + return tempPath; } } } From fbd9ad411f85ceb5b8894701b830d5da8b6bac11 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2020 09:05:46 +0000 Subject: [PATCH 253/871] Bump DiffPlex from 1.6.2 to 1.6.3 Bumps [DiffPlex](https://github.com/mmanela/diffplex) from 1.6.2 to 1.6.3. - [Release notes](https://github.com/mmanela/diffplex/releases) - [Commits](https://github.com/mmanela/diffplex/commits) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3d2a4f3081..305e4e0a92 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -19,7 +19,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 8a7f75b515..016f2ba35d 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -75,7 +75,7 @@ - + From e9b09373e784e50a9bd6c718656df9dc2dd371b1 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Mon, 1 Jun 2020 17:41:04 +0200 Subject: [PATCH 254/871] Fix crashing if selected ruleset doesn't have an autoplay mod. --- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Screens/Select/PlaySongSelect.cs | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index bee11accca..8f41e421a3 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -100,7 +100,7 @@ namespace osu.Game.Rulesets return value; } - public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().First(); + public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().FirstOrDefault(); public virtual ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => null; diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 0a4c0e2085..71ab3715e0 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -49,8 +49,11 @@ namespace osu.Game.Screens.Select if (removeAutoModOnResume) { - var autoType = Ruleset.Value.CreateInstance().GetAutoplayMod().GetType(); - ModSelect.DeselectTypes(new[] { autoType }, true); + var autoType = Ruleset.Value.CreateInstance().GetAutoplayMod()?.GetType(); + + if (autoType != null) + ModSelect.DeselectTypes(new[] { autoType }, true); + removeAutoModOnResume = false; } } @@ -78,14 +81,17 @@ namespace osu.Game.Screens.Select if (GetContainingInputManager().CurrentState?.Keyboard.ControlPressed == true) { var auto = Ruleset.Value.CreateInstance().GetAutoplayMod(); - var autoType = auto.GetType(); + var autoType = auto?.GetType(); - var mods = Mods.Value; - - if (mods.All(m => m.GetType() != autoType)) + if (autoType != null) { - Mods.Value = mods.Append(auto).ToArray(); - removeAutoModOnResume = true; + var mods = Mods.Value; + + if (mods.All(m => m.GetType() != autoType)) + { + Mods.Value = mods.Append(auto).ToArray(); + removeAutoModOnResume = true; + } } } From fea5c8460a45026fbe667d780d484863437e804c Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 1 Jun 2020 22:50:24 +0200 Subject: [PATCH 255/871] Fixed path is empty exception Also converted method to property get, private set --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 20 +++++++++---------- osu.Game.Tournament/Screens/SetupScreen.cs | 2 +- .../Screens/StablePathSelectScreen.cs | 7 ++++++- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 4ec9d2012a..44a010e506 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tournament.IPC private int lastBeatmapId; private ScheduledDelegate scheduled; - private StableInfo stableInfo; + public StableInfo StableInfo { get; private set; } public const string STABLE_CONFIG = "tournament/stable.json"; @@ -160,12 +160,10 @@ namespace osu.Game.Tournament.IPC public static bool CheckExists(string p) => File.Exists(Path.Combine(p, "ipc.txt")); - public StableInfo GetStableInfo() => stableInfo; - private string findStablePath() { if (!string.IsNullOrEmpty(readStableConfig())) - return stableInfo.StablePath.Value; + return StableInfo.StablePath.Value; string stableInstallPath = string.Empty; @@ -186,7 +184,7 @@ namespace osu.Game.Tournament.IPC if (stableInstallPath != null) { SaveStableConfig(stableInstallPath); - return stableInstallPath; + return null; } } @@ -200,12 +198,12 @@ namespace osu.Game.Tournament.IPC public void SaveStableConfig(string path) { - stableInfo.StablePath.Value = path; + StableInfo.StablePath.Value = path; using (var stream = tournamentStorage.GetStream(STABLE_CONFIG, FileAccess.Write, FileMode.Create)) using (var sw = new StreamWriter(stream)) { - sw.Write(JsonConvert.SerializeObject(stableInfo, + sw.Write(JsonConvert.SerializeObject(StableInfo, new JsonSerializerSettings { Formatting = Formatting.Indented, @@ -217,18 +215,18 @@ namespace osu.Game.Tournament.IPC private string readStableConfig() { - if (stableInfo == null) - stableInfo = new StableInfo(); + if (StableInfo == null) + StableInfo = new StableInfo(); if (tournamentStorage.Exists(FileBasedIPC.STABLE_CONFIG)) { using (Stream stream = tournamentStorage.GetStream(FileBasedIPC.STABLE_CONFIG, FileAccess.Read, FileMode.Open)) using (var sr = new StreamReader(stream)) { - stableInfo = JsonConvert.DeserializeObject(sr.ReadToEnd()); + StableInfo = JsonConvert.DeserializeObject(sr.ReadToEnd()); } - return stableInfo.StablePath.Value; + return StableInfo.StablePath.Value; } return null; diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 19ac84dea3..db7669184f 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tournament.Screens private void reload() { var fileBasedIpc = ipc as FileBasedIPC; - StableInfo stableInfo = fileBasedIpc?.GetStableInfo(); + StableInfo stableInfo = fileBasedIpc?.StableInfo; fillFlow.Children = new Drawable[] { new ActionableInfo diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index 50db0afa66..fee2696c4c 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -38,7 +38,12 @@ namespace osu.Game.Tournament.Screens private void load(Storage storage, OsuColour colours) { var fileBasedIpc = ipc as FileBasedIPC; - var initialPath = new DirectoryInfo(host.GetStorage(fileBasedIpc?.GetStableInfo().StablePath.Value).GetFullPath(string.Empty) ?? storage.GetFullPath(string.Empty)).Parent?.FullName; + var initialPath = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent?.FullName; + + if (!string.IsNullOrEmpty(fileBasedIpc?.StableInfo.StablePath.Value)) + { + initialPath = new DirectoryInfo(host.GetStorage(fileBasedIpc.StableInfo.StablePath.Value).GetFullPath(string.Empty)).Parent?.FullName; + } AddRangeInternal(new Drawable[] { From 578c955658fb4846acb022b64d965896c9d0b897 Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 2 Jun 2020 03:48:23 +0200 Subject: [PATCH 256/871] Add fallback intro screen --- .../Visual/Menus/TestSceneIntroFallback.cs | 15 +++++ osu.Game/Configuration/IntroSequence.cs | 1 + osu.Game/Screens/Loader.cs | 3 + osu.Game/Screens/Menu/IntroFallback.cs | 56 +++++++++++++++++++ 4 files changed, 75 insertions(+) create mode 100644 osu.Game.Tests/Visual/Menus/TestSceneIntroFallback.cs create mode 100644 osu.Game/Screens/Menu/IntroFallback.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroFallback.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroFallback.cs new file mode 100644 index 0000000000..cb32d6bf32 --- /dev/null +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroFallback.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. + +using NUnit.Framework; +using osu.Framework.Screens; +using osu.Game.Screens.Menu; + +namespace osu.Game.Tests.Visual.Menus +{ + [TestFixture] + public class TestSceneIntroFallback : IntroTestScene + { + protected override IScreen CreateScreen() => new IntroFallback(); + } +} diff --git a/osu.Game/Configuration/IntroSequence.cs b/osu.Game/Configuration/IntroSequence.cs index 1ee7da8bac..24f8c0f048 100644 --- a/osu.Game/Configuration/IntroSequence.cs +++ b/osu.Game/Configuration/IntroSequence.cs @@ -6,6 +6,7 @@ namespace osu.Game.Configuration public enum IntroSequence { Circles, + Fallback, Triangles, Random } diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index a5b55a24e5..690868bd36 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -51,6 +51,9 @@ namespace osu.Game.Screens case IntroSequence.Circles: return new IntroCircles(); + case IntroSequence.Fallback: + return new IntroFallback(); + default: return new IntroTriangles(); } diff --git a/osu.Game/Screens/Menu/IntroFallback.cs b/osu.Game/Screens/Menu/IntroFallback.cs new file mode 100644 index 0000000000..bc01e9c502 --- /dev/null +++ b/osu.Game/Screens/Menu/IntroFallback.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Screens; +using osu.Framework.Graphics; + +namespace osu.Game.Screens.Menu +{ + public class IntroFallback : IntroScreen + { + protected override string BeatmapHash => "64E00D7022195959BFA3109D09C2E2276C8F12F486B91FCF6175583E973B48F2"; + protected override string BeatmapFile => "welcome.osz"; + private const double delay_step_two = 3000; + + private SampleChannel welcome; + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + if (MenuVoice.Value) + welcome = audio.Samples.Get(@"welcome"); + } + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + + if (!resuming) + { + welcome?.Play(); + + Scheduler.AddDelayed(delegate + { + StartTrack(); + + PrepareMenuLoad(); + + Scheduler.AddDelayed(LoadMenu, 0); + }, delay_step_two); + + logo.ScaleTo(1); + logo.FadeIn(); + logo.PlayIntro(); + } + } + + public override void OnSuspending(IScreen next) + { + this.FadeOut(300); + base.OnSuspending(next); + } + } +} From 1ccdfd736429a43f913491a6d562086c97e5133d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 14:03:13 +0900 Subject: [PATCH 257/871] Pull playlist beatmap checksum from api --- osu.Game/Online/API/APIPlaylistBeatmap.cs | 23 +++++++++++++++++++ .../API/Requests/Responses/APIBeatmap.cs | 2 +- osu.Game/Online/Multiplayer/PlaylistItem.cs | 3 +-- 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Online/API/APIPlaylistBeatmap.cs diff --git a/osu.Game/Online/API/APIPlaylistBeatmap.cs b/osu.Game/Online/API/APIPlaylistBeatmap.cs new file mode 100644 index 0000000000..4f7786e880 --- /dev/null +++ b/osu.Game/Online/API/APIPlaylistBeatmap.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. + +using Newtonsoft.Json; +using osu.Game.Beatmaps; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Rulesets; + +namespace osu.Game.Online.API +{ + public class APIPlaylistBeatmap : APIBeatmap + { + [JsonProperty("checksum")] + public string Checksum { get; set; } + + public override BeatmapInfo ToBeatmap(RulesetStore rulesets) + { + var b = base.ToBeatmap(rulesets); + b.MD5Hash = Checksum; + return b; + } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs b/osu.Game/Online/API/Requests/Responses/APIBeatmap.cs index e023a2502f..ae65ac09b2 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 BeatmapInfo ToBeatmap(RulesetStore rulesets) + public virtual BeatmapInfo ToBeatmap(RulesetStore rulesets) { var set = BeatmapSet?.ToBeatmapSet(rulesets); diff --git a/osu.Game/Online/Multiplayer/PlaylistItem.cs b/osu.Game/Online/Multiplayer/PlaylistItem.cs index 9d6e8eb8e3..416091a1aa 100644 --- a/osu.Game/Online/Multiplayer/PlaylistItem.cs +++ b/osu.Game/Online/Multiplayer/PlaylistItem.cs @@ -7,7 +7,6 @@ using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Online.API; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -37,7 +36,7 @@ namespace osu.Game.Online.Multiplayer public readonly BindableList RequiredMods = new BindableList(); [JsonProperty("beatmap")] - private APIBeatmap apiBeatmap { get; set; } + private APIPlaylistBeatmap apiBeatmap { get; set; } private APIMod[] allowedModsBacking; From 68fbe9f4c144ad8be6be89286053fb08ccd5f50d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 14:03:50 +0900 Subject: [PATCH 258/871] Add checksum validation to the ready/start button --- .../Multi/Match/Components/ReadyButton.cs | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs index e1f86fcc97..a64f24dd7e 100644 --- a/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs +++ b/osu.Game/Screens/Multi/Match/Components/ReadyButton.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Linq.Expressions; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Game.Beatmaps; @@ -52,24 +53,14 @@ namespace osu.Game.Screens.Multi.Match.Components private void updateSelectedItem(PlaylistItem item) { - hasBeatmap = false; - - int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID; - if (beatmapId == null) - return; - - hasBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId) != null; + hasBeatmap = findBeatmap(expr => beatmaps.QueryBeatmap(expr)); } private void beatmapUpdated(ValueChangedEvent> weakSet) { if (weakSet.NewValue.TryGetTarget(out var set)) { - int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID; - if (beatmapId == null) - return; - - if (set.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId)) + if (findBeatmap(expr => set.Beatmaps.AsQueryable().FirstOrDefault(expr))) Schedule(() => hasBeatmap = true); } } @@ -78,15 +69,22 @@ namespace osu.Game.Screens.Multi.Match.Components { if (weakSet.NewValue.TryGetTarget(out var set)) { - int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID; - if (beatmapId == null) - return; - - if (set.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId)) + if (findBeatmap(expr => set.Beatmaps.AsQueryable().FirstOrDefault(expr))) Schedule(() => hasBeatmap = false); } } + private bool findBeatmap(Func>, BeatmapInfo> expression) + { + int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID; + string checksum = SelectedItem.Value?.Beatmap.Value?.MD5Hash; + + if (beatmapId == null || checksum == null) + return false; + + return expression(b => b.OnlineBeatmapID == beatmapId && b.MD5Hash == checksum) != null; + } + protected override void Update() { base.Update(); From b41bb5a6824d8f4467b4178708cce88ace77011c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 14:04:00 +0900 Subject: [PATCH 259/871] Update databased MD5 hash on save --- osu.Game/Beatmaps/BeatmapManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f626b45e42..e5907809f3 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -201,7 +201,9 @@ namespace osu.Game.Beatmaps using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) new LegacyBeatmapEncoder(beatmapContent).Encode(sw); - stream.Seek(0, SeekOrigin.Begin); + var attachedInfo = setInfo.Beatmaps.Single(b => b.ID == info.ID); + var md5Hash = stream.ComputeMD5Hash(); + attachedInfo.MD5Hash = md5Hash; UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream); } From 17e91695e0cf982b76b34db1184aca8be4a7020b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 14:04:51 +0900 Subject: [PATCH 260/871] Add checksum validation to the panel download buttons --- .../Screens/Multi/DrawableRoomPlaylistItem.cs | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index c024304856..414c1f5748 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -188,7 +188,7 @@ namespace osu.Game.Screens.Multi X = -18, Children = new Drawable[] { - new PlaylistDownloadButton(item.Beatmap.Value.BeatmapSet) + new PlaylistDownloadButton(item) { Size = new Vector2(50, 30) }, @@ -212,9 +212,15 @@ namespace osu.Game.Screens.Multi private class PlaylistDownloadButton : BeatmapPanelDownloadButton { - public PlaylistDownloadButton(BeatmapSetInfo beatmapSet) - : base(beatmapSet) + private readonly PlaylistItem playlistItem; + + [Resolved] + private BeatmapManager beatmapManager { get; set; } + + public PlaylistDownloadButton(PlaylistItem playlistItem) + : base(playlistItem.Beatmap.Value.BeatmapSet) { + this.playlistItem = playlistItem; Alpha = 0; } @@ -223,11 +229,26 @@ namespace osu.Game.Screens.Multi base.LoadComplete(); State.BindValueChanged(stateChanged, true); + FinishTransforms(true); } private void stateChanged(ValueChangedEvent state) { - this.FadeTo(state.NewValue == DownloadState.LocallyAvailable ? 0 : 1, 500); + switch (state.NewValue) + { + case DownloadState.LocallyAvailable: + // Perform a local query of the beatmap by beatmap checksum, and reset the state if not matching. + if (beatmapManager.QueryBeatmap(b => b.MD5Hash == playlistItem.Beatmap.Value.MD5Hash) == null) + State.Value = DownloadState.NotDownloaded; + else + this.FadeTo(0, 500); + + break; + + default: + this.FadeTo(1, 500); + break; + } } } From 46a209540e65ee40bbbd4020128e4e01fbb91ce8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jun 2020 14:28:48 +0900 Subject: [PATCH 261/871] 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 7ea1f3140b..8dcd2f1c6f 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 305e4e0a92..a70a263cdc 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 016f2ba35d..9652f967f2 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 90f9905ed0f142121d8e295f8df4873eee3682e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jun 2020 14:29:17 +0900 Subject: [PATCH 262/871] Update resourcse --- 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 8dcd2f1c6f..07be3ab0d2 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 a70a263cdc..4d6358575b 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -25,7 +25,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 9652f967f2..6b55fa51ff 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 3c85561cdce09b7531aac9ea14bd8a681c159d8c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 14:31:43 +0900 Subject: [PATCH 263/871] Add tests --- .../TestSceneDrawableRoomPlaylist.cs | 73 +++++++++++++++++++ osu.Game/Tests/Beatmaps/TestBeatmap.cs | 21 +++++- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs index 5ef4dd6773..55b026eff6 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneDrawableRoomPlaylist.cs @@ -4,12 +4,18 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Platform; using osu.Framework.Testing; +using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; +using osu.Game.Overlays; +using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Multi; @@ -23,6 +29,18 @@ namespace osu.Game.Tests.Visual.Multiplayer { private TestPlaylist playlist; + private BeatmapManager manager; + private RulesetStore rulesets; + + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); + + manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait(); + } + [Test] public void TestNonEditableNonSelectable() { @@ -182,6 +200,28 @@ namespace osu.Game.Tests.Visual.Multiplayer AddStep("click delete button", () => InputManager.Click(MouseButton.Left)); } + [Test] + public void TestDownloadButtonHiddenInitiallyWhenBeatmapExists() + { + createPlaylist(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo); + + AddAssert("download button hidden", () => !playlist.ChildrenOfType().Single().IsPresent); + } + + [Test] + public void TestDownloadButtonVisibleInitiallyWhenBeatmapDoesNotExist() + { + var byOnlineId = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; + byOnlineId.BeatmapSet.OnlineBeatmapSetID = 1337; // Some random ID that does not exist locally. + + var byChecksum = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo; + byChecksum.MD5Hash = "1337"; // Some random checksum that does not exist locally. + + createPlaylist(byOnlineId, byChecksum); + + AddAssert("download buttons shown", () => playlist.ChildrenOfType().All(d => d.IsPresent)); + } + private void moveToItem(int index, Vector2? offset = null) => AddStep($"move mouse to item {index}", () => InputManager.MoveMouseTo(playlist.ChildrenOfType>().ElementAt(index), offset)); @@ -235,6 +275,39 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); } + private void createPlaylist(params BeatmapInfo[] beatmaps) + { + AddStep("create playlist", () => + { + Child = playlist = new TestPlaylist(false, false) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500, 300) + }; + + int index = 0; + + foreach (var b in beatmaps) + { + playlist.Items.Add(new PlaylistItem + { + ID = index++, + Beatmap = { Value = b }, + Ruleset = { Value = new OsuRuleset().RulesetInfo }, + RequiredMods = + { + new OsuModHardRock(), + new OsuModDoubleTime(), + new OsuModAutoplay() + } + }); + } + }); + + AddUntilStep("wait for items to load", () => playlist.ItemMap.Values.All(i => i.IsLoaded)); + } + private class TestPlaylist : DrawableRoomPlaylist { public new IReadOnlyDictionary> ItemMap => base.ItemMap; diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index a7c84bf692..9fc20fd0f2 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.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 System; using System.Collections.Generic; using System.IO; using System.Text; +using osu.Framework.Extensions; using osu.Game.Beatmaps; using osu.Game.IO; using osu.Game.Rulesets; @@ -43,10 +45,25 @@ namespace osu.Game.Tests.Beatmaps private static Beatmap createTestBeatmap() { using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(test_beatmap_data))) - using (var reader = new LineBufferedReader(stream)) - return Decoder.GetDecoder(reader).Decode(reader); + { + using (var reader = new LineBufferedReader(stream)) + { + var b = Decoder.GetDecoder(reader).Decode(reader); + + b.BeatmapInfo.MD5Hash = test_beatmap_hash.Value.md5; + b.BeatmapInfo.Hash = test_beatmap_hash.Value.sha2; + + return b; + } + } } + private static readonly Lazy<(string md5, string sha2)> test_beatmap_hash = new Lazy<(string md5, string sha2)>(() => + { + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(test_beatmap_data))) + return (stream.ComputeMD5Hash(), stream.ComputeSHA2Hash()); + }); + private const string test_beatmap_data = @"osu file format v14 [General] From 800c46f7524277b9aa8e9af5732bcfb06487a863 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jun 2020 14:39:15 +0900 Subject: [PATCH 264/871] Fix test function override --- .../NonVisual/Skinning/LegacySkinTextureFallbackTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs index 867af9c1b8..69e66942ab 100644 --- a/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs +++ b/osu.Game.Tests/NonVisual/Skinning/LegacySkinTextureFallbackTest.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Textures; using osu.Game.Skinning; @@ -103,7 +104,7 @@ namespace osu.Game.Tests.NonVisual.Skinning Textures = fileNames.ToDictionary(fileName => fileName, fileName => new Texture(1, 1)); } - public override Texture Get(string name) => Textures.GetValueOrDefault(name); + public override Texture Get(string name, WrapMode wrapModeS, WrapMode wrapModeT) => Textures.GetValueOrDefault(name); } } } From 6c8c95677f65d707cc9ac657138f1274a393b1b7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jun 2020 14:54:55 +0900 Subject: [PATCH 265/871] Fix incorrect usage of Directory.GetFiles --- osu.Game/Rulesets/RulesetStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/RulesetStore.cs b/osu.Game/Rulesets/RulesetStore.cs index 10b6edca8c..58a2ba056e 100644 --- a/osu.Game/Rulesets/RulesetStore.cs +++ b/osu.Game/Rulesets/RulesetStore.cs @@ -154,7 +154,7 @@ namespace osu.Game.Rulesets { try { - var files = Directory.GetFiles(Path.Combine(RuntimeInfo.StartupDirectory, $"{ruleset_library_prefix}.*.dll")); + var files = Directory.GetFiles(RuntimeInfo.StartupDirectory, $"{ruleset_library_prefix}.*.dll"); foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests"))) loadRulesetFromFile(file); From 70c84811ed00a37b438cea3941000bba12ed418d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jun 2020 15:49:51 +0900 Subject: [PATCH 266/871] Revert incorrect change --- osu.Game.Tests/Resources/TestResources.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Resources/TestResources.cs b/osu.Game.Tests/Resources/TestResources.cs index 1c264f66e0..e882229570 100644 --- a/osu.Game.Tests/Resources/TestResources.cs +++ b/osu.Game.Tests/Resources/TestResources.cs @@ -3,7 +3,6 @@ using System.IO; using NUnit.Framework; -using osu.Framework; using osu.Framework.IO.Stores; namespace osu.Game.Tests.Resources @@ -18,7 +17,7 @@ namespace osu.Game.Tests.Resources public static string GetTestBeatmapForImport(bool virtualTrack = false) { - var tempPath = Path.Combine(RuntimeInfo.StartupDirectory, Path.GetTempFileName() + ".osz"); + var tempPath = Path.GetTempFileName() + ".osz"; using (var stream = GetTestBeatmapStream(virtualTrack)) using (var newFile = File.Create(tempPath)) From fac96f6dddf9dba724fa0b298444694310c508af Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 17:02:01 +0900 Subject: [PATCH 267/871] Fix match beatmap not updating after re-download --- .../Multiplayer/TestSceneMatchSubScreen.cs | 57 +++++++++++++++++-- .../Match/Components/MatchSettingsOverlay.cs | 2 +- .../Screens/Multi/Match/MatchSubScreen.cs | 13 +---- 3 files changed, 55 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs index d678d5a814..6154e646f8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs @@ -5,7 +5,9 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; +using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -29,14 +31,20 @@ namespace osu.Game.Tests.Visual.Multiplayer [Cached(typeof(IRoomManager))] private readonly TestRoomManager roomManager = new TestRoomManager(); - [Resolved] - private BeatmapManager beatmaps { get; set; } - - [Resolved] - private RulesetStore rulesets { get; set; } + private BeatmapManager manager; + private RulesetStore rulesets; private TestMatchSubScreen match; + [BackgroundDependencyLoader] + private void load(GameHost host, AudioManager audio) + { + Dependencies.Cache(rulesets = new RulesetStore(ContextFactory)); + Dependencies.Cache(manager = new BeatmapManager(LocalStorage, ContextFactory, rulesets, null, audio, host, Beatmap.Default)); + + manager.Import(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo.BeatmapSet).Wait(); + } + [SetUp] public void Setup() => Schedule(() => { @@ -75,10 +83,49 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("first playlist item selected", () => match.SelectedItem.Value == Room.Playlist[0]); } + [Test] + public void TestBeatmapUpdatedOnReImport() + { + BeatmapSetInfo importedSet = null; + + AddStep("import altered beatmap", () => + { + var beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo); + beatmap.BeatmapInfo.BaseDifficulty.CircleSize = 1; + + importedSet = manager.Import(beatmap.BeatmapInfo.BeatmapSet).Result; + }); + + AddStep("load room", () => + { + Room.Name.Value = "my awesome room"; + Room.Host.Value = new User { Id = 2, Username = "peppy" }; + Room.Playlist.Add(new PlaylistItem + { + Beatmap = { Value = importedSet.Beatmaps[0] }, + Ruleset = { Value = new OsuRuleset().RulesetInfo } + }); + }); + + AddStep("create room", () => + { + InputManager.MoveMouseTo(match.ChildrenOfType().Single()); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("match has altered beatmap", () => match.Beatmap.Value.Beatmap.BeatmapInfo.BaseDifficulty.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); + } + private class TestMatchSubScreen : MatchSubScreen { public new Bindable SelectedItem => base.SelectedItem; + public new Bindable Beatmap => base.Beatmap; + public TestMatchSubScreen(Room room) : base(room) { diff --git a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs index 54c4f8f7c7..49a0fc434b 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchSettingsOverlay.cs @@ -433,7 +433,7 @@ namespace osu.Game.Screens.Multi.Match.Components } } - private class CreateRoomButton : TriangleButton + public class CreateRoomButton : TriangleButton { public CreateRoomButton() { diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index e1d72d9600..bbfbaf81af 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -207,6 +207,8 @@ namespace osu.Game.Screens.Multi.Match Ruleset.Value = item.Ruleset.Value; } + private void beatmapUpdated(ValueChangedEvent> weakSet) => Schedule(updateWorkingBeatmap); + private void updateWorkingBeatmap() { var beatmap = SelectedItem.Value?.Beatmap.Value; @@ -217,17 +219,6 @@ namespace osu.Game.Screens.Multi.Match Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap); } - private void beatmapUpdated(ValueChangedEvent> weakSet) - { - Schedule(() => - { - if (Beatmap.Value != beatmapManager.DefaultBeatmap) - return; - - updateWorkingBeatmap(); - }); - } - private void onStart() { switch (type.Value) From dfb9687fb5bfc47670ea6b017410e46c76c5905c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 17:22:09 +0900 Subject: [PATCH 268/871] Extract update into PreUpdate(), add test --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 3 +++ osu.Game/Beatmaps/BeatmapManager.cs | 17 +++++++++++++---- osu.Game/Database/ArchiveModelManager.cs | 10 ++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 5eb11a3264..88bb39a521 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -602,6 +602,8 @@ namespace osu.Game.Tests.Beatmaps.IO Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap; BeatmapSetFileInfo fileToUpdate = setToUpdate.Files.First(f => beatmapToUpdate.BeatmapInfo.Path.Contains(f.Filename)); + string oldMd5Hash = beatmapToUpdate.BeatmapInfo.MD5Hash; + using (var stream = new MemoryStream()) { using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) @@ -624,6 +626,7 @@ namespace osu.Game.Tests.Beatmaps.IO Beatmap updatedBeatmap = (Beatmap)manager.GetWorkingBeatmap(manager.QueryBeatmap(b => b.ID == beatmapToUpdate.BeatmapInfo.ID)).Beatmap; Assert.That(updatedBeatmap.HitObjects.Count, Is.EqualTo(1)); Assert.That(updatedBeatmap.HitObjects[0].StartTime, Is.EqualTo(5000)); + Assert.That(updatedBeatmap.BeatmapInfo.MD5Hash, Is.Not.EqualTo(oldMd5Hash)); } finally { diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index e5907809f3..668ac6ee10 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -201,10 +201,6 @@ namespace osu.Game.Beatmaps using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) new LegacyBeatmapEncoder(beatmapContent).Encode(sw); - var attachedInfo = setInfo.Beatmaps.Single(b => b.ID == info.ID); - var md5Hash = stream.ComputeMD5Hash(); - attachedInfo.MD5Hash = md5Hash; - UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream); } @@ -213,6 +209,19 @@ namespace osu.Game.Beatmaps workingCache.Remove(working); } + protected override void PreUpdate(BeatmapSetInfo item) + { + base.PreUpdate(item); + + foreach (var info in item.Beatmaps) + { + var file = item.Files.FirstOrDefault(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + + using (var stream = Files.Store.GetStream(file)) + info.MD5Hash = stream.ComputeMD5Hash(); + } + } + private readonly WeakList workingCache = new WeakList(); /// diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index ae55a7b14a..f7e81ae4bc 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -430,10 +430,20 @@ namespace osu.Game.Database { item.Hash = computeHash(item); + PreUpdate(item); + ModelStore.Update(item); } } + /// + /// Perform any final actions before the update to database executes. + /// + /// The that is being updated. + protected virtual void PreUpdate(TModel item) + { + } + /// /// Delete an item from the manager. /// Is a no-op for already deleted items. From 665530f1c3a9b43ac2925220ee41efc767919974 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jun 2020 17:22:59 +0900 Subject: [PATCH 269/871] Remove excess newline --- .../Edit/Blueprints/TaikoSpanPlacementBlueprint.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs index 7f96b5a46e..a88eff13a1 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs @@ -25,7 +25,6 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints public TaikoSpanPlacementBlueprint(HitObject hitObject) : base(hitObject) - { spanPlacementObject = hitObject as IHasDuration; From 2aadb9deba79800cbbbc22ce0a960dd6c709cfe6 Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 2 Jun 2020 11:04:56 +0200 Subject: [PATCH 270/871] Implement welcome and seeya samples --- osu.Game/Screens/Menu/IntroCircles.cs | 2 +- osu.Game/Screens/Menu/IntroFallback.cs | 16 ++++++++++++---- osu.Game/Screens/Menu/IntroScreen.cs | 4 ++-- osu.Game/Screens/Menu/IntroTriangles.cs | 2 +- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroCircles.cs b/osu.Game/Screens/Menu/IntroCircles.cs index aa9cee969c..08a170f606 100644 --- a/osu.Game/Screens/Menu/IntroCircles.cs +++ b/osu.Game/Screens/Menu/IntroCircles.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Menu private void load(AudioManager audio) { if (MenuVoice.Value) - welcome = audio.Samples.Get(@"welcome"); + welcome = audio.Samples.Get(@"Intro/welcome-lazer"); } protected override void LogoArriving(OsuLogo logo, bool resuming) diff --git a/osu.Game/Screens/Menu/IntroFallback.cs b/osu.Game/Screens/Menu/IntroFallback.cs index bc01e9c502..ea3c4fb040 100644 --- a/osu.Game/Screens/Menu/IntroFallback.cs +++ b/osu.Game/Screens/Menu/IntroFallback.cs @@ -13,15 +13,22 @@ namespace osu.Game.Screens.Menu { protected override string BeatmapHash => "64E00D7022195959BFA3109D09C2E2276C8F12F486B91FCF6175583E973B48F2"; protected override string BeatmapFile => "welcome.osz"; - private const double delay_step_two = 3000; + private const double delay_step_two = 2142; private SampleChannel welcome; + private SampleChannel pianoReverb; + [BackgroundDependencyLoader] private void load(AudioManager audio) { + seeya = audio.Samples.Get(@"Intro/seeya-fallback"); + if (MenuVoice.Value) - welcome = audio.Samples.Get(@"welcome"); + { + welcome = audio.Samples.Get(@"Intro/welcome-fallback"); + pianoReverb = audio.Samples.Get(@"Intro/welcome_piano"); + } } protected override void LogoArriving(OsuLogo logo, bool resuming) @@ -31,14 +38,15 @@ namespace osu.Game.Screens.Menu if (!resuming) { welcome?.Play(); - + pianoReverb?.Play(); Scheduler.AddDelayed(delegate { StartTrack(); PrepareMenuLoad(); - Scheduler.AddDelayed(LoadMenu, 0); + Scheduler.Add(LoadMenu); + }, delay_step_two); logo.ScaleTo(1); diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 0d5f3d1142..20cd9671a0 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Menu private const int exit_delay = 3000; - private SampleChannel seeya; + protected SampleChannel seeya { get; set; } private LeasedBindable beatmap; @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Menu MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); - seeya = audio.Samples.Get(@"seeya"); + seeya = audio.Samples.Get(@"Intro/seeya-lazer"); BeatmapSetInfo setInfo = null; diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 188a49c147..b44fea99e8 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Menu private void load() { if (MenuVoice.Value && !MenuMusic.Value) - welcome = audio.Samples.Get(@"welcome"); + welcome = audio.Samples.Get(@"Intro/welcome-lazer"); } protected override void LogoArriving(OsuLogo logo, bool resuming) From 3ae97c963454bd407a0132242fac4a309c61b7e0 Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 2 Jun 2020 11:25:57 +0200 Subject: [PATCH 271/871] Change "Fallback" to "Welcome" visually --- osu.Game/Configuration/IntroSequence.cs | 2 +- osu.Game/Screens/Loader.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/IntroSequence.cs b/osu.Game/Configuration/IntroSequence.cs index 24f8c0f048..5672c44bbe 100644 --- a/osu.Game/Configuration/IntroSequence.cs +++ b/osu.Game/Configuration/IntroSequence.cs @@ -6,7 +6,7 @@ namespace osu.Game.Configuration public enum IntroSequence { Circles, - Fallback, + Welcome, Triangles, Random } diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 690868bd36..9330226bda 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens case IntroSequence.Circles: return new IntroCircles(); - case IntroSequence.Fallback: + case IntroSequence.Welcome: return new IntroFallback(); default: From 828180ad9b3e6006e70c811e107d340ae18b603e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jun 2020 19:29:22 +0900 Subject: [PATCH 272/871] Add default --- osu.Game.Tournament/Screens/SetupScreen.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index e1594de69e..2bd0bcf2f2 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -211,7 +211,8 @@ namespace osu.Game.Tournament.Screens private class ResolutionSelector : ActionableInfo { private const int minimum_window_height = 480; - private const int maximum_window_height = 2160; // 4k + private const int maximum_window_height = 2160; + public new Action Action; private OsuNumberBox numberBox; @@ -221,6 +222,7 @@ namespace osu.Game.Tournament.Screens var drawable = base.CreateComponent(); FlowContainer.Insert(-1, numberBox = new OsuNumberBox { + Text = "1080", Width = 100 }); From 78fddc895738ec770447f025bdc8a487bdd5c988 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jun 2020 19:29:59 +0900 Subject: [PATCH 273/871] Make button match height --- osu.Game.Tournament/Screens/SetupScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 2bd0bcf2f2..cf8eb8bd6c 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -199,7 +199,7 @@ namespace osu.Game.Tournament.Screens { button = new TriangleButton { - Size = new Vector2(100, 30), + Size = new Vector2(100, 40), Action = () => Action?.Invoke() } } From 19d73af90d2ca01faf33c320ee9015d5a218b2d5 Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 2 Jun 2020 12:51:42 +0200 Subject: [PATCH 274/871] Implement basic intro sequence --- osu.Game/Screens/Menu/IntroFallback.cs | 65 +++++++++++++++++++++++--- osu.Game/Screens/Menu/IntroScreen.cs | 6 +-- 2 files changed, 62 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroFallback.cs b/osu.Game/Screens/Menu/IntroFallback.cs index ea3c4fb040..7c23f00d3f 100644 --- a/osu.Game/Screens/Menu/IntroFallback.cs +++ b/osu.Game/Screens/Menu/IntroFallback.cs @@ -1,11 +1,16 @@ // 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 osuTK; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Screens; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; namespace osu.Game.Screens.Menu { @@ -22,8 +27,8 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(AudioManager audio) { - seeya = audio.Samples.Get(@"Intro/seeya-fallback"); - + Seeya = audio.Samples.Get(@"Intro/seeya-fallback"); + if (MenuVoice.Value) { welcome = audio.Samples.Get(@"Intro/welcome-fallback"); @@ -45,13 +50,20 @@ namespace osu.Game.Screens.Menu PrepareMenuLoad(); + logo.ScaleTo(1); + logo.FadeIn(); + Scheduler.Add(LoadMenu); - }, delay_step_two); - logo.ScaleTo(1); - logo.FadeIn(); - logo.PlayIntro(); + LoadComponentAsync(new FallbackIntroSequence + { + RelativeSizeAxes = Axes.Both + }, t => + { + AddInternal(t); + t.Start(delay_step_two); + }); } } @@ -60,5 +72,46 @@ namespace osu.Game.Screens.Menu this.FadeOut(300); base.OnSuspending(next); } + + private class FallbackIntroSequence : Container + { + private OsuSpriteText welcomeText; + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + welcomeText = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "welcome", + Padding = new MarginPadding { Bottom = 10 }, + Font = OsuFont.GetFont(weight: FontWeight.Light, size: 42), + Alpha = 0, + Spacing = new Vector2(5), + }, + }; + } + + public void Start(double length) + { + if (Children.Any()) + { + // restart if we were already run previously. + FinishTransforms(true); + load(); + } + + double remainingTime() => length - TransformDelay; + + using (BeginDelayedSequence(250, true)) + { + welcomeText.FadeIn(700); + welcomeText.ScaleTo(welcomeText.Scale + new Vector2(0.5f), remainingTime(), Easing.Out); + } + } + } } } diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 20cd9671a0..8588e2a41b 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Menu private const int exit_delay = 3000; - protected SampleChannel seeya { get; set; } + protected SampleChannel Seeya { get; set; } private LeasedBindable beatmap; @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Menu MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); - seeya = audio.Samples.Get(@"Intro/seeya-lazer"); + Seeya = audio.Samples.Get(@"Intro/seeya-lazer"); BeatmapSetInfo setInfo = null; @@ -103,7 +103,7 @@ namespace osu.Game.Screens.Menu double fadeOutTime = exit_delay; // we also handle the exit transition. if (MenuVoice.Value) - seeya.Play(); + Seeya.Play(); else fadeOutTime = 500; From 888b90b426f077a84e9b9e7e12fcba05858dbfde Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 2 Jun 2020 13:14:50 +0200 Subject: [PATCH 275/871] Rename IntroFallback classes to IntroLegacy This commit also renames files accordingly with https://github.com/ppy/osu-resources/pull/103 --- ...{TestSceneIntroFallback.cs => TestSceneIntroLegacy.cs} | 4 ++-- osu.Game/Screens/Loader.cs | 2 +- osu.Game/Screens/Menu/IntroCircles.cs | 2 +- .../Screens/Menu/{IntroFallback.cs => IntroLegacy.cs} | 8 ++++---- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- osu.Game/Screens/Menu/IntroTriangles.cs | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) rename osu.Game.Tests/Visual/Menus/{TestSceneIntroFallback.cs => TestSceneIntroLegacy.cs} (70%) rename osu.Game/Screens/Menu/{IntroFallback.cs => IntroLegacy.cs} (92%) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroFallback.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroLegacy.cs similarity index 70% rename from osu.Game.Tests/Visual/Menus/TestSceneIntroFallback.cs rename to osu.Game.Tests/Visual/Menus/TestSceneIntroLegacy.cs index cb32d6bf32..7cb99467ad 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroFallback.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroLegacy.cs @@ -8,8 +8,8 @@ using osu.Game.Screens.Menu; namespace osu.Game.Tests.Visual.Menus { [TestFixture] - public class TestSceneIntroFallback : IntroTestScene + public class TestSceneIntroLegacy : IntroTestScene { - protected override IScreen CreateScreen() => new IntroFallback(); + protected override IScreen CreateScreen() => new IntroLegacy(); } } diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index 9330226bda..aa959e7d35 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -52,7 +52,7 @@ namespace osu.Game.Screens return new IntroCircles(); case IntroSequence.Welcome: - return new IntroFallback(); + return new IntroLegacy(); default: return new IntroTriangles(); diff --git a/osu.Game/Screens/Menu/IntroCircles.cs b/osu.Game/Screens/Menu/IntroCircles.cs index 08a170f606..113d496855 100644 --- a/osu.Game/Screens/Menu/IntroCircles.cs +++ b/osu.Game/Screens/Menu/IntroCircles.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Menu private void load(AudioManager audio) { if (MenuVoice.Value) - welcome = audio.Samples.Get(@"Intro/welcome-lazer"); + welcome = audio.Samples.Get(@"Intro/lazer/welcome"); } protected override void LogoArriving(OsuLogo logo, bool resuming) diff --git a/osu.Game/Screens/Menu/IntroFallback.cs b/osu.Game/Screens/Menu/IntroLegacy.cs similarity index 92% rename from osu.Game/Screens/Menu/IntroFallback.cs rename to osu.Game/Screens/Menu/IntroLegacy.cs index 7c23f00d3f..c1a360bca1 100644 --- a/osu.Game/Screens/Menu/IntroFallback.cs +++ b/osu.Game/Screens/Menu/IntroLegacy.cs @@ -14,7 +14,7 @@ using osu.Game.Graphics.Sprites; namespace osu.Game.Screens.Menu { - public class IntroFallback : IntroScreen + public class IntroLegacy : IntroScreen { protected override string BeatmapHash => "64E00D7022195959BFA3109D09C2E2276C8F12F486B91FCF6175583E973B48F2"; protected override string BeatmapFile => "welcome.osz"; @@ -27,12 +27,12 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(AudioManager audio) { - Seeya = audio.Samples.Get(@"Intro/seeya-fallback"); + Seeya = audio.Samples.Get(@"Intro/legacy/seeya"); if (MenuVoice.Value) { - welcome = audio.Samples.Get(@"Intro/welcome-fallback"); - pianoReverb = audio.Samples.Get(@"Intro/welcome_piano"); + welcome = audio.Samples.Get(@"Intro/legacy/welcome"); + pianoReverb = audio.Samples.Get(@"Intro/legacy/welcome_piano"); } } diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 8588e2a41b..d8769e3125 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -67,7 +67,7 @@ namespace osu.Game.Screens.Menu MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); - Seeya = audio.Samples.Get(@"Intro/seeya-lazer"); + Seeya = audio.Samples.Get(@"Intro/lazer/seeya"); BeatmapSetInfo setInfo = null; diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index b44fea99e8..ef26038a6f 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Menu private void load() { if (MenuVoice.Value && !MenuMusic.Value) - welcome = audio.Samples.Get(@"Intro/welcome-lazer"); + welcome = audio.Samples.Get(@"Intro/lazer/welcome"); } protected override void LogoArriving(OsuLogo logo, bool resuming) From 3d78ec90ac879c6d064943629df1c2b2959a8dc1 Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 2 Jun 2020 13:26:37 +0200 Subject: [PATCH 276/871] Rename legacy to welcome to match osu-resources --- osu.Game/Screens/Menu/IntroLegacy.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroLegacy.cs b/osu.Game/Screens/Menu/IntroLegacy.cs index c1a360bca1..3980a0cc8b 100644 --- a/osu.Game/Screens/Menu/IntroLegacy.cs +++ b/osu.Game/Screens/Menu/IntroLegacy.cs @@ -27,12 +27,12 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(AudioManager audio) { - Seeya = audio.Samples.Get(@"Intro/legacy/seeya"); + Seeya = audio.Samples.Get(@"Intro/welcome/seeya"); if (MenuVoice.Value) { - welcome = audio.Samples.Get(@"Intro/legacy/welcome"); - pianoReverb = audio.Samples.Get(@"Intro/legacy/welcome_piano"); + welcome = audio.Samples.Get(@"Intro/welcome/welcome"); + pianoReverb = audio.Samples.Get(@"Intro/welcome/welcome_piano"); } } From f63c66396f7a2f1f462377ae4a3b6c4c9e4dce40 Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 2 Jun 2020 13:32:52 +0200 Subject: [PATCH 277/871] Apply review suggestions. --- .../Visual/Gameplay/TestSceneReplay.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 2 ++ osu.Game/Screens/Select/PlaySongSelect.cs | 25 +++++++++++++------ 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs index 1908988739..3a71d4ca54 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplay.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Visual.Gameplay { var beatmap = Beatmap.Value.GetPlayableBeatmap(ruleset.RulesetInfo, Array.Empty()); - return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod().CreateReplayScore(beatmap)); + return new ScoreAccessibleReplayPlayer(ruleset.GetAutoplayMod()?.CreateReplayScore(beatmap)); } protected override void AddCheckSteps() diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 8f41e421a3..4f28607733 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -22,6 +22,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; using osu.Game.Users; +using JetBrains.Annotations; namespace osu.Game.Rulesets { @@ -100,6 +101,7 @@ namespace osu.Game.Rulesets return value; } + [CanBeNull] public ModAutoplay GetAutoplayMod() => GetAllMods().OfType().FirstOrDefault(); public virtual ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => null; diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 71ab3715e0..a0201e696f 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -7,6 +7,8 @@ using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Screens; using osu.Game.Graphics; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; using osu.Game.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; @@ -20,6 +22,9 @@ namespace osu.Game.Screens.Select private bool removeAutoModOnResume; private OsuScreen player; + [Resolved] + private NotificationOverlay notifications { get; set; } + public override bool AllowExternalScreenChange => true; protected override UserActivity InitialActivity => new UserActivity.ChoosingBeatmap(); @@ -83,15 +88,21 @@ namespace osu.Game.Screens.Select var auto = Ruleset.Value.CreateInstance().GetAutoplayMod(); var autoType = auto?.GetType(); - if (autoType != null) - { - var mods = Mods.Value; + var mods = Mods.Value; - if (mods.All(m => m.GetType() != autoType)) + if (autoType == null) + { + notifications.Post(new SimpleNotification { - Mods.Value = mods.Append(auto).ToArray(); - removeAutoModOnResume = true; - } + Text = "The current ruleset doesn't have an autoplay mod avalaible!" + }); + return false; + } + + if (mods.All(m => m.GetType() != autoType)) + { + Mods.Value = mods.Append(auto).ToArray(); + removeAutoModOnResume = true; } } From 61f906d9c462279b89881f1083758f493eb34450 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 2 Jun 2020 21:02:09 +0900 Subject: [PATCH 278/871] Fix span piece being incorrect in some drag scenarios --- .../Blueprints/TaikoSpanPlacementBlueprint.cs | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs index a88eff13a1..468d980b23 100644 --- a/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Taiko/Edit/Blueprints/TaikoSpanPlacementBlueprint.cs @@ -48,6 +48,7 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints } private double originalStartTime; + private Vector2 originalPosition; protected override bool OnMouseDown(MouseDownEvent e) { @@ -73,22 +74,25 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints if (PlacementActive) { - if (result.Time is double endTime) + if (result.Time is double dragTime) { - if (endTime < originalStartTime) + if (dragTime < originalStartTime) { - HitObject.StartTime = endTime; - spanPlacementObject.Duration = Math.Abs(endTime - originalStartTime); + HitObject.StartTime = dragTime; + spanPlacementObject.Duration = Math.Abs(dragTime - originalStartTime); headPiece.Position = ToLocalSpace(result.ScreenSpacePosition); - lengthPiece.X = headPiece.X; - lengthPiece.Width = tailPiece.X - headPiece.X; + tailPiece.Position = originalPosition; } else { - spanPlacementObject.Duration = Math.Abs(endTime - originalStartTime); + HitObject.StartTime = originalStartTime; + spanPlacementObject.Duration = Math.Abs(dragTime - originalStartTime); tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition); - lengthPiece.Width = tailPiece.X - headPiece.X; + headPiece.Position = originalPosition; } + + lengthPiece.X = headPiece.X; + lengthPiece.Width = tailPiece.X - headPiece.X; } } else @@ -96,7 +100,10 @@ namespace osu.Game.Rulesets.Taiko.Edit.Blueprints lengthPiece.Position = headPiece.Position = tailPiece.Position = ToLocalSpace(result.ScreenSpacePosition); if (result.Time is double startTime) + { originalStartTime = HitObject.StartTime = startTime; + originalPosition = ToLocalSpace(result.ScreenSpacePosition); + } } } } From 275d95082a88f63e5bc0ba7be8d6efd4950fc30e Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 2 Jun 2020 16:01:01 +0200 Subject: [PATCH 279/871] Fix crash in testing environment. --- osu.Game/Screens/Select/PlaySongSelect.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index a0201e696f..2236aa4d72 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Select private bool removeAutoModOnResume; private OsuScreen player; - [Resolved] + [Resolved(CanBeNull = true)] private NotificationOverlay notifications { get; set; } public override bool AllowExternalScreenChange => true; @@ -92,7 +92,7 @@ namespace osu.Game.Screens.Select if (autoType == null) { - notifications.Post(new SimpleNotification + notifications?.Post(new SimpleNotification { Text = "The current ruleset doesn't have an autoplay mod avalaible!" }); From a7f8c5935dd611843ff92c9d4193a94281b16a98 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 23:36:56 +0900 Subject: [PATCH 280/871] Expose LowestSuccessfulHitResult() --- osu.Game/Rulesets/Scoring/HitWindows.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs index 018b50bd3d..77acbd4137 100644 --- a/osu.Game/Rulesets/Scoring/HitWindows.cs +++ b/osu.Game/Rulesets/Scoring/HitWindows.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Scoring /// Retrieves the with the largest hit window that produces a successful hit. /// /// The lowest allowed successful . - protected HitResult LowestSuccessfulHitResult() + public HitResult LowestSuccessfulHitResult() { for (var result = HitResult.Meh; result <= HitResult.Perfect; ++result) { From e98f51923a7c242cae1ec275726d2a18a82dd48b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 23:38:24 +0900 Subject: [PATCH 281/871] Add timing distribution to OsuScoreProcessor --- .../Scoring/OsuScoreProcessor.cs | 76 +++++++++++++++++++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 12 +++ 2 files changed, 88 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 79a6ea7e92..83339bd061 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -1,17 +1,93 @@ // 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.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu.Scoring { public class OsuScoreProcessor : ScoreProcessor { + /// + /// The number of bins on each side of the timing distribution. + /// + private const int timing_distribution_bins = 25; + + /// + /// The total number of bins in the timing distribution, including bins on both sides and the centre bin at 0. + /// + private const int total_timing_distribution_bins = timing_distribution_bins * 2 + 1; + + /// + /// The centre bin, with a timing distribution very close to/at 0. + /// + private const int timing_distribution_centre_bin_index = timing_distribution_bins; + + private TimingDistribution timingDistribution; + + public override void ApplyBeatmap(IBeatmap beatmap) + { + var hitWindows = CreateHitWindows(); + hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); + + timingDistribution = new TimingDistribution(total_timing_distribution_bins, hitWindows.WindowFor(hitWindows.LowestSuccessfulHitResult()) / timing_distribution_bins); + + base.ApplyBeatmap(beatmap); + } + + protected override void OnResultApplied(JudgementResult result) + { + base.OnResultApplied(result); + + if (result.IsHit) + { + int binOffset = (int)(result.TimeOffset / timingDistribution.BinSize); + timingDistribution.Bins[timing_distribution_centre_bin_index + binOffset]++; + } + } + + protected override void OnResultReverted(JudgementResult result) + { + base.OnResultReverted(result); + + if (result.IsHit) + { + int binOffset = (int)(result.TimeOffset / timingDistribution.BinSize); + timingDistribution.Bins[timing_distribution_centre_bin_index + binOffset]--; + } + } + + public override void PopulateScore(ScoreInfo score) + { + base.PopulateScore(score); + } + + protected override void Reset(bool storeResults) + { + base.Reset(storeResults); + + timingDistribution.Bins.AsSpan().Clear(); + } + protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new OsuJudgementResult(hitObject, judgement); public override HitWindows CreateHitWindows() => new OsuHitWindows(); } + + public class TimingDistribution + { + public readonly int[] Bins; + public readonly double BinSize; + + public TimingDistribution(int binCount, double binSize) + { + Bins = new int[binCount]; + BinSize = binSize; + } + } } diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 1f40f44dce..619547aef4 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -129,6 +129,12 @@ namespace osu.Game.Rulesets.Scoring } updateScore(); + + OnResultApplied(result); + } + + protected virtual void OnResultApplied(JudgementResult result) + { } protected sealed override void RevertResultInternal(JudgementResult result) @@ -154,6 +160,12 @@ namespace osu.Game.Rulesets.Scoring } updateScore(); + + OnResultReverted(result); + } + + protected virtual void OnResultReverted(JudgementResult result) + { } private void updateScore() From c7c94eb3fdd6c25363c9379bc9c881a407952171 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 23:38:50 +0900 Subject: [PATCH 282/871] Initial implementation of timing distribution graph --- .../TestSceneTimingDistributionGraph.cs | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs new file mode 100644 index 0000000000..456ac19383 --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs @@ -0,0 +1,103 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Osu.Scoring; +using osuTK; + +namespace osu.Game.Tests.Visual.Ranking +{ + public class TestSceneTimingDistributionGraph : OsuTestScene + { + public TestSceneTimingDistributionGraph() + { + Add(new TimingDistributionGraph(createNormalDistribution()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(300, 100) + }); + } + + private TimingDistribution createNormalDistribution() + { + var distribution = new TimingDistribution(51, 5); + + // We create an approximately-normal distribution of 51 elements by using the 13th binomial row (14 initial elements) and subdividing the inner values twice. + var row = new List { 1 }; + for (int i = 0; i < 13; i++) + row.Add(row[i] * (13 - i) / (i + 1)); + + // Each subdivision yields 2n-1 total elements, so first subdivision will contain 27 elements, and the second will contain 53 elements. + for (int div = 0; div < 2; div++) + { + var newRow = new List { 1 }; + + for (int i = 0; i < row.Count - 1; i++) + { + newRow.Add((row[i] + row[i + 1]) / 2); + newRow.Add(row[i + 1]); + } + + row = newRow; + } + + // After the subdivisions take place, we're left with 53 values which we use the inner 51 of. + for (int i = 1; i < row.Count - 1; i++) + distribution.Bins[i - 1] = row[i]; + + return distribution; + } + } + + public class TimingDistributionGraph : CompositeDrawable + { + private readonly TimingDistribution distribution; + + public TimingDistributionGraph(TimingDistribution distribution) + { + this.distribution = distribution; + } + + [BackgroundDependencyLoader] + private void load() + { + int maxCount = distribution.Bins.Max(); + + var bars = new Drawable[distribution.Bins.Length]; + for (int i = 0; i < bars.Length; i++) + bars[i] = new Bar { Height = (float)distribution.Bins[i] / maxCount }; + + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] { bars } + }; + } + + private class Bar : CompositeDrawable + { + public Bar() + { + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + + RelativeSizeAxes = Axes.Both; + + Padding = new MarginPadding { Horizontal = 1 }; + + InternalChild = new Circle + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#66FFCC") + }; + } + } + } +} From dc41e74e1912ea9a49a641ab091ed536f6f5e38a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 2 Jun 2020 23:47:18 +0900 Subject: [PATCH 283/871] Fix cursor trail not displaying --- osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 37df5ec540..9bcb3abc63 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -237,6 +237,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y + size.Y / 2), TexturePosition = textureRect.BottomLeft, + TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomLeft.Linear, Time = part.Time }); @@ -245,6 +246,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y + size.Y / 2), TexturePosition = textureRect.BottomRight, + TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.BottomRight.Linear, Time = part.Time }); @@ -253,6 +255,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { Position = new Vector2(part.Position.X + size.X / 2, part.Position.Y - size.Y / 2), TexturePosition = textureRect.TopRight, + TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopRight.Linear, Time = part.Time }); @@ -261,6 +264,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { Position = new Vector2(part.Position.X - size.X / 2, part.Position.Y - size.Y / 2), TexturePosition = textureRect.TopLeft, + TextureRect = new Vector4(0, 0, 1, 1), Colour = DrawColourInfo.Colour.TopLeft.Linear, Time = part.Time }); @@ -290,6 +294,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor [VertexMember(2, VertexAttribPointerType.Float)] public Vector2 TexturePosition; + [VertexMember(4, VertexAttribPointerType.Float)] + public Vector4 TextureRect; + [VertexMember(1, VertexAttribPointerType.Float)] public float Time; From a2fdf9448394f451c2243e0e7c6ebd2ba72db94e Mon Sep 17 00:00:00 2001 From: Power Maker <42269909+power9maker@users.noreply.github.com> Date: Tue, 2 Jun 2020 20:55:21 +0200 Subject: [PATCH 284/871] Add cursor rotation on right mouse button --- osu.Game/Graphics/Cursor/MenuCursor.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 580177d17a..740c809afc 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -83,10 +83,13 @@ namespace osu.Game.Graphics.Cursor activeCursor.AdditiveLayer.FadeInFromZero(800, Easing.OutQuint); } - if (e.Button == MouseButton.Left && cursorRotate.Value) + if ((e.Button == MouseButton.Left || e.Button == MouseButton.Right) && cursorRotate.Value) { - dragRotationState = DragRotationState.DragStarted; - positionMouseDown = e.MousePosition; + if(!(dragRotationState == DragRotationState.Rotating)) + { + positionMouseDown = e.MousePosition; + dragRotationState = DragRotationState.DragStarted; + } } return base.OnMouseDown(e); @@ -94,13 +97,13 @@ namespace osu.Game.Graphics.Cursor protected override void OnMouseUp(MouseUpEvent e) { - if (!e.IsPressed(MouseButton.Left) && !e.IsPressed(MouseButton.Right)) + if (!e.IsPressed(MouseButton.Left) && !e.IsPressed(MouseButton.Middle) && !e.IsPressed(MouseButton.Right)) { activeCursor.AdditiveLayer.FadeOutFromOne(500, Easing.OutQuint); activeCursor.ScaleTo(1, 500, Easing.OutElastic); } - if (e.Button == MouseButton.Left) + if (!e.IsPressed(MouseButton.Left) && !e.IsPressed(MouseButton.Right)) { if (dragRotationState == DragRotationState.Rotating) activeCursor.RotateTo(0, 600 * (1 + Math.Abs(activeCursor.Rotation / 720)), Easing.OutElasticHalf); From 85d0c04e61222d9734297f9365bb22fcfec6514a Mon Sep 17 00:00:00 2001 From: Power Maker <42269909+power9maker@users.noreply.github.com> Date: Tue, 2 Jun 2020 20:57:02 +0200 Subject: [PATCH 285/871] Add cursor rotation on right mouse button --- osu.Game/Graphics/Cursor/MenuCursor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 740c809afc..c92304b2d2 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -87,8 +87,8 @@ namespace osu.Game.Graphics.Cursor { if(!(dragRotationState == DragRotationState.Rotating)) { - positionMouseDown = e.MousePosition; dragRotationState = DragRotationState.DragStarted; + positionMouseDown = e.MousePosition; } } From 4ebc1d3721f50e758eb473465b90edcc7271c75f Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 2 Jun 2020 21:06:22 +0200 Subject: [PATCH 286/871] Add original sprite and visualiser Notes: This is using a modified version of welcome.osz to facilitate the visualiser and the animation of the sprite is not accurate. --- ...ntroLegacy.cs => TestSceneIntroWelcome.cs} | 4 +- osu.Game/Screens/Loader.cs | 2 +- osu.Game/Screens/Menu/IntroLegacy.cs | 117 -------------- osu.Game/Screens/Menu/IntroWelcome.cs | 152 ++++++++++++++++++ 4 files changed, 155 insertions(+), 120 deletions(-) rename osu.Game.Tests/Visual/Menus/{TestSceneIntroLegacy.cs => TestSceneIntroWelcome.cs} (70%) delete mode 100644 osu.Game/Screens/Menu/IntroLegacy.cs create mode 100644 osu.Game/Screens/Menu/IntroWelcome.cs diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroLegacy.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs similarity index 70% rename from osu.Game.Tests/Visual/Menus/TestSceneIntroLegacy.cs rename to osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs index 7cb99467ad..905f17ef0b 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroLegacy.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs @@ -8,8 +8,8 @@ using osu.Game.Screens.Menu; namespace osu.Game.Tests.Visual.Menus { [TestFixture] - public class TestSceneIntroLegacy : IntroTestScene + public class TestSceneIntroWelcome : IntroTestScene { - protected override IScreen CreateScreen() => new IntroLegacy(); + protected override IScreen CreateScreen() => new IntroWelcome(); } } diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs index aa959e7d35..0bfabdaa15 100644 --- a/osu.Game/Screens/Loader.cs +++ b/osu.Game/Screens/Loader.cs @@ -52,7 +52,7 @@ namespace osu.Game.Screens return new IntroCircles(); case IntroSequence.Welcome: - return new IntroLegacy(); + return new IntroWelcome(); default: return new IntroTriangles(); diff --git a/osu.Game/Screens/Menu/IntroLegacy.cs b/osu.Game/Screens/Menu/IntroLegacy.cs deleted file mode 100644 index 3980a0cc8b..0000000000 --- a/osu.Game/Screens/Menu/IntroLegacy.cs +++ /dev/null @@ -1,117 +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.Linq; -using osuTK; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.Audio.Sample; -using osu.Framework.Screens; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; - -namespace osu.Game.Screens.Menu -{ - public class IntroLegacy : IntroScreen - { - protected override string BeatmapHash => "64E00D7022195959BFA3109D09C2E2276C8F12F486B91FCF6175583E973B48F2"; - protected override string BeatmapFile => "welcome.osz"; - private const double delay_step_two = 2142; - - private SampleChannel welcome; - - private SampleChannel pianoReverb; - - [BackgroundDependencyLoader] - private void load(AudioManager audio) - { - Seeya = audio.Samples.Get(@"Intro/welcome/seeya"); - - if (MenuVoice.Value) - { - welcome = audio.Samples.Get(@"Intro/welcome/welcome"); - pianoReverb = audio.Samples.Get(@"Intro/welcome/welcome_piano"); - } - } - - protected override void LogoArriving(OsuLogo logo, bool resuming) - { - base.LogoArriving(logo, resuming); - - if (!resuming) - { - welcome?.Play(); - pianoReverb?.Play(); - Scheduler.AddDelayed(delegate - { - StartTrack(); - - PrepareMenuLoad(); - - logo.ScaleTo(1); - logo.FadeIn(); - - Scheduler.Add(LoadMenu); - }, delay_step_two); - - LoadComponentAsync(new FallbackIntroSequence - { - RelativeSizeAxes = Axes.Both - }, t => - { - AddInternal(t); - t.Start(delay_step_two); - }); - } - } - - public override void OnSuspending(IScreen next) - { - this.FadeOut(300); - base.OnSuspending(next); - } - - private class FallbackIntroSequence : Container - { - private OsuSpriteText welcomeText; - - [BackgroundDependencyLoader] - private void load() - { - Children = new Drawable[] - { - welcomeText = new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "welcome", - Padding = new MarginPadding { Bottom = 10 }, - Font = OsuFont.GetFont(weight: FontWeight.Light, size: 42), - Alpha = 0, - Spacing = new Vector2(5), - }, - }; - } - - public void Start(double length) - { - if (Children.Any()) - { - // restart if we were already run previously. - FinishTransforms(true); - load(); - } - - double remainingTime() => length - TransformDelay; - - using (BeginDelayedSequence(250, true)) - { - welcomeText.FadeIn(700); - welcomeText.ScaleTo(welcomeText.Scale + new Vector2(0.5f), remainingTime(), Easing.Out); - } - } - } - } -} diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs new file mode 100644 index 0000000000..fbed0bf654 --- /dev/null +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -0,0 +1,152 @@ +// 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.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; +using osu.Framework.Screens; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osuTK.Graphics; + +namespace osu.Game.Screens.Menu +{ + public class IntroWelcome : IntroScreen + { + protected override string BeatmapHash => "64E00D7022195959BFA3109D09C2E2276C8F12F486B91FCF6175583E973B48F2"; + protected override string BeatmapFile => "welcome.osz"; + private const double delay_step_two = 2142; + private SampleChannel welcome; + + [BackgroundDependencyLoader] + private void load(AudioManager audio) + { + Seeya = audio.Samples.Get(@"Intro/welcome/seeya"); + + if (MenuVoice.Value) + welcome = audio.Samples.Get(@"Intro/welcome/welcome"); + } + + protected override void LogoArriving(OsuLogo logo, bool resuming) + { + base.LogoArriving(logo, resuming); + + if (!resuming) + { + welcome?.Play(); + StartTrack(); + Scheduler.AddDelayed(delegate + { + PrepareMenuLoad(); + + logo.ScaleTo(1); + logo.FadeIn(); + + Scheduler.Add(LoadMenu); + }, delay_step_two); + + LoadComponentAsync(new WelcomeIntroSequence + { + RelativeSizeAxes = Axes.Both + }, AddInternal); + } + } + + public override void OnSuspending(IScreen next) + { + this.FadeOut(300); + base.OnSuspending(next); + } + + private class WelcomeIntroSequence : Container + { + private Sprite welcomeText; + private LogoVisualisation visualizer; + private Container elementContainer; + private Container circleContainer; + private Circle blackCircle; + + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + Origin = Anchor.Centre; + Anchor = Anchor.Centre; + Children = new Drawable[] + { + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + elementContainer = new Container + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + visualizer = new LogoVisualisation + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0.5f, + AccentColour = Color4.Blue, + Size = new Vector2(0.96f) + }, + circleContainer = new Container + { + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + blackCircle = new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(480), + Colour = Color4.Black + } + } + } + } + } + } + }, + welcomeText = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Scale = new Vector2(0.5f), + Texture = textures.Get(@"Welcome/welcome_text@2x") + }, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + double remainingTime() => delay_step_two - TransformDelay; + + using (BeginDelayedSequence(250, true)) + { + welcomeText.FadeIn(700); + welcomeText.ScaleTo(welcomeText.Scale + new Vector2(0.5f), remainingTime(), Easing.Out).OnComplete(_ => + { + elementContainer.Remove(visualizer); + circleContainer.Remove(blackCircle); + elementContainer.Remove(circleContainer); + Remove(welcomeText); + visualizer.Dispose(); + blackCircle.Dispose(); + welcomeText.Dispose(); + }); + } + } + } + } +} From b79773cdb17524bc7fae1da25016cfe2ff90ac46 Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 2 Jun 2020 21:50:50 +0200 Subject: [PATCH 287/871] Modify LogoVisualisation to allow color changes Also change the color from blue to dark blue --- osu.Game/Screens/Menu/IntroWelcome.cs | 3 ++- osu.Game/Screens/Menu/LogoVisualisation.cs | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index fbed0bf654..7c60048d1c 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -95,7 +95,8 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.Centre, Origin = Anchor.Centre, Alpha = 0.5f, - AccentColour = Color4.Blue, + isIntro = true, + AccentColour = Color4.DarkBlue, Size = new Vector2(0.96f) }, circleContainer = new Container diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 0db7f2a2dc..0e77d8d171 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -70,6 +70,7 @@ namespace osu.Game.Screens.Menu private IShader shader; private readonly Texture texture; + public bool isIntro = false; private Bindable user; private Bindable skin; @@ -88,8 +89,11 @@ namespace osu.Game.Screens.Menu user = api.LocalUser.GetBoundCopy(); skin = skinManager.CurrentSkin.GetBoundCopy(); - user.ValueChanged += _ => updateColour(); - skin.BindValueChanged(_ => updateColour(), true); + if (!isIntro) + { + user.ValueChanged += _ => updateColour(); + skin.BindValueChanged(_ => updateColour(), true); + } } private void updateAmplitudes() From 9cd66dcdef26d39bd771f375b8a27fa25ddcb6bc Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 2 Jun 2020 21:54:39 +0200 Subject: [PATCH 288/871] Fix styling error --- osu.Game/Screens/Menu/IntroWelcome.cs | 2 +- osu.Game/Screens/Menu/LogoVisualisation.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 7c60048d1c..9f9012cb2b 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.Centre, Origin = Anchor.Centre, Alpha = 0.5f, - isIntro = true, + IsIntro = true, AccentColour = Color4.DarkBlue, Size = new Vector2(0.96f) }, diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 0e77d8d171..c72b3a6576 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -70,7 +70,7 @@ namespace osu.Game.Screens.Menu private IShader shader; private readonly Texture texture; - public bool isIntro = false; + public bool IsIntro = false; private Bindable user; private Bindable skin; @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Menu user = api.LocalUser.GetBoundCopy(); skin = skinManager.CurrentSkin.GetBoundCopy(); - if (!isIntro) + if (!IsIntro) { user.ValueChanged += _ => updateColour(); skin.BindValueChanged(_ => updateColour(), true); From fa4d13a22b68440e885288f57157cfb2d3466007 Mon Sep 17 00:00:00 2001 From: Power Maker Date: Tue, 2 Jun 2020 22:25:25 +0200 Subject: [PATCH 289/871] Fixed whitespace --- osu.Game/Graphics/Cursor/MenuCursor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index c92304b2d2..1aa7b68d1a 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -85,7 +85,7 @@ namespace osu.Game.Graphics.Cursor if ((e.Button == MouseButton.Left || e.Button == MouseButton.Right) && cursorRotate.Value) { - if(!(dragRotationState == DragRotationState.Rotating)) + if (!(dragRotationState == DragRotationState.Rotating)) { dragRotationState = DragRotationState.DragStarted; positionMouseDown = e.MousePosition; From 40e64eed475b7c09de60643671035e5cd0ec9967 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 2 Jun 2020 23:15:14 +0200 Subject: [PATCH 290/871] Add contributing guidelines --- CONTRIBUTING.md | 121 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..441521f9ef --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,121 @@ +# Contributing Guidelines + +Thank you for showing interest in the development of osu!lazer! We aim to provide a good collaborating environment for everyone involved, and as such have decided to list some of the most important things to keep in mind in the process. The guidelines below have been chosen based on past experience. + +These are not "official rules" *per se*, but following them will help everyone deal with things in the most efficient manner. + +## Table of contents + +1. [I would like to submit an issue!](#i-would-like-to-submit-an-issue) +2. [I would like to submit a pull request!](#i-would-like-to-submit-a-pull-request) + +## I would like to submit an issue! + +When it comes to issues, bug reports and feature suggestions are welcomed, although please keep in mind that at any point in time, hundreds of issues are open, which vary in severity and the amount of time needed to address them. As such it's not uncommon for issues to remain unresolved for a long time or even closed outright if they are deemed not important enough to fix in the foreseeable future. Issues that are required to "go live" or otherwise achieve parity with stable are prioritised the most. + +* **Before submitting an issue, try searching existing issues first.** + + For housekeeping purposes, we close issues that overlap with or duplicate other pre-existing issues - you can help us not have to do that by searching existing issues yourself first. The issue search box, as well as the issue tag system, are tools you can use to check if an issue has been reported before. + +* **When submitting a bug report, please try to include as much detail as possible.** + + Bugs are not equal - some of them will be reproducible every time on pretty much all hardware, while others will be hard to track down due to being specific to particular hardware or even somewhat random in nature. As such, providing as much detail as possible when reporting a bug is hugely appreciated. A good starting set of information contains of: + + * the in-game logs, which are located at: + * `%AppData%/osu/logs` (on Windows), + * `~/.local/share/osu/logs` (on Linux and macOS), + * `Android/Data/sh.ppy.osulazer/logs` (on Android), + * on iOS they can be obtained by connecting your device to your desktop and copying the `logs` directory from the app's own document storage using iTunes, + * your system specifications (including the operating system and platform you are playing on), + * a reproduction scenario (list of steps you have performed leading up to the occurrence of the bug), + * a video or picture of the bug, if at all possible. + +* **Provide more information when asked to do so.** + + Sometimes when a bug is more elusive or complicated, none of the information listed above will pinpoint a concrete cause of the problem. In this case we will most likely ask you for additional info, such as a Windows Event Log dump or a copy of your local lazer database (`client.db`). Providing that information is beneficial to both parties - we can track down the problem better, and hopefully fix it for you at some point once we know where it is! + +* **When submitting a feature proposal, please describe it in the most understandable way you can.** + + Communicating your idea for a feature can often be hard, and we would like to avoid any misunderstandings. As such, please try to explain your idea in a short, but understandable manner - it's best to avoid jargon or terms and references that could be considered obscure. A mock-up picture (doesn't have to be good!) of the feature can also go a long way in explaining. + +* **Refrain from posting "+1" comments.** + + If an issue has already been created, saying that you also experience it without providing any additional details doesn't really help us in any way. To express support for a proposal or indicate that you are also affected by a particular bug, you can use comment reactions instead. + +* **Refrain from asking if an issue has been resolved yet.** + + As mentioned above, the issue tracker has hundreds of issues open at any given time. Currently the game is being worked on by two members of the core team, and a handful of outside contributors who offer their free time to help out. As such, it can happen that an issue gets placed on the backburner due to being less important; generally posting a comment demanding its resolution some months or years after it is reported is not very likely to increase its priority. + +* **Avoid long discussions about non-development topics.** + + GitHub is mostly a developer space, and as such isn't really fit for lengthened discussions about gameplay mechanics (which might not even be in any way confirmed for the final release) and similar non-technical matters. Such matters are probably best addressed at the osu! forums. + +## I would like to submit a pull request! + +We also welcome pull requests from unaffiliated contributors. The issue tracker should provide plenty of issues that you can work on; we also mark issues that we think would be good for newcomers with the [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label. + +However, do keep in mind that the core team is committed to bringing osu!lazer up to par with stable first and foremost, so depending on what your contribution concerns, it might not be merged and released right away. Our approach to managing issues and their priorities is described [in the wiki](https://github.com/ppy/osu/wiki/Project-management). + +Here are some key things to note before jumping in: + +* **Make sure you are comfortable with C\# and your development environment.** + + While we are accepting of all kinds of contributions, we also have a certain quality standard we'd like to uphold and limited time to review your code. Therefore, we would like to avoid providing entry-level advice, and as such if you're not very familiar with C\# as a programming language, we'd recommend that you start off with a few personal projects to get acquainted with the language's syntax, toolchain and principles of object-oriented programming first. + +* **Make sure you are familiar with git and the pull request workflow.** + + [git](https://git-scm.com/) is a distributed version control system that might not be very intuitive at the beginning if you're not familiar with version control. In particular, projects using git have a particular workflow for submitting code changes, which is called the pull request workflow. + + To make things run more smoothly, we recommend that you look up some online resources to familiarise yourself with the git vocabulary and commands, and practice working with forks and submitting pull requests at your own pace. A high-level overview of the process can be found in [this article by GitHub](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/proposing-changes-to-your-work-with-pull-requests). + +* **Make sure to submit pull requests off of a topic branch.** + + As described in the article linked in the previous point, topic branches help you parallelise your work and separate it from the main `master` branch, and additionally are easier for maintainers to work with. Working with multiple `master` branches across many remotes is difficult to keep track of, and it's easy to make a mistake and push to the wrong `master` branch by accident. + +* **Refrain from making changes through the GitHub web interface.** + + Even though GitHub provides an option to edit code or replace files in the repository using the web interface, we strongly discourage using it in most scenarios. Editing files this way is inefficient and likely to introduce whitespace or file encoding changes that make it more difficult to review the code. + + Code written through the web interface will also very likely be questioned outright by the reviewers, as it is likely that it has not been properly tested or that it will fail continuous integration checks. We strongly encourage using an IDE like [Visual Studio](https://visualstudio.microsoft.com/), [Visual Studio Code](https://code.visualstudio.com/) or [JetBrains Rider](https://www.jetbrains.com/rider/) instead. + +* **Add tests for your code whenever possible.** + + Automated tests are an essential part of a quality and reliable codebase. They help to make the code more maintainable by ensuring it is safe to reorganise (or refactor) the code in various ways, and also prevent regressions - bugs that resurface after having been fixed at some point in the past. If it is viable, please put in the time to add tests, so that the changes you make can last for a (hopefully) very long time. + +* **Run tests before opening a pull request.** + + Tying into the previous point, sometimes changes in one part of the codebase can result in unpredictable changes in behaviour in other pieces of the code. This is why it is best to always try to run tests before opening a PR. + + Continuous integration will always run the tests for you (and us), too, but it is best not to rely on it, as there might be many builds queued at any time. Running tests on your own will help you be more certain that at the point of clicking the "Create pull request" button, your changes are as ready as can be. + +* **Run code style analysis before opening a pull request.** + + As part of continuous integration, we also run code style analysis, which is supposed to make sure that your code is formatted the same way as all the pre-existing code in the repository. The reason we enforce a particular code style everywhere is to make sure the codebase is consistent in that regard - having one whitespace convention in one place and another one elsewhere causes disorganisation. + +* **Make sure to keep the *Allow edits from maintainers* check box checked.** + + To speed up the merging process, collaborators and team members will sometimes want to push changes to your branch themselves, to make minor code style adjustments or to otherwise refactor the code without having to describe how they'd like the code to look like in painstaking detail. Having the *Allow edits from maintainers* check box checked lets them do that; without it they are forced to report issues back to you and wait for you to address them. + +* **Refrain from continually merging the master branch back to the PR.** + + Unless there are merge conflicts that need resolution, there is no need to keep merging `master` back to a branch over and over again. One of the maintainers will merge `master` themselves before merging the PR itself anyway, and continual merge commits can cause CI to get overwhelmed due to queueing up too many builds. + +* **Refrain from force-pushing to the PR branch.** + + Force-pushing should be avoided, as it can lead to accidentally overwriting a maintainer's changes or CI building wrong commits. We value all history in the project, so there is no need to squash or amend commits in most cases. + + The cases in which force-pushing is warranted are very rare (such as accidentally leaking sensitive info in one of the files committed, adding unrelated files, or mis-merging a dependent PR). + +* **Be patient when waiting for the code to be reviewed and merged.** + + As much as we'd like to review all contributions as fast as possible, our time is limited, as team members have to work on their own tasks in addition to reviewing code. As such, work needs to be prioritised, and it can unfortunately take weeks or months for your PR to be merged, depending on how important it is deemed to be. + +* **Don't mistake criticism of code for criticism of your person.** + + As mentioned before, we are highly committed to quality when it comes to the lazer project. This means that contributions from less experienced community members can take multiple rounds of review to get to a mergeable state. We try our utmost best to never conflate a person with the code they authored, and to keep the discussion focused on the code at all times. Please consider our comments and requests a learning experience, and don't treat it as a personal attack. + +* **Feel free to reach out for help.** + + If you're uncertain about some part of the codebase or some inner workings of the game and framework, please reach out either by leaving a comment in the relevant issue or PR thread, or by posting a message in the [development Discord server](https://discord.gg/ppy). We will try to help you as much as we can. + + When it comes to which form of communication is best, GitHub generally lends better to longer-form discussions, while Discord is better for snappy call-and-response answers. Use your best discretion when deciding, and try to keep a single discussion in one place instead of moving back and forth. From f4f84ede6a2ff88b3f0cf7517c89a59ef309ed20 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 3 Jun 2020 10:43:16 +0930 Subject: [PATCH 291/871] Fix results screen crashing for beatmaps with no online ID --- osu.Game/Screens/Ranking/ResultsScreen.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index fbb9b95478..145ba93573 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -140,14 +140,17 @@ namespace osu.Game.Screens.Ranking { base.LoadComplete(); - var req = FetchScores(scores => Schedule(() => + if (Score.Beatmap.OnlineBeatmapID != null) { - foreach (var s in scores) - panels.AddScore(s); - })); + var req = FetchScores(scores => Schedule(() => + { + foreach (var s in scores) + panels.AddScore(s); + })); - if (req != null) - api.Queue(req); + if (req != null) + api.Queue(req); + } } /// From 90213d079d33713d8729739675910ebe1cfdda24 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 3 Jun 2020 10:48:27 +0930 Subject: [PATCH 292/871] Include submission status in check --- osu.Game/Screens/Ranking/ResultsScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 145ba93573..25c8205c30 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; +using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -140,7 +141,7 @@ namespace osu.Game.Screens.Ranking { base.LoadComplete(); - if (Score.Beatmap.OnlineBeatmapID != null) + if (Score.Beatmap.OnlineBeatmapID != null && Score.Beatmap.Status > BeatmapSetOnlineStatus.Pending) { var req = FetchScores(scores => Schedule(() => { From 96e3c6e8e888d47069cc636c3049178cae09733b Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 3 Jun 2020 11:36:47 +0930 Subject: [PATCH 293/871] Move check to SoloResultsScreen --- osu.Game/Screens/Ranking/ResultsScreen.cs | 15 ++++++--------- osu.Game/Screens/Ranking/SoloResultsScreen.cs | 4 ++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 25c8205c30..aff0540652 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -141,17 +141,14 @@ namespace osu.Game.Screens.Ranking { base.LoadComplete(); - if (Score.Beatmap.OnlineBeatmapID != null && Score.Beatmap.Status > BeatmapSetOnlineStatus.Pending) + var req = FetchScores(scores => Schedule(() => { - var req = FetchScores(scores => Schedule(() => - { - foreach (var s in scores) - panels.AddScore(s); - })); + foreach (var s in scores) + panels.AddScore(s); + })); - if (req != null) - api.Queue(req); - } + if (req != null) + api.Queue(req); } /// diff --git a/osu.Game/Screens/Ranking/SoloResultsScreen.cs b/osu.Game/Screens/Ranking/SoloResultsScreen.cs index 3ae723683a..9cf2e6757a 100644 --- a/osu.Game/Screens/Ranking/SoloResultsScreen.cs +++ b/osu.Game/Screens/Ranking/SoloResultsScreen.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Rulesets; @@ -24,6 +25,9 @@ namespace osu.Game.Screens.Ranking protected override APIRequest FetchScores(Action> scoresCallback) { + if (Score.Beatmap.OnlineBeatmapID == null || Score.Beatmap.Status <= BeatmapSetOnlineStatus.Pending) + return null; + var req = new GetScoresRequest(Score.Beatmap, Score.Ruleset); req.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.OnlineScoreID != Score.OnlineScoreID).Select(s => s.CreateScoreInfo(rulesets))); return req; From 0d5a2cf96d089038615d8f30572ae4b7e9b7c7ed Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 3 Jun 2020 11:36:59 +0930 Subject: [PATCH 294/871] Add unit tests --- .../Visual/Ranking/TestSceneResultsScreen.cs | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 242766ad4b..125aa0a1e7 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -36,12 +36,14 @@ namespace osu.Game.Tests.Visual.Ranking Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); } - private TestSoloResults createResultsScreen() => new TestSoloResults(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + private TestResultsScreen createResultsScreen() => new TestResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + + private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo)); [Test] public void ResultsWithoutPlayer() { - TestSoloResults screen = null; + TestResultsScreen screen = null; OsuScreenStack stack; AddStep("load results", () => @@ -60,13 +62,23 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void ResultsWithPlayer() { - TestSoloResults screen = null; + TestResultsScreen screen = null; AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); AddUntilStep("wait for loaded", () => screen.IsLoaded); AddAssert("retry overlay present", () => screen.RetryOverlay != null); } + [Test] + public void ResultsForUnranked() + { + UnrankedSoloResultsScreen screen = null; + + AddStep("load results", () => Child = new TestResultsContainer(screen = createUnrankedSoloResultsScreen())); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddAssert("retry overlay present", () => screen.RetryOverlay != null); + } + private class TestResultsContainer : Container { [Cached(typeof(Player))] @@ -86,11 +98,11 @@ namespace osu.Game.Tests.Visual.Ranking } } - private class TestSoloResults : ResultsScreen + private class TestResultsScreen : ResultsScreen { public HotkeyRetryOverlay RetryOverlay; - public TestSoloResults(ScoreInfo score) + public TestResultsScreen(ScoreInfo score) : base(score) { } @@ -102,5 +114,24 @@ namespace osu.Game.Tests.Visual.Ranking RetryOverlay = InternalChildren.OfType().SingleOrDefault(); } } + + private class UnrankedSoloResultsScreen : SoloResultsScreen + { + public HotkeyRetryOverlay RetryOverlay; + + public UnrankedSoloResultsScreen(ScoreInfo score) + : base(score) + { + Score.Beatmap.OnlineBeatmapID = 0; + Score.Beatmap.Status = BeatmapSetOnlineStatus.Pending; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + RetryOverlay = InternalChildren.OfType().SingleOrDefault(); + } + } } } From b174daa94a5f7968ac9ef31b50257ecf6a96698d Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Wed, 3 Jun 2020 11:58:56 +0930 Subject: [PATCH 295/871] Remove unused using --- osu.Game/Screens/Ranking/ResultsScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index aff0540652..fbb9b95478 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; From 13622eff1f12e8338f96ff5005ccbc2a9e12b8f6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Jun 2020 12:54:07 +0900 Subject: [PATCH 296/871] Fix response value --- .../Multiplayer/TestSceneTimeshiftResultsScreen.cs | 2 +- .../Online/API/Requests/GetRoomPlaylistScoresRequest.cs | 9 ++++++++- osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs index d87a2e3408..8559e7e2f4 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs @@ -66,7 +66,7 @@ namespace osu.Game.Tests.Visual.Multiplayer switch (request) { case GetRoomPlaylistScoresRequest r: - r.TriggerSuccess(roomScores); + r.TriggerSuccess(new RoomPlaylistScores { Scores = roomScores }); break; } }); diff --git a/osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs b/osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs index dd7f80fd46..38f852870b 100644 --- a/osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetRoomPlaylistScoresRequest.cs @@ -2,10 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using Newtonsoft.Json; namespace osu.Game.Online.API.Requests { - public class GetRoomPlaylistScoresRequest : APIRequest> + public class GetRoomPlaylistScoresRequest : APIRequest { private readonly int roomId; private readonly int playlistItemId; @@ -18,4 +19,10 @@ namespace osu.Game.Online.API.Requests protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores"; } + + public class RoomPlaylistScores + { + [JsonProperty("scores")] + public List Scores { get; set; } + } } diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index f2afe15d35..d95cee2ab8 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Multi.Ranking protected override APIRequest FetchScores(Action> scoresCallback) { var req = new GetRoomPlaylistScoresRequest(roomId, playlistItem.ID); - req.Success += r => scoresCallback?.Invoke(r.Where(s => s.ID != Score?.OnlineScoreID).Select(s => s.CreateScoreInfo(playlistItem))); + req.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.ID != Score?.OnlineScoreID).Select(s => s.CreateScoreInfo(playlistItem))); return req; } } From 22f4e9012c58e96b8b03f5e9bb4b9639efa83c34 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Jun 2020 12:54:16 +0900 Subject: [PATCH 297/871] Remove temporary code --- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 01a90139c3..3609be2dfe 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -13,7 +13,6 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.GameTypes; using osu.Game.Rulesets.Mods; @@ -212,17 +211,6 @@ namespace osu.Game.Screens.Multi.Match managerAdded = beatmapManager.ItemAdded.GetBoundCopy(); managerAdded.BindValueChanged(beatmapAdded); - - if (roomId.Value != null) - { - var req = new GetRoomPlaylistScoresRequest(roomId.Value.Value, playlist[0].ID); - - req.Success += scores => - { - }; - - api.Queue(req); - } } public override bool OnExiting(IScreen next) From 74875f9b629715a1e5a13e65704af277fb2c8c35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 Jun 2020 06:47:10 +0200 Subject: [PATCH 298/871] Apply review suggestions & other cleanups --- CONTRIBUTING.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 441521f9ef..331534ad73 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -11,21 +11,21 @@ These are not "official rules" *per se*, but following them will help everyone d ## I would like to submit an issue! -When it comes to issues, bug reports and feature suggestions are welcomed, although please keep in mind that at any point in time, hundreds of issues are open, which vary in severity and the amount of time needed to address them. As such it's not uncommon for issues to remain unresolved for a long time or even closed outright if they are deemed not important enough to fix in the foreseeable future. Issues that are required to "go live" or otherwise achieve parity with stable are prioritised the most. +Issues, bug reports and feature suggestions are welcomed, though please keep in mind that at any point in time, hundreds of issues are open, which vary in severity and the amount of time needed to address them. As such it's not uncommon for issues to remain unresolved for a long time or even closed outright if they are deemed not important enough to fix in the foreseeable future. Issues that are required to "go live" or otherwise achieve parity with stable are prioritised the most. * **Before submitting an issue, try searching existing issues first.** - For housekeeping purposes, we close issues that overlap with or duplicate other pre-existing issues - you can help us not have to do that by searching existing issues yourself first. The issue search box, as well as the issue tag system, are tools you can use to check if an issue has been reported before. + For housekeeping purposes, we close issues that overlap with or duplicate other pre-existing issues - you can help us not to have to do that by searching existing issues yourself first. The issue search box, as well as the issue tag system, are tools you can use to check if an issue has been reported before. * **When submitting a bug report, please try to include as much detail as possible.** - Bugs are not equal - some of them will be reproducible every time on pretty much all hardware, while others will be hard to track down due to being specific to particular hardware or even somewhat random in nature. As such, providing as much detail as possible when reporting a bug is hugely appreciated. A good starting set of information contains of: + Bugs are not equal - some of them will be reproducible every time on pretty much all hardware, while others will be hard to track down due to being specific to particular hardware or even somewhat random in nature. As such, providing as much detail as possible when reporting a bug is hugely appreciated. A good starting set of information consists of: * the in-game logs, which are located at: * `%AppData%/osu/logs` (on Windows), * `~/.local/share/osu/logs` (on Linux and macOS), * `Android/Data/sh.ppy.osulazer/logs` (on Android), - * on iOS they can be obtained by connecting your device to your desktop and copying the `logs` directory from the app's own document storage using iTunes, + * on iOS they can be obtained by connecting your device to your desktop and [copying the `logs` directory from the app's own document storage using iTunes](https://support.apple.com/en-us/HT201301#copy-to-computer), * your system specifications (including the operating system and platform you are playing on), * a reproduction scenario (list of steps you have performed leading up to the occurrence of the bug), * a video or picture of the bug, if at all possible. From 1992a3db546126f536ddc9974cd5074bff5f4876 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 15:50:00 +0900 Subject: [PATCH 299/871] Fix redundant override showing up in build warnings --- osu.Game/Rulesets/Objects/SliderEventGenerator.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs index 6df0041e7a..d8c6da86f9 100644 --- a/osu.Game/Rulesets/Objects/SliderEventGenerator.cs +++ b/osu.Game/Rulesets/Objects/SliderEventGenerator.cs @@ -11,6 +11,7 @@ namespace osu.Game.Rulesets.Objects public static class SliderEventGenerator { [Obsolete("Use the overload with cancellation support instead.")] // can be removed 20201115 + // ReSharper disable once RedundantOverload.Global public static IEnumerable Generate(double startTime, double spanDuration, double velocity, double tickDistance, double totalDistance, int spanCount, double? legacyLastTickOffset) { From 5f1d44a2bef173fc2745e0e1212672db4e3b5d86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 15:52:47 +0900 Subject: [PATCH 300/871] Update inspectcode / CodeFileSanity versions used in CI --- build/InspectCode.cake | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/InspectCode.cake b/build/InspectCode.cake index 2e7a1d1b28..c8f4f37c94 100644 --- a/build/InspectCode.cake +++ b/build/InspectCode.cake @@ -1,5 +1,5 @@ -#addin "nuget:?package=CodeFileSanity&version=0.0.33" -#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2019.3.2" +#addin "nuget:?package=CodeFileSanity&version=0.0.36" +#addin "nuget:?package=JetBrains.ReSharper.CommandLineTools&version=2020.1.3" #tool "nuget:?package=NVika.MSBuild&version=1.0.1" var nVikaToolPath = GetFiles("./tools/NVika.MSBuild.*/tools/NVika.exe").First(); From 3c07defa1a48a4a778d7e21b9fe9e8fc9bd1abe5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Jun 2020 15:57:01 +0900 Subject: [PATCH 301/871] Push to main multiplayer screen instead --- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 8 ++++---- osu.Game/Screens/Multi/Multiplayer.cs | 14 -------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 3609be2dfe..b0717d3d28 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -20,6 +20,7 @@ using osu.Game.Screens.Multi.Components; using osu.Game.Screens.Multi.Match.Components; using osu.Game.Screens.Multi.Play; using osu.Game.Screens.Multi.Ranking; +using osu.Game.Screens.Play; using osu.Game.Screens.Select; using Footer = osu.Game.Screens.Multi.Match.Components.Footer; @@ -260,10 +261,10 @@ namespace osu.Game.Screens.Multi.Match { default: case GameTypeTimeshift _: - multiplayer?.Start(() => new TimeshiftPlayer(SelectedItem.Value) + multiplayer?.Push(new PlayerLoader(() => new TimeshiftPlayer(SelectedItem.Value) { Exited = () => leaderboardChatDisplay.RefreshScores() - }); + })); break; } } @@ -271,8 +272,7 @@ namespace osu.Game.Screens.Multi.Match private void showBeatmapResults() { Debug.Assert(roomId.Value != null); - - this.Push(new TimeshiftResultsScreen(null, roomId.Value.Value, SelectedItem.Value, false)); + multiplayer?.Push(new TimeshiftResultsScreen(null, roomId.Value.Value, SelectedItem.Value, false)); } } } diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 863a28609b..e724152e08 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -24,7 +23,6 @@ using osu.Game.Screens.Multi.Lounge; using osu.Game.Screens.Multi.Lounge.Components; using osu.Game.Screens.Multi.Match; using osu.Game.Screens.Multi.Match.Components; -using osu.Game.Screens.Play; using osuTK; namespace osu.Game.Screens.Multi @@ -197,18 +195,6 @@ namespace osu.Game.Screens.Multi Logger.Log($"Polling adjusted (listing: {roomManager.TimeBetweenListingPolls}, selection: {roomManager.TimeBetweenSelectionPolls})"); } - /// - /// Push a to the main screen stack to begin gameplay. - /// Generally called from a via DI resolution. - /// - public void Start(Func player) - { - if (!this.IsCurrentScreen()) - return; - - this.Push(new PlayerLoader(player)); - } - public void APIStateChanged(IAPIProvider api, APIState state) { if (state != APIState.Online) From f3b514964894fd38c69cff189d59f71b04b2fe82 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Jun 2020 16:48:44 +0900 Subject: [PATCH 302/871] Move some suggestions to warnings, resolve issues --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 2 +- osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs | 3 +-- .../Drawables/Connections/FollowPointConnection.cs | 3 +-- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 4 ++-- osu.Game.Tests/Scores/IO/ImportScoreTest.cs | 7 ++----- osu.Game.Tournament.Tests/LadderTestScene.cs | 3 +-- osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs | 3 +-- osu.Game.Tournament/TournamentGameBase.cs | 7 ++----- osu.Game/Beatmaps/BeatmapManager.cs | 3 +-- osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs | 3 +-- osu.Game/Online/Chat/ChannelManager.cs | 9 +++------ osu.Game/Online/Chat/StandAloneChatDisplay.cs | 3 +-- osu.Game/OsuGameBase.cs | 9 ++++----- osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs | 3 +-- osu.Game/Rulesets/Objects/HitObject.cs | 3 +-- osu.Game/Rulesets/UI/FrameStabilityContainer.cs | 3 +-- osu.Game/Scoring/ScoreInfo.cs | 8 ++------ osu.Game/Screens/Edit/Timing/Section.cs | 2 +- .../Screens/Multi/Lounge/Components/FilterControl.cs | 3 +-- osu.Game/Screens/Select/BeatmapCarousel.cs | 5 +---- osu.Game/Users/Drawables/DrawableAvatar.cs | 2 +- osu.sln.DotSettings | 4 ++++ 22 files changed, 34 insertions(+), 58 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index ade8460dd7..dd50b05c75 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -48,7 +48,7 @@ namespace osu.Desktop.Updater try { - if (updateManager == null) updateManager = await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true); + updateManager ??= await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true); var info = await updateManager.CheckForUpdate(!useDeltaPatching); if (info.ReleasesToApply.Count == 0) diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index 5cd2f1f581..918ed77683 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -35,8 +35,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills { var catchCurrent = (CatchDifficultyHitObject)current; - if (lastPlayerPosition == null) - lastPlayerPosition = catchCurrent.LastNormalizedPosition; + lastPlayerPosition ??= catchCurrent.LastNormalizedPosition; float playerPosition = Math.Clamp( lastPlayerPosition.Value, diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 8a0ef22c4a..2c41e6b0e9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -135,8 +135,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections fp.Alpha = 0; fp.Scale = new Vector2(1.5f * osuEnd.Scale); - if (firstTransformStartTime == null) - firstTransformStartTime = fadeInTime; + firstTransformStartTime ??= fadeInTime; fp.AnimationStartTime = fadeInTime; diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 5eb11a3264..195fec6278 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -175,7 +175,7 @@ namespace osu.Game.Tests.Beatmaps.IO var breakTemp = TestResources.GetTestBeatmapForImport(); MemoryStream brokenOsu = new MemoryStream(); - MemoryStream brokenOsz = new MemoryStream(File.ReadAllBytes(breakTemp)); + MemoryStream brokenOsz = new MemoryStream(await File.ReadAllBytesAsync(breakTemp)); File.Delete(breakTemp); @@ -522,7 +522,7 @@ namespace osu.Game.Tests.Beatmaps.IO using (var resourceForkFile = File.CreateText(resourceForkFilePath)) { - resourceForkFile.WriteLine("adding content so that it's not empty"); + await resourceForkFile.WriteLineAsync("adding content so that it's not empty"); } try diff --git a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs index 90bf419644..57f0d7e957 100644 --- a/osu.Game.Tests/Scores/IO/ImportScoreTest.cs +++ b/osu.Game.Tests/Scores/IO/ImportScoreTest.cs @@ -183,11 +183,8 @@ namespace osu.Game.Tests.Scores.IO { var beatmapManager = osu.Dependencies.Get(); - if (score.Beatmap == null) - score.Beatmap = beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); - - if (score.Ruleset == null) - score.Ruleset = new OsuRuleset().RulesetInfo; + score.Beatmap ??= beatmapManager.GetAllUsableBeatmapSets().First().Beatmaps.First(); + score.Ruleset ??= new OsuRuleset().RulesetInfo; var scoreManager = osu.Dependencies.Get(); await scoreManager.Import(score, archive); diff --git a/osu.Game.Tournament.Tests/LadderTestScene.cs b/osu.Game.Tournament.Tests/LadderTestScene.cs index b962d035ab..2f4373679c 100644 --- a/osu.Game.Tournament.Tests/LadderTestScene.cs +++ b/osu.Game.Tournament.Tests/LadderTestScene.cs @@ -24,8 +24,7 @@ namespace osu.Game.Tournament.Tests [BackgroundDependencyLoader] private void load() { - if (Ladder.Ruleset.Value == null) - Ladder.Ruleset.Value = rulesetStore.AvailableRulesets.First(); + Ladder.Ruleset.Value ??= rulesetStore.AvailableRulesets.First(); Ruleset.BindTo(Ladder.Ruleset); } diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 8be66ff98c..e10154b722 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -47,8 +47,7 @@ namespace osu.Game.Tournament.Screens.Drawings this.storage = storage; - if (TeamList == null) - TeamList = new StorageBackedTeamList(storage); + TeamList ??= new StorageBackedTeamList(storage); if (!TeamList.Teams.Any()) { diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 85db9e61fb..928c6deb3c 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -150,11 +150,8 @@ namespace osu.Game.Tournament ladder = JsonConvert.DeserializeObject(sr.ReadToEnd()); } - if (ladder == null) - ladder = new LadderInfo(); - - if (ladder.Ruleset.Value == null) - ladder.Ruleset.Value = RulesetStore.AvailableRulesets.First(); + ladder ??= new LadderInfo(); + ladder.Ruleset.Value ??= RulesetStore.AvailableRulesets.First(); Ruleset.BindTo(ladder.Ruleset); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f626b45e42..0785f9ef0e 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -239,8 +239,7 @@ namespace osu.Game.Beatmaps if (working == null) { - if (beatmapInfo.Metadata == null) - beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata; + beatmapInfo.Metadata ??= beatmapInfo.BeatmapSet.Metadata; workingCache.Add(working = new BeatmapManagerWorkingBeatmap(Files.Store, new LargeTextureStore(host?.CreateTextureLoaderStore(Files.Store)), beatmapInfo, audioManager)); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 388abf4648..be5cd78dc8 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -425,8 +425,7 @@ namespace osu.Game.Beatmaps.Formats private void handleHitObject(string line) { // If the ruleset wasn't specified, assume the osu!standard ruleset. - if (parser == null) - parser = new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion); + parser ??= new Rulesets.Objects.Legacy.Osu.ConvertHitObjectParser(getOffsetTime(), FormatVersion); var obj = parser.Parse(line); if (obj != null) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 53872ddcba..90d4c2a03b 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -114,8 +114,7 @@ namespace osu.Game.Online.Chat /// An optional target channel. If null, will be used. public void PostMessage(string text, bool isAction = false, Channel target = null) { - if (target == null) - target = CurrentChannel.Value; + target ??= CurrentChannel.Value; if (target == null) return; @@ -198,8 +197,7 @@ namespace osu.Game.Online.Chat /// An optional target channel. If null, will be used. public void PostCommand(string text, Channel target = null) { - if (target == null) - target = CurrentChannel.Value; + target ??= CurrentChannel.Value; if (target == null) return; @@ -378,8 +376,7 @@ namespace osu.Game.Online.Chat } } - if (CurrentChannel.Value == null) - CurrentChannel.Value = channel; + CurrentChannel.Value ??= channel; return channel; } diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 4fbeac1db9..f8810c778f 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -73,8 +73,7 @@ namespace osu.Game.Online.Chat [BackgroundDependencyLoader(true)] private void load(ChannelManager manager) { - if (ChannelManager == null) - ChannelManager = manager; + ChannelManager ??= manager; } protected virtual StandAloneDrawableChannel CreateDrawableChannel(Channel channel) => diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 5e44562144..3e7311092e 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -164,7 +164,7 @@ namespace osu.Game dependencies.Cache(SkinManager = new SkinManager(Storage, contextFactory, Host, Audio, new NamespacedResourceStore(Resources, "Skins/Legacy"))); dependencies.CacheAs(SkinManager); - if (API == null) API = new APIAccess(LocalConfig); + API ??= new APIAccess(LocalConfig); dependencies.CacheAs(API); @@ -311,11 +311,10 @@ namespace osu.Game { base.SetHost(host); - if (Storage == null) // may be non-null for certain tests - Storage = new OsuStorage(host); + // may be non-null for certain tests + Storage ??= new OsuStorage(host); - if (LocalConfig == null) - LocalConfig = new OsuConfigManager(Storage); + LocalConfig ??= new OsuConfigManager(Storage); } private readonly List fileImporters = new List(); diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index a72f182450..cb6abb7cc6 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -68,8 +68,7 @@ namespace osu.Game.Overlays.Chat.Tabs if (!Items.Contains(channel)) AddItem(channel); - if (Current.Value == null) - Current.Value = channel; + Current.Value ??= channel; } /// diff --git a/osu.Game/Rulesets/Objects/HitObject.cs b/osu.Game/Rulesets/Objects/HitObject.cs index e2cc98813a..1d60b266e3 100644 --- a/osu.Game/Rulesets/Objects/HitObject.cs +++ b/osu.Game/Rulesets/Objects/HitObject.cs @@ -133,8 +133,7 @@ namespace osu.Game.Rulesets.Objects { Kiai = controlPointInfo.EffectPointAt(StartTime + control_point_leniency).KiaiMode; - if (HitWindows == null) - HitWindows = CreateHitWindows(); + HitWindows ??= CreateHitWindows(); HitWindows?.SetDifficulty(difficulty.OverallDifficulty); } diff --git a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs index bc9401a095..d574991fa0 100644 --- a/osu.Game/Rulesets/UI/FrameStabilityContainer.cs +++ b/osu.Game/Rulesets/UI/FrameStabilityContainer.cs @@ -181,8 +181,7 @@ namespace osu.Game.Rulesets.UI private void setClock() { // in case a parent gameplay clock isn't available, just use the parent clock. - if (parentGameplayClock == null) - parentGameplayClock = Clock; + parentGameplayClock ??= Clock; Clock = GameplayClock; ProcessCustomClock = false; diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index a40f436a6e..7b37c267bc 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -115,9 +115,7 @@ namespace osu.Game.Scoring get => User?.Username; set { - if (User == null) - User = new User(); - + User ??= new User(); User.Username = value; } } @@ -129,9 +127,7 @@ namespace osu.Game.Scoring get => User?.Id ?? 1; set { - if (User == null) - User = new User(); - + User ??= new User(); User.Id = value ?? 1; } } diff --git a/osu.Game/Screens/Edit/Timing/Section.cs b/osu.Game/Screens/Edit/Timing/Section.cs index ccf1582486..603fb77f31 100644 --- a/osu.Game/Screens/Edit/Timing/Section.cs +++ b/osu.Game/Screens/Edit/Timing/Section.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Edit.Timing { checkbox = new OsuCheckbox { - LabelText = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty) + LabelText = typeof(T).Name.Replace(nameof(Beatmaps.ControlPoints.ControlPoint), string.Empty) } } }, diff --git a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs index 300418441e..2742ef3404 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs @@ -34,8 +34,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components [BackgroundDependencyLoader] private void load() { - if (filter == null) - filter = new Bindable(); + filter ??= new Bindable(); } protected override void LoadComplete() diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 2d714d1794..e174c46610 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -607,10 +607,7 @@ namespace osu.Game.Screens.Select // todo: remove the need for this. foreach (var b in beatmapSet.Beatmaps) - { - if (b.Metadata == null) - b.Metadata = beatmapSet.Metadata; - } + b.Metadata ??= beatmapSet.Metadata; var set = new CarouselBeatmapSet(beatmapSet) { diff --git a/osu.Game/Users/Drawables/DrawableAvatar.cs b/osu.Game/Users/Drawables/DrawableAvatar.cs index 09750c5bfe..42d2dbb1c6 100644 --- a/osu.Game/Users/Drawables/DrawableAvatar.cs +++ b/osu.Game/Users/Drawables/DrawableAvatar.cs @@ -43,7 +43,7 @@ namespace osu.Game.Users.Drawables Texture texture = null; if (user != null && user.Id > 1) texture = textures.Get($@"https://a.ppy.sh/{user.Id}"); - if (texture == null) texture = textures.Get(@"Online/avatar-guest"); + texture ??= textures.Get(@"Online/avatar-guest"); ClickableArea clickableArea; Add(clickableArea = new ClickableArea diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index b9fc3de734..6e8ecb42d6 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -60,6 +60,7 @@ WARNING WARNING WARNING + WARNING WARNING WARNING WARNING @@ -105,6 +106,8 @@ HINT WARNING WARNING + WARNING + WARNING WARNING WARNING WARNING @@ -222,6 +225,7 @@ WARNING WARNING WARNING + WARNING True WARNING From 8aa8d2c88011adafa5b84ac0adaa7ff88eddda35 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Jun 2020 16:59:37 +0900 Subject: [PATCH 303/871] Resolve NREs --- osu.Desktop/OsuGameDesktop.cs | 2 +- osu.Game.Tests/Chat/MessageFormatterTests.cs | 10 +++++----- .../Visual/UserInterface/TestSceneOsuIcon.cs | 2 +- .../Converters/TypedListConverter.cs | 18 +++++++++++++++--- osu.Game/Online/API/APIAccess.cs | 2 +- 5 files changed, 23 insertions(+), 11 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 9351e17419..5f74883803 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -59,7 +59,7 @@ namespace osu.Desktop try { using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) - stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); + stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString()?.Split('"')[1].Replace("osu!.exe", ""); if (checkExists(stableInstallPath)) return stableInstallPath; diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index fbb0416c45..d1a859c84b 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -428,23 +428,23 @@ namespace osu.Game.Tests.Chat Assert.AreEqual(5, result.Links.Count); Link f = result.Links.Find(l => l.Url == "https://osu.ppy.sh/wiki/wiki links"); - Assert.AreEqual(44, f.Index); + Assert.AreEqual(44, f!.Index); Assert.AreEqual(10, f.Length); f = result.Links.Find(l => l.Url == "http://www.simple-test.com"); - Assert.AreEqual(10, f.Index); + Assert.AreEqual(10, f!.Index); Assert.AreEqual(11, f.Length); f = result.Links.Find(l => l.Url == "http://google.com"); - Assert.AreEqual(97, f.Index); + Assert.AreEqual(97, f!.Index); Assert.AreEqual(4, f.Length); f = result.Links.Find(l => l.Url == "https://osu.ppy.sh"); - Assert.AreEqual(78, f.Index); + Assert.AreEqual(78, f!.Index); Assert.AreEqual(18, f.Length); f = result.Links.Find(l => l.Url == "\uD83D\uDE12"); - Assert.AreEqual(101, f.Index); + Assert.AreEqual(101, f!.Index); Assert.AreEqual(3, f.Length); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs index 061039b297..246eb119e8 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs @@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.UserInterface }); foreach (var p in typeof(OsuIcon).GetProperties(BindingFlags.Public | BindingFlags.Static)) - flow.Add(new Icon($"{nameof(OsuIcon)}.{p.Name}", (IconUsage)p.GetValue(null))); + flow.Add(new Icon($"{nameof(OsuIcon)}.{p.Name}", (IconUsage)p.GetValue(null)!)); AddStep("toggle shadows", () => flow.Children.ForEach(i => i.SpriteIcon.Shadow = !i.SpriteIcon.Shadow)); AddStep("change icons", () => flow.Children.ForEach(i => i.SpriteIcon.Icon = new IconUsage((char)(i.SpriteIcon.Icon.Icon + 1)))); diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index f98fa05821..ddfdf08f52 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -41,13 +41,25 @@ namespace osu.Game.IO.Serialization.Converters var list = new List(); var obj = JObject.Load(reader); - var lookupTable = serializer.Deserialize>(obj["$lookup_table"].CreateReader()); - foreach (var tok in obj["$items"]) + if (!obj.TryGetValue("$lookup_table", out var lookupTableToken) || lookupTableToken == null) + return list; + + var lookupTable = serializer.Deserialize>(lookupTableToken.CreateReader()); + if (lookupTable == null) + return list; + + if (!obj.TryGetValue("$items", out var itemsToken) || itemsToken == null) + return list; + + foreach (var tok in itemsToken) { var itemReader = tok.CreateReader(); - var typeName = lookupTable[(int)tok["$type"]]; + if (!obj.TryGetValue("$type", out var typeToken) || typeToken == null) + throw new JsonException("Expected $type token."); + + var typeName = lookupTable[(int)typeToken]; var instance = (T)Activator.CreateInstance(Type.GetType(typeName)); serializer.Populate(itemReader, instance); diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 4945f7f185..f9e2da9af8 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -250,7 +250,7 @@ namespace osu.Game.Online.API { try { - return JObject.Parse(req.GetResponseString()).SelectToken("form_error", true).ToObject(); + return JObject.Parse(req.GetResponseString()).SelectToken("form_error", true)!.ToObject(); } catch { From 86a4664d9ba73522ae51320686f0f60082521a2e Mon Sep 17 00:00:00 2001 From: Power Maker Date: Wed, 3 Jun 2020 10:03:39 +0200 Subject: [PATCH 304/871] Add method for checking if cursor should rotate --- osu.Game/Graphics/Cursor/MenuCursor.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 1aa7b68d1a..e0b39ac311 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -83,8 +83,9 @@ namespace osu.Game.Graphics.Cursor activeCursor.AdditiveLayer.FadeInFromZero(800, Easing.OutQuint); } - if ((e.Button == MouseButton.Left || e.Button == MouseButton.Right) && cursorRotate.Value) + if (shouldRotate(e) && cursorRotate.Value) { + // if cursor is already rotating don't reset its rotate origin if (!(dragRotationState == DragRotationState.Rotating)) { dragRotationState = DragRotationState.DragStarted; @@ -97,13 +98,14 @@ namespace osu.Game.Graphics.Cursor protected override void OnMouseUp(MouseUpEvent e) { - if (!e.IsPressed(MouseButton.Left) && !e.IsPressed(MouseButton.Middle) && !e.IsPressed(MouseButton.Right)) + // cursor should go back to original size when none of main buttons are pressed + if (!(e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Middle) || e.IsPressed(MouseButton.Right))) { activeCursor.AdditiveLayer.FadeOutFromOne(500, Easing.OutQuint); activeCursor.ScaleTo(1, 500, Easing.OutElastic); } - if (!e.IsPressed(MouseButton.Left) && !e.IsPressed(MouseButton.Right)) + if (!shouldRotate(e)) { if (dragRotationState == DragRotationState.Rotating) activeCursor.RotateTo(0, 600 * (1 + Math.Abs(activeCursor.Rotation / 720)), Easing.OutElasticHalf); @@ -125,6 +127,14 @@ namespace osu.Game.Graphics.Cursor activeCursor.ScaleTo(0.6f, 250, Easing.In); } + private static bool shouldRotate(MouseEvent e) + { + if (e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Right)) + return true; + else + return false; + } + public class Cursor : Container { private Container cursorContainer; From 1ba3f0ac14dd2d293bf453972f26ebc3db98c544 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 17:31:55 +0900 Subject: [PATCH 305/871] Fix chat history not being loaded for multiplayer matches --- osu.Game/Online/Chat/ChannelManager.cs | 51 ++++++++++++++------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 53872ddcba..6812052eeb 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -93,12 +93,6 @@ namespace osu.Game.Online.Chat { if (!(e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel)) JoinChannel(e.NewValue); - - if (e.NewValue?.MessagesLoaded == false) - { - // let's fetch a small number of messages to bring us up-to-date with the backlog. - fetchInitalMessages(e.NewValue); - } } /// @@ -240,7 +234,6 @@ namespace osu.Game.Online.Chat } JoinChannel(channel); - CurrentChannel.Value = channel; break; case "help": @@ -275,7 +268,7 @@ namespace osu.Game.Online.Chat // join any channels classified as "defaults" if (joinDefaults && defaultChannels.Any(c => c.Equals(channel.Name, StringComparison.OrdinalIgnoreCase))) - JoinChannel(ch); + joinChannel(ch); } }; req.Failure += error => @@ -296,7 +289,7 @@ namespace osu.Game.Online.Chat /// The channel private void fetchInitalMessages(Channel channel) { - if (channel.Id <= 0) return; + if (channel.Id <= 0 || channel.MessagesLoaded) return; var fetchInitialMsgReq = new GetMessagesRequest(channel); fetchInitialMsgReq.Success += messages => @@ -351,9 +344,10 @@ namespace osu.Game.Online.Chat /// Joins a channel if it has not already been joined. /// /// The channel to join. - /// Whether the channel has already been joined server-side. Will skip a join request. /// The joined channel. Note that this may not match the parameter channel as it is a backed object. - public Channel JoinChannel(Channel channel, bool alreadyJoined = false) + public Channel JoinChannel(Channel channel) => joinChannel(channel, true); + + private Channel joinChannel(Channel channel, bool fetchInitialMessages = false) { if (channel == null) return null; @@ -362,21 +356,29 @@ namespace osu.Game.Online.Chat // ensure we are joined to the channel if (!channel.Joined.Value) { - if (alreadyJoined) - channel.Joined.Value = true; - else + switch (channel.Type) { - switch (channel.Type) - { - case ChannelType.Public: - var req = new JoinChannelRequest(channel, api.LocalUser.Value); - req.Success += () => JoinChannel(channel, true); - req.Failure += ex => LeaveChannel(channel); - api.Queue(req); - return channel; - } + case ChannelType.Private: + // can't do this yet. + break; + + default: + var req = new JoinChannelRequest(channel, api.LocalUser.Value); + req.Success += () => + { + channel.Joined.Value = true; + joinChannel(channel, fetchInitialMessages); + }; + req.Failure += ex => LeaveChannel(channel); + api.Queue(req); + return channel; } } + else + { + if (fetchInitialMessages) + fetchInitalMessages(channel); + } if (CurrentChannel.Value == null) CurrentChannel.Value = channel; @@ -420,7 +422,8 @@ namespace osu.Game.Online.Chat foreach (var channel in updates.Presence) { // we received this from the server so should mark the channel already joined. - JoinChannel(channel, true); + channel.Joined.Value = true; + joinChannel(channel); } //todo: handle left channels From 092f5b6521c1617d363d9c953a01438e4b38607e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Jun 2020 17:41:05 +0900 Subject: [PATCH 306/871] Fix incorrect reference + simplify --- .../Serialization/Converters/TypedListConverter.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs index ddfdf08f52..50b28ea74b 100644 --- a/osu.Game/IO/Serialization/Converters/TypedListConverter.cs +++ b/osu.Game/IO/Serialization/Converters/TypedListConverter.cs @@ -42,24 +42,24 @@ namespace osu.Game.IO.Serialization.Converters var obj = JObject.Load(reader); - if (!obj.TryGetValue("$lookup_table", out var lookupTableToken) || lookupTableToken == null) + if (obj["$lookup_table"] == null) return list; - var lookupTable = serializer.Deserialize>(lookupTableToken.CreateReader()); + var lookupTable = serializer.Deserialize>(obj["$lookup_table"].CreateReader()); if (lookupTable == null) return list; - if (!obj.TryGetValue("$items", out var itemsToken) || itemsToken == null) + if (obj["$items"] == null) return list; - foreach (var tok in itemsToken) + foreach (var tok in obj["$items"]) { var itemReader = tok.CreateReader(); - if (!obj.TryGetValue("$type", out var typeToken) || typeToken == null) + if (tok["$type"] == null) throw new JsonException("Expected $type token."); - var typeName = lookupTable[(int)typeToken]; + var typeName = lookupTable[(int)tok["$type"]]; var instance = (T)Activator.CreateInstance(Type.GetType(typeName)); serializer.Populate(itemReader, instance); From c0881e14ab176ac500e9308607b37f1368ac8d78 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Jun 2020 17:44:14 +0900 Subject: [PATCH 307/871] Add "struct can be made readonly" inspection --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 6e8ecb42d6..85be2077be 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -199,6 +199,7 @@ WARNING WARNING HINT + WARNING DO_NOT_SHOW DO_NOT_SHOW DO_NOT_SHOW From 3c7e5a5b42832d2d3b801b6baf09481b47a583e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 18:00:31 +0900 Subject: [PATCH 308/871] Fix ChannelManager not being loaded in tests --- osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 05b33e4386..0025a26baf 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -246,7 +246,12 @@ namespace osu.Game.Tests.Visual.Online { ((BindableList)ChannelManager.AvailableChannels).AddRange(channels); - Child = ChatOverlay = new TestChatOverlay { RelativeSizeAxes = Axes.Both, }; + InternalChildren = new Drawable[] + { + ChannelManager, + ChatOverlay = new TestChatOverlay { RelativeSizeAxes = Axes.Both, }, + }; + ChatOverlay.Show(); } } From c155ab83399a51bdab44a48d5805463c8520318a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 18:03:10 +0900 Subject: [PATCH 309/871] Check filenames and timestamps before reusing an already imported model --- osu.Game/Beatmaps/BeatmapManager.cs | 4 ++-- osu.Game/Database/ArchiveModelManager.cs | 22 +++++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f626b45e42..e7cef13c68 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -258,9 +258,9 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); - protected override bool CanUndelete(BeatmapSetInfo existing, BeatmapSetInfo import) + protected override bool CanReuseExisting(BeatmapSetInfo existing, BeatmapSetInfo import) { - if (!base.CanUndelete(existing, import)) + if (!base.CanReuseExisting(existing, import)) return false; var existingIds = existing.Beatmaps.Select(b => b.OnlineBeatmapID).OrderBy(i => i); diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index ae55a7b14a..4d7d3e96e6 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -332,7 +332,7 @@ namespace osu.Game.Database if (existing != null) { - if (CanUndelete(existing, item)) + if (CanReuseExisting(existing, item)) { Undelete(existing); LogForModel(item, $"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) – skipping import."); @@ -660,13 +660,29 @@ namespace osu.Game.Database protected TModel CheckForExisting(TModel model) => model.Hash == null ? null : ModelStore.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); /// - /// After an existing is found during an import process, the default behaviour is to restore the existing + /// After an existing is found during an import process, the default behaviour is to use/restore the existing /// item and skip the import. This method allows changing that behaviour. /// /// The existing model. /// The newly imported model. /// Whether the existing model should be restored and used. Returning false will delete the existing and force a re-import. - protected virtual bool CanUndelete(TModel existing, TModel import) => true; + protected virtual bool CanReuseExisting(TModel existing, TModel import) => + getFilenames(existing.Files).SequenceEqual(getFilenames(import.Files)) && + // poor-man's (cheap) equality comparison, avoiding hashing unnecessarily. + // can switch to full hash checks on a per-case basis (or for all) if we decide this is not a performance issue. + getTimestamps(existing.Files).SequenceEqual(getTimestamps(import.Files)); + + private IEnumerable getFilenames(List files) + { + foreach (var f in files.OrderBy(f => f.Filename)) + yield return f.Filename; + } + + private IEnumerable getTimestamps(List files) + { + foreach (var f in files.OrderBy(f => f.Filename)) + yield return File.GetLastWriteTimeUtc(Files.Storage.GetFullPath(f.FileInfo.StoragePath)).ToFileTime(); + } private DbSet queryModel() => ContextFactory.Get().Set(); From 012933545eccd4510dc3c0a70b040610dad0bf8d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 18:30:27 +0900 Subject: [PATCH 310/871] Add test coverage --- .../Beatmaps/IO/ImportBeatmapTest.cs | 161 ++++++++++++++++++ osu.Game/Database/ArchiveModelManager.cs | 2 +- 2 files changed, 162 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 5eb11a3264..12c9c92e90 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -12,6 +12,7 @@ using NUnit.Framework; using osu.Framework.Platform; using osu.Game.IPC; using osu.Framework.Allocation; +using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; @@ -22,6 +23,7 @@ using SharpCompress.Archives; using SharpCompress.Archives.Zip; using SharpCompress.Common; using SharpCompress.Writers.Zip; +using FileInfo = System.IO.FileInfo; namespace osu.Game.Tests.Beatmaps.IO { @@ -93,6 +95,165 @@ namespace osu.Game.Tests.Beatmaps.IO } } + [Test] + public async Task TestImportThenImportWithReZip() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithNewerTimestamp))) + { + try + { + var osu = loadOsu(host); + + var temp = TestResources.GetTestBeatmapForImport(); + + string extractedFolder = $"{temp}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + var imported = await LoadOszIntoOsu(osu); + + string hashBefore = hashFile(temp); + + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(extractedFolder); + zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); + } + + // zip files differ because different compression or encoder. + Assert.AreNotEqual(hashBefore, hashFile(temp)); + + var importedSecondTime = await osu.Dependencies.Get().Import(temp); + + 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); + } + finally + { + Directory.Delete(extractedFolder, true); + } + } + finally + { + host.Exit(); + } + } + } + + private string hashFile(string filename) + { + using (var s = File.OpenRead(filename)) + return s.ComputeMD5Hash(); + } + + [Test] + public async Task TestImportThenImportWithNewerTimestamp() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithNewerTimestamp))) + { + try + { + var osu = loadOsu(host); + + var temp = TestResources.GetTestBeatmapForImport(); + + string extractedFolder = $"{temp}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + var imported = await LoadOszIntoOsu(osu); + + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); + + // change timestamp + new FileInfo(Directory.GetFiles(extractedFolder).First()).LastWriteTime = DateTime.Now; + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(extractedFolder); + zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); + } + + var importedSecondTime = await osu.Dependencies.Get().Import(temp); + + 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); + } + finally + { + Directory.Delete(extractedFolder, true); + } + } + finally + { + host.Exit(); + } + } + } + + [Test] + public async Task TestImportThenImportWithDifferentFilename() + { + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithDifferentFilename))) + { + try + { + var osu = loadOsu(host); + + var temp = TestResources.GetTestBeatmapForImport(); + + string extractedFolder = $"{temp}_extracted"; + Directory.CreateDirectory(extractedFolder); + + try + { + var imported = await LoadOszIntoOsu(osu); + + using (var zip = ZipArchive.Open(temp)) + zip.WriteToDirectory(extractedFolder); + + // change filename + var firstFile = new FileInfo(Directory.GetFiles(extractedFolder).First()); + firstFile.MoveTo(Path.Combine(firstFile.DirectoryName, $"{firstFile.Name}-changed{firstFile.Extension}")); + + using (var zip = ZipArchive.Create()) + { + zip.AddAllFromDirectory(extractedFolder); + zip.SaveTo(temp, new ZipWriterOptions(CompressionType.Deflate)); + } + + var importedSecondTime = await osu.Dependencies.Get().Import(temp); + + 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); + } + finally + { + Directory.Delete(extractedFolder, true); + } + } + finally + { + host.Exit(); + } + } + } + [Test] public async Task TestImportCorruptThenImport() { diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 4d7d3e96e6..5ca9423de2 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -276,7 +276,7 @@ namespace osu.Game.Database // for now, concatenate all .osu files in the set to create a unique hash. MemoryStream hashable = new MemoryStream(); - foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(f.Filename.EndsWith))) + foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(f.Filename.EndsWith)).OrderBy(f => f.Filename)) { using (Stream s = Files.Store.GetStream(file.FileInfo.StoragePath)) s.CopyTo(hashable); From d002c0c03fbbbc2463edb9f9e1a8ee9b031a3ca0 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 3 Jun 2020 11:39:08 +0200 Subject: [PATCH 311/871] Revert piano reverb to a separate sample --- osu.Game/Screens/Menu/IntroWelcome.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 9f9012cb2b..7019e1f1a6 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -21,6 +21,7 @@ namespace osu.Game.Screens.Menu protected override string BeatmapFile => "welcome.osz"; private const double delay_step_two = 2142; private SampleChannel welcome; + private SampleChannel pianoReverb; [BackgroundDependencyLoader] private void load(AudioManager audio) @@ -28,7 +29,10 @@ namespace osu.Game.Screens.Menu Seeya = audio.Samples.Get(@"Intro/welcome/seeya"); if (MenuVoice.Value) + { welcome = audio.Samples.Get(@"Intro/welcome/welcome"); + pianoReverb = audio.Samples.Get(@"Intro/welcome/welcome_piano"); + } } protected override void LogoArriving(OsuLogo logo, bool resuming) @@ -38,9 +42,11 @@ namespace osu.Game.Screens.Menu if (!resuming) { welcome?.Play(); - StartTrack(); + pianoReverb?.Play(); + Scheduler.AddDelayed(delegate { + StartTrack(); PrepareMenuLoad(); logo.ScaleTo(1); From 6133c7d74784d0f848add452953ebf56aa39c0a2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 3 Jun 2020 18:51:02 +0900 Subject: [PATCH 312/871] Change suggestion to warning --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 85be2077be..85d5fce29a 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -141,6 +141,7 @@ WARNING WARNING WARNING + WARNING WARNING WARNING WARNING From 25160dc220d9f2f0bde4125f0bafd7446e3ad354 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 19:15:52 +0900 Subject: [PATCH 313/871] Fix test name --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 12c9c92e90..12f06059f7 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -98,7 +98,7 @@ namespace osu.Game.Tests.Beatmaps.IO [Test] public async Task TestImportThenImportWithReZip() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithNewerTimestamp))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithReZip))) { try { From f6d9f0597b970c9411c623392ffea35a8bcc0fe4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 21:28:29 +0900 Subject: [PATCH 314/871] Add implicit join logic for multiplayer rooms --- osu.Game/Online/Chat/ChannelManager.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 6812052eeb..b17e0812da 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -358,6 +358,13 @@ namespace osu.Game.Online.Chat { switch (channel.Type) { + case ChannelType.Multiplayer: + // join is implicit. happens when you join a multiplayer game. + // this will probably change in the future. + channel.Joined.Value = true; + joinChannel(channel, fetchInitialMessages); + return channel; + case ChannelType.Private: // can't do this yet. break; From 5ed3cd205f068d572caddc0ae01aa7ab8a580a6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 22:35:01 +0900 Subject: [PATCH 315/871] Simplify reuse check using FileInfo IDs --- .../Beatmaps/IO/ImportBeatmapTest.cs | 9 +++++---- osu.Game/Database/ArchiveModelManager.cs | 20 +++++++++---------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 12f06059f7..9b34eece5f 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -154,9 +154,9 @@ namespace osu.Game.Tests.Beatmaps.IO } [Test] - public async Task TestImportThenImportWithNewerTimestamp() + public async Task TestImportThenImportWithChangedFile() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithNewerTimestamp))) + using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportWithChangedFile))) { try { @@ -174,8 +174,9 @@ namespace osu.Game.Tests.Beatmaps.IO using (var zip = ZipArchive.Open(temp)) zip.WriteToDirectory(extractedFolder); - // change timestamp - new FileInfo(Directory.GetFiles(extractedFolder).First()).LastWriteTime = DateTime.Now; + // arbitrary write to non-hashed file + using (var sw = new FileInfo(Directory.GetFiles(extractedFolder, "*.mp3").First()).AppendText()) + sw.WriteLine("text"); using (var zip = ZipArchive.Create()) { diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 5ca9423de2..0fe8dd1268 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -667,10 +667,16 @@ namespace osu.Game.Database /// The newly imported model. /// Whether the existing model should be restored and used. Returning false will delete the existing and force a re-import. protected virtual bool CanReuseExisting(TModel existing, TModel import) => - getFilenames(existing.Files).SequenceEqual(getFilenames(import.Files)) && - // poor-man's (cheap) equality comparison, avoiding hashing unnecessarily. - // can switch to full hash checks on a per-case basis (or for all) if we decide this is not a performance issue. - getTimestamps(existing.Files).SequenceEqual(getTimestamps(import.Files)); + // for the best or worst, we copy and import files of a new import before checking whether + // it is a duplicate. so to check if anything has changed, we can just compare all FileInfo IDs. + getIDs(existing.Files).SequenceEqual(getIDs(import.Files)) && + getFilenames(existing.Files).SequenceEqual(getFilenames(import.Files)); + + private IEnumerable getIDs(List files) + { + foreach (var f in files.OrderBy(f => f.Filename)) + yield return f.FileInfo.ID; + } private IEnumerable getFilenames(List files) { @@ -678,12 +684,6 @@ namespace osu.Game.Database yield return f.Filename; } - private IEnumerable getTimestamps(List files) - { - foreach (var f in files.OrderBy(f => f.Filename)) - yield return File.GetLastWriteTimeUtc(Files.Storage.GetFullPath(f.FileInfo.StoragePath)).ToFileTime(); - } - private DbSet queryModel() => ContextFactory.Get().Set(); protected virtual string HumanisedModelName => $"{typeof(TModel).Name.Replace("Info", "").ToLower()}"; From 66ec2afe5cdcd9eb77adf5015966e5dcb652c1d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 3 Jun 2020 23:38:40 +0900 Subject: [PATCH 316/871] Remove broken import test --- .../Beatmaps/IO/ImportBeatmapTest.cs | 33 +------------------ 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 9b34eece5f..546bf758c1 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -374,37 +374,6 @@ namespace osu.Game.Tests.Beatmaps.IO } } - [Test] - public async Task TestImportThenImportDifferentHash() - { - // unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestImportThenImportDifferentHash))) - { - try - { - var osu = loadOsu(host); - var manager = osu.Dependencies.Get(); - - var imported = await LoadOszIntoOsu(osu); - - imported.Hash += "-changed"; - manager.Update(imported); - - var importedSecondTime = await LoadOszIntoOsu(osu); - - Assert.IsTrue(imported.ID != importedSecondTime.ID); - Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID); - - // only one beatmap will exist as the online set ID matched, causing purging of the first import. - checkBeatmapSetCount(osu, 1); - } - finally - { - host.Exit(); - } - } - } - [Test] public async Task TestImportThenDeleteThenImport() { From 89d973416a1f9807b0d44bdb519c1c846ae5816d Mon Sep 17 00:00:00 2001 From: Power Maker <42269909+power9maker@users.noreply.github.com> Date: Wed, 3 Jun 2020 20:35:44 +0200 Subject: [PATCH 317/871] Simplify shouldRotate method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bartłomiej Dach --- osu.Game/Graphics/Cursor/MenuCursor.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index e0b39ac311..40735d6de0 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -127,13 +127,7 @@ namespace osu.Game.Graphics.Cursor activeCursor.ScaleTo(0.6f, 250, Easing.In); } - private static bool shouldRotate(MouseEvent e) - { - if (e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Right)) - return true; - else - return false; - } + private static bool shouldRotate(MouseEvent e) => e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Right); public class Cursor : Container { From 3fa02a5782e95b0b92362ac83ad15ae6e1ae5caa Mon Sep 17 00:00:00 2001 From: Power Maker Date: Wed, 3 Jun 2020 20:43:47 +0200 Subject: [PATCH 318/871] Add method for any mouse button pressed. --- osu.Game/Graphics/Cursor/MenuCursor.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 40735d6de0..ad413f187a 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -15,6 +15,7 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osuTK.Input; using osu.Framework.Utils; +using osu.Game.Screens.Multi.Components; namespace osu.Game.Graphics.Cursor { @@ -83,7 +84,7 @@ namespace osu.Game.Graphics.Cursor activeCursor.AdditiveLayer.FadeInFromZero(800, Easing.OutQuint); } - if (shouldRotate(e) && cursorRotate.Value) + if (shouldRotateCursor(e) && cursorRotate.Value) { // if cursor is already rotating don't reset its rotate origin if (!(dragRotationState == DragRotationState.Rotating)) @@ -99,13 +100,13 @@ namespace osu.Game.Graphics.Cursor protected override void OnMouseUp(MouseUpEvent e) { // cursor should go back to original size when none of main buttons are pressed - if (!(e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Middle) || e.IsPressed(MouseButton.Right))) + if (!anyMouseButtonPressed(e)) { activeCursor.AdditiveLayer.FadeOutFromOne(500, Easing.OutQuint); activeCursor.ScaleTo(1, 500, Easing.OutElastic); } - if (!shouldRotate(e)) + if (!shouldRotateCursor(e)) { if (dragRotationState == DragRotationState.Rotating) activeCursor.RotateTo(0, 600 * (1 + Math.Abs(activeCursor.Rotation / 720)), Easing.OutElasticHalf); @@ -127,7 +128,9 @@ namespace osu.Game.Graphics.Cursor activeCursor.ScaleTo(0.6f, 250, Easing.In); } - private static bool shouldRotate(MouseEvent e) => e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Right); + private static bool shouldRotateCursor(MouseEvent e) => e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Right); + + private static bool anyMouseButtonPressed(MouseEvent e) => e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Middle) || e.IsPressed(MouseButton.Right); public class Cursor : Container { From eb15fc0bf9c4280de88ce215b98a4c8f4a36cdfa Mon Sep 17 00:00:00 2001 From: Power Maker Date: Wed, 3 Jun 2020 20:46:24 +0200 Subject: [PATCH 319/871] Remove unnecessary comment --- osu.Game/Graphics/Cursor/MenuCursor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index ad413f187a..33715ad7f1 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -99,7 +99,6 @@ namespace osu.Game.Graphics.Cursor protected override void OnMouseUp(MouseUpEvent e) { - // cursor should go back to original size when none of main buttons are pressed if (!anyMouseButtonPressed(e)) { activeCursor.AdditiveLayer.FadeOutFromOne(500, Easing.OutQuint); From 747ecd5ab23aaf5d625e6daee39d7cda2c6d826b Mon Sep 17 00:00:00 2001 From: Power Maker Date: Wed, 3 Jun 2020 20:50:37 +0200 Subject: [PATCH 320/871] Rename method to avoid confusion --- osu.Game/Graphics/Cursor/MenuCursor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 33715ad7f1..df3eabe7c6 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -99,7 +99,7 @@ namespace osu.Game.Graphics.Cursor protected override void OnMouseUp(MouseUpEvent e) { - if (!anyMouseButtonPressed(e)) + if (!anyMainButtonPressed(e)) { activeCursor.AdditiveLayer.FadeOutFromOne(500, Easing.OutQuint); activeCursor.ScaleTo(1, 500, Easing.OutElastic); @@ -129,7 +129,7 @@ namespace osu.Game.Graphics.Cursor private static bool shouldRotateCursor(MouseEvent e) => e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Right); - private static bool anyMouseButtonPressed(MouseEvent e) => e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Middle) || e.IsPressed(MouseButton.Right); + private static bool anyMainButtonPressed(MouseEvent e) => e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Middle) || e.IsPressed(MouseButton.Right); public class Cursor : Container { From ff220b2ebeece677fe1836fd0124f9a9939407de Mon Sep 17 00:00:00 2001 From: Power Maker Date: Wed, 3 Jun 2020 21:13:11 +0200 Subject: [PATCH 321/871] Remove unnecessary using statement. --- osu.Game/Graphics/Cursor/MenuCursor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index df3eabe7c6..f4a16c7727 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -15,7 +15,6 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; using osuTK.Input; using osu.Framework.Utils; -using osu.Game.Screens.Multi.Components; namespace osu.Game.Graphics.Cursor { From 939a76b08f32af92c6d425d7ff8003ad736d3126 Mon Sep 17 00:00:00 2001 From: Power Maker Date: Wed, 3 Jun 2020 21:42:23 +0200 Subject: [PATCH 322/871] Simplify negative equality expression --- osu.Game/Graphics/Cursor/MenuCursor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index f4a16c7727..507d218fb8 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -86,7 +86,7 @@ namespace osu.Game.Graphics.Cursor if (shouldRotateCursor(e) && cursorRotate.Value) { // if cursor is already rotating don't reset its rotate origin - if (!(dragRotationState == DragRotationState.Rotating)) + if (dragRotationState != DragRotationState.Rotating) { dragRotationState = DragRotationState.DragStarted; positionMouseDown = e.MousePosition; From 611f64fd364525be3f98c553ce9501a4c3505291 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 3 Jun 2020 23:23:56 +0300 Subject: [PATCH 323/871] Add base ready-made abstract scene for osu! mod tests --- osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs | 12 ++++++++++++ .../Mods/TestSceneOsuModDifficultyAdjust.cs | 5 +---- .../Mods/TestSceneOsuModDoubleTime.cs | 5 +---- .../Mods/TestSceneOsuModHidden.cs | 5 +---- 4 files changed, 15 insertions(+), 12 deletions(-) create mode 100644 osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs b/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs new file mode 100644 index 0000000000..7697f46160 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/Mods/OsuModTestScene.cs @@ -0,0 +1,12 @@ +// 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.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests.Mods +{ + public class OsuModTestScene : ModTestScene + { + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + } +} diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs index 7c396054f1..49c1fe8540 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDifficultyAdjust.cs @@ -9,14 +9,11 @@ using osu.Framework.Utils; using osu.Game.Graphics.Containers; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests.Mods { - public class TestSceneOsuModDifficultyAdjust : ModTestScene + public class TestSceneOsuModDifficultyAdjust : OsuModTestScene { - protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); - [Test] public void TestNoAdjustment() => CreateModTest(new ModTestData { diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs index 94ef6140e9..335ef31019 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModDoubleTime.cs @@ -4,14 +4,11 @@ using NUnit.Framework; using osu.Framework.Utils; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Osu.Tests.Mods { - public class TestSceneOsuModDoubleTime : ModTestScene + public class TestSceneOsuModDoubleTime : OsuModTestScene { - protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); - [TestCase(0.5)] [TestCase(1.01)] [TestCase(1.5)] diff --git a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs index 8ef2240c66..40f1c4a52f 100644 --- a/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs +++ b/osu.Game.Rulesets.Osu.Tests/Mods/TestSceneOsuModHidden.cs @@ -8,15 +8,12 @@ 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.Tests.Visual; using osuTK; namespace osu.Game.Rulesets.Osu.Tests.Mods { - public class TestSceneOsuModHidden : ModTestScene + public class TestSceneOsuModHidden : OsuModTestScene { - protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); - [Test] public void TestDefaultBeatmapTest() => CreateModTest(new ModTestData { From 11da045d8cc54111157e76a9f91e6ae93a7b2c3d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 3 Jun 2020 23:43:18 +0300 Subject: [PATCH 324/871] Reorder declaration position of ruleset-creation methods Should be recognized as a normal protected method in its declaring class. --- .../TestSceneHyperDash.cs | 1 - osu.Game/Tests/Visual/PlayerTestScene.cs | 17 +++++++------- osu.Game/Tests/Visual/SkinnableTestScene.cs | 23 +++++++++++-------- 3 files changed, 22 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index 83a6dc3d07..a0dcb86d57 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -16,7 +16,6 @@ namespace osu.Game.Rulesets.Catch.Tests [TestFixture] public class TestSceneHyperDash : TestSceneCatchPlayer { - protected override bool Autoplay => true; [Test] diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 53abf83e72..d663848bbf 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -24,15 +24,6 @@ namespace osu.Game.Tests.Visual protected OsuConfigManager LocalConfig; - /// - /// Creates the ruleset for setting up the component. - /// - [NotNull] - protected abstract Ruleset CreatePlayerRuleset(); - - protected sealed override Ruleset CreateRuleset() => CreatePlayerRuleset(); - - [NotNull] private readonly Ruleset ruleset; protected PlayerTestScene() @@ -97,6 +88,14 @@ namespace osu.Game.Tests.Visual LoadScreen(Player); } + /// + /// Creates the ruleset for setting up the component. + /// + [NotNull] + protected abstract Ruleset CreatePlayerRuleset(); + + protected sealed override Ruleset CreateRuleset() => CreatePlayerRuleset(); + protected virtual TestPlayer CreatePlayer(Ruleset ruleset) => new TestPlayer(false, false); } } diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index 98164031b0..41147d3768 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -23,27 +23,24 @@ namespace osu.Game.Tests.Visual { public abstract class SkinnableTestScene : OsuGridTestScene { + private readonly Ruleset ruleset; + private Skin metricsSkin; private Skin defaultSkin; private Skin specialSkin; private Skin oldSkin; - /// - /// Creates the ruleset for adding the ruleset-specific skin transforming component. - /// - [NotNull] - protected abstract Ruleset CreateRulesetForSkinProvider(); - - protected sealed override Ruleset CreateRuleset() => CreateRulesetForSkinProvider(); - protected SkinnableTestScene() : base(2, 3) { + ruleset = CreateRulesetForSkinProvider(); } [BackgroundDependencyLoader] private void load(AudioManager audio, SkinManager skinManager) { + Ruleset.Value = ruleset.RulesetInfo; + var dllStore = new DllResourceStore(DynamicCompilationOriginal.GetType().Assembly); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true); @@ -113,7 +110,7 @@ namespace osu.Game.Tests.Visual { new OutlineBox { Alpha = autoSize ? 1 : 0 }, mainProvider.WithChild( - new SkinProvidingContainer(CreateRulesetForSkinProvider().CreateLegacySkinProvider(mainProvider, beatmap)) + new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(mainProvider, beatmap)) { Child = created, RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None, @@ -126,6 +123,14 @@ namespace osu.Game.Tests.Visual }; } + /// + /// Creates the ruleset for adding the corresponding skin transforming component. + /// + [NotNull] + protected abstract Ruleset CreateRulesetForSkinProvider(); + + protected sealed override Ruleset CreateRuleset() => CreateRulesetForSkinProvider(); + protected virtual IBeatmap CreateBeatmapForSkinProvider() => CreateWorkingBeatmap(Ruleset.Value).GetPlayableBeatmap(Ruleset.Value); private class OutlineBox : CompositeDrawable From 136e10086acef397193487e11597623f2867f05b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 4 Jun 2020 00:37:06 +0300 Subject: [PATCH 325/871] Set the ruleset bindable value at the BDL for its subclasses usages There are test scenes using current value of ruleset bindable on their BDL (example in TestSceneSliderSnaking's BDL) --- osu.Game/Tests/Visual/PlayerTestScene.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index d663848bbf..1e267726e0 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -34,6 +34,8 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { + Ruleset.Value = ruleset.RulesetInfo; + Dependencies.Cache(LocalConfig = new OsuConfigManager(LocalStorage)); LocalConfig.GetBindable(OsuSetting.DimLevel).Value = 1.0; } @@ -67,7 +69,6 @@ namespace osu.Game.Tests.Visual var beatmap = CreateBeatmap(ruleset.RulesetInfo); Beatmap.Value = CreateWorkingBeatmap(beatmap); - Ruleset.Value = ruleset.RulesetInfo; SelectedMods.Value = Array.Empty(); if (!AllowFail) From bbad70c3f0101fed3121bb894f28b3d6884a1322 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 4 Jun 2020 00:40:24 +0300 Subject: [PATCH 326/871] Fix mod perfect test scenes failing due to null ruleset provided Just a workaround for now, a better fix may be to put the test data creation in an action that is guaranteed to be invoked after the test scene has fully loaded (all dependencies would've been resolved by then). --- osu.Game/Tests/Visual/ModPerfectTestScene.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Tests/Visual/ModPerfectTestScene.cs b/osu.Game/Tests/Visual/ModPerfectTestScene.cs index bfd540093b..93b38a149c 100644 --- a/osu.Game/Tests/Visual/ModPerfectTestScene.cs +++ b/osu.Game/Tests/Visual/ModPerfectTestScene.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual Mod = mod, Beatmap = new Beatmap { - BeatmapInfo = { Ruleset = Ruleset.Value }, + BeatmapInfo = { Ruleset = CreatePlayerRuleset().RulesetInfo }, HitObjects = { testData.HitObject } }, Autoplay = !shouldMiss, From c2fd2b861642a6fcac8412891d0365104c6ed6b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 Jun 2020 23:20:43 +0200 Subject: [PATCH 327/871] Add notes about draft PRs & pushing --- CONTRIBUTING.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 331534ad73..9666f249e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -92,6 +92,16 @@ Here are some key things to note before jumping in: As part of continuous integration, we also run code style analysis, which is supposed to make sure that your code is formatted the same way as all the pre-existing code in the repository. The reason we enforce a particular code style everywhere is to make sure the codebase is consistent in that regard - having one whitespace convention in one place and another one elsewhere causes disorganisation. +* **Make sure that the pull request is complete before opening it.** + + Whether it's fixing a bug or implementing new functionality, it's best that you make sure that the change you want to submit as a pull request is as complete as it can be before clicking the *Create pull request* button. Having to track if a pull request is ready for review or not places additional burden on reviewers. + + Draft pull requests are an option, but use them sparingly and within reason. They are best suited to discuss code changes that cannot be easily described in natural language or have a potential large impact on the future direction of the project. When in doubt, don't open drafts unless a maintainer asks you to do so. + +* **Only push code when it's ready.** + + As an extension of the above, when making changes to an already-open PR, please try to only push changes you are reasonably certain of. Pushing after every commit causes the continuous integration build queue to grow in size, slowing down work and taking up time that could be spent verifying other changes. + * **Make sure to keep the *Allow edits from maintainers* check box checked.** To speed up the merging process, collaborators and team members will sometimes want to push changes to your branch themselves, to make minor code style adjustments or to otherwise refactor the code without having to describe how they'd like the code to look like in painstaking detail. Having the *Allow edits from maintainers* check box checked lets them do that; without it they are forced to report issues back to you and wait for you to address them. From ddf5282d0e24d798a157d2c9704f8b30a7944731 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 Jun 2020 23:33:49 +0200 Subject: [PATCH 328/871] Move items from README.md to contributing guidelines --- CONTRIBUTING.md | 8 +++++++- README.md | 6 ------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9666f249e2..6c327f01b3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -52,7 +52,7 @@ Issues, bug reports and feature suggestions are welcomed, though please keep in ## I would like to submit a pull request! -We also welcome pull requests from unaffiliated contributors. The issue tracker should provide plenty of issues that you can work on; we also mark issues that we think would be good for newcomers with the [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label. +We also welcome pull requests from unaffiliated contributors. The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues that you can work on; we also mark issues that we think would be good for newcomers with the [`good-first-issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) label. However, do keep in mind that the core team is committed to bringing osu!lazer up to par with stable first and foremost, so depending on what your contribution concerns, it might not be merged and released right away. Our approach to managing issues and their priorities is described [in the wiki](https://github.com/ppy/osu/wiki/Project-management). @@ -62,12 +62,18 @@ Here are some key things to note before jumping in: While we are accepting of all kinds of contributions, we also have a certain quality standard we'd like to uphold and limited time to review your code. Therefore, we would like to avoid providing entry-level advice, and as such if you're not very familiar with C\# as a programming language, we'd recommend that you start off with a few personal projects to get acquainted with the language's syntax, toolchain and principles of object-oriented programming first. + In addition, please take the time to take a look at and get acquainted with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up. + * **Make sure you are familiar with git and the pull request workflow.** [git](https://git-scm.com/) is a distributed version control system that might not be very intuitive at the beginning if you're not familiar with version control. In particular, projects using git have a particular workflow for submitting code changes, which is called the pull request workflow. To make things run more smoothly, we recommend that you look up some online resources to familiarise yourself with the git vocabulary and commands, and practice working with forks and submitting pull requests at your own pace. A high-level overview of the process can be found in [this article by GitHub](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/proposing-changes-to-your-work-with-pull-requests). +* **Double-check designs before starting work on new functionality.** + + When implementing new features, keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time to ensure no effort is wasted. + * **Make sure to submit pull requests off of a topic branch.** As described in the article linked in the previous point, topic branches help you parallelise your work and separate it from the main `master` branch, and additionally are easier for maintainers to work with. Working with multiple `master` branches across many remotes is difficult to keep track of, and it's easy to make a mistake and push to the wrong `master` branch by accident. diff --git a/README.md b/README.md index 336bf33f7e..9e1cc20c8b 100644 --- a/README.md +++ b/README.md @@ -93,12 +93,6 @@ JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it ## Contributing -We welcome all contributions, but keep in mind that we already have a lot of the UI designed. If you wish to work on something with the intention of having it included in the official distribution, please open an issue for discussion and we will give you what you need from a design perspective to proceed. If you want to make *changes* to the design, we recommend you open an issue with your intentions before spending too much time to ensure no effort is wasted. - -If you're unsure of what you can help with, check out the [list of open issues](https://github.com/ppy/osu/issues) (especially those with the ["good first issue"](https://github.com/ppy/osu/issues?q=is%3Aopen+label%3Agood-first-issue+sort%3Aupdated-desc) label). - -Before starting, please make sure you are familiar with the [development and testing](https://github.com/ppy/osu-framework/wiki/Development-and-Testing) procedure we have set up. New component development, and where possible, bug fixing and debugging existing components **should always be done under VisualTests**. - Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured, with any libraries we are using, or with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as painless as possible. For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via PayPal or osu!supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project. From af3daaaeafd294a740d9586ee56e13858b75b5e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 3 Jun 2020 23:39:29 +0200 Subject: [PATCH 329/871] Add reference to contributing guidelines in README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9e1cc20c8b..dc3ee63844 100644 --- a/README.md +++ b/README.md @@ -93,6 +93,8 @@ JetBrains ReSharper InspectCode is also used for wider rule sets. You can run it ## Contributing +When it comes to contributing to the project, the two main things you can do to help out are reporting issues and submitting pull requests. Based on past experiences, we have prepared a [list of contributing guidelines](CONTRIBUTING.md) that should hopefully ease you into our collaboration process and answer the most frequently-asked questions. + Note that while we already have certain standards in place, nothing is set in stone. If you have an issue with the way code is structured, with any libraries we are using, or with any processes involved with contributing, *please* bring it up. We welcome all feedback so we can make contributing to this project as painless as possible. For those interested, we love to reward quality contributions via [bounties](https://docs.google.com/spreadsheets/d/1jNXfj_S3Pb5PErA-czDdC9DUu4IgUbe1Lt8E7CYUJuE/view?&rm=minimal#gid=523803337), paid out via PayPal or osu!supporter tags. Don't hesitate to [request a bounty](https://docs.google.com/forms/d/e/1FAIpQLSet_8iFAgPMG526pBZ2Kic6HSh7XPM3fE8xPcnWNkMzINDdYg/viewform) for your work on this project. From c72592c52ce200cd1500d261f349d72caa99dd41 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 4 Jun 2020 00:44:28 +0300 Subject: [PATCH 330/871] Remove bindable-disabling logic and don't tie immediately to CreateRuleset() --- osu.Game/Tests/Visual/OsuTestScene.cs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 1b0dff162b..88bd087215 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -72,22 +72,7 @@ namespace osu.Game.Tests.Visual Beatmap.SetDefault(); Ruleset = Dependencies.Ruleset; - - var definedRuleset = CreateRuleset()?.RulesetInfo; - - if (definedRuleset != null) - { - // re-enable the bindable in case it was disabled. - // happens when restarting current test scene. - Ruleset.Disabled = false; - - // Set global ruleset bindable to the ruleset defined - // for this test scene and disallow changing it. - Ruleset.Value = definedRuleset; - Ruleset.Disabled = true; - } - else - Ruleset.SetDefault(); + Ruleset.SetDefault(); SelectedMods = Dependencies.Mods; SelectedMods.SetDefault(); @@ -145,7 +130,7 @@ namespace osu.Game.Tests.Visual /// Creates the ruleset to be used for this test scene. /// /// - /// When testing against ruleset-specific components, this method must be overriden to their ruleset. + /// When testing against ruleset-specific components, this method must be overriden to their corresponding ruleset. /// [CanBeNull] protected virtual Ruleset CreateRuleset() => null; From 741fa201492c41b916744315f28593f1e3c57cd9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 4 Jun 2020 00:47:10 +0300 Subject: [PATCH 331/871] Use CreateRuleset() for editor test scenes as well --- .../TestSceneEditor.cs | 3 ++- .../TestSceneEditor.cs | 5 +---- .../TestSceneEditor.cs | 5 +---- .../Editing/TestSceneEditorChangeStates.cs | 8 +++----- osu.Game/Tests/Visual/EditorTestScene.cs | 19 +++++++++++-------- 5 files changed, 18 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs index 7ed886be49..3b9c03b86a 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneEditor.cs @@ -15,8 +15,9 @@ namespace osu.Game.Rulesets.Mania.Tests { private readonly Bindable direction = new Bindable(); + protected override Ruleset CreateEditorRuleset() => new ManiaRuleset(); + public TestSceneEditor() - : base(new ManiaRuleset()) { AddStep("upwards scroll", () => direction.Value = ManiaScrollingDirection.Up); AddStep("downwards scroll", () => direction.Value = ManiaScrollingDirection.Down); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs index 4aca34bf64..9239034a53 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneEditor.cs @@ -9,9 +9,6 @@ namespace osu.Game.Rulesets.Osu.Tests [TestFixture] public class TestSceneEditor : EditorTestScene { - public TestSceneEditor() - : base(new OsuRuleset()) - { - } + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); } } diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs index 089a7ad00b..411fe08bcf 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneEditor.cs @@ -9,9 +9,6 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestFixture] public class TestSceneEditor : EditorTestScene { - public TestSceneEditor() - : base(new TaikoRuleset()) - { - } + protected override Ruleset CreateEditorRuleset() => new TaikoRuleset(); } } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs index 20862e9cac..293a6e6869 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorChangeStates.cs @@ -4,6 +4,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Testing; +using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Objects; @@ -13,13 +14,10 @@ namespace osu.Game.Tests.Visual.Editing { public class TestSceneEditorChangeStates : EditorTestScene { - public TestSceneEditorChangeStates() - : base(new OsuRuleset()) - { - } - private EditorBeatmap editorBeatmap; + protected override Ruleset CreateEditorRuleset() => new OsuRuleset(); + public override void SetUpSteps() { base.SetUpSteps(); diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 2f6e6fb599..4f9a5b53b8 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Testing; using osu.Game.Rulesets; @@ -15,17 +16,11 @@ namespace osu.Game.Tests.Visual { protected Editor Editor { get; private set; } - private readonly Ruleset ruleset; - - protected EditorTestScene(Ruleset ruleset) - { - this.ruleset = ruleset; - } - [BackgroundDependencyLoader] private void load() { - Beatmap.Value = CreateWorkingBeatmap(ruleset.RulesetInfo); + Ruleset.Value = CreateEditorRuleset().RulesetInfo; + Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); } public override void SetUpSteps() @@ -37,6 +32,14 @@ namespace osu.Game.Tests.Visual && Editor.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); } + /// + /// Creates the ruleset for providing a corresponding beatmap to load the editor on. + /// + [NotNull] + protected abstract Ruleset CreateEditorRuleset(); + + protected sealed override Ruleset CreateRuleset() => CreateEditorRuleset(); + protected virtual Editor CreateEditor() => new Editor(); } } From 7e5db5e933aea2b39dbf7faf769f5e1ffba9322b Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 3 Jun 2020 23:49:06 +0200 Subject: [PATCH 332/871] Apply review suggestions --- .../Components/IPCErrorDialog.cs | 2 +- osu.Game.Tournament/IPC/FileBasedIPC.cs | 22 ++++++++++++------- .../Screens/StablePathSelectScreen.cs | 4 +--- 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tournament/Components/IPCErrorDialog.cs b/osu.Game.Tournament/Components/IPCErrorDialog.cs index 07fd0ac973..dc039cd3bc 100644 --- a/osu.Game.Tournament/Components/IPCErrorDialog.cs +++ b/osu.Game.Tournament/Components/IPCErrorDialog.cs @@ -18,7 +18,7 @@ namespace osu.Game.Tournament.Components new PopupDialogOkButton { Text = @"Alright.", - Action = () => { Expire(); } + Action = () => Expire() } }; } diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 44a010e506..aad44cd385 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -158,7 +158,7 @@ namespace osu.Game.Tournament.IPC return IPCStorage; } - public static bool CheckExists(string p) => File.Exists(Path.Combine(p, "ipc.txt")); + private static bool ipcFileExistsInDirectory(string p) => File.Exists(Path.Combine(p, "ipc.txt")); private string findStablePath() { @@ -183,8 +183,8 @@ namespace osu.Game.Tournament.IPC if (stableInstallPath != null) { - SaveStableConfig(stableInstallPath); - return null; + SetIPCLocation(stableInstallPath); + return stableInstallPath; } } @@ -196,8 +196,11 @@ namespace osu.Game.Tournament.IPC } } - public void SaveStableConfig(string path) + public bool SetIPCLocation(string path) { + if (!ipcFileExistsInDirectory(path)) + return false; + StableInfo.StablePath.Value = path; using (var stream = tournamentStorage.GetStream(STABLE_CONFIG, FileAccess.Write, FileMode.Create)) @@ -211,6 +214,9 @@ namespace osu.Game.Tournament.IPC DefaultValueHandling = DefaultValueHandling.Ignore, })); } + + LocateStableStorage(); + return true; } private string readStableConfig() @@ -239,7 +245,7 @@ namespace osu.Game.Tournament.IPC Logger.Log("Trying to find stable with environment variables"); string stableInstallPath = Environment.GetEnvironmentVariable("OSU_STABLE_PATH"); - if (CheckExists(stableInstallPath)) + if (ipcFileExistsInDirectory(stableInstallPath)) return stableInstallPath; } catch @@ -254,7 +260,7 @@ namespace osu.Game.Tournament.IPC Logger.Log("Trying to find stable in %LOCALAPPDATA%"); string stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"osu!"); - if (CheckExists(stableInstallPath)) + if (ipcFileExistsInDirectory(stableInstallPath)) return stableInstallPath; return null; @@ -265,7 +271,7 @@ namespace osu.Game.Tournament.IPC Logger.Log("Trying to find stable in dotfolders"); string stableInstallPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".osu"); - if (CheckExists(stableInstallPath)) + if (ipcFileExistsInDirectory(stableInstallPath)) return stableInstallPath; return null; @@ -280,7 +286,7 @@ namespace osu.Game.Tournament.IPC using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); - if (CheckExists(stableInstallPath)) + if (ipcFileExistsInDirectory(stableInstallPath)) return stableInstallPath; return null; diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index fee2696c4c..ad0c06e4f9 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -139,7 +139,7 @@ namespace osu.Game.Tournament.Screens var fileBasedIpc = ipc as FileBasedIPC; Logger.Log($"Changing Stable CE location to {target}"); - if (!FileBasedIPC.CheckExists(target)) + if (!fileBasedIpc?.SetIPCLocation(target) ?? false) { overlay = new DialogOverlay(); overlay.Push(new IPCErrorDialog("This is an invalid IPC Directory", "Select a directory that contains an osu! stable cutting edge installation and make sure it has an empty ipc.txt file in it.")); @@ -148,8 +148,6 @@ namespace osu.Game.Tournament.Screens return; } - fileBasedIpc?.SaveStableConfig(target); - fileBasedIpc?.LocateStableStorage(); sceneManager?.SetScreen(typeof(SetupScreen)); } From 9920911390833ffb35fddffaaa62803ebf92ecd1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 4 Jun 2020 17:20:08 +0900 Subject: [PATCH 333/871] Fix tournament displayed beatmap potentially being out of order on quick changes --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 53ba597a7e..de4d482d13 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -34,6 +34,7 @@ namespace osu.Game.Tournament.IPC private int lastBeatmapId; private ScheduledDelegate scheduled; + private GetBeatmapRequest beatmapLookupRequest; public Storage Storage { get; private set; } @@ -77,6 +78,8 @@ namespace osu.Game.Tournament.IPC if (lastBeatmapId != beatmapId) { + beatmapLookupRequest?.Cancel(); + lastBeatmapId = beatmapId; var existing = ladder.CurrentMatch.Value?.Round.Value?.Beatmaps.FirstOrDefault(b => b.ID == beatmapId && b.BeatmapInfo != null); @@ -85,9 +88,9 @@ namespace osu.Game.Tournament.IPC Beatmap.Value = existing.BeatmapInfo; else { - var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = beatmapId }); - req.Success += b => Beatmap.Value = b.ToBeatmap(Rulesets); - API.Queue(req); + beatmapLookupRequest = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = beatmapId }); + beatmapLookupRequest.Success += b => Beatmap.Value = b.ToBeatmap(Rulesets); + API.Queue(beatmapLookupRequest); } } From 5d7bb8cb4e9e44eeb8a504f7d8e43f9046003aca Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 4 Jun 2020 21:33:38 +0900 Subject: [PATCH 334/871] Change format of date on score panel --- osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index fd8ac33aef..81d5d113ae 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -211,7 +211,7 @@ namespace osu.Game.Screens.Ranking.Expanded Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), - Text = $"Played on {score.Date.ToLocalTime():g}" + Text = $"Played on {score.Date.ToLocalTime():d MMMM yyyy HH:mm}" } } }; From afcefe01bf74177240c59e70b3ea2b87745a4223 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 4 Jun 2020 21:48:55 +0900 Subject: [PATCH 335/871] Fix score panel not receiving input in some places --- osu.Game/Screens/Ranking/ScorePanel.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index a99b48e8f0..65fb901c89 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -243,5 +243,10 @@ namespace osu.Game.Screens.Ranking return true; } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) + => base.ReceivePositionalInputAt(screenSpacePos) + || topLayerContainer.ReceivePositionalInputAt(screenSpacePos) + || middleLayerContainer.ReceivePositionalInputAt(screenSpacePos); } } From 9c1542f8979637fcea83b327b5252f2fae8933a1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 4 Jun 2020 22:17:00 +0900 Subject: [PATCH 336/871] Fix crash when pressing clear button twice --- .../Settings/TestSceneKeyBindingPanel.cs | 45 ++++++++++++++++++- osu.Game/Overlays/KeyBinding/KeyBindingRow.cs | 5 ++- 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs index 745820696a..3d335995ac 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneKeyBindingPanel.cs @@ -1,13 +1,19 @@ // 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 System.Linq; using NUnit.Framework; +using osu.Framework.Testing; +using osu.Framework.Threading; using osu.Game.Overlays; +using osu.Game.Overlays.KeyBinding; +using osuTK.Input; namespace osu.Game.Tests.Visual.Settings { [TestFixture] - public class TestSceneKeyBindingPanel : OsuTestScene + public class TestSceneKeyBindingPanel : OsuManualInputManagerTestScene { private readonly KeyBindingPanel panel; @@ -21,5 +27,42 @@ namespace osu.Game.Tests.Visual.Settings base.LoadComplete(); panel.Show(); } + + [Test] + public void TestClickTwiceOnClearButton() + { + KeyBindingRow firstRow = null; + + AddStep("click first row", () => + { + firstRow = panel.ChildrenOfType().First(); + InputManager.MoveMouseTo(firstRow); + InputManager.Click(MouseButton.Left); + }); + + AddStep("schedule button clicks", () => + { + var clearButton = firstRow.ChildrenOfType().Single(); + + InputManager.MoveMouseTo(clearButton); + + int buttonClicks = 0; + ScheduledDelegate clickDelegate = null; + + clickDelegate = Scheduler.AddDelayed(() => + { + InputManager.PressButton(MouseButton.Left); + InputManager.ReleaseButton(MouseButton.Left); + + if (++buttonClicks == 2) + { + // ReSharper disable once AccessToModifiedClosure + Debug.Assert(clickDelegate != null); + // ReSharper disable once AccessToModifiedClosure + clickDelegate.Cancel(); + } + }, 0, true); + }); + } } } diff --git a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs index 01d5991d3e..eafb4572ca 100644 --- a/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs +++ b/osu.Game/Overlays/KeyBinding/KeyBindingRow.cs @@ -274,6 +274,9 @@ namespace osu.Game.Overlays.KeyBinding private void clear() { + if (bindTarget == null) + return; + bindTarget.UpdateKeyCombination(InputKey.None); finalise(); } @@ -333,7 +336,7 @@ namespace osu.Game.Overlays.KeyBinding } } - private class ClearButton : TriangleButton + public class ClearButton : TriangleButton { public ClearButton() { From 6b88141e58b6d3863b1aeb9db41d39225cd00bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 4 Jun 2020 21:30:59 +0200 Subject: [PATCH 337/871] Add mania sample conversion test --- .../ManiaBeatmapSampleConversionTest.cs | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs new file mode 100644 index 0000000000..dbf1cf5f72 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs @@ -0,0 +1,72 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Utils; +using osu.Game.Audio; +using osu.Game.Rulesets.Mania.Objects; +using osu.Game.Rulesets.Objects; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Mania.Tests +{ + [TestFixture] + public class ManiaBeatmapSampleConversionTest : BeatmapConversionTest, SampleConvertValue> + { + protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; + + public void Test(string name) => base.Test(name); + + protected override IEnumerable CreateConvertValue(HitObject hitObject) + { + yield return new SampleConvertValue + { + StartTime = hitObject.StartTime, + EndTime = hitObject.GetEndTime(), + Column = ((ManiaHitObject)hitObject).Column, + NodeSamples = getSampleNames((hitObject as HoldNote)?.NodeSamples) + }; + } + + private IList> getSampleNames(List> hitSampleInfo) + => hitSampleInfo?.Select(samples => + (IList)samples.Select(sample => sample.LookupNames.First()).ToList()) + .ToList(); + + protected override Ruleset CreateRuleset() => new ManiaRuleset(); + } + + public struct SampleConvertValue : IEquatable + { + /// + /// A sane value to account for osu!stable using ints everywhere. + /// + private const float conversion_lenience = 2; + + public double StartTime; + public double EndTime; + public int Column; + public IList> NodeSamples; + + public bool Equals(SampleConvertValue other) + => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) + && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience) + && samplesEqual(NodeSamples, other.NodeSamples); + + private static bool samplesEqual(ICollection> first, ICollection> second) + { + if (first == null && second == null) + return true; + + // both items can't be null now, so if any single one is, then they're not equal + if (first == null || second == null) + return false; + + return first.Count == second.Count + && first.Zip(second).All(samples => samples.First.SequenceEqual(samples.Second)); + } + } +} From 35544ede50069851ff7cfa0fecdf141fe94345db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 4 Jun 2020 21:54:19 +0200 Subject: [PATCH 338/871] Add failing test cases --- .../ManiaBeatmapSampleConversionTest.cs | 2 ++ .../convert-samples-expected-conversion.json | 30 +++++++++++++++++++ .../Testing/Beatmaps/convert-samples.osu | 16 ++++++++++ .../mania-samples-expected-conversion.json | 25 ++++++++++++++++ .../Testing/Beatmaps/mania-samples.osu | 19 ++++++++++++ 5 files changed, 92 insertions(+) create mode 100644 osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json create mode 100644 osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu create mode 100644 osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json create mode 100644 osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples.osu diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs index dbf1cf5f72..2f6918d263 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Mania.Tests { protected override string ResourceAssembly => "osu.Game.Rulesets.Mania"; + [TestCase("convert-samples")] + [TestCase("mania-samples")] public void Test(string name) => base.Test(name); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json new file mode 100644 index 0000000000..b8ce85eef5 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json @@ -0,0 +1,30 @@ +{ + "Mappings": [{ + "StartTime": 1000.0, + "Objects": [{ + "StartTime": 1000.0, + "EndTime": 2750.0, + "Column": 1, + "NodeSamples": [ + ["normal-hitnormal"], + ["soft-hitnormal"], + ["drum-hitnormal"] + ] + }, { + "StartTime": 1875.0, + "EndTime": 2750.0, + "Column": 0, + "NodeSamples": [ + ["soft-hitnormal"], + ["drum-hitnormal"] + ] + }] + }, { + "StartTime": 3750.0, + "Objects": [{ + "StartTime": 3750.0, + "EndTime": 3750.0, + "Column": 3 + }] + }] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu new file mode 100644 index 0000000000..16b73992d2 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu @@ -0,0 +1,16 @@ +osu file format v14 + +[Difficulty] +HPDrainRate:5 +CircleSize:5 +OverallDifficulty:5 +ApproachRate:5 +SliderMultiplier:1.4 +SliderTickRate:1 + +[TimingPoints] +0,500,4,1,0,100,1,0 + +[HitObjects] +88,99,1000,6,0,L|306:259,2,245,0|0|0,1:0|2:0|3:0,0:0:0:0: +259,118,3750,1,0,0:0:0:0: diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json new file mode 100644 index 0000000000..e22540614d --- /dev/null +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json @@ -0,0 +1,25 @@ +{ + "Mappings": [{ + "StartTime": 500.0, + "Objects": [{ + "StartTime": 500.0, + "EndTime": 1500.0, + "Column": 0, + "NodeSamples": [ + ["normal-hitnormal"], + [] + ] + }] + }, { + "StartTime": 2000.0, + "Objects": [{ + "StartTime": 2000.0, + "EndTime": 3000.0, + "Column": 2, + "NodeSamples": [ + ["drum-hitnormal"], + [] + ] + }] + }] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples.osu b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples.osu new file mode 100644 index 0000000000..7c75b45e5f --- /dev/null +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples.osu @@ -0,0 +1,19 @@ +osu file format v14 + +[General] +Mode: 3 + +[Difficulty] +HPDrainRate:5 +CircleSize:5 +OverallDifficulty:5 +ApproachRate:5 +SliderMultiplier:1.4 +SliderTickRate:1 + +[TimingPoints] +0,500,4,1,0,100,1,0 + +[HitObjects] +51,192,500,128,0,1500:1:0:0:0: +256,192,2000,128,0,3000:3:0:0:0: From ac019bddd61b798f86c0f9e545a5d1e45de5a746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 4 Jun 2020 22:28:55 +0200 Subject: [PATCH 339/871] Only play samples at start of hold note in mania maps --- .../Beatmaps/ManiaBeatmapConverter.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs index 32abf5e7f9..b025ac7992 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/ManiaBeatmapConverter.cs @@ -6,6 +6,7 @@ using System; using System.Linq; using System.Collections.Generic; using osu.Framework.Utils; +using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -239,7 +240,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps Duration = endTimeData.Duration, Column = column, Samples = HitObject.Samples, - NodeSamples = (HitObject as IHasRepeats)?.NodeSamples + NodeSamples = (HitObject as IHasRepeats)?.NodeSamples ?? defaultNodeSamples }); } else if (HitObject is IHasXPosition) @@ -254,6 +255,16 @@ namespace osu.Game.Rulesets.Mania.Beatmaps return pattern; } + + /// + /// osu!mania-specific beatmaps in stable only play samples at the start of the hold note. + /// + private List> defaultNodeSamples + => new List> + { + HitObject.Samples, + new List() + }; } } } From c4cae006aa800e78f29b46dfcdccda0220838a60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 4 Jun 2020 22:47:14 +0200 Subject: [PATCH 340/871] Correctly slice node sample list when converting --- .../Legacy/DistanceObjectPatternGenerator.cs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 1bd796511b..b49b881656 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -472,15 +472,21 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// /// The time to retrieve the sample info list from. /// - private IList sampleInfoListAt(double time) + private IList sampleInfoListAt(double time) => nodeSamplesAt(time)?.First() ?? HitObject.Samples; + + /// + /// Retrieves the list of node samples that occur at time greater than or equal to . + /// + /// The time to retrieve node samples at. + private IEnumerable> nodeSamplesAt(double time) { if (!(HitObject is IHasPathWithRepeats curveData)) - return HitObject.Samples; + return null; double segmentTime = (EndTime - HitObject.StartTime) / spanCount; int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); - return curveData.NodeSamples[index]; + return curveData.NodeSamples.Skip(index); } /// @@ -511,7 +517,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy Duration = endTime - startTime, Column = column, Samples = HitObject.Samples, - NodeSamples = (HitObject as IHasRepeats)?.NodeSamples + NodeSamples = nodeSamplesAt(startTime)?.ToList() }; } From 4c6116e6e7c9aa1300430954ef4e6cbcc793f39b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 4 Jun 2020 23:50:58 +0200 Subject: [PATCH 341/871] Fix compilation failure in Android test project --- .../ManiaBeatmapSampleConversionTest.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs index 2f6918d263..d8f87195d1 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs @@ -58,17 +58,19 @@ namespace osu.Game.Rulesets.Mania.Tests && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience) && samplesEqual(NodeSamples, other.NodeSamples); - private static bool samplesEqual(ICollection> first, ICollection> second) + private static bool samplesEqual(ICollection> firstSampleList, ICollection> secondSampleList) { - if (first == null && second == null) + if (firstSampleList == null && secondSampleList == null) return true; // both items can't be null now, so if any single one is, then they're not equal - if (first == null || second == null) + if (firstSampleList == null || secondSampleList == null) return false; - return first.Count == second.Count - && first.Zip(second).All(samples => samples.First.SequenceEqual(samples.Second)); + return firstSampleList.Count == secondSampleList.Count + // cannot use .Zip() without the selector function as it doesn't compile in android test project + && firstSampleList.Zip(secondSampleList, (first, second) => (first, second)) + .All(samples => samples.first.SequenceEqual(samples.second)); } } } From 896177801a57e0bd2161309d05775be4d0d087bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 5 Jun 2020 00:07:27 +0200 Subject: [PATCH 342/871] Avoid creating copies of node samples every time --- .../Patterns/Legacy/DistanceObjectPatternGenerator.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index b49b881656..9fbdf58e21 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -478,7 +478,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy /// Retrieves the list of node samples that occur at time greater than or equal to . /// /// The time to retrieve node samples at. - private IEnumerable> nodeSamplesAt(double time) + private List> nodeSamplesAt(double time) { if (!(HitObject is IHasPathWithRepeats curveData)) return null; @@ -486,7 +486,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy double segmentTime = (EndTime - HitObject.StartTime) / spanCount; int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); - return curveData.NodeSamples.Skip(index); + + // avoid slicing the list & creating copies, if at all possible. + return index == 0 ? curveData.NodeSamples : curveData.NodeSamples.Skip(index).ToList(); } /// @@ -517,7 +519,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy Duration = endTime - startTime, Column = column, Samples = HitObject.Samples, - NodeSamples = nodeSamplesAt(startTime)?.ToList() + NodeSamples = nodeSamplesAt(startTime) }; } From c6c88a901ceaab95cab924c42b82e9723a601c30 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 5 Jun 2020 06:42:46 +0300 Subject: [PATCH 343/871] Add text box sample playback logic in OsuTextBox Moved from osu!framework. --- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 6f440d8138..f749326b0e 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -1,7 +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.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; @@ -11,6 +14,7 @@ using osuTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Utils; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Containers; using osuTK; @@ -19,6 +23,18 @@ namespace osu.Game.Graphics.UserInterface { public class OsuTextBox : BasicTextBox { + private readonly SampleChannel[] textAddedSamples = new SampleChannel[4]; + private SampleChannel capsTextAddedSample; + private SampleChannel textRemovedSample; + private SampleChannel textCommittedSample; + private SampleChannel caretMovedSample; + + /// + /// Whether to allow playing a different sample when inserting upper case text. + /// If set to false, same sample will be played for both letter cases. + /// + protected virtual bool AllowUpperCaseSamples => true; + protected override float LeftRightPadding => 10; protected override float CaretWidth => 3; @@ -41,15 +57,54 @@ namespace osu.Game.Graphics.UserInterface } [BackgroundDependencyLoader] - private void load(OsuColour colour) + private void load(OsuColour colour, AudioManager audio) { BackgroundUnfocused = Color4.Black.Opacity(0.5f); BackgroundFocused = OsuColour.Gray(0.3f).Opacity(0.8f); BackgroundCommit = BorderColour = colour.Yellow; + + for (int i = 0; i < textAddedSamples.Length; i++) + textAddedSamples[i] = audio.Samples.Get($@"Keyboard/key-press-{1 + i}"); + + capsTextAddedSample = audio.Samples.Get(@"Keyboard/key-caps"); + textRemovedSample = audio.Samples.Get(@"Keyboard/key-delete"); + textCommittedSample = audio.Samples.Get(@"Keyboard/key-confirm"); + caretMovedSample = audio.Samples.Get(@"Keyboard/key-movement"); } protected override Color4 SelectionColour => new Color4(249, 90, 255, 255); + protected override void OnTextAdded(string added) + { + base.OnTextAdded(added); + + if (added.Any(char.IsUpper) && AllowUpperCaseSamples) + capsTextAddedSample?.Play(); + else + textAddedSamples[RNG.Next(0, 3)]?.Play(); + } + + protected override void OnTextRemoved(string removed) + { + base.OnTextRemoved(removed); + + textRemovedSample?.Play(); + } + + protected override void OnTextCommitted(bool textChanged) + { + base.OnTextCommitted(textChanged); + + textCommittedSample?.Play(); + } + + protected override void OnCaretMoved(bool selecting) + { + base.OnCaretMoved(selecting); + + caretMovedSample?.Play(); + } + protected override void OnFocus(FocusEvent e) { BorderThickness = 3; From 178bbf16d180483397555d5d2137194deaa31fea Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 5 Jun 2020 06:44:41 +0300 Subject: [PATCH 344/871] Fix password text boxes having distinguishable key sounds Closes https://github.com/ppy/osu-framework/issues/3280 --- osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs index 0c82a869f8..11867cf103 100644 --- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs @@ -24,6 +24,8 @@ namespace osu.Game.Graphics.UserInterface Child = new PasswordMaskChar(CalculatedTextSize), }; + protected override bool AllowUpperCaseSamples => false; + protected override bool AllowClipboardExport => false; private readonly CapsWarning warning; From 495f89ddaebdb30558b571dd20346dce9ef04245 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 5 Jun 2020 06:45:42 +0300 Subject: [PATCH 345/871] Expand number text box test scene to one holding all OsuTextBox's types --- .../UserInterface/TestSceneNumberBox.cs | 48 ----------- .../UserInterface/TestSceneOsuTextBox.cs | 80 +++++++++++++++++++ 2 files changed, 80 insertions(+), 48 deletions(-) delete mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneNumberBox.cs create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneNumberBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneNumberBox.cs deleted file mode 100644 index 97a3f62b2d..0000000000 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneNumberBox.cs +++ /dev/null @@ -1,48 +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 NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Tests.Visual.UserInterface -{ - [TestFixture] - public class TestSceneNumberBox : OsuTestScene - { - private OsuNumberBox numberBox; - - [BackgroundDependencyLoader] - private void load() - { - Child = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Padding = new MarginPadding { Horizontal = 250 }, - Child = numberBox = new OsuNumberBox - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - PlaceholderText = "Insert numbers here" - } - }; - - clearInput(); - AddStep("enter numbers", () => numberBox.Text = "987654321"); - expectedValue("987654321"); - clearInput(); - AddStep("enter text + single number", () => numberBox.Text = "1 hello 2 world 3"); - expectedValue("123"); - clearInput(); - } - - private void clearInput() => AddStep("clear input", () => numberBox.Text = null); - - private void expectedValue(string value) => AddAssert("expect number", () => numberBox.Text == value); - } -} diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs new file mode 100644 index 0000000000..756928d3ec --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuTextBox.cs @@ -0,0 +1,80 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneOsuTextBox : OsuTestScene + { + private readonly OsuNumberBox numberBox; + + public TestSceneOsuTextBox() + { + Child = new Container + { + Masking = true, + CornerRadius = 10f, + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding(15f), + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.DarkSlateGray, + Alpha = 0.75f, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Padding = new MarginPadding(50f), + Spacing = new Vector2(0f, 50f), + Children = new[] + { + new OsuTextBox + { + Width = 500f, + PlaceholderText = "Normal textbox", + }, + new OsuPasswordTextBox + { + Width = 500f, + PlaceholderText = "Password textbox", + }, + numberBox = new OsuNumberBox + { + Width = 500f, + PlaceholderText = "Number textbox" + } + } + } + } + }; + } + + [Test] + public void TestNumberBox() + { + clearTextbox(numberBox); + AddStep("enter numbers", () => numberBox.Text = "987654321"); + expectedValue(numberBox, "987654321"); + + clearTextbox(numberBox); + AddStep("enter text + single number", () => numberBox.Text = "1 hello 2 world 3"); + expectedValue(numberBox, "123"); + + clearTextbox(numberBox); + } + + private void clearTextbox(OsuTextBox textBox) => AddStep("clear textbox", () => textBox.Text = null); + private void expectedValue(OsuTextBox textBox, string value) => AddAssert("expected textbox value", () => textBox.Text == value); + } +} From 0107e9ba16deb94cd8f04c5dcf36b7ca2a781adc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Jun 2020 19:18:00 +0900 Subject: [PATCH 346/871] Change lookups to use SingleOrDefault() --- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 668ac6ee10..1f92d5461f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -215,7 +215,7 @@ namespace osu.Game.Beatmaps foreach (var info in item.Beatmaps) { - var file = item.Files.FirstOrDefault(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + var file = item.Files.SingleOrDefault(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; using (var stream = Files.Store.GetStream(file)) info.MD5Hash = stream.ComputeMD5Hash(); diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index e62a9bb39d..39c5ccab27 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -42,7 +42,7 @@ namespace osu.Game.Beatmaps } } - private string getPathForFile(string filename) => BeatmapSetInfo.Files.FirstOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; + private string getPathForFile(string filename) => BeatmapSetInfo.Files.SingleOrDefault(f => string.Equals(f.Filename, filename, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; private TextureStore textureStore; From bb89114b70eb820096ae3dc1b371c0d3ce8c05c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 5 Jun 2020 20:52:27 +0900 Subject: [PATCH 347/871] Show a loading spinner on multiplayer lounge loads --- .../TestSceneLoungeRoomsContainer.cs | 3 ++ .../TestSceneMatchSettingsOverlay.cs | 2 ++ .../Multiplayer/TestSceneMatchSubScreen.cs | 2 ++ osu.Game/Screens/Multi/IRoomManager.cs | 5 +++ .../Screens/Multi/Lounge/LoungeSubScreen.cs | 33 +++++++++++++++++-- osu.Game/Screens/Multi/RoomManager.cs | 14 +++++++- 6 files changed, 56 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 77b41c89b0..83f2297bd2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -141,6 +141,9 @@ namespace osu.Game.Tests.Visual.Multiplayer } public readonly BindableList Rooms = new BindableList(); + + public Bindable InitialRoomsReceived { get; } = new Bindable(true); + IBindableList IRoomManager.Rooms => Rooms; public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) => Rooms.Add(room); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs index 34c6940552..fdc20dc477 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSettingsOverlay.cs @@ -133,6 +133,8 @@ namespace osu.Game.Tests.Visual.Multiplayer remove { } } + public Bindable InitialRoomsReceived { get; } = new Bindable(true); + public IBindableList Rooms { get; } = null; public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs index d678d5a814..9d0c159549 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs @@ -93,6 +93,8 @@ namespace osu.Game.Tests.Visual.Multiplayer remove => throw new NotImplementedException(); } + public Bindable InitialRoomsReceived { get; } = new Bindable(true); + public IBindableList Rooms { get; } = new BindableList(); public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) diff --git a/osu.Game/Screens/Multi/IRoomManager.cs b/osu.Game/Screens/Multi/IRoomManager.cs index f6c979851e..bf75843c3e 100644 --- a/osu.Game/Screens/Multi/IRoomManager.cs +++ b/osu.Game/Screens/Multi/IRoomManager.cs @@ -14,6 +14,11 @@ namespace osu.Game.Screens.Multi /// event Action RoomsUpdated; + /// + /// Whether an initial listing of rooms has been received. + /// + Bindable InitialRoomsReceived { get; } + /// /// All the active s. /// diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index 7c10f0f975..d4b6a3b79f 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -22,12 +22,16 @@ namespace osu.Game.Screens.Multi.Lounge protected readonly FilterControl Filter; + private readonly Bindable initialRoomsReceived = new Bindable(); + private readonly Container content; private readonly LoadingLayer loadingLayer; [Resolved] private Bindable selectedRoom { get; set; } + private bool joiningRoom; + public LoungeSubScreen() { SearchContainer searchContainer; @@ -73,6 +77,14 @@ namespace osu.Game.Screens.Multi.Lounge }; } + protected override void LoadComplete() + { + base.LoadComplete(); + + initialRoomsReceived.BindTo(RoomManager.InitialRoomsReceived); + initialRoomsReceived.BindValueChanged(onInitialRoomsReceivedChanged, true); + } + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -126,12 +138,29 @@ namespace osu.Game.Screens.Multi.Lounge private void joinRequested(Room room) { - loadingLayer.Show(); + joiningRoom = true; + updateLoadingLayer(); + RoomManager?.JoinRoom(room, r => { Open(room); + joiningRoom = false; + updateLoadingLayer(); + }, _ => + { + joiningRoom = false; + updateLoadingLayer(); + }); + } + + private void onInitialRoomsReceivedChanged(ValueChangedEvent received) => updateLoadingLayer(); + + private void updateLoadingLayer() + { + if (joiningRoom || !initialRoomsReceived.Value) + loadingLayer.Show(); + else loadingLayer.Hide(); - }, _ => loadingLayer.Hide()); } /// diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index ad461af57f..4d6ac46c84 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -25,6 +25,9 @@ namespace osu.Game.Screens.Multi public event Action RoomsUpdated; private readonly BindableList rooms = new BindableList(); + + public Bindable InitialRoomsReceived { get; } = new Bindable(); + public IBindableList Rooms => rooms; public double TimeBetweenListingPolls @@ -62,7 +65,11 @@ namespace osu.Game.Screens.Multi InternalChildren = new Drawable[] { - listingPollingComponent = new ListingPollingComponent { RoomsReceived = onListingReceived }, + listingPollingComponent = new ListingPollingComponent + { + InitialRoomsReceived = { BindTarget = InitialRoomsReceived }, + RoomsReceived = onListingReceived + }, selectionPollingComponent = new SelectionPollingComponent { RoomReceived = onSelectedRoomReceived } }; } @@ -262,6 +269,8 @@ namespace osu.Game.Screens.Multi { public Action> RoomsReceived; + public readonly Bindable InitialRoomsReceived = new Bindable(); + [Resolved] private IAPIProvider api { get; set; } @@ -273,6 +282,8 @@ namespace osu.Game.Screens.Multi { currentFilter.BindValueChanged(_ => { + InitialRoomsReceived.Value = false; + if (IsLoaded) PollImmediately(); }); @@ -292,6 +303,7 @@ namespace osu.Game.Screens.Multi pollReq.Success += result => { + InitialRoomsReceived.Value = true; RoomsReceived?.Invoke(result); tcs.SetResult(true); }; From 0f78af7252a08179cce65ee47d9bbe3acbf70c00 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 6 Jun 2020 19:19:30 +0300 Subject: [PATCH 348/871] Remove unnecessary disabled check I have a bad memory here, til. --- osu.Game/Tests/Visual/OsuTestScene.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index 88bd087215..e5d5442074 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -146,8 +146,7 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - if (!Ruleset.Disabled) - Ruleset.Value = rulesets.AvailableRulesets.First(); + Ruleset.Value = rulesets.AvailableRulesets.First(); } protected override void Dispose(bool isDisposing) From efd5e144103cfb7a06563674f23d712017550dd2 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Sat, 6 Jun 2020 19:20:06 +0300 Subject: [PATCH 349/871] Clarify why ruleset bindable must be set at the BDL of any base test scene --- osu.Game/Tests/Visual/PlayerTestScene.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 1e267726e0..05b1eea6b3 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -34,6 +34,8 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { + // There are test scenes using current value of the ruleset bindable + // on their BDLs (example in TestSceneSliderSnaking's BDL) Ruleset.Value = ruleset.RulesetInfo; Dependencies.Cache(LocalConfig = new OsuConfigManager(LocalStorage)); From 101604e741c70ffa92f0b10c39191d687749316b Mon Sep 17 00:00:00 2001 From: Craftplacer Date: Mon, 8 Jun 2020 00:39:33 +0200 Subject: [PATCH 350/871] Redesign classes and generally improve code --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 20 +++------ .../Sections/General/UpdateSettings.cs | 14 +++--- osu.Game/Updater/SimpleUpdateManager.cs | 15 +------ osu.Game/Updater/UpdateManager.cs | 43 +++++++++++++------ 4 files changed, 43 insertions(+), 49 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 5c553f18f4..c55917fb5f 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -22,33 +22,25 @@ namespace osu.Desktop.Updater { public class SquirrelUpdateManager : osu.Game.Updater.UpdateManager { - public override bool CanCheckForUpdate => true; - private UpdateManager updateManager; private NotificationOverlay notificationOverlay; - private OsuGameBase gameBase; public Task PrepareUpdateAsync() => UpdateManager.RestartAppWhenExited(); private static readonly Logger logger = Logger.GetLogger("updater"); [BackgroundDependencyLoader] - private void load(NotificationOverlay notification, OsuGameBase game) + private void load(NotificationOverlay notification) { - gameBase = game; notificationOverlay = notification; Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger)); - CheckForUpdate(); + Schedule(() => Task.Run(CheckForUpdateAsync)); } - public override void CheckForUpdate() - { - if (gameBase.IsDeployedBuild) - Schedule(() => Task.Run(() => checkForUpdateAsync())); - } + protected override async Task InternalCheckForUpdateAsync() => await checkForUpdateAsync(); - private async void checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) + private async Task checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) { // should we schedule a retry on completion of this check? bool scheduleRecheck = true; @@ -90,7 +82,7 @@ namespace osu.Desktop.Updater // could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959) // try again without deltas. - checkForUpdateAsync(false, notification); + await checkForUpdateAsync(false, notification); scheduleRecheck = false; } else @@ -109,7 +101,7 @@ namespace osu.Desktop.Updater if (scheduleRecheck) { // check again in 30 minutes. - Scheduler.AddDelayed(() => checkForUpdateAsync(), 60000 * 30); + Scheduler.AddDelayed(async () => await checkForUpdateAsync(), 60000 * 30); } } } diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 62d1ef162f..4a2a50885e 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.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.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Platform; @@ -28,15 +29,12 @@ namespace osu.Game.Overlays.Settings.Sections.General }); // We should only display the button for UpdateManagers that do check for updates - if (updateManager?.CanCheckForUpdate == true) + Add(new SettingsButton { - Add(new SettingsButton - { - Text = "Check for updates", - Action = updateManager.CheckForUpdate, - Enabled = { Value = game.IsDeployedBuild } - }); - } + Text = "Check for updates", + Action = () => Schedule(() => Task.Run(updateManager.CheckForUpdateAsync)), + Enabled = { Value = updateManager.CanCheckForUpdate } + }); if (RuntimeInfo.IsDesktop) { diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index d4e8aed5ae..78d27ab754 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -19,31 +19,20 @@ namespace osu.Game.Updater /// public class SimpleUpdateManager : UpdateManager { - public override bool CanCheckForUpdate => true; - private string version; [Resolved] private GameHost host { get; set; } - private OsuGameBase gameBase; - [BackgroundDependencyLoader] private void load(OsuGameBase game) { - gameBase = game; version = game.Version; - CheckForUpdate(); + Schedule(() => Task.Run(CheckForUpdateAsync)); } - public override void CheckForUpdate() - { - if (gameBase.IsDeployedBuild) - Schedule(() => Task.Run(checkForUpdateAsync)); - } - - private async void checkForUpdateAsync() + protected override async Task InternalCheckForUpdateAsync() { try { diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 41bbfb76a5..abe21f08a4 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -1,10 +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.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Overlays; @@ -18,9 +18,11 @@ namespace osu.Game.Updater public class UpdateManager : CompositeDrawable { /// - /// Whether this UpdateManager is capable of checking for updates. + /// Whether this UpdateManager should be or is capable of checking for updates. /// - public virtual bool CanCheckForUpdate => false; + public bool CanCheckForUpdate => game.IsDeployedBuild; + + private string lastVersion; [Resolved] private OsuConfigManager config { get; set; } @@ -35,24 +37,37 @@ namespace osu.Game.Updater { base.LoadComplete(); - var version = game.Version; - var lastVersion = config.Get(OsuSetting.Version); + Schedule(() => Task.Run(CheckForUpdateAsync)); - if (game.IsDeployedBuild && version != lastVersion) + // debug / local compilations will reset to a non-release string. + // can be useful to check when an install has transitioned between release and otherwise (see OsuConfigManager's migrations). + config.Set(OsuSetting.Version, game.Version); + } + + public async Task CheckForUpdateAsync() + { + if (!CanCheckForUpdate) + return; + + await InternalCheckForUpdateAsync(); + } + + protected virtual Task InternalCheckForUpdateAsync() + { + // Query last version only *once*, so the user can re-check for updates, in case they closed the notification or else. + lastVersion ??= config.Get(OsuSetting.Version); + + var version = game.Version; + + if (version != lastVersion) { // only show a notification if we've previously saved a version to the config file (ie. not the first run). if (!string.IsNullOrEmpty(lastVersion)) Notifications.Post(new UpdateCompleteNotification(version)); } - // debug / local compilations will reset to a non-release string. - // can be useful to check when an install has transitioned between release and otherwise (see OsuConfigManager's migrations). - config.Set(OsuSetting.Version, version); - } - - public virtual void CheckForUpdate() - { - Logger.Log("CheckForUpdate was called on the base class (UpdateManager)", LoggingTarget.Information); + // we aren't doing any async in this method, so we return a completed task instead. + return Task.CompletedTask; } private class UpdateCompleteNotification : SimpleNotification From 72ada020a2515a1ed839fecbeb0af52c3ce86abc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jun 2020 13:42:16 +0900 Subject: [PATCH 351/871] Don't attempt to use virtual track for intro sequence clock --- osu.Game/Screens/Menu/IntroTriangles.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 188a49c147..cb05dcc932 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -7,6 +7,7 @@ using System.IO; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Audio.Track; using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -61,7 +62,7 @@ namespace osu.Game.Screens.Menu LoadComponentAsync(new TrianglesIntroSequence(logo, background) { RelativeSizeAxes = Axes.Both, - Clock = new FramedClock(MenuMusic.Value ? Track : null), + Clock = new FramedClock(MenuMusic.Value && !(Track is TrackVirtual) ? Track : null), LoadMenu = LoadMenu }, t => { From dfed27bd4633e5b2d1268f8851fec97a698d61e6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Jun 2020 14:24:21 +0900 Subject: [PATCH 352/871] Add back stream seeking for sanity --- osu.Game/Beatmaps/BeatmapManager.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index f11e94e63d..4e3714a582 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -201,6 +201,8 @@ namespace osu.Game.Beatmaps using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) new LegacyBeatmapEncoder(beatmapContent).Encode(sw); + stream.Seek(0, SeekOrigin.Begin); + UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream); } From 443977aa8d71071a7566a4be643ffea72b77fee1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Jun 2020 14:40:17 +0900 Subject: [PATCH 353/871] Remove PreUpdate, update hash in Save() --- osu.Game/Beatmaps/BeatmapManager.cs | 22 ++++++++-------------- osu.Game/Database/ArchiveModelManager.cs | 11 ----------- 2 files changed, 8 insertions(+), 25 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4e3714a582..cbcdf51551 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -203,7 +203,14 @@ namespace osu.Game.Beatmaps stream.Seek(0, SeekOrigin.Begin); - UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream); + using (ContextFactory.GetForWrite()) + { + var beatmapInfo = setInfo.Beatmaps.Single(b => b.ID == info.ID); + beatmapInfo.MD5Hash = stream.ComputeMD5Hash(); + + stream.Seek(0, SeekOrigin.Begin); + UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream); + } } var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == info.ID); @@ -211,19 +218,6 @@ namespace osu.Game.Beatmaps workingCache.Remove(working); } - protected override void PreUpdate(BeatmapSetInfo item) - { - base.PreUpdate(item); - - foreach (var info in item.Beatmaps) - { - var file = item.Files.SingleOrDefault(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath; - - using (var stream = Files.Store.GetStream(file)) - info.MD5Hash = stream.ComputeMD5Hash(); - } - } - private readonly WeakList workingCache = new WeakList(); /// diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index b9479af623..915d980d24 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -429,21 +429,10 @@ namespace osu.Game.Database using (ContextFactory.GetForWrite()) { item.Hash = computeHash(item); - - PreUpdate(item); - ModelStore.Update(item); } } - /// - /// Perform any final actions before the update to database executes. - /// - /// The that is being updated. - protected virtual void PreUpdate(TModel item) - { - } - /// /// Delete an item from the manager. /// Is a no-op for already deleted items. From 63003757c4fee77ca055861cb0538019509138a9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jun 2020 14:48:26 +0900 Subject: [PATCH 354/871] Remove WorkingBeatmap cache when deleting or updating a beatmap --- osu.Game/Beatmaps/BeatmapManager.cs | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index e7cef13c68..73e4c119e4 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -79,6 +79,8 @@ namespace osu.Game.Beatmaps beatmaps = (BeatmapStore)ModelStore; beatmaps.BeatmapHidden += b => beatmapHidden.Value = new WeakReference(b); beatmaps.BeatmapRestored += b => beatmapRestored.Value = new WeakReference(b); + beatmaps.ItemRemoved += removeWorkingCache; + beatmaps.ItemUpdated += removeWorkingCache; onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); } @@ -206,9 +208,7 @@ namespace osu.Game.Beatmaps UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream); } - var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == info.ID); - if (working != null) - workingCache.Remove(working); + removeWorkingCache(info); } private readonly WeakList workingCache = new WeakList(); @@ -410,6 +410,24 @@ namespace osu.Game.Beatmaps return endTime - startTime; } + private void removeWorkingCache(BeatmapSetInfo info) + { + if (info.Beatmaps == null) return; + + foreach (var b in info.Beatmaps) + removeWorkingCache(b); + } + + private void removeWorkingCache(BeatmapInfo info) + { + lock (workingCache) + { + var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == info.ID); + if (working != null) + workingCache.Remove(working); + } + } + public void Dispose() { onlineLookupQueue?.Dispose(); From dd61d6ed04f47aa77739e974b29949a898d79c74 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jun 2020 14:48:42 +0900 Subject: [PATCH 355/871] Attempt to reimport intro if a bad state is detected --- osu.Game/Screens/Menu/IntroScreen.cs | 62 ++++++++++++++++--------- osu.Game/Screens/Menu/IntroTriangles.cs | 5 +- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 0d5f3d1142..b99d8ae9d1 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -41,9 +41,9 @@ namespace osu.Game.Screens.Menu protected IBindable MenuMusic { get; private set; } - private WorkingBeatmap introBeatmap; + private WorkingBeatmap initialBeatmap; - protected Track Track { get; private set; } + protected Track Track => initialBeatmap?.Track; private readonly BindableDouble exitingVolumeFade = new BindableDouble(1); @@ -58,6 +58,11 @@ namespace osu.Game.Screens.Menu [Resolved] private AudioManager audio { get; set; } + /// + /// Whether the is provided by osu! resources, rather than a user beatmap. + /// + protected bool UsingThemedIntro { get; private set; } + [BackgroundDependencyLoader] private void load(OsuConfigManager config, SkinManager skinManager, BeatmapManager beatmaps, Framework.Game game) { @@ -71,29 +76,45 @@ namespace osu.Game.Screens.Menu BeatmapSetInfo setInfo = null; + // if the user has requested not to play theme music, we should attempt to find a random beatmap from their collection. if (!MenuMusic.Value) { var sets = beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal); + if (sets.Count > 0) - setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID); - } - - if (setInfo == null) - { - setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); - - if (setInfo == null) { - // we need to import the default menu background beatmap - setInfo = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream($"Tracks/{BeatmapFile}"), BeatmapFile)).Result; - - setInfo.Protected = true; - beatmaps.Update(setInfo); + setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID); + initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); } } - introBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); - Track = introBeatmap.Track; + // we generally want a song to be playing on startup, so use the intro music even if a user has specified not to if no other track is available. + if (setInfo == null) + { + if (!loadThemedIntro()) + { + // 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); + + loadThemedIntro(); + } + } + + bool loadThemedIntro() + { + setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == BeatmapHash); + + if (setInfo != null) + { + initialBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]); + UsingThemedIntro = !(Track is TrackVirtual); + } + + return UsingThemedIntro; + } } public override void OnResuming(IScreen last) @@ -119,7 +140,7 @@ namespace osu.Game.Screens.Menu public override void OnSuspending(IScreen next) { base.OnSuspending(next); - Track = null; + initialBeatmap = null; } protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); @@ -127,7 +148,7 @@ namespace osu.Game.Screens.Menu protected void StartTrack() { // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. - if (MenuMusic.Value) + if (UsingThemedIntro) Track.Restart(); } @@ -141,8 +162,7 @@ namespace osu.Game.Screens.Menu if (!resuming) { - beatmap.Value = introBeatmap; - introBeatmap = null; + beatmap.Value = initialBeatmap; logo.MoveTo(new Vector2(0.5f)); logo.ScaleTo(Vector2.One); diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index cb05dcc932..225ad02ec4 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -7,7 +7,6 @@ using System.IO; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Audio.Track; using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -47,7 +46,7 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load() { - if (MenuVoice.Value && !MenuMusic.Value) + if (MenuVoice.Value && !UsingThemedIntro) welcome = audio.Samples.Get(@"welcome"); } @@ -62,7 +61,7 @@ namespace osu.Game.Screens.Menu LoadComponentAsync(new TrianglesIntroSequence(logo, background) { RelativeSizeAxes = Axes.Both, - Clock = new FramedClock(MenuMusic.Value && !(Track is TrackVirtual) ? Track : null), + Clock = new FramedClock(UsingThemedIntro ? Track : null), LoadMenu = LoadMenu }, t => { From 712fd6a944cc957bcff10721451ffe613c7180c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 8 Jun 2020 17:49:45 +0900 Subject: [PATCH 356/871] Fetch existing private message channels on re-joining --- .../API/Requests/CreateChannelRequest.cs | 34 +++++++++++++++++++ .../API/Requests/Responses/APIChatChannel.cs | 18 ++++++++++ osu.Game/Online/Chat/Channel.cs | 3 +- osu.Game/Online/Chat/ChannelManager.cs | 29 ++++++++++------ 4 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 osu.Game/Online/API/Requests/CreateChannelRequest.cs create mode 100644 osu.Game/Online/API/Requests/Responses/APIChatChannel.cs diff --git a/osu.Game/Online/API/Requests/CreateChannelRequest.cs b/osu.Game/Online/API/Requests/CreateChannelRequest.cs new file mode 100644 index 0000000000..42cb201969 --- /dev/null +++ b/osu.Game/Online/API/Requests/CreateChannelRequest.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.Linq; +using System.Net.Http; +using osu.Framework.IO.Network; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Online.Chat; + +namespace osu.Game.Online.API.Requests +{ + public class CreateChannelRequest : APIRequest + { + private readonly Channel channel; + + public CreateChannelRequest(Channel channel) + { + this.channel = channel; + } + + protected override WebRequest CreateWebRequest() + { + var req = base.CreateWebRequest(); + req.Method = HttpMethod.Post; + + req.AddParameter("type", $"{ChannelType.PM}"); + req.AddParameter("target_id", $"{channel.Users.First().Id}"); + + return req; + } + + protected override string Target => @"chat/channels"; + } +} diff --git a/osu.Game/Online/API/Requests/Responses/APIChatChannel.cs b/osu.Game/Online/API/Requests/Responses/APIChatChannel.cs new file mode 100644 index 0000000000..fc3b2a8e31 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APIChatChannel.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. + +using System.Collections.Generic; +using Newtonsoft.Json; +using osu.Game.Online.Chat; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APIChatChannel + { + [JsonProperty(@"channel_id")] + public int? ChannelID { get; set; } + + [JsonProperty(@"recent_messages")] + public List RecentMessages { get; set; } + } +} diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index dbb2da5c03..8c1e1ad128 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -84,7 +84,8 @@ namespace osu.Game.Online.Chat public long? LastReadId; /// - /// Signalles if the current user joined this channel or not. Defaults to false. + /// Signals if the current user joined this channel or not. Defaults to false. + /// Note that this does not guarantee a join has completed. Check Id > 0 for confirmation. /// public Bindable Joined = new Bindable(); diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index b17e0812da..9350887feb 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -86,7 +86,7 @@ namespace osu.Game.Online.Chat return; CurrentChannel.Value = JoinedChannels.FirstOrDefault(c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Users.Any(u => u.Id == user.Id)) - ?? new Channel(user); + ?? JoinChannel(new Channel(user)); } private void currentChannelChanged(ValueChangedEvent e) @@ -140,7 +140,7 @@ namespace osu.Game.Online.Chat target.AddLocalEcho(message); // if this is a PM and the first message, we need to do a special request to create the PM channel - if (target.Type == ChannelType.PM && !target.Joined.Value) + if (target.Type == ChannelType.PM && target.Id == 0) { var createNewPrivateMessageRequest = new CreateNewPrivateMessageRequest(target.Users.First(), message); @@ -356,26 +356,35 @@ namespace osu.Game.Online.Chat // ensure we are joined to the channel if (!channel.Joined.Value) { + channel.Joined.Value = true; + switch (channel.Type) { case ChannelType.Multiplayer: // join is implicit. happens when you join a multiplayer game. // this will probably change in the future. - channel.Joined.Value = true; joinChannel(channel, fetchInitialMessages); return channel; - case ChannelType.Private: - // can't do this yet. + case ChannelType.PM: + var createRequest = new CreateChannelRequest(channel); + createRequest.Success += resChannel => + { + if (resChannel.ChannelID.HasValue) + { + channel.Id = resChannel.ChannelID.Value; + + handleChannelMessages(resChannel.RecentMessages); + channel.MessagesLoaded = true; // this will mark the channel as having received messages even if there were none. + } + }; + + api.Queue(createRequest); break; default: var req = new JoinChannelRequest(channel, api.LocalUser.Value); - req.Success += () => - { - channel.Joined.Value = true; - joinChannel(channel, fetchInitialMessages); - }; + req.Success += () => joinChannel(channel, fetchInitialMessages); req.Failure += ex => LeaveChannel(channel); api.Queue(req); return channel; From ff555c41c6b667ebd91ba46f166cbd247ffeece3 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2020 08:57:44 +0000 Subject: [PATCH 357/871] Bump Sentry from 2.1.1 to 2.1.3 Bumps [Sentry](https://github.com/getsentry/sentry-dotnet) from 2.1.1 to 2.1.3. - [Release notes](https://github.com/getsentry/sentry-dotnet/releases) - [Commits](https://github.com/getsentry/sentry-dotnet/compare/2.1.1...2.1.3) Signed-off-by: dependabot-preview[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 4d6358575b..c41d0a0cf6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + From bbf8864f1478d609fe2eb7184cbd303e0cc9a14b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2020 09:45:31 +0000 Subject: [PATCH 358/871] Bump Microsoft.Build.Traversal from 2.0.34 to 2.0.48 Bumps [Microsoft.Build.Traversal](https://github.com/Microsoft/MSBuildSdks) from 2.0.34 to 2.0.48. - [Release notes](https://github.com/Microsoft/MSBuildSdks/releases) - [Changelog](https://github.com/microsoft/MSBuildSdks/blob/master/RELEASE.md) - [Commits](https://github.com/Microsoft/MSBuildSdks/compare/Microsoft.Build.Traversal.2.0.34...Microsoft.Build.Traversal.2.0.48) Signed-off-by: dependabot-preview[bot] --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 6c793a3f1d..bdb90eb0e9 100644 --- a/global.json +++ b/global.json @@ -5,6 +5,6 @@ "version": "3.1.100" }, "msbuild-sdks": { - "Microsoft.Build.Traversal": "2.0.34" + "Microsoft.Build.Traversal": "2.0.48" } } \ No newline at end of file From e0c94304c79637c86e9304e0471ce37bb139f223 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 8 Jun 2020 09:45:31 +0000 Subject: [PATCH 359/871] Bump Humanizer from 2.8.11 to 2.8.26 Bumps [Humanizer](https://github.com/Humanizr/Humanizer) from 2.8.11 to 2.8.26. - [Release notes](https://github.com/Humanizr/Humanizer/releases) - [Changelog](https://github.com/Humanizr/Humanizer/blob/master/release_notes.md) - [Commits](https://github.com/Humanizr/Humanizer/compare/v2.8.11...v2.8.26) Signed-off-by: dependabot-preview[bot] --- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index c41d0a0cf6..8213719c01 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -20,7 +20,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 6b55fa51ff..fd13455c63 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -76,7 +76,7 @@ - + From f80cdeac5ce2e6820dd3ba4cb0c6d6530e08105c Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 15:31:30 +0200 Subject: [PATCH 360/871] Change transforms to roughly match fallback visually --- osu.Game/Screens/Menu/IntroWelcome.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 7019e1f1a6..8110b973f6 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -127,7 +127,10 @@ namespace osu.Game.Screens.Menu { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Scale = new Vector2(0.5f), + Scale = new Vector2(0.3f), + Width = 750, + Height = 78, + Alpha = 0, Texture = textures.Get(@"Welcome/welcome_text@2x") }, }; @@ -139,10 +142,11 @@ namespace osu.Game.Screens.Menu double remainingTime() => delay_step_two - TransformDelay; - using (BeginDelayedSequence(250, true)) + using (BeginDelayedSequence(0, true)) { - welcomeText.FadeIn(700); - welcomeText.ScaleTo(welcomeText.Scale + new Vector2(0.5f), remainingTime(), Easing.Out).OnComplete(_ => + welcomeText.ResizeHeightTo(welcomeText.Height*2, 500, Easing.In); + welcomeText.FadeIn(remainingTime()); + welcomeText.ScaleTo(welcomeText.Scale + new Vector2(0.1f), remainingTime(), Easing.Out).OnComplete(_ => { elementContainer.Remove(visualizer); circleContainer.Remove(blackCircle); From 8a021e0beb39a897816d8da99983eb9de6e4b419 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 8 Jun 2020 22:35:01 +0900 Subject: [PATCH 361/871] Use save method in test --- .../Beatmaps/IO/ImportBeatmapTest.cs | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index 55368f6676..249a8caba9 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -1,11 +1,10 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// 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.IO; using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; @@ -15,7 +14,6 @@ using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Resources; @@ -730,25 +728,17 @@ namespace osu.Game.Tests.Beatmaps.IO await osu.Dependencies.Get().Import(temp); BeatmapSetInfo setToUpdate = manager.GetAllUsableBeatmapSets()[0]; + + var beatmapInfo = setToUpdate.Beatmaps.First(b => b.RulesetID == 0); Beatmap beatmapToUpdate = (Beatmap)manager.GetWorkingBeatmap(setToUpdate.Beatmaps.First(b => b.RulesetID == 0)).Beatmap; BeatmapSetFileInfo fileToUpdate = setToUpdate.Files.First(f => beatmapToUpdate.BeatmapInfo.Path.Contains(f.Filename)); string oldMd5Hash = beatmapToUpdate.BeatmapInfo.MD5Hash; - using (var stream = new MemoryStream()) - { - using (var writer = new StreamWriter(stream, Encoding.UTF8, 1024, true)) - { - beatmapToUpdate.HitObjects.Clear(); - beatmapToUpdate.HitObjects.Add(new HitCircle { StartTime = 5000 }); + beatmapToUpdate.HitObjects.Clear(); + beatmapToUpdate.HitObjects.Add(new HitCircle { StartTime = 5000 }); - new LegacyBeatmapEncoder(beatmapToUpdate).Encode(writer); - } - - stream.Seek(0, SeekOrigin.Begin); - - manager.UpdateFile(setToUpdate, fileToUpdate, stream); - } + manager.Save(beatmapInfo, beatmapToUpdate); // Check that the old file reference has been removed Assert.That(manager.QueryBeatmapSet(s => s.ID == setToUpdate.ID).Files.All(f => f.ID != fileToUpdate.ID)); From 229a40e6e36ffcba060dc4a2f8594a9f5f6eda60 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 15:39:15 +0200 Subject: [PATCH 362/871] Code formatting fixed Somehow slipped through after pushing --- osu.Game/Screens/Menu/IntroWelcome.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 8110b973f6..34be0b6a9f 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -144,7 +144,7 @@ namespace osu.Game.Screens.Menu using (BeginDelayedSequence(0, true)) { - welcomeText.ResizeHeightTo(welcomeText.Height*2, 500, Easing.In); + welcomeText.ResizeHeightTo(welcomeText.Height * 2, 500, Easing.In); welcomeText.FadeIn(remainingTime()); welcomeText.ScaleTo(welcomeText.Scale + new Vector2(0.1f), remainingTime(), Easing.Out).OnComplete(_ => { From e821d787b42993865b165f309d843cea5ae2bd38 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 20:13:02 +0200 Subject: [PATCH 363/871] Implement suggested changes Note: LogoVisualisation is likely going to be needed in a separate PR to conform to the review. --- osu.Game/Screens/Menu/IntroWelcome.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 34be0b6a9f..4534107ae8 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -44,7 +44,7 @@ namespace osu.Game.Screens.Menu welcome?.Play(); pianoReverb?.Play(); - Scheduler.AddDelayed(delegate + Scheduler.AddDelayed(() => { StartTrack(); PrepareMenuLoad(); @@ -146,16 +146,7 @@ namespace osu.Game.Screens.Menu { welcomeText.ResizeHeightTo(welcomeText.Height * 2, 500, Easing.In); welcomeText.FadeIn(remainingTime()); - welcomeText.ScaleTo(welcomeText.Scale + new Vector2(0.1f), remainingTime(), Easing.Out).OnComplete(_ => - { - elementContainer.Remove(visualizer); - circleContainer.Remove(blackCircle); - elementContainer.Remove(circleContainer); - Remove(welcomeText); - visualizer.Dispose(); - blackCircle.Dispose(); - welcomeText.Dispose(); - }); + welcomeText.ScaleTo(welcomeText.Scale + new Vector2(0.1f), remainingTime(), Easing.Out).OnComplete(_ => Expire()); } } } From 2a5e96002548e306ffe0837747029d0f3f62a4f1 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 21:15:51 +0200 Subject: [PATCH 364/871] Move user and skin specific settings to a subclass --- .../Screens/Menu/BasicLogoVisualisation.cs | 229 ++++++++++++++++++ osu.Game/Screens/Menu/LogoVisualisation.cs | 216 +---------------- 2 files changed, 231 insertions(+), 214 deletions(-) create mode 100644 osu.Game/Screens/Menu/BasicLogoVisualisation.cs diff --git a/osu.Game/Screens/Menu/BasicLogoVisualisation.cs b/osu.Game/Screens/Menu/BasicLogoVisualisation.cs new file mode 100644 index 0000000000..ab86c38cb4 --- /dev/null +++ b/osu.Game/Screens/Menu/BasicLogoVisualisation.cs @@ -0,0 +1,229 @@ +// 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 osuTK.Graphics; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Batches; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.OpenGL.Vertices; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Textures; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using System; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Utils; + +namespace osu.Game.Screens.Menu +{ + public class BasicLogoVisualisation : Drawable, IHasAccentColour + { + private readonly IBindable beatmap = new Bindable(); + + /// + /// The number of bars to jump each update iteration. + /// + private const int index_change = 5; + + /// + /// The maximum length of each bar in the visualiser. Will be reduced when kiai is not activated. + /// + private const float bar_length = 600; + + /// + /// The number of bars in one rotation of the visualiser. + /// + private const int bars_per_visualiser = 200; + + /// + /// How many times we should stretch around the circumference (overlapping overselves). + /// + private const float visualiser_rounds = 5; + + /// + /// How much should each bar go down each millisecond (based on a full bar). + /// + private const float decay_per_milisecond = 0.0024f; + + /// + /// Number of milliseconds between each amplitude update. + /// + private const float time_between_updates = 50; + + /// + /// The minimum amplitude to show a bar. + /// + private const float amplitude_dead_zone = 1f / bar_length; + + private int indexOffset; + + public Color4 AccentColour { get; set; } + + private readonly float[] frequencyAmplitudes = new float[256]; + + private IShader shader; + private readonly Texture texture; + + public BasicLogoVisualisation() + { + texture = Texture.WhitePixel; + Blending = BlendingParameters.Additive; + } + + [BackgroundDependencyLoader] + private void load(ShaderManager shaders, IBindable beatmap) + { + this.beatmap.BindTo(beatmap); + shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); + } + + private void updateAmplitudes() + { + var track = beatmap.Value.TrackLoaded ? beatmap.Value.Track : null; + var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.Beatmap?.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null; + + float[] temporalAmplitudes = track?.CurrentAmplitudes.FrequencyAmplitudes; + + for (int i = 0; i < bars_per_visualiser; i++) + { + if (track?.IsRunning ?? false) + { + float targetAmplitude = (temporalAmplitudes?[(i + indexOffset) % bars_per_visualiser] ?? 0) * (effect?.KiaiMode == true ? 1 : 0.5f); + if (targetAmplitude > frequencyAmplitudes[i]) + frequencyAmplitudes[i] = targetAmplitude; + } + else + { + int index = (i + index_change) % bars_per_visualiser; + if (frequencyAmplitudes[index] > frequencyAmplitudes[i]) + frequencyAmplitudes[i] = frequencyAmplitudes[index]; + } + } + + indexOffset = (indexOffset + index_change) % bars_per_visualiser; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + var delayed = Scheduler.AddDelayed(updateAmplitudes, time_between_updates, true); + delayed.PerformRepeatCatchUpExecutions = false; + } + + protected override void Update() + { + base.Update(); + + float decayFactor = (float)Time.Elapsed * decay_per_milisecond; + + for (int i = 0; i < bars_per_visualiser; i++) + { + //3% of extra bar length to make it a little faster when bar is almost at it's minimum + frequencyAmplitudes[i] -= decayFactor * (frequencyAmplitudes[i] + 0.03f); + if (frequencyAmplitudes[i] < 0) + frequencyAmplitudes[i] = 0; + } + + Invalidate(Invalidation.DrawNode); + } + + protected override DrawNode CreateDrawNode() => new VisualisationDrawNode(this); + + private class VisualisationDrawNode : DrawNode + { + protected new BasicLogoVisualisation Source => (BasicLogoVisualisation)base.Source; + + private IShader shader; + private Texture texture; + + // Assuming the logo is a circle, we don't need a second dimension. + private float size; + + private Color4 colour; + private float[] audioData; + + private readonly QuadBatch vertexBatch = new QuadBatch(100, 10); + + public VisualisationDrawNode(BasicLogoVisualisation source) + : base(source) + { + } + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + texture = Source.texture; + size = Source.DrawSize.X; + colour = Source.AccentColour; + audioData = Source.frequencyAmplitudes; + } + + public override void Draw(Action vertexAction) + { + base.Draw(vertexAction); + + shader.Bind(); + + Vector2 inflation = DrawInfo.MatrixInverse.ExtractScale().Xy; + + ColourInfo colourInfo = DrawColourInfo.Colour; + colourInfo.ApplyChild(colour); + + if (audioData != null) + { + for (int j = 0; j < visualiser_rounds; j++) + { + for (int i = 0; i < bars_per_visualiser; i++) + { + if (audioData[i] < amplitude_dead_zone) + continue; + + float rotation = MathUtils.DegreesToRadians(i / (float)bars_per_visualiser * 360 + j * 360 / visualiser_rounds); + float rotationCos = MathF.Cos(rotation); + float rotationSin = MathF.Sin(rotation); + // taking the cos and sin to the 0..1 range + var barPosition = new Vector2(rotationCos / 2 + 0.5f, rotationSin / 2 + 0.5f) * size; + + var barSize = new Vector2(size * MathF.Sqrt(2 * (1 - MathF.Cos(MathUtils.DegreesToRadians(360f / bars_per_visualiser)))) / 2f, bar_length * audioData[i]); + // The distance between the position and the sides of the bar. + var bottomOffset = new Vector2(-rotationSin * barSize.X / 2, rotationCos * barSize.X / 2); + // The distance between the bottom side of the bar and the top side. + var amplitudeOffset = new Vector2(rotationCos * barSize.Y, rotationSin * barSize.Y); + + var rectangle = new Quad( + Vector2Extensions.Transform(barPosition - bottomOffset, DrawInfo.Matrix), + Vector2Extensions.Transform(barPosition - bottomOffset + amplitudeOffset, DrawInfo.Matrix), + Vector2Extensions.Transform(barPosition + bottomOffset, DrawInfo.Matrix), + Vector2Extensions.Transform(barPosition + bottomOffset + amplitudeOffset, DrawInfo.Matrix) + ); + + DrawQuad( + texture, + rectangle, + colourInfo, + null, + vertexBatch.AddAction, + // barSize by itself will make it smooth more in the X axis than in the Y axis, this reverts that. + Vector2.Divide(inflation, barSize.Yx)); + } + } + } + + shader.Unbind(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + vertexBatch.Dispose(); + } + } + } +} diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 0db7f2a2dc..e893ef91bb 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -1,90 +1,24 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; using osuTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Batches; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.OpenGL.Vertices; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shaders; -using osu.Framework.Graphics.Textures; -using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Skinning; using osu.Game.Online.API; using osu.Game.Users; -using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; -using osu.Framework.Utils; namespace osu.Game.Screens.Menu { - public class LogoVisualisation : Drawable, IHasAccentColour + public class LogoVisualisation : BasicLogoVisualisation { - private readonly IBindable beatmap = new Bindable(); - - /// - /// The number of bars to jump each update iteration. - /// - private const int index_change = 5; - - /// - /// The maximum length of each bar in the visualiser. Will be reduced when kiai is not activated. - /// - private const float bar_length = 600; - - /// - /// The number of bars in one rotation of the visualiser. - /// - private const int bars_per_visualiser = 200; - - /// - /// How many times we should stretch around the circumference (overlapping overselves). - /// - private const float visualiser_rounds = 5; - - /// - /// How much should each bar go down each millisecond (based on a full bar). - /// - private const float decay_per_milisecond = 0.0024f; - - /// - /// Number of milliseconds between each amplitude update. - /// - private const float time_between_updates = 50; - - /// - /// The minimum amplitude to show a bar. - /// - private const float amplitude_dead_zone = 1f / bar_length; - - private int indexOffset; - - public Color4 AccentColour { get; set; } - - private readonly float[] frequencyAmplitudes = new float[256]; - - private IShader shader; - private readonly Texture texture; - private Bindable user; private Bindable skin; - public LogoVisualisation() - { - texture = Texture.WhitePixel; - Blending = BlendingParameters.Additive; - } - [BackgroundDependencyLoader] - private void load(ShaderManager shaders, IBindable beatmap, IAPIProvider api, SkinManager skinManager) + private void load(IAPIProvider api, SkinManager skinManager) { - this.beatmap.BindTo(beatmap); - shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); user = api.LocalUser.GetBoundCopy(); skin = skinManager.CurrentSkin.GetBoundCopy(); @@ -92,32 +26,6 @@ namespace osu.Game.Screens.Menu skin.BindValueChanged(_ => updateColour(), true); } - private void updateAmplitudes() - { - var track = beatmap.Value.TrackLoaded ? beatmap.Value.Track : null; - var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.Beatmap?.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null; - - float[] temporalAmplitudes = track?.CurrentAmplitudes.FrequencyAmplitudes; - - for (int i = 0; i < bars_per_visualiser; i++) - { - if (track?.IsRunning ?? false) - { - float targetAmplitude = (temporalAmplitudes?[(i + indexOffset) % bars_per_visualiser] ?? 0) * (effect?.KiaiMode == true ? 1 : 0.5f); - if (targetAmplitude > frequencyAmplitudes[i]) - frequencyAmplitudes[i] = targetAmplitude; - } - else - { - int index = (i + index_change) % bars_per_visualiser; - if (frequencyAmplitudes[index] > frequencyAmplitudes[i]) - frequencyAmplitudes[i] = frequencyAmplitudes[index]; - } - } - - indexOffset = (indexOffset + index_change) % bars_per_visualiser; - } - private void updateColour() { Color4 defaultColour = Color4.White.Opacity(0.2f); @@ -127,125 +35,5 @@ namespace osu.Game.Screens.Menu else AccentColour = defaultColour; } - - protected override void LoadComplete() - { - base.LoadComplete(); - - var delayed = Scheduler.AddDelayed(updateAmplitudes, time_between_updates, true); - delayed.PerformRepeatCatchUpExecutions = false; - } - - protected override void Update() - { - base.Update(); - - float decayFactor = (float)Time.Elapsed * decay_per_milisecond; - - for (int i = 0; i < bars_per_visualiser; i++) - { - //3% of extra bar length to make it a little faster when bar is almost at it's minimum - frequencyAmplitudes[i] -= decayFactor * (frequencyAmplitudes[i] + 0.03f); - if (frequencyAmplitudes[i] < 0) - frequencyAmplitudes[i] = 0; - } - - Invalidate(Invalidation.DrawNode); - } - - protected override DrawNode CreateDrawNode() => new VisualisationDrawNode(this); - - private class VisualisationDrawNode : DrawNode - { - protected new LogoVisualisation Source => (LogoVisualisation)base.Source; - - private IShader shader; - private Texture texture; - - // Assuming the logo is a circle, we don't need a second dimension. - private float size; - - private Color4 colour; - private float[] audioData; - - private readonly QuadBatch vertexBatch = new QuadBatch(100, 10); - - public VisualisationDrawNode(LogoVisualisation source) - : base(source) - { - } - - public override void ApplyState() - { - base.ApplyState(); - - shader = Source.shader; - texture = Source.texture; - size = Source.DrawSize.X; - colour = Source.AccentColour; - audioData = Source.frequencyAmplitudes; - } - - public override void Draw(Action vertexAction) - { - base.Draw(vertexAction); - - shader.Bind(); - - Vector2 inflation = DrawInfo.MatrixInverse.ExtractScale().Xy; - - ColourInfo colourInfo = DrawColourInfo.Colour; - colourInfo.ApplyChild(colour); - - if (audioData != null) - { - for (int j = 0; j < visualiser_rounds; j++) - { - for (int i = 0; i < bars_per_visualiser; i++) - { - if (audioData[i] < amplitude_dead_zone) - continue; - - float rotation = MathUtils.DegreesToRadians(i / (float)bars_per_visualiser * 360 + j * 360 / visualiser_rounds); - float rotationCos = MathF.Cos(rotation); - float rotationSin = MathF.Sin(rotation); - // taking the cos and sin to the 0..1 range - var barPosition = new Vector2(rotationCos / 2 + 0.5f, rotationSin / 2 + 0.5f) * size; - - var barSize = new Vector2(size * MathF.Sqrt(2 * (1 - MathF.Cos(MathUtils.DegreesToRadians(360f / bars_per_visualiser)))) / 2f, bar_length * audioData[i]); - // The distance between the position and the sides of the bar. - var bottomOffset = new Vector2(-rotationSin * barSize.X / 2, rotationCos * barSize.X / 2); - // The distance between the bottom side of the bar and the top side. - var amplitudeOffset = new Vector2(rotationCos * barSize.Y, rotationSin * barSize.Y); - - var rectangle = new Quad( - Vector2Extensions.Transform(barPosition - bottomOffset, DrawInfo.Matrix), - Vector2Extensions.Transform(barPosition - bottomOffset + amplitudeOffset, DrawInfo.Matrix), - Vector2Extensions.Transform(barPosition + bottomOffset, DrawInfo.Matrix), - Vector2Extensions.Transform(barPosition + bottomOffset + amplitudeOffset, DrawInfo.Matrix) - ); - - DrawQuad( - texture, - rectangle, - colourInfo, - null, - vertexBatch.AddAction, - // barSize by itself will make it smooth more in the X axis than in the Y axis, this reverts that. - Vector2.Divide(inflation, barSize.Yx)); - } - } - } - - shader.Unbind(); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - vertexBatch.Dispose(); - } - } } } From d52e3f938637e26aa46e643d57ee6ed4eb25cacd Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 21:26:48 +0200 Subject: [PATCH 365/871] Removed logovisualisation changes Now depends on https://github.com/ppy/osu/pull/9236 for accent color changes to apply --- osu.Game/Screens/Menu/IntroWelcome.cs | 1 - osu.Game/Screens/Menu/LogoVisualisation.cs | 8 ++------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 4534107ae8..c1cfccaa69 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -101,7 +101,6 @@ namespace osu.Game.Screens.Menu Anchor = Anchor.Centre, Origin = Anchor.Centre, Alpha = 0.5f, - IsIntro = true, AccentColour = Color4.DarkBlue, Size = new Vector2(0.96f) }, diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index c72b3a6576..0db7f2a2dc 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -70,7 +70,6 @@ namespace osu.Game.Screens.Menu private IShader shader; private readonly Texture texture; - public bool IsIntro = false; private Bindable user; private Bindable skin; @@ -89,11 +88,8 @@ namespace osu.Game.Screens.Menu user = api.LocalUser.GetBoundCopy(); skin = skinManager.CurrentSkin.GetBoundCopy(); - if (!IsIntro) - { - user.ValueChanged += _ => updateColour(); - skin.BindValueChanged(_ => updateColour(), true); - } + user.ValueChanged += _ => updateColour(); + skin.BindValueChanged(_ => updateColour(), true); } private void updateAmplitudes() From 0b6ae08c93b16c7c055e99f493d52a91ff922a20 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 21:31:03 +0200 Subject: [PATCH 366/871] Removed unneeded properties --- osu.Game/Screens/Menu/IntroWelcome.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index c1cfccaa69..38405fab6a 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -71,10 +71,6 @@ namespace osu.Game.Screens.Menu private class WelcomeIntroSequence : Container { private Sprite welcomeText; - private LogoVisualisation visualizer; - private Container elementContainer; - private Container circleContainer; - private Circle blackCircle; [BackgroundDependencyLoader] private void load(TextureStore textures) @@ -90,12 +86,12 @@ namespace osu.Game.Screens.Menu AutoSizeAxes = Axes.Both, Children = new Drawable[] { - elementContainer = new Container + new Container { AutoSizeAxes = Axes.Both, Children = new Drawable[] { - visualizer = new LogoVisualisation + new LogoVisualisation { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, @@ -104,12 +100,12 @@ namespace osu.Game.Screens.Menu AccentColour = Color4.DarkBlue, Size = new Vector2(0.96f) }, - circleContainer = new Container + new Container { AutoSizeAxes = Axes.Both, Children = new Drawable[] { - blackCircle = new Circle + new Circle { Anchor = Anchor.Centre, Origin = Anchor.Centre, From a60bb5feac2eae08be730b5def7e9a3df82c3c1d Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 8 Jun 2020 23:45:40 +0200 Subject: [PATCH 367/871] Rename baseclass, add xmldoc & change access to internal --- .../Screens/Menu/BasicLogoVisualisation.cs | 229 ----------------- osu.Game/Screens/Menu/LogoVisualisation.cs | 233 ++++++++++++++++-- .../Screens/Menu/MenuLogoVisualisation.cs | 39 +++ osu.Game/Screens/Menu/OsuLogo.cs | 4 +- 4 files changed, 254 insertions(+), 251 deletions(-) delete mode 100644 osu.Game/Screens/Menu/BasicLogoVisualisation.cs create mode 100644 osu.Game/Screens/Menu/MenuLogoVisualisation.cs diff --git a/osu.Game/Screens/Menu/BasicLogoVisualisation.cs b/osu.Game/Screens/Menu/BasicLogoVisualisation.cs deleted file mode 100644 index ab86c38cb4..0000000000 --- a/osu.Game/Screens/Menu/BasicLogoVisualisation.cs +++ /dev/null @@ -1,229 +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 osuTK; -using osuTK.Graphics; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Batches; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.OpenGL.Vertices; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shaders; -using osu.Framework.Graphics.Textures; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using System; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Utils; - -namespace osu.Game.Screens.Menu -{ - public class BasicLogoVisualisation : Drawable, IHasAccentColour - { - private readonly IBindable beatmap = new Bindable(); - - /// - /// The number of bars to jump each update iteration. - /// - private const int index_change = 5; - - /// - /// The maximum length of each bar in the visualiser. Will be reduced when kiai is not activated. - /// - private const float bar_length = 600; - - /// - /// The number of bars in one rotation of the visualiser. - /// - private const int bars_per_visualiser = 200; - - /// - /// How many times we should stretch around the circumference (overlapping overselves). - /// - private const float visualiser_rounds = 5; - - /// - /// How much should each bar go down each millisecond (based on a full bar). - /// - private const float decay_per_milisecond = 0.0024f; - - /// - /// Number of milliseconds between each amplitude update. - /// - private const float time_between_updates = 50; - - /// - /// The minimum amplitude to show a bar. - /// - private const float amplitude_dead_zone = 1f / bar_length; - - private int indexOffset; - - public Color4 AccentColour { get; set; } - - private readonly float[] frequencyAmplitudes = new float[256]; - - private IShader shader; - private readonly Texture texture; - - public BasicLogoVisualisation() - { - texture = Texture.WhitePixel; - Blending = BlendingParameters.Additive; - } - - [BackgroundDependencyLoader] - private void load(ShaderManager shaders, IBindable beatmap) - { - this.beatmap.BindTo(beatmap); - shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); - } - - private void updateAmplitudes() - { - var track = beatmap.Value.TrackLoaded ? beatmap.Value.Track : null; - var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.Beatmap?.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null; - - float[] temporalAmplitudes = track?.CurrentAmplitudes.FrequencyAmplitudes; - - for (int i = 0; i < bars_per_visualiser; i++) - { - if (track?.IsRunning ?? false) - { - float targetAmplitude = (temporalAmplitudes?[(i + indexOffset) % bars_per_visualiser] ?? 0) * (effect?.KiaiMode == true ? 1 : 0.5f); - if (targetAmplitude > frequencyAmplitudes[i]) - frequencyAmplitudes[i] = targetAmplitude; - } - else - { - int index = (i + index_change) % bars_per_visualiser; - if (frequencyAmplitudes[index] > frequencyAmplitudes[i]) - frequencyAmplitudes[i] = frequencyAmplitudes[index]; - } - } - - indexOffset = (indexOffset + index_change) % bars_per_visualiser; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - var delayed = Scheduler.AddDelayed(updateAmplitudes, time_between_updates, true); - delayed.PerformRepeatCatchUpExecutions = false; - } - - protected override void Update() - { - base.Update(); - - float decayFactor = (float)Time.Elapsed * decay_per_milisecond; - - for (int i = 0; i < bars_per_visualiser; i++) - { - //3% of extra bar length to make it a little faster when bar is almost at it's minimum - frequencyAmplitudes[i] -= decayFactor * (frequencyAmplitudes[i] + 0.03f); - if (frequencyAmplitudes[i] < 0) - frequencyAmplitudes[i] = 0; - } - - Invalidate(Invalidation.DrawNode); - } - - protected override DrawNode CreateDrawNode() => new VisualisationDrawNode(this); - - private class VisualisationDrawNode : DrawNode - { - protected new BasicLogoVisualisation Source => (BasicLogoVisualisation)base.Source; - - private IShader shader; - private Texture texture; - - // Assuming the logo is a circle, we don't need a second dimension. - private float size; - - private Color4 colour; - private float[] audioData; - - private readonly QuadBatch vertexBatch = new QuadBatch(100, 10); - - public VisualisationDrawNode(BasicLogoVisualisation source) - : base(source) - { - } - - public override void ApplyState() - { - base.ApplyState(); - - shader = Source.shader; - texture = Source.texture; - size = Source.DrawSize.X; - colour = Source.AccentColour; - audioData = Source.frequencyAmplitudes; - } - - public override void Draw(Action vertexAction) - { - base.Draw(vertexAction); - - shader.Bind(); - - Vector2 inflation = DrawInfo.MatrixInverse.ExtractScale().Xy; - - ColourInfo colourInfo = DrawColourInfo.Colour; - colourInfo.ApplyChild(colour); - - if (audioData != null) - { - for (int j = 0; j < visualiser_rounds; j++) - { - for (int i = 0; i < bars_per_visualiser; i++) - { - if (audioData[i] < amplitude_dead_zone) - continue; - - float rotation = MathUtils.DegreesToRadians(i / (float)bars_per_visualiser * 360 + j * 360 / visualiser_rounds); - float rotationCos = MathF.Cos(rotation); - float rotationSin = MathF.Sin(rotation); - // taking the cos and sin to the 0..1 range - var barPosition = new Vector2(rotationCos / 2 + 0.5f, rotationSin / 2 + 0.5f) * size; - - var barSize = new Vector2(size * MathF.Sqrt(2 * (1 - MathF.Cos(MathUtils.DegreesToRadians(360f / bars_per_visualiser)))) / 2f, bar_length * audioData[i]); - // The distance between the position and the sides of the bar. - var bottomOffset = new Vector2(-rotationSin * barSize.X / 2, rotationCos * barSize.X / 2); - // The distance between the bottom side of the bar and the top side. - var amplitudeOffset = new Vector2(rotationCos * barSize.Y, rotationSin * barSize.Y); - - var rectangle = new Quad( - Vector2Extensions.Transform(barPosition - bottomOffset, DrawInfo.Matrix), - Vector2Extensions.Transform(barPosition - bottomOffset + amplitudeOffset, DrawInfo.Matrix), - Vector2Extensions.Transform(barPosition + bottomOffset, DrawInfo.Matrix), - Vector2Extensions.Transform(barPosition + bottomOffset + amplitudeOffset, DrawInfo.Matrix) - ); - - DrawQuad( - texture, - rectangle, - colourInfo, - null, - vertexBatch.AddAction, - // barSize by itself will make it smooth more in the X axis than in the Y axis, this reverts that. - Vector2.Divide(inflation, barSize.Yx)); - } - } - } - - shader.Unbind(); - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - vertexBatch.Dispose(); - } - } - } -} diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index e893ef91bb..6a28740d4e 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -1,39 +1,232 @@ // 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 osuTK.Graphics; -using osu.Game.Skinning; -using osu.Game.Online.API; -using osu.Game.Users; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Batches; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.OpenGL.Vertices; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Textures; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using System; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Utils; namespace osu.Game.Screens.Menu { - public class LogoVisualisation : BasicLogoVisualisation + /// + /// A visualiser that reacts to music coming from beatmaps. + /// + public class LogoVisualisation : Drawable, IHasAccentColour { - private Bindable user; - private Bindable skin; + private readonly IBindable beatmap = new Bindable(); - [BackgroundDependencyLoader] - private void load(IAPIProvider api, SkinManager skinManager) + /// + /// The number of bars to jump each update iteration. + /// + private const int index_change = 5; + + /// + /// The maximum length of each bar in the visualiser. Will be reduced when kiai is not activated. + /// + private const float bar_length = 600; + + /// + /// The number of bars in one rotation of the visualiser. + /// + private const int bars_per_visualiser = 200; + + /// + /// How many times we should stretch around the circumference (overlapping overselves). + /// + private const float visualiser_rounds = 5; + + /// + /// How much should each bar go down each millisecond (based on a full bar). + /// + private const float decay_per_milisecond = 0.0024f; + + /// + /// Number of milliseconds between each amplitude update. + /// + private const float time_between_updates = 50; + + /// + /// The minimum amplitude to show a bar. + /// + private const float amplitude_dead_zone = 1f / bar_length; + + private int indexOffset; + + public Color4 AccentColour { get; set; } + + private readonly float[] frequencyAmplitudes = new float[256]; + + private IShader shader; + private readonly Texture texture; + + public LogoVisualisation() { - user = api.LocalUser.GetBoundCopy(); - skin = skinManager.CurrentSkin.GetBoundCopy(); - - user.ValueChanged += _ => updateColour(); - skin.BindValueChanged(_ => updateColour(), true); + texture = Texture.WhitePixel; + Blending = BlendingParameters.Additive; } - private void updateColour() + [BackgroundDependencyLoader] + private void load(ShaderManager shaders, IBindable beatmap) { - Color4 defaultColour = Color4.White.Opacity(0.2f); + this.beatmap.BindTo(beatmap); + shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); + } - if (user.Value?.IsSupporter ?? false) - AccentColour = skin.Value.GetConfig(GlobalSkinColours.MenuGlow)?.Value ?? defaultColour; - else - AccentColour = defaultColour; + private void updateAmplitudes() + { + var track = beatmap.Value.TrackLoaded ? beatmap.Value.Track : null; + var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.Beatmap?.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null; + + float[] temporalAmplitudes = track?.CurrentAmplitudes.FrequencyAmplitudes; + + for (int i = 0; i < bars_per_visualiser; i++) + { + if (track?.IsRunning ?? false) + { + float targetAmplitude = (temporalAmplitudes?[(i + indexOffset) % bars_per_visualiser] ?? 0) * (effect?.KiaiMode == true ? 1 : 0.5f); + if (targetAmplitude > frequencyAmplitudes[i]) + frequencyAmplitudes[i] = targetAmplitude; + } + else + { + int index = (i + index_change) % bars_per_visualiser; + if (frequencyAmplitudes[index] > frequencyAmplitudes[i]) + frequencyAmplitudes[i] = frequencyAmplitudes[index]; + } + } + + indexOffset = (indexOffset + index_change) % bars_per_visualiser; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + var delayed = Scheduler.AddDelayed(updateAmplitudes, time_between_updates, true); + delayed.PerformRepeatCatchUpExecutions = false; + } + + protected override void Update() + { + base.Update(); + + float decayFactor = (float)Time.Elapsed * decay_per_milisecond; + + for (int i = 0; i < bars_per_visualiser; i++) + { + //3% of extra bar length to make it a little faster when bar is almost at it's minimum + frequencyAmplitudes[i] -= decayFactor * (frequencyAmplitudes[i] + 0.03f); + if (frequencyAmplitudes[i] < 0) + frequencyAmplitudes[i] = 0; + } + + Invalidate(Invalidation.DrawNode); + } + + protected override DrawNode CreateDrawNode() => new VisualisationDrawNode(this); + + private class VisualisationDrawNode : DrawNode + { + protected new LogoVisualisation Source => (LogoVisualisation)base.Source; + + private IShader shader; + private Texture texture; + + // Assuming the logo is a circle, we don't need a second dimension. + private float size; + + private Color4 colour; + private float[] audioData; + + private readonly QuadBatch vertexBatch = new QuadBatch(100, 10); + + public VisualisationDrawNode(LogoVisualisation source) + : base(source) + { + } + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + texture = Source.texture; + size = Source.DrawSize.X; + colour = Source.AccentColour; + audioData = Source.frequencyAmplitudes; + } + + public override void Draw(Action vertexAction) + { + base.Draw(vertexAction); + + shader.Bind(); + + Vector2 inflation = DrawInfo.MatrixInverse.ExtractScale().Xy; + + ColourInfo colourInfo = DrawColourInfo.Colour; + colourInfo.ApplyChild(colour); + + if (audioData != null) + { + for (int j = 0; j < visualiser_rounds; j++) + { + for (int i = 0; i < bars_per_visualiser; i++) + { + if (audioData[i] < amplitude_dead_zone) + continue; + + float rotation = MathUtils.DegreesToRadians(i / (float)bars_per_visualiser * 360 + j * 360 / visualiser_rounds); + float rotationCos = MathF.Cos(rotation); + float rotationSin = MathF.Sin(rotation); + // taking the cos and sin to the 0..1 range + var barPosition = new Vector2(rotationCos / 2 + 0.5f, rotationSin / 2 + 0.5f) * size; + + var barSize = new Vector2(size * MathF.Sqrt(2 * (1 - MathF.Cos(MathUtils.DegreesToRadians(360f / bars_per_visualiser)))) / 2f, bar_length * audioData[i]); + // The distance between the position and the sides of the bar. + var bottomOffset = new Vector2(-rotationSin * barSize.X / 2, rotationCos * barSize.X / 2); + // The distance between the bottom side of the bar and the top side. + var amplitudeOffset = new Vector2(rotationCos * barSize.Y, rotationSin * barSize.Y); + + var rectangle = new Quad( + Vector2Extensions.Transform(barPosition - bottomOffset, DrawInfo.Matrix), + Vector2Extensions.Transform(barPosition - bottomOffset + amplitudeOffset, DrawInfo.Matrix), + Vector2Extensions.Transform(barPosition + bottomOffset, DrawInfo.Matrix), + Vector2Extensions.Transform(barPosition + bottomOffset + amplitudeOffset, DrawInfo.Matrix) + ); + + DrawQuad( + texture, + rectangle, + colourInfo, + null, + vertexBatch.AddAction, + // barSize by itself will make it smooth more in the X axis than in the Y axis, this reverts that. + Vector2.Divide(inflation, barSize.Yx)); + } + } + } + + shader.Unbind(); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + vertexBatch.Dispose(); + } } } } diff --git a/osu.Game/Screens/Menu/MenuLogoVisualisation.cs b/osu.Game/Screens/Menu/MenuLogoVisualisation.cs new file mode 100644 index 0000000000..5eb3f1efa0 --- /dev/null +++ b/osu.Game/Screens/Menu/MenuLogoVisualisation.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 osuTK.Graphics; +using osu.Game.Skinning; +using osu.Game.Online.API; +using osu.Game.Users; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions.Color4Extensions; + +namespace osu.Game.Screens.Menu +{ + internal class MenuLogoVisualisation : LogoVisualisation + { + private Bindable user; + private Bindable skin; + + [BackgroundDependencyLoader] + private void load(IAPIProvider api, SkinManager skinManager) + { + user = api.LocalUser.GetBoundCopy(); + skin = skinManager.CurrentSkin.GetBoundCopy(); + + user.ValueChanged += _ => updateColour(); + skin.BindValueChanged(_ => updateColour(), true); + } + + private void updateColour() + { + Color4 defaultColour = Color4.White.Opacity(0.2f); + + if (user.Value?.IsSupporter ?? false) + AccentColour = skin.Value.GetConfig(GlobalSkinColours.MenuGlow)?.Value ?? defaultColour; + else + AccentColour = defaultColour; + } + } +} diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 800520100e..9cadfd7df6 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -38,7 +38,7 @@ namespace osu.Game.Screens.Menu private readonly Container logoBeatContainer; private readonly Container logoAmplitudeContainer; private readonly Container logoHoverContainer; - private readonly LogoVisualisation visualizer; + private readonly MenuLogoVisualisation visualizer; private readonly IntroSequence intro; @@ -139,7 +139,7 @@ namespace osu.Game.Screens.Menu AutoSizeAxes = Axes.Both, Children = new Drawable[] { - visualizer = new LogoVisualisation + visualizer = new MenuLogoVisualisation { RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, From 44dd7d65bee35d52cce4de57ab79080decb3dce9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 9 Jun 2020 18:21:37 +0900 Subject: [PATCH 368/871] Fix duplicate scores showing --- osu.Game/Online/API/Requests/SubmitRoomScoreRequest.cs | 2 +- osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs | 10 +++------- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/API/Requests/SubmitRoomScoreRequest.cs b/osu.Game/Online/API/Requests/SubmitRoomScoreRequest.cs index 50b62cd6ed..8eb2952159 100644 --- a/osu.Game/Online/API/Requests/SubmitRoomScoreRequest.cs +++ b/osu.Game/Online/API/Requests/SubmitRoomScoreRequest.cs @@ -8,7 +8,7 @@ using osu.Game.Scoring; namespace osu.Game.Online.API.Requests { - public class SubmitRoomScoreRequest : APIRequest + public class SubmitRoomScoreRequest : APIRequest { private readonly int scoreId; private readonly int roomId; diff --git a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs index fbe9e3480f..cf0197d26b 100644 --- a/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs +++ b/osu.Game/Screens/Multi/Play/TimeshiftPlayer.cs @@ -97,22 +97,18 @@ namespace osu.Game.Screens.Multi.Play } protected override ScoreInfo CreateScore() - { - submitScore(); - return base.CreateScore(); - } - - private void submitScore() { var score = base.CreateScore(); - score.TotalScore = (int)Math.Round(ScoreProcessor.GetStandardisedScore()); Debug.Assert(token != null); var request = new SubmitRoomScoreRequest(token.Value, roomId.Value ?? 0, playlistItem.ID, score); + request.Success += s => score.OnlineScoreID = s.ID; request.Failure += e => Logger.Error(e, "Failed to submit score"); api.Queue(request); + + return score; } protected override void Dispose(bool isDisposing) From 4fd5ff61eb1b6a01543689c04b2f84daf9160dd0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 9 Jun 2020 18:53:55 +0900 Subject: [PATCH 369/871] Add loading spinner --- .../TestSceneTimeshiftResultsScreen.cs | 67 +++++++++++++++---- .../Multi/Ranking/TimeshiftResultsScreen.cs | 29 +++++++- 2 files changed, 81 insertions(+), 15 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs index 8559e7e2f4..66ebf9abda 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using NUnit.Framework; using osu.Game.Online.API; using osu.Game.Online.API.Requests; @@ -18,19 +19,52 @@ namespace osu.Game.Tests.Visual.Multiplayer { public class TestSceneTimeshiftResultsScreen : ScreenTestScene { + private bool roomsReceived; + + [SetUp] + public void Setup() => Schedule(() => + { + roomsReceived = false; + bindHandler(); + }); + [Test] public void TestShowResultsWithScore() { createResults(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + AddWaitStep("wait for display", 5); } [Test] public void TestShowResultsNullScore() { createResults(null); + AddWaitStep("wait for display", 5); + } + + [Test] + public void TestShowResultsNullScoreWithDelay() + { + AddStep("bind delayed handler", () => bindHandler(3000)); + createResults(null); + AddUntilStep("wait for rooms to be received", () => roomsReceived); + AddWaitStep("wait for display", 5); } private void createResults(ScoreInfo score) + { + AddStep("load results", () => + { + LoadScreen(new TimeshiftResultsScreen(score, 1, new PlaylistItem + { + Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo } + })); + }); + + } + + private void bindHandler(double delay = 0) { var roomScores = new List(); @@ -61,26 +95,31 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } - AddStep("bind request handler", () => ((DummyAPIAccess)API).HandleRequest = request => + ((DummyAPIAccess)API).HandleRequest = request => { switch (request) { case GetRoomPlaylistScoresRequest r: - r.TriggerSuccess(new RoomPlaylistScores { Scores = roomScores }); + if (delay == 0) + success(); + else + { + Task.Run(async () => + { + await Task.Delay(TimeSpan.FromMilliseconds(delay)); + Schedule(success); + }); + } + + void success() + { + r.TriggerSuccess(new RoomPlaylistScores { Scores = roomScores }); + roomsReceived = true; + } + break; } - }); - - AddStep("load results", () => - { - LoadScreen(new TimeshiftResultsScreen(score, 1, new PlaylistItem - { - Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, - Ruleset = { Value = new OsuRuleset().RulesetInfo } - })); - }); - - AddWaitStep("wait for display", 10); + }; } } } diff --git a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs index d95cee2ab8..5cafc974f1 100644 --- a/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs +++ b/osu.Game/Screens/Multi/Ranking/TimeshiftResultsScreen.cs @@ -4,6 +4,10 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; @@ -17,6 +21,8 @@ namespace osu.Game.Screens.Multi.Ranking private readonly int roomId; private readonly PlaylistItem playlistItem; + private LoadingSpinner loadingLayer; + public TimeshiftResultsScreen(ScoreInfo score, int roomId, PlaylistItem playlistItem, bool allowRetry = true) : base(score, allowRetry) { @@ -24,10 +30,31 @@ namespace osu.Game.Screens.Multi.Ranking this.playlistItem = playlistItem; } + [BackgroundDependencyLoader] + private void load() + { + AddInternal(loadingLayer = new LoadingLayer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + X = -10, + State = { Value = Score == null ? Visibility.Visible : Visibility.Hidden }, + Padding = new MarginPadding { Bottom = TwoLayerButton.SIZE_EXTENDED.Y } + }); + } + protected override APIRequest FetchScores(Action> scoresCallback) { var req = new GetRoomPlaylistScoresRequest(roomId, playlistItem.ID); - req.Success += r => scoresCallback?.Invoke(r.Scores.Where(s => s.ID != Score?.OnlineScoreID).Select(s => s.CreateScoreInfo(playlistItem))); + + req.Success += r => + { + scoresCallback?.Invoke(r.Scores.Where(s => s.ID != Score?.OnlineScoreID).Select(s => s.CreateScoreInfo(playlistItem))); + loadingLayer.Hide(); + }; + + req.Failure += _ => loadingLayer.Hide(); + return req; } } From 05b1edb9d88135667cf9893a414fa08e4cc4dad6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 9 Jun 2020 19:01:02 +0900 Subject: [PATCH 370/871] Fix incorrect beatmap showing --- .../Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs | 1 - .../Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs | 6 ++---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs index 66ebf9abda..9fc7c336cb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneTimeshiftResultsScreen.cs @@ -61,7 +61,6 @@ namespace osu.Game.Tests.Visual.Multiplayer Ruleset = { Value = new OsuRuleset().RulesetInfo } })); }); - } private void bindHandler(double delay = 0) diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index 81d5d113ae..b06ef8ae83 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -4,11 +4,9 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Localisation; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -52,9 +50,9 @@ namespace osu.Game.Screens.Ranking.Expanded } [BackgroundDependencyLoader] - private void load(Bindable working) + private void load() { - var beatmap = working.Value.BeatmapInfo; + var beatmap = score.Beatmap; var metadata = beatmap.Metadata; var creator = metadata.Author?.Username; From ab10732a788c8dbe09d658e19f35c2707c70c8cd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 9 Jun 2020 22:13:48 +0900 Subject: [PATCH 371/871] Remove usages of null-forgiving operator --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 15 ++++++++++----- .../Visual/UserInterface/TestSceneOsuIcon.cs | 8 +++++++- osu.Game/Online/API/APIAccess.cs | 3 ++- 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index d1a859c84b..600c820ce1 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -428,23 +428,28 @@ namespace osu.Game.Tests.Chat Assert.AreEqual(5, result.Links.Count); Link f = result.Links.Find(l => l.Url == "https://osu.ppy.sh/wiki/wiki links"); - Assert.AreEqual(44, f!.Index); + Assert.That(f, Is.Not.Null); + Assert.AreEqual(44, f.Index); Assert.AreEqual(10, f.Length); f = result.Links.Find(l => l.Url == "http://www.simple-test.com"); - Assert.AreEqual(10, f!.Index); + Assert.That(f, Is.Not.Null); + Assert.AreEqual(10, f.Index); Assert.AreEqual(11, f.Length); f = result.Links.Find(l => l.Url == "http://google.com"); - Assert.AreEqual(97, f!.Index); + Assert.That(f, Is.Not.Null); + Assert.AreEqual(97, f.Index); Assert.AreEqual(4, f.Length); f = result.Links.Find(l => l.Url == "https://osu.ppy.sh"); - Assert.AreEqual(78, f!.Index); + Assert.That(f, Is.Not.Null); + Assert.AreEqual(78, f.Index); Assert.AreEqual(18, f.Length); f = result.Links.Find(l => l.Url == "\uD83D\uDE12"); - Assert.AreEqual(101, f!.Index); + Assert.That(f, Is.Not.Null); + Assert.AreEqual(101, f.Index); Assert.AreEqual(3, f.Length); } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs index 246eb119e8..c5374d50ab 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneOsuIcon.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 System.Reflection; using NUnit.Framework; using osu.Framework.Extensions.IEnumerableExtensions; @@ -45,7 +46,12 @@ namespace osu.Game.Tests.Visual.UserInterface }); foreach (var p in typeof(OsuIcon).GetProperties(BindingFlags.Public | BindingFlags.Static)) - flow.Add(new Icon($"{nameof(OsuIcon)}.{p.Name}", (IconUsage)p.GetValue(null)!)); + { + var propValue = p.GetValue(null); + Debug.Assert(propValue != null); + + flow.Add(new Icon($"{nameof(OsuIcon)}.{p.Name}", (IconUsage)propValue)); + } AddStep("toggle shadows", () => flow.Children.ForEach(i => i.SpriteIcon.Shadow = !i.SpriteIcon.Shadow)); AddStep("change icons", () => flow.Children.ForEach(i => i.SpriteIcon.Icon = new IconUsage((char)(i.SpriteIcon.Icon.Icon + 1)))); diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index f9e2da9af8..4ea5c192fe 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -11,6 +11,7 @@ using System.Threading.Tasks; using Newtonsoft.Json.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions.ExceptionExtensions; +using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Logging; using osu.Game.Configuration; @@ -250,7 +251,7 @@ namespace osu.Game.Online.API { try { - return JObject.Parse(req.GetResponseString()).SelectToken("form_error", true)!.ToObject(); + return JObject.Parse(req.GetResponseString()).SelectToken("form_error", true).AsNonNull().ToObject(); } catch { From 7274213cce3d5a1776bcc715d0f90e73b235a808 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jun 2020 23:30:42 +0900 Subject: [PATCH 372/871] 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 07be3ab0d2..596e5bfa8b 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8213719c01..1d3bafbfd6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index fd13455c63..ad7850599b 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 7dc19220e51947f92b9c1dfe381d96098ffe637e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jun 2020 23:38:54 +0900 Subject: [PATCH 373/871] Apply new resharper formatting fixes --- osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs | 11 ++++++----- .../Screens/Gameplay/Components/TeamScoreDisplay.cs | 5 ++++- osu.Game/Rulesets/Mods/ModEasy.cs | 4 +++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs index f5b20fd1c5..a69646507a 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSnaking.cs @@ -61,7 +61,9 @@ namespace osu.Game.Rulesets.Osu.Tests private DrawableSlider slider; [SetUpSteps] - public override void SetUpSteps() { } + public override void SetUpSteps() + { + } [TestCase(0)] [TestCase(1)] @@ -132,10 +134,9 @@ namespace osu.Game.Rulesets.Osu.Tests checkPositionChange(16600, sliderRepeat, positionDecreased); } - private void retrieveDrawableSlider(int index) => AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => - { - slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index); - }); + private void retrieveDrawableSlider(int index) => + AddStep($"retrieve {(index + 1).ToOrdinalWords()} slider", () => + slider = (DrawableSlider)Player.DrawableRuleset.Playfield.AllHitObjects.ElementAt(index)); private void ensureSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionIncreased); private void ensureNoSnakingIn(double startTime) => checkPositionChange(startTime, sliderEnd, positionRemainsSame); diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs index 3e60a03f92..da55ba53ea 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamScoreDisplay.cs @@ -21,7 +21,10 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components private TeamDisplay teamDisplay; - public bool ShowScore { set => teamDisplay.ShowScore = value; } + public bool ShowScore + { + set => teamDisplay.ShowScore = value; + } public TeamScoreDisplay(TeamColour teamColour) { diff --git a/osu.Game/Rulesets/Mods/ModEasy.cs b/osu.Game/Rulesets/Mods/ModEasy.cs index 7cf9656810..c6f3930029 100644 --- a/osu.Game/Rulesets/Mods/ModEasy.cs +++ b/osu.Game/Rulesets/Mods/ModEasy.cs @@ -35,7 +35,9 @@ namespace osu.Game.Rulesets.Mods private BindableNumber health; - public void ReadFromDifficulty(BeatmapDifficulty difficulty) { } + public void ReadFromDifficulty(BeatmapDifficulty difficulty) + { + } public void ApplyToDifficulty(BeatmapDifficulty difficulty) { From 880a1272288d04cf7eea92b94b39ca7037375e48 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jun 2020 00:08:48 +0900 Subject: [PATCH 374/871] Use async overload --- osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index f145d90356..0151678db3 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -174,7 +174,7 @@ namespace osu.Game.Tests.Beatmaps.IO // arbitrary write to non-hashed file using (var sw = new FileInfo(Directory.GetFiles(extractedFolder, "*.mp3").First()).AppendText()) - sw.WriteLine("text"); + await sw.WriteLineAsync("text"); using (var zip = ZipArchive.Create()) { From 3ae1df07b0c13acefc6a700f07f7a9b74da4a102 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 10 Jun 2020 00:09:29 +0900 Subject: [PATCH 375/871] Fix a couple more new formatting issues --- .../Screens/Gameplay/Components/TeamDisplay.cs | 5 ++++- osu.Game/Rulesets/Mods/ModHardRock.cs | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs index 29908e8e7c..b01c93ae03 100644 --- a/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs +++ b/osu.Game.Tournament/Screens/Gameplay/Components/TeamDisplay.cs @@ -14,7 +14,10 @@ namespace osu.Game.Tournament.Screens.Gameplay.Components { private readonly TeamScore score; - public bool ShowScore { set => score.FadeTo(value ? 1 : 0, 200); } + public bool ShowScore + { + set => score.FadeTo(value ? 1 : 0, 200); + } public TeamDisplay(TournamentTeam team, TeamColour colour, Bindable currentTeamScore, int pointsToWin) : base(team) diff --git a/osu.Game/Rulesets/Mods/ModHardRock.cs b/osu.Game/Rulesets/Mods/ModHardRock.cs index 58c9a58408..0e589735c1 100644 --- a/osu.Game/Rulesets/Mods/ModHardRock.cs +++ b/osu.Game/Rulesets/Mods/ModHardRock.cs @@ -17,7 +17,9 @@ 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(BeatmapDifficulty difficulty) + { + } public void ApplyToDifficulty(BeatmapDifficulty difficulty) { From e57a2294743e216415aeffa3cfdc12514721dd49 Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 9 Jun 2020 20:22:30 +0200 Subject: [PATCH 376/871] Move all the graphics related code to TournamentGame --- osu.Game.Tournament/TournamentGame.cs | 97 +++++++++++++++++++++-- osu.Game.Tournament/TournamentGameBase.cs | 94 +--------------------- 2 files changed, 92 insertions(+), 99 deletions(-) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 78bb66d553..8a0190b902 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -1,11 +1,19 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Drawing; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Configuration; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Colour; using osu.Game.Graphics.Cursor; using osu.Game.Tournament.Models; +using osu.Game.Graphics; +using osuTK; using osuTK.Graphics; namespace osu.Game.Tournament @@ -21,17 +29,94 @@ namespace osu.Game.Tournament public static readonly Color4 ELEMENT_FOREGROUND_COLOUR = Color4Extensions.FromHex("#000"); public static readonly Color4 TEXT_COLOUR = Color4Extensions.FromHex("#fff"); + private Drawable heightWarning; + private Bindable windowSize; + + [BackgroundDependencyLoader] + private void load(FrameworkConfigManager frameworkConfig) + { + windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); + windowSize.BindValueChanged(size => ScheduleAfterChildren(() => + { + var minWidth = (int)(size.NewValue.Height / 768f * TournamentSceneManager.REQUIRED_WIDTH) - 1; + + heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0; + }), true); + + AddRange(new[] + { + new Container + { + CornerRadius = 10, + Depth = float.MinValue, + Position = new Vector2(5), + Masking = true, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Children = new Drawable[] + { + new Box + { + Colour = OsuColour.Gray(0.2f), + RelativeSizeAxes = Axes.Both, + }, + new TourneyButton + { + Text = "Save Changes", + Width = 140, + Height = 50, + Padding = new MarginPadding + { + Top = 10, + Left = 10, + }, + Margin = new MarginPadding + { + Right = 10, + Bottom = 10, + }, + Action = SaveChanges, + }, + } + }, + heightWarning = new Container + { + Masking = true, + CornerRadius = 5, + Depth = float.MinValue, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Box + { + Colour = Color4.Red, + RelativeSizeAxes = Axes.Both, + }, + new TournamentSpriteText + { + Text = "Please make the window wider", + Font = OsuFont.Torus.With(weight: FontWeight.Bold), + Colour = Color4.White, + Padding = new MarginPadding(20) + } + } + }, + new OsuContextMenuContainer + { + RelativeSizeAxes = Axes.Both, + Child = new TournamentSceneManager() + } + }); + } protected override void LoadComplete() { base.LoadComplete(); - Add(new OsuContextMenuContainer - { - RelativeSizeAxes = Axes.Both, - Child = new TournamentSceneManager() - }); - + MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display // we don't want to show the menu cursor as it would appear on stream output. MenuCursorContainer.Cursor.Alpha = 0; } diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 85db9e61fb..cc7bb863ed 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -2,28 +2,19 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Drawing; using System.IO; using System.Linq; using Newtonsoft.Json; using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Configuration; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Beatmaps; -using osu.Game.Graphics; using osu.Game.Online.API.Requests; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; using osu.Game.Users; -using osuTK; -using osuTK.Graphics; using osuTK.Input; namespace osu.Game.Tournament @@ -40,19 +31,15 @@ namespace osu.Game.Tournament private TournamentStorage tournamentStorage; private DependencyContainer dependencies; - - private Bindable windowSize; private FileBasedIPC ipc; - private Drawable heightWarning; - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { return dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); } [BackgroundDependencyLoader] - private void load(Storage storage, FrameworkConfigManager frameworkConfig) + private void load(Storage storage) { Resources.AddStore(new DllResourceStore(typeof(TournamentGameBase).Assembly)); @@ -62,83 +49,12 @@ namespace osu.Game.Tournament this.storage = storage; - windowSize = frameworkConfig.GetBindable(FrameworkSetting.WindowedSize); - windowSize.BindValueChanged(size => ScheduleAfterChildren(() => - { - var minWidth = (int)(size.NewValue.Height / 768f * TournamentSceneManager.REQUIRED_WIDTH) - 1; - - heightWarning.Alpha = size.NewValue.Width < minWidth ? 1 : 0; - }), true); - readBracket(); ladder.CurrentMatch.Value = ladder.Matches.FirstOrDefault(p => p.Current.Value); dependencies.CacheAs(ipc = new FileBasedIPC()); Add(ipc); - - AddRange(new[] - { - new Container - { - CornerRadius = 10, - Depth = float.MinValue, - Position = new Vector2(5), - Masking = true, - AutoSizeAxes = Axes.Both, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - Children = new Drawable[] - { - new Box - { - Colour = OsuColour.Gray(0.2f), - RelativeSizeAxes = Axes.Both, - }, - new TourneyButton - { - Text = "Save Changes", - Width = 140, - Height = 50, - Padding = new MarginPadding - { - Top = 10, - Left = 10, - }, - Margin = new MarginPadding - { - Right = 10, - Bottom = 10, - }, - Action = SaveChanges, - }, - } - }, - heightWarning = new Container - { - Masking = true, - CornerRadius = 5, - Depth = float.MinValue, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Box - { - Colour = Color4.Red, - RelativeSizeAxes = Axes.Both, - }, - new TournamentSpriteText - { - Text = "Please make the window wider", - Font = OsuFont.Torus.With(weight: FontWeight.Bold), - Colour = Color4.White, - Padding = new MarginPadding(20) - } - } - }, - }); } private void readBracket() @@ -313,14 +229,6 @@ namespace osu.Game.Tournament API.Queue(req); } - protected override void LoadComplete() - { - MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display - MenuCursorContainer.Cursor.Alpha = 0; - - base.LoadComplete(); - } - protected virtual void SaveChanges() { foreach (var r in ladder.Rounds) From af05ee67cbe67e0577a9cdb8bad11aa01dbfd22a Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 9 Jun 2020 20:30:15 +0200 Subject: [PATCH 377/871] move base.loadcomplete to the bottom --- osu.Game.Tournament/TournamentGame.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 8a0190b902..3392440902 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -114,11 +114,12 @@ namespace osu.Game.Tournament protected override void LoadComplete() { - base.LoadComplete(); - MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display + // we don't want to show the menu cursor as it would appear on stream output. MenuCursorContainer.Cursor.Alpha = 0; + + base.LoadComplete(); } } } From c9b4fa92f57b6c6023cbedb9c43aa846b1c7b855 Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 9 Jun 2020 20:40:54 +0200 Subject: [PATCH 378/871] Hide in-game cursor manually in the testbrowser --- osu.Game.Tournament.Tests/TournamentTestBrowser.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game.Tournament.Tests/TournamentTestBrowser.cs b/osu.Game.Tournament.Tests/TournamentTestBrowser.cs index f7ad757926..3bc719be7c 100644 --- a/osu.Game.Tournament.Tests/TournamentTestBrowser.cs +++ b/osu.Game.Tournament.Tests/TournamentTestBrowser.cs @@ -19,6 +19,9 @@ namespace osu.Game.Tournament.Tests Depth = 10 }, AddInternal); + MenuCursorContainer.Cursor.AlwaysPresent = true; + MenuCursorContainer.Cursor.Alpha = 0; + // Have to construct this here, rather than in the constructor, because // we depend on some dependencies to be loaded within OsuGameBase.load(). Add(new TestBrowser()); From aacacd75f08f289fa77cc5d9be211145b8378b7d Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 9 Jun 2020 21:14:05 +0200 Subject: [PATCH 379/871] Remove abstract from the class --- osu.Game.Tournament/TournamentGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index cc7bb863ed..bb8c134ecb 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -20,7 +20,7 @@ using osuTK.Input; namespace osu.Game.Tournament { [Cached(typeof(TournamentGameBase))] - public abstract class TournamentGameBase : OsuGameBase + public class TournamentGameBase : OsuGameBase { private const string bracket_filename = "bracket.json"; From 0f39558da2aa09555c0a50e5bb74b32325d07a16 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 10 Jun 2020 08:04:34 +0200 Subject: [PATCH 380/871] Apply review comment --- osu.Game.Tournament.Tests/TournamentTestBrowser.cs | 3 --- osu.Game.Tournament/TournamentGame.cs | 10 ---------- osu.Game.Tournament/TournamentGameBase.cs | 9 +++++++++ 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tournament.Tests/TournamentTestBrowser.cs b/osu.Game.Tournament.Tests/TournamentTestBrowser.cs index 3bc719be7c..f7ad757926 100644 --- a/osu.Game.Tournament.Tests/TournamentTestBrowser.cs +++ b/osu.Game.Tournament.Tests/TournamentTestBrowser.cs @@ -19,9 +19,6 @@ namespace osu.Game.Tournament.Tests Depth = 10 }, AddInternal); - MenuCursorContainer.Cursor.AlwaysPresent = true; - MenuCursorContainer.Cursor.Alpha = 0; - // Have to construct this here, rather than in the constructor, because // we depend on some dependencies to be loaded within OsuGameBase.load(). Add(new TestBrowser()); diff --git a/osu.Game.Tournament/TournamentGame.cs b/osu.Game.Tournament/TournamentGame.cs index 3392440902..7b1a174c1e 100644 --- a/osu.Game.Tournament/TournamentGame.cs +++ b/osu.Game.Tournament/TournamentGame.cs @@ -111,15 +111,5 @@ namespace osu.Game.Tournament } }); } - - protected override void LoadComplete() - { - MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display - - // we don't want to show the menu cursor as it would appear on stream output. - MenuCursorContainer.Cursor.Alpha = 0; - - base.LoadComplete(); - } } } diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index bb8c134ecb..0160065cc4 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -228,6 +228,15 @@ namespace osu.Game.Tournament API.Queue(req); } + protected override void LoadComplete() + { + MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display + + // we don't want to show the menu cursor as it would appear on stream output. + MenuCursorContainer.Cursor.Alpha = 0; + + base.LoadComplete(); + } protected virtual void SaveChanges() { From a43e1a0ae345722b922cd7b3bb0b533969657103 Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 10 Jun 2020 08:41:13 +0200 Subject: [PATCH 381/871] Remove whitespace --- osu.Game.Tournament/TournamentGameBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 0160065cc4..d17b93bf5d 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -228,6 +228,7 @@ namespace osu.Game.Tournament API.Queue(req); } + protected override void LoadComplete() { MenuCursorContainer.Cursor.AlwaysPresent = true; // required for tooltip display From 4fb71eeb20dcf96a46a54d025eca6689489bf2a5 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Wed, 10 Jun 2020 18:23:31 +0300 Subject: [PATCH 382/871] Move setting up the ruleset bindable to top-base test scene --- osu.Game/Tests/Visual/EditorTestScene.cs | 1 - osu.Game/Tests/Visual/OsuTestScene.cs | 2 +- osu.Game/Tests/Visual/PlayerTestScene.cs | 14 ++------------ osu.Game/Tests/Visual/SkinnableTestScene.cs | 7 +------ 4 files changed, 4 insertions(+), 20 deletions(-) diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 4f9a5b53b8..cd08f4712a 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -19,7 +19,6 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { - Ruleset.Value = CreateEditorRuleset().RulesetInfo; Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value); } diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index e5d5442074..6d0fc199c4 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -146,7 +146,7 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load(RulesetStore rulesets) { - Ruleset.Value = rulesets.AvailableRulesets.First(); + Ruleset.Value = CreateRuleset()?.RulesetInfo ?? rulesets.AvailableRulesets.First(); } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Tests/Visual/PlayerTestScene.cs b/osu.Game/Tests/Visual/PlayerTestScene.cs index 05b1eea6b3..2c46e7f6d3 100644 --- a/osu.Game/Tests/Visual/PlayerTestScene.cs +++ b/osu.Game/Tests/Visual/PlayerTestScene.cs @@ -24,20 +24,9 @@ namespace osu.Game.Tests.Visual protected OsuConfigManager LocalConfig; - private readonly Ruleset ruleset; - - protected PlayerTestScene() - { - ruleset = CreatePlayerRuleset(); - } - [BackgroundDependencyLoader] private void load() { - // There are test scenes using current value of the ruleset bindable - // on their BDLs (example in TestSceneSliderSnaking's BDL) - Ruleset.Value = ruleset.RulesetInfo; - Dependencies.Cache(LocalConfig = new OsuConfigManager(LocalStorage)); LocalConfig.GetBindable(OsuSetting.DimLevel).Value = 1.0; } @@ -58,7 +47,7 @@ namespace osu.Game.Tests.Visual action?.Invoke(); - AddStep(ruleset.Description, LoadPlayer); + AddStep(CreatePlayerRuleset().Description, LoadPlayer); AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1); } @@ -68,6 +57,7 @@ namespace osu.Game.Tests.Visual protected void LoadPlayer() { + var ruleset = Ruleset.Value.CreateInstance(); var beatmap = CreateBeatmap(ruleset.RulesetInfo); Beatmap.Value = CreateWorkingBeatmap(beatmap); diff --git a/osu.Game/Tests/Visual/SkinnableTestScene.cs b/osu.Game/Tests/Visual/SkinnableTestScene.cs index 41147d3768..ea7cdaaac6 100644 --- a/osu.Game/Tests/Visual/SkinnableTestScene.cs +++ b/osu.Game/Tests/Visual/SkinnableTestScene.cs @@ -23,8 +23,6 @@ namespace osu.Game.Tests.Visual { public abstract class SkinnableTestScene : OsuGridTestScene { - private readonly Ruleset ruleset; - private Skin metricsSkin; private Skin defaultSkin; private Skin specialSkin; @@ -33,14 +31,11 @@ namespace osu.Game.Tests.Visual protected SkinnableTestScene() : base(2, 3) { - ruleset = CreateRulesetForSkinProvider(); } [BackgroundDependencyLoader] private void load(AudioManager audio, SkinManager skinManager) { - Ruleset.Value = ruleset.RulesetInfo; - var dllStore = new DllResourceStore(DynamicCompilationOriginal.GetType().Assembly); metricsSkin = new TestLegacySkin(new SkinInfo { Name = "metrics-skin" }, new NamespacedResourceStore(dllStore, "Resources/metrics_skin"), audio, true); @@ -110,7 +105,7 @@ namespace osu.Game.Tests.Visual { new OutlineBox { Alpha = autoSize ? 1 : 0 }, mainProvider.WithChild( - new SkinProvidingContainer(ruleset.CreateLegacySkinProvider(mainProvider, beatmap)) + new SkinProvidingContainer(Ruleset.Value.CreateInstance().CreateLegacySkinProvider(mainProvider, beatmap)) { Child = created, RelativeSizeAxes = !autoSize ? Axes.Both : Axes.None, From b89dcb6a77de715a84faa28fd1b91fc37b167e5b Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Thu, 11 Jun 2020 13:02:47 +0930 Subject: [PATCH 383/871] Fix cursor not hiding with SDL2 backend --- osu.Desktop/OsuGameDesktop.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index 5f74883803..bca30f3f9e 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -122,14 +122,22 @@ namespace osu.Desktop { base.SetHost(host); - if (host.Window is DesktopGameWindow desktopWindow) + switch (host.Window) { - desktopWindow.CursorState |= CursorState.Hidden; + // Legacy osuTK DesktopGameWindow + case DesktopGameWindow desktopGameWindow: + desktopGameWindow.CursorState |= CursorState.Hidden; + desktopGameWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico")); + desktopGameWindow.Title = Name; + desktopGameWindow.FileDrop += fileDrop; + break; - desktopWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico")); - desktopWindow.Title = Name; - - desktopWindow.FileDrop += fileDrop; + // SDL2 DesktopWindow + case DesktopWindow desktopWindow: + desktopWindow.CursorState.Value |= CursorState.Hidden; + desktopWindow.Title = Name; + desktopWindow.FileDrop += fileDrop; + break; } } From 702bd2b65d4c4546a57d83b4decaee64304013af Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 11 Jun 2020 13:41:53 +0900 Subject: [PATCH 384/871] Fix potential nullref in test --- .../SongSelect/TestScenePlaySongSelect.cs | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs index a7e2dbeccb..f7d66ca5cf 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestScenePlaySongSelect.cs @@ -38,13 +38,9 @@ namespace osu.Game.Tests.Visual.SongSelect public class TestScenePlaySongSelect : ScreenTestScene { private BeatmapManager manager; - private RulesetStore rulesets; - private MusicController music; - private WorkingBeatmap defaultBeatmap; - private TestSongSelect songSelect; [BackgroundDependencyLoader] @@ -308,15 +304,13 @@ namespace osu.Game.Tests.Visual.SongSelect AddAssert("random map selected", () => songSelect.CurrentBeatmap != defaultBeatmap); - var sortMode = config.GetBindable(OsuSetting.SongSelectSortingMode); - - AddStep(@"Sort by Artist", delegate { sortMode.Value = SortMode.Artist; }); - AddStep(@"Sort by Title", delegate { sortMode.Value = SortMode.Title; }); - AddStep(@"Sort by Author", delegate { sortMode.Value = SortMode.Author; }); - AddStep(@"Sort by DateAdded", delegate { sortMode.Value = SortMode.DateAdded; }); - AddStep(@"Sort by BPM", delegate { sortMode.Value = SortMode.BPM; }); - AddStep(@"Sort by Length", delegate { sortMode.Value = SortMode.Length; }); - AddStep(@"Sort by Difficulty", delegate { sortMode.Value = SortMode.Difficulty; }); + AddStep(@"Sort by Artist", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Artist)); + AddStep(@"Sort by Title", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Title)); + AddStep(@"Sort by Author", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Author)); + AddStep(@"Sort by DateAdded", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.DateAdded)); + AddStep(@"Sort by BPM", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.BPM)); + AddStep(@"Sort by Length", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Length)); + AddStep(@"Sort by Difficulty", () => config.Set(OsuSetting.SongSelectSortingMode, SortMode.Difficulty)); } [Test] From 7b012f1def0eb2c0e8e1d0af48ff86184224a802 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 11 Jun 2020 14:55:49 +0900 Subject: [PATCH 385/871] Fix test failures --- .../Background/TestSceneUserDimBackgrounds.cs | 12 ++++++-- .../TestSceneExpandedPanelMiddleContent.cs | 30 +++++++++---------- .../Expanded/ExpandedPanelMiddleContent.cs | 2 +- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs index d601f40afe..19294d12fc 100644 --- a/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs +++ b/osu.Game.Tests/Visual/Background/TestSceneUserDimBackgrounds.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Rulesets; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; using osu.Game.Scoring; using osu.Game.Screens; @@ -27,6 +28,7 @@ using osu.Game.Screens.Play; using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; using osu.Game.Users; using osuTK; @@ -186,9 +188,15 @@ namespace osu.Game.Tests.Visual.Background public void TestTransition() { performFullSetup(); + FadeAccessibleResults results = null; - AddStep("Transition to Results", () => player.Push(results = - new FadeAccessibleResults(new ScoreInfo { User = new User { Username = "osu!" } }))); + + AddStep("Transition to Results", () => player.Push(results = new FadeAccessibleResults(new ScoreInfo + { + User = new User { Username = "osu!" }, + Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo + }))); + AddUntilStep("Wait for results is current", () => results.IsCurrentScreen()); AddUntilStep("Screen is undimmed, original background retained", () => songSelect.IsBackgroundUndimmed() && songSelect.IsBackgroundCurrent() && results.IsBlurCorrect()); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs index 69511b85c0..7be44a62de 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneExpandedPanelMiddleContent.cs @@ -4,7 +4,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -33,7 +32,10 @@ namespace osu.Game.Tests.Visual.Ranking { var author = new User { Username = "mapper_name" }; - AddStep("show example score", () => showPanel(createTestBeatmap(author), new TestScoreInfo(new OsuRuleset().RulesetInfo))); + AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo) + { + Beatmap = createTestBeatmap(author) + })); AddAssert("mapper name present", () => this.ChildrenOfType().Any(spriteText => spriteText.Text == "mapper_name")); } @@ -41,38 +43,34 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestMapWithUnknownMapper() { - AddStep("show example score", () => showPanel(createTestBeatmap(null), new TestScoreInfo(new OsuRuleset().RulesetInfo))); + AddStep("show example score", () => showPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo) + { + Beatmap = createTestBeatmap(null) + })); AddAssert("mapped by text not present", () => this.ChildrenOfType().All(spriteText => !containsAny(spriteText.Text, "mapped", "by"))); } - private void showPanel(WorkingBeatmap workingBeatmap, ScoreInfo score) - { - Child = new ExpandedPanelMiddleContentContainer(workingBeatmap, score); - } + private void showPanel(ScoreInfo score) => Child = new ExpandedPanelMiddleContentContainer(score); - private WorkingBeatmap createTestBeatmap(User author) + private BeatmapInfo createTestBeatmap(User author) { - var beatmap = new TestBeatmap(rulesetStore.GetRuleset(0)); + var beatmap = new TestBeatmap(rulesetStore.GetRuleset(0)).BeatmapInfo; + beatmap.Metadata.Author = author; beatmap.Metadata.Title = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap title"; beatmap.Metadata.Artist = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap artist"; - return new TestWorkingBeatmap(beatmap); + return beatmap; } private bool containsAny(string text, params string[] stringsToMatch) => stringsToMatch.Any(text.Contains); private class ExpandedPanelMiddleContentContainer : Container { - [Cached] - private Bindable workingBeatmap { get; set; } - - public ExpandedPanelMiddleContentContainer(WorkingBeatmap beatmap, ScoreInfo score) + public ExpandedPanelMiddleContentContainer(ScoreInfo score) { - workingBeatmap = new Bindable(beatmap); - Anchor = Anchor.Centre; Origin = Anchor.Centre; Size = new Vector2(ScorePanel.EXPANDED_WIDTH, 700); diff --git a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs index b06ef8ae83..01502c0913 100644 --- a/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs +++ b/osu.Game/Screens/Ranking/Expanded/ExpandedPanelMiddleContent.cs @@ -53,7 +53,7 @@ namespace osu.Game.Screens.Ranking.Expanded private void load() { var beatmap = score.Beatmap; - var metadata = beatmap.Metadata; + var metadata = beatmap.BeatmapSet?.Metadata ?? beatmap.Metadata; var creator = metadata.Author?.Username; var topStatistics = new List From b7c1cfbe6306b262db5eb1880c74fe105147df78 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 11 Jun 2020 15:07:14 +0900 Subject: [PATCH 386/871] Adjust display to avoid overlaps --- osu.Game/Screens/Multi/Components/OverlinedDisplay.cs | 2 +- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs b/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs index 71cabd8b50..8d8d4cc404 100644 --- a/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs +++ b/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs @@ -80,7 +80,7 @@ namespace osu.Game.Screens.Multi.Components }, new Drawable[] { - Content = new Container { Margin = new MarginPadding { Top = 5 } } + Content = new Container { Padding = new MarginPadding { Top = 5 } } } } }; diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 9296fe81bd..f837a407a5 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -130,6 +130,7 @@ namespace osu.Game.Screens.Multi.Match SelectedItem = { BindTarget = SelectedItem } } }, + null, new Drawable[] { new TriangleButton @@ -139,6 +140,12 @@ namespace osu.Game.Screens.Multi.Match Action = showBeatmapResults } } + }, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 5), + new Dimension(GridSizeMode.AutoSize) } } }, From fca6a6d69f3724091c2f72b1f4ea7034614633d7 Mon Sep 17 00:00:00 2001 From: Shane Woolcock Date: Fri, 12 Jun 2020 09:46:21 +0930 Subject: [PATCH 387/871] Implement file drop with DragDrop event --- osu.Desktop/OsuGameDesktop.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index bca30f3f9e..cd31df316a 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -10,7 +10,6 @@ using Microsoft.Win32; using osu.Desktop.Overlays; using osu.Framework.Platform; using osu.Game; -using osuTK.Input; using osu.Desktop.Updater; using osu.Framework; using osu.Framework.Logging; @@ -129,22 +128,20 @@ namespace osu.Desktop desktopGameWindow.CursorState |= CursorState.Hidden; desktopGameWindow.SetIconFromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico")); desktopGameWindow.Title = Name; - desktopGameWindow.FileDrop += fileDrop; + desktopGameWindow.FileDrop += (_, e) => fileDrop(e.FileNames); break; // SDL2 DesktopWindow case DesktopWindow desktopWindow: desktopWindow.CursorState.Value |= CursorState.Hidden; desktopWindow.Title = Name; - desktopWindow.FileDrop += fileDrop; + desktopWindow.DragDrop += f => fileDrop(new[] { f }); break; } } - private void fileDrop(object sender, FileDropEventArgs e) + private void fileDrop(string[] filePaths) { - var filePaths = e.FileNames; - var firstExtension = Path.GetExtension(filePaths.First()); if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return; From a48e36fd31d5e4850a3f132883173a794a457c25 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 12 Jun 2020 12:58:33 +0900 Subject: [PATCH 388/871] Fix dotnet publish with runtime specification not working --- osu.Desktop/osu.Desktop.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Desktop/osu.Desktop.csproj b/osu.Desktop/osu.Desktop.csproj index c34e1e1221..7a99c70999 100644 --- a/osu.Desktop/osu.Desktop.csproj +++ b/osu.Desktop/osu.Desktop.csproj @@ -30,6 +30,10 @@ + + + + From 91b6979c970cce7f6eadc11533b34848add6b8a8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jun 2020 13:38:20 +0900 Subject: [PATCH 389/871] Fix LoadingSpinner not always playing fade in animation --- osu.Game/Graphics/UserInterface/LoadingSpinner.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/LoadingSpinner.cs b/osu.Game/Graphics/UserInterface/LoadingSpinner.cs index 4f4607c114..8174c4d5fe 100644 --- a/osu.Game/Graphics/UserInterface/LoadingSpinner.cs +++ b/osu.Game/Graphics/UserInterface/LoadingSpinner.cs @@ -17,6 +17,8 @@ namespace osu.Game.Graphics.UserInterface { private readonly SpriteIcon spinner; + protected override bool StartHidden => true; + protected Container MainContents; public const float TRANSITION_DURATION = 500; From 95f57ca88c3f17ddebe7e449d8345e9c672d98fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jun 2020 18:05:23 +0900 Subject: [PATCH 390/871] Remove duplicate calls to CheckForUpdatesAsync --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 1 - osu.Game/Updater/SimpleUpdateManager.cs | 2 -- 2 files changed, 3 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 3bd10215c2..748969ade5 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -35,7 +35,6 @@ namespace osu.Desktop.Updater notificationOverlay = notification; Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger)); - Schedule(() => Task.Run(CheckForUpdateAsync)); } protected override async Task InternalCheckForUpdateAsync() => await checkForUpdateAsync(); diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index 78d27ab754..b61c88a280 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -28,8 +28,6 @@ namespace osu.Game.Updater private void load(OsuGameBase game) { version = game.Version; - - Schedule(() => Task.Run(CheckForUpdateAsync)); } protected override async Task InternalCheckForUpdateAsync() From 6beb28b685205a886e98235c6c332912b612ad18 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jun 2020 18:07:39 +0900 Subject: [PATCH 391/871] Rename method to be less bad --- osu.Desktop/Updater/SquirrelUpdateManager.cs | 2 +- osu.Game/Updater/SimpleUpdateManager.cs | 2 +- osu.Game/Updater/UpdateManager.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Desktop/Updater/SquirrelUpdateManager.cs b/osu.Desktop/Updater/SquirrelUpdateManager.cs index 748969ade5..05c8e835ac 100644 --- a/osu.Desktop/Updater/SquirrelUpdateManager.cs +++ b/osu.Desktop/Updater/SquirrelUpdateManager.cs @@ -37,7 +37,7 @@ namespace osu.Desktop.Updater Splat.Locator.CurrentMutable.Register(() => new SquirrelLogger(), typeof(Splat.ILogger)); } - protected override async Task InternalCheckForUpdateAsync() => await checkForUpdateAsync(); + protected override async Task PerformUpdateCheck() => await checkForUpdateAsync(); private async Task checkForUpdateAsync(bool useDeltaPatching = true, UpdateProgressNotification notification = null) { diff --git a/osu.Game/Updater/SimpleUpdateManager.cs b/osu.Game/Updater/SimpleUpdateManager.cs index b61c88a280..ebb9995c66 100644 --- a/osu.Game/Updater/SimpleUpdateManager.cs +++ b/osu.Game/Updater/SimpleUpdateManager.cs @@ -30,7 +30,7 @@ namespace osu.Game.Updater version = game.Version; } - protected override async Task InternalCheckForUpdateAsync() + protected override async Task PerformUpdateCheck() { try { diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index abe21f08a4..9037187e8d 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -49,10 +49,10 @@ namespace osu.Game.Updater if (!CanCheckForUpdate) return; - await InternalCheckForUpdateAsync(); + await PerformUpdateCheck(); } - protected virtual Task InternalCheckForUpdateAsync() + protected virtual Task PerformUpdateCheck() { // Query last version only *once*, so the user can re-check for updates, in case they closed the notification or else. lastVersion ??= config.Get(OsuSetting.Version); From 3dd642a33667d53d5d78a857c1f5548e335f9882 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jun 2020 18:29:21 +0900 Subject: [PATCH 392/871] Ensure only one update check can be running at a time --- osu.Game/Updater/UpdateManager.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 9037187e8d..06d6a39066 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -44,12 +44,22 @@ namespace osu.Game.Updater config.Set(OsuSetting.Version, game.Version); } + private readonly object updateTaskLock = new object(); + + private Task updateCheckTask; + public async Task CheckForUpdateAsync() { if (!CanCheckForUpdate) return; - await PerformUpdateCheck(); + lock (updateTaskLock) + updateCheckTask ??= PerformUpdateCheck(); + + await updateCheckTask; + + lock (updateTaskLock) + updateCheckTask = null; } protected virtual Task PerformUpdateCheck() From 4f809767a5f9cf762244843021a6f33ac99c94af Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jun 2020 18:36:36 +0900 Subject: [PATCH 393/871] Disable button while update check is in progress --- .../Settings/Sections/General/UpdateSettings.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 4a2a50885e..869e6c9c51 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -19,6 +19,8 @@ namespace osu.Game.Overlays.Settings.Sections.General protected override string Header => "Updates"; + private SettingsButton checkForUpdatesButton; + [BackgroundDependencyLoader(true)] private void load(Storage storage, OsuConfigManager config, OsuGame game) { @@ -29,11 +31,14 @@ namespace osu.Game.Overlays.Settings.Sections.General }); // We should only display the button for UpdateManagers that do check for updates - Add(new SettingsButton + Add(checkForUpdatesButton = new SettingsButton { Text = "Check for updates", - Action = () => Schedule(() => Task.Run(updateManager.CheckForUpdateAsync)), - Enabled = { Value = updateManager.CanCheckForUpdate } + Action = () => + { + checkForUpdatesButton.Enabled.Value = false; + Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(t => Schedule(() => checkForUpdatesButton.Enabled.Value = true)); + } }); if (RuntimeInfo.IsDesktop) From 6217fb26daf0a88c9c8bd867f1efb533c0c5c1bb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 12 Jun 2020 18:50:25 +0900 Subject: [PATCH 394/871] Finish up design implementation of timing distribution graph --- .../TestSceneTimingDistributionGraph.cs | 85 ++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs index 456ac19383..4129975166 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs @@ -8,6 +8,8 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu.Scoring; using osuTK; @@ -21,7 +23,7 @@ namespace osu.Game.Tests.Visual.Ranking { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(300, 100) + Size = new Vector2(400, 130) }); } @@ -58,6 +60,17 @@ namespace osu.Game.Tests.Visual.Ranking public class TimingDistributionGraph : CompositeDrawable { + /// + /// The number of data points shown on the axis below the graph. + /// + private const float axis_points = 5; + + /// + /// An amount to adjust the value of the axis points by, effectively insetting the axis in the graph. + /// Without an inset, the final data point will be placed halfway outside the graph. + /// + private const float axis_value_inset = 0.2f; + private readonly TimingDistribution distribution; public TimingDistributionGraph(TimingDistribution distribution) @@ -74,11 +87,79 @@ namespace osu.Game.Tests.Visual.Ranking for (int i = 0; i < bars.Length; i++) bars[i] = new Bar { Height = (float)distribution.Bins[i] / maxCount }; + Container axisFlow; + InternalChild = new GridContainer { RelativeSizeAxes = Axes.Both, - Content = new[] { bars } + Content = new[] + { + new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] { bars } + } + }, + new Drawable[] + { + axisFlow = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + }, + }, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + } }; + + // We know the total number of bins on each side of the centre ((n - 1) / 2), and the size of each bin. + // So our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. + int sideBins = (distribution.Bins.Length - 1) / 2; + double maxValue = sideBins * distribution.BinSize; + double axisValueStep = maxValue / axis_points * (1 - axis_value_inset); + + axisFlow.Add(new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "0", + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) + }); + + for (int i = 1; i <= axis_points; i++) + { + double axisValue = i * axisValueStep; + float position = (float)(axisValue / maxValue); + float alpha = 1f - position * 0.8f; + + axisFlow.Add(new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + X = -position / 2, + Alpha = alpha, + Text = axisValue.ToString("-0"), + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) + }); + + axisFlow.Add(new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + X = position / 2, + Alpha = alpha, + Text = axisValue.ToString("+0"), + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) + }); + } } private class Bar : CompositeDrawable From 35f577375c53c9f1a7c2cc159c32d468a2d9ce41 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jun 2020 19:20:45 +0900 Subject: [PATCH 395/871] Restore notification code --- osu.Game/Updater/UpdateManager.cs | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 06d6a39066..35f9ad512f 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -39,6 +39,18 @@ namespace osu.Game.Updater Schedule(() => Task.Run(CheckForUpdateAsync)); + // Query last version only *once*, so the user can re-check for updates, in case they closed the notification or else. + lastVersion ??= config.Get(OsuSetting.Version); + + var version = game.Version; + + if (game.IsDeployedBuild && version != lastVersion) + { + // only show a notification if we've previously saved a version to the config file (ie. not the first run). + if (!string.IsNullOrEmpty(lastVersion)) + Notifications.Post(new UpdateCompleteNotification(version)); + } + // debug / local compilations will reset to a non-release string. // can be useful to check when an install has transitioned between release and otherwise (see OsuConfigManager's migrations). config.Set(OsuSetting.Version, game.Version); @@ -62,23 +74,7 @@ namespace osu.Game.Updater updateCheckTask = null; } - protected virtual Task PerformUpdateCheck() - { - // Query last version only *once*, so the user can re-check for updates, in case they closed the notification or else. - lastVersion ??= config.Get(OsuSetting.Version); - - var version = game.Version; - - if (version != lastVersion) - { - // only show a notification if we've previously saved a version to the config file (ie. not the first run). - if (!string.IsNullOrEmpty(lastVersion)) - Notifications.Post(new UpdateCompleteNotification(version)); - } - - // we aren't doing any async in this method, so we return a completed task instead. - return Task.CompletedTask; - } + protected virtual Task PerformUpdateCheck() => Task.CompletedTask; private class UpdateCompleteNotification : SimpleNotification { From 89cf146d18a804cca959ff18dfceb399bbb31828 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jun 2020 19:24:50 +0900 Subject: [PATCH 396/871] Fix base UpdateManager thinking it can check for updates --- osu.Game/Updater/UpdateManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 35f9ad512f..d3a05deac5 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -20,7 +20,9 @@ namespace osu.Game.Updater /// /// Whether this UpdateManager should be or is capable of checking for updates. /// - public bool CanCheckForUpdate => game.IsDeployedBuild; + public bool CanCheckForUpdate => game.IsDeployedBuild && + // only implementations will actually check for updates. + GetType() != typeof(UpdateManager); private string lastVersion; From 446ce2590cf93c4c69072f0e384d803ded88fc16 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jun 2020 19:25:54 +0900 Subject: [PATCH 397/871] Move local back in place --- osu.Game/Updater/UpdateManager.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index d3a05deac5..51f48264b8 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -24,8 +24,6 @@ namespace osu.Game.Updater // only implementations will actually check for updates. GetType() != typeof(UpdateManager); - private string lastVersion; - [Resolved] private OsuConfigManager config { get; set; } @@ -42,7 +40,7 @@ namespace osu.Game.Updater Schedule(() => Task.Run(CheckForUpdateAsync)); // Query last version only *once*, so the user can re-check for updates, in case they closed the notification or else. - lastVersion ??= config.Get(OsuSetting.Version); + var lastVersion = config.Get(OsuSetting.Version); var version = game.Version; From f5c3863e6d97f71b65f06de59f2754b03f71f642 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jun 2020 19:26:46 +0900 Subject: [PATCH 398/871] Revert variable usage --- osu.Game/Updater/UpdateManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 51f48264b8..bcaaf8e343 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -53,7 +53,7 @@ namespace osu.Game.Updater // debug / local compilations will reset to a non-release string. // can be useful to check when an install has transitioned between release and otherwise (see OsuConfigManager's migrations). - config.Set(OsuSetting.Version, game.Version); + config.Set(OsuSetting.Version, version); } private readonly object updateTaskLock = new object(); From 7ae421cc8e8a64f73a65e77e92c40c318f23b6a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jun 2020 19:32:32 +0900 Subject: [PATCH 399/871] Revert more incorrect changes --- osu.Game/Updater/UpdateManager.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index bcaaf8e343..5da366bde9 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -39,11 +39,10 @@ namespace osu.Game.Updater Schedule(() => Task.Run(CheckForUpdateAsync)); - // Query last version only *once*, so the user can re-check for updates, in case they closed the notification or else. - var lastVersion = config.Get(OsuSetting.Version); - var version = game.Version; + var lastVersion = config.Get(OsuSetting.Version); + if (game.IsDeployedBuild && version != lastVersion) { // only show a notification if we've previously saved a version to the config file (ie. not the first run). From 9746e24d1efb5e85e22053e867629fc77910c4e7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 12 Jun 2020 19:40:54 +0900 Subject: [PATCH 400/871] Rename abstract TestScene --- osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs | 2 +- .../Gameplay/{TestPlayerTestScene.cs => OsuPlayerTestScene.cs} | 2 +- .../Visual/Gameplay/TestSceneCompletionCancellation.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) rename osu.Game.Tests/Visual/Gameplay/{TestPlayerTestScene.cs => OsuPlayerTestScene.cs} (87%) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index 7d3d8b7f16..acefaa006a 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -25,7 +25,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Gameplay { [HeadlessTest] - public class TestSceneHitObjectSamples : TestPlayerTestScene + public class TestSceneHitObjectSamples : OsuPlayerTestScene { private readonly SkinInfo userSkinInfo = new SkinInfo(); diff --git a/osu.Game.Tests/Visual/Gameplay/TestPlayerTestScene.cs b/osu.Game.Tests/Visual/Gameplay/OsuPlayerTestScene.cs similarity index 87% rename from osu.Game.Tests/Visual/Gameplay/TestPlayerTestScene.cs rename to osu.Game.Tests/Visual/Gameplay/OsuPlayerTestScene.cs index bbf0136b00..cbf8515567 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestPlayerTestScene.cs +++ b/osu.Game.Tests/Visual/Gameplay/OsuPlayerTestScene.cs @@ -9,7 +9,7 @@ namespace osu.Game.Tests.Visual.Gameplay /// /// A with an arbitrary ruleset value to test with. /// - public abstract class TestPlayerTestScene : PlayerTestScene + public abstract class OsuPlayerTestScene : PlayerTestScene { protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs index f87999ae61..79275d70a7 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneCompletionCancellation.cs @@ -16,7 +16,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneCompletionCancellation : TestPlayerTestScene + public class TestSceneCompletionCancellation : OsuPlayerTestScene { private Track track; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs index 744eeed022..2a119f5199 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneGameplayRewinding.cs @@ -16,7 +16,7 @@ using osuTK; namespace osu.Game.Tests.Visual.Gameplay { - public class TestSceneGameplayRewinding : TestPlayerTestScene + public class TestSceneGameplayRewinding : OsuPlayerTestScene { [Resolved] private AudioManager audioManager { get; set; } diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 411265d600..387ac42f67 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -16,7 +16,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { - public class TestScenePause : TestPlayerTestScene + public class TestScenePause : OsuPlayerTestScene { protected new PausePlayer Player => (PausePlayer)base.Player; diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs index 20911bfa4d..e43e5ba3ce 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePauseWhenInactive.cs @@ -12,7 +12,7 @@ using osu.Game.Rulesets; namespace osu.Game.Tests.Visual.Gameplay { [HeadlessTest] // we alter unsafe properties on the game host to test inactive window state. - public class TestScenePauseWhenInactive : TestPlayerTestScene + public class TestScenePauseWhenInactive : OsuPlayerTestScene { protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) { From b076cf96b71b07bdd4929f2a92eaa7f303d66a98 Mon Sep 17 00:00:00 2001 From: Power Maker Date: Fri, 12 Jun 2020 13:20:09 +0200 Subject: [PATCH 401/871] move cursorRotate.Value check into shouldRotateCursor() method --- osu.Game/Graphics/Cursor/MenuCursor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 507d218fb8..b89ad6a356 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -83,7 +83,7 @@ namespace osu.Game.Graphics.Cursor activeCursor.AdditiveLayer.FadeInFromZero(800, Easing.OutQuint); } - if (shouldRotateCursor(e) && cursorRotate.Value) + if (shouldRotateCursor(e)) { // if cursor is already rotating don't reset its rotate origin if (dragRotationState != DragRotationState.Rotating) @@ -126,7 +126,7 @@ namespace osu.Game.Graphics.Cursor activeCursor.ScaleTo(0.6f, 250, Easing.In); } - private static bool shouldRotateCursor(MouseEvent e) => e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Right); + private bool shouldRotateCursor(MouseEvent e) => cursorRotate.Value && (e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Right)); private static bool anyMainButtonPressed(MouseEvent e) => e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Middle) || e.IsPressed(MouseButton.Right); From 7c3e7b65a820365799af5ebb590276263d28bb2a Mon Sep 17 00:00:00 2001 From: mcendu Date: Fri, 12 Jun 2020 21:22:22 +0800 Subject: [PATCH 402/871] add custom file path support for osu\!mania judgement sprite --- .../Skinning/ManiaLegacySkinTransformer.cs | 66 +++++++++++-------- .../LegacyManiaSkinConfigurationLookup.cs | 8 ++- osu.Game/Skinning/LegacySkin.cs | 18 +++++ 3 files changed, 65 insertions(+), 27 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index e64178083a..9ba544ed59 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -11,6 +11,7 @@ using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Skinning; +using System.Collections.Generic; namespace osu.Game.Rulesets.Mania.Skinning { @@ -19,6 +20,36 @@ namespace osu.Game.Rulesets.Mania.Skinning private readonly ISkin source; private readonly ManiaBeatmap beatmap; + /// + /// Mapping of to ther corresponding + /// value. + /// + private static readonly IReadOnlyDictionary componentMapping + = new Dictionary + { + { HitResult.Perfect, LegacyManiaSkinConfigurationLookups.Hit300g }, + { HitResult.Great, LegacyManiaSkinConfigurationLookups.Hit300 }, + { HitResult.Good, LegacyManiaSkinConfigurationLookups.Hit200 }, + { HitResult.Ok, LegacyManiaSkinConfigurationLookups.Hit100 }, + { HitResult.Meh, LegacyManiaSkinConfigurationLookups.Hit50 }, + { HitResult.Miss, LegacyManiaSkinConfigurationLookups.Hit0 } + }; + + /// + /// Mapping of to their corresponding + /// default filenames. + /// + private static readonly IReadOnlyDictionary defaultName + = new Dictionary + { + { HitResult.Perfect, "mania-hit300g" }, + { HitResult.Great, "mania-hit300" }, + { HitResult.Good, "mania-hit200" }, + { HitResult.Ok, "mania-hit100" }, + { HitResult.Meh, "mania-hit50" }, + { HitResult.Miss, "mania-hit0" } + }; + private Lazy isLegacySkin; /// @@ -47,15 +78,15 @@ namespace osu.Game.Rulesets.Mania.Skinning public Drawable GetDrawableComponent(ISkinComponent component) { + if (!isLegacySkin.Value || !hasKeyTexture.Value) + return null; + switch (component) { case GameplaySkinComponent resultComponent: - return getResult(resultComponent); + return getResult(resultComponent.Component); case ManiaSkinComponent maniaComponent: - if (!isLegacySkin.Value || !hasKeyTexture.Value) - return null; - switch (maniaComponent.Component) { case ManiaSkinComponents.ColumnBackground: @@ -95,30 +126,13 @@ namespace osu.Game.Rulesets.Mania.Skinning return null; } - private Drawable getResult(GameplaySkinComponent resultComponent) + private Drawable getResult(HitResult result) { - switch (resultComponent.Component) - { - case HitResult.Miss: - return this.GetAnimation("mania-hit0", true, true); + string image = GetConfig( + new ManiaSkinConfigurationLookup(componentMapping[result]) + )?.Value ?? defaultName[result]; - case HitResult.Meh: - return this.GetAnimation("mania-hit50", true, true); - - case HitResult.Ok: - return this.GetAnimation("mania-hit100", true, true); - - case HitResult.Good: - return this.GetAnimation("mania-hit200", true, true); - - case HitResult.Great: - return this.GetAnimation("mania-hit300", true, true); - - case HitResult.Perfect: - return this.GetAnimation("mania-hit300g", true, true); - } - - return null; + return this.GetAnimation(image, true, true); } public Texture GetTexture(string componentName) => source.GetTexture(componentName); diff --git a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs index c76d5c8784..4990ca8e60 100644 --- a/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs +++ b/osu.Game/Skinning/LegacyManiaSkinConfigurationLookup.cs @@ -43,6 +43,12 @@ namespace osu.Game.Skinning MinimumColumnWidth, LeftStageImage, RightStageImage, - BottomStageImage + BottomStageImage, + Hit300g, + Hit300, + Hit200, + Hit100, + Hit50, + Hit0, } } diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 003fa24d5b..390dc871e4 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -257,6 +257,24 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.RightLineWidth: Debug.Assert(maniaLookup.TargetColumn != null); return SkinUtils.As(new Bindable(existing.ColumnLineWidth[maniaLookup.TargetColumn.Value + 1])); + + case LegacyManiaSkinConfigurationLookups.Hit0: + return SkinUtils.As(getManiaImage(existing, "Hit0")); + + case LegacyManiaSkinConfigurationLookups.Hit50: + return SkinUtils.As(getManiaImage(existing, "Hit50")); + + case LegacyManiaSkinConfigurationLookups.Hit100: + return SkinUtils.As(getManiaImage(existing, "Hit100")); + + case LegacyManiaSkinConfigurationLookups.Hit200: + return SkinUtils.As(getManiaImage(existing, "Hit200")); + + case LegacyManiaSkinConfigurationLookups.Hit300: + return SkinUtils.As(getManiaImage(existing, "Hit300")); + + case LegacyManiaSkinConfigurationLookups.Hit300g: + return SkinUtils.As(getManiaImage(existing, "Hit300g")); } return null; From 8924ff4ba6585e883b8da322d8d3b1662b1fcc76 Mon Sep 17 00:00:00 2001 From: Power Maker Date: Fri, 12 Jun 2020 15:43:19 +0200 Subject: [PATCH 403/871] Rename shouldRotateCursor() to shouldKeepRotating() --- osu.Game/Graphics/Cursor/MenuCursor.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index b89ad6a356..8305f33e25 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -83,7 +83,7 @@ namespace osu.Game.Graphics.Cursor activeCursor.AdditiveLayer.FadeInFromZero(800, Easing.OutQuint); } - if (shouldRotateCursor(e)) + if (shouldKeepRotating(e)) { // if cursor is already rotating don't reset its rotate origin if (dragRotationState != DragRotationState.Rotating) @@ -104,7 +104,7 @@ namespace osu.Game.Graphics.Cursor activeCursor.ScaleTo(1, 500, Easing.OutElastic); } - if (!shouldRotateCursor(e)) + if (!shouldKeepRotating(e)) { if (dragRotationState == DragRotationState.Rotating) activeCursor.RotateTo(0, 600 * (1 + Math.Abs(activeCursor.Rotation / 720)), Easing.OutElasticHalf); @@ -126,7 +126,7 @@ namespace osu.Game.Graphics.Cursor activeCursor.ScaleTo(0.6f, 250, Easing.In); } - private bool shouldRotateCursor(MouseEvent e) => cursorRotate.Value && (e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Right)); + private bool shouldKeepRotating(MouseEvent e) => cursorRotate.Value && (e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Right)); private static bool anyMainButtonPressed(MouseEvent e) => e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Middle) || e.IsPressed(MouseButton.Right); From c9469dc0ddaf98b6d119e01581e09b7c209720fe Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 12 Jun 2020 22:48:43 +0900 Subject: [PATCH 404/871] Add background --- .../TestSceneTimingDistributionGraph.cs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs index 4129975166..73225ff599 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs @@ -19,12 +19,20 @@ namespace osu.Game.Tests.Visual.Ranking { public TestSceneTimingDistributionGraph() { - Add(new TimingDistributionGraph(createNormalDistribution()) + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(400, 130) - }); + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#333") + }, + new TimingDistributionGraph(createNormalDistribution()) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(400, 130) + } + }; } private TimingDistribution createNormalDistribution() From ce56c457218879b78b14bd1226c300e27731e194 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 12 Jun 2020 22:48:52 +0900 Subject: [PATCH 405/871] Implement the accuracy heatmap --- .../Ranking/TestSceneAccuracyHeatmap.cs | 273 ++++++++++++++++++ 1 file changed, 273 insertions(+) create mode 100644 osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs new file mode 100644 index 0000000000..8386ee5992 --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs @@ -0,0 +1,273 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Framework.Utils; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual.Ranking +{ + public class TestSceneAccuracyHeatmap : OsuManualInputManagerTestScene + { + private readonly Box background; + private readonly Drawable object1; + private readonly Drawable object2; + private readonly Heatmap heatmap; + + public TestSceneAccuracyHeatmap() + { + Children = new[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#333"), + }, + object1 = new BorderCircle + { + Position = new Vector2(256, 192), + Colour = Color4.Yellow, + }, + object2 = new BorderCircle + { + Position = new Vector2(500, 300), + }, + heatmap = new Heatmap + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + Scheduler.AddDelayed(() => + { + var randomPos = new Vector2( + RNG.NextSingle(object1.DrawPosition.X - object1.DrawSize.X / 2, object1.DrawPosition.X + object1.DrawSize.X / 2), + RNG.NextSingle(object1.DrawPosition.Y - object1.DrawSize.Y / 2, object1.DrawPosition.Y + object1.DrawSize.Y / 2)); + + // The background is used for ToLocalSpace() since we need to go _inside_ the DrawSizePreservingContainer (Content of TestScene). + heatmap.AddPoint(object2.Position, object1.Position, randomPos, RNG.NextSingle(10, 500)); + InputManager.MoveMouseTo(background.ToScreenSpace(randomPos)); + }, 1, true); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + heatmap.AddPoint(object2.Position, object1.Position, background.ToLocalSpace(e.ScreenSpaceMouseDownPosition), 50); + return true; + } + + private class Heatmap : CompositeDrawable + { + /// + /// Full size of the heatmap. + /// + private const float size = 100; + + /// + /// Size of the inner circle containing the "hit" points, relative to . + /// All other points outside of the inner circle are "miss" points. + /// + private const float inner_portion = 0.8f; + + private const float rotation = 45; + private const float point_size = 4; + + private Container allPoints; + + public Heatmap() + { + Size = new Vector2(size); + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(inner_portion), + Masking = true, + BorderThickness = 2f, + BorderColour = Color4.White, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#202624") + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 2, // We're rotating along a diagonal - we don't really care how big this is. + Width = 1f, + Rotation = -rotation, + Alpha = 0.3f, + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 2, // We're rotating along a diagonal - we don't really care how big this is. + Width = 1f, + Rotation = rotation + }, + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Width = 10, + Height = 2f, + }, + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Y = -1, + Width = 2f, + Height = 10, + } + } + }, + allPoints = new Container { RelativeSizeAxes = Axes.Both } + }; + + Vector2 centre = new Vector2(size / 2); + int rows = (int)Math.Ceiling(size / point_size); + int cols = (int)Math.Ceiling(size / point_size); + + for (int r = 0; r < rows; r++) + { + for (int c = 0; c < cols; c++) + { + Vector2 pos = new Vector2(c * point_size, r * point_size); + HitType type = HitType.Hit; + + if (Vector2.Distance(pos, centre) > size * inner_portion / 2) + type = HitType.Miss; + + allPoints.Add(new HitPoint(pos, type) + { + Size = new Vector2(point_size), + Colour = type == HitType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) + }); + } + } + } + + public void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) + { + double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point. + double angle2 = Math.Atan2(end.Y - start.Y, start.X - end.X); // Angle between the end point and the start point. + double finalAngle = angle2 - angle1; // Angle between start, end, and hit points. + + float normalisedDistance = Vector2.Distance(hitPoint, end) / radius; + + // Find the most relevant hit point. + double minDist = double.PositiveInfinity; + HitPoint point = null; + + foreach (var p in allPoints) + { + Vector2 localCentre = new Vector2(size / 2); + float localRadius = localCentre.X * inner_portion * normalisedDistance; + double localAngle = finalAngle + 3 * Math.PI / 4; + Vector2 localPoint = localCentre + localRadius * new Vector2((float)Math.Cos(localAngle), (float)Math.Sin(localAngle)); + + float dist = Vector2.Distance(p.DrawPosition + p.DrawSize / 2, localPoint); + + if (dist < minDist) + { + minDist = dist; + point = p; + } + } + + Debug.Assert(point != null); + point.Increment(); + } + } + + private class HitPoint : Circle + { + private readonly HitType type; + + public HitPoint(Vector2 position, HitType type) + { + this.type = type; + + Position = position; + Alpha = 0; + } + + public void Increment() + { + if (Alpha < 1) + Alpha += 0.1f; + else if (type == HitType.Hit) + Colour = ((Color4)Colour).Lighten(0.1f); + } + } + + private enum HitType + { + Hit, + Miss + } + + private class BorderCircle : CircularContainer + { + public BorderCircle() + { + Origin = Anchor.Centre; + Size = new Vector2(100); + + Masking = true; + BorderThickness = 2; + BorderColour = Color4.White; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + }, + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(4), + } + }; + } + } + } +} From 81c392b841999e9f237a7b3f2e24d467957c876f Mon Sep 17 00:00:00 2001 From: Shivam Date: Fri, 12 Jun 2020 15:57:23 +0200 Subject: [PATCH 406/871] Change hash to be lowercase and change sample directories --- osu.Game/Screens/Menu/IntroCircles.cs | 2 +- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- osu.Game/Screens/Menu/IntroTriangles.cs | 2 +- osu.Game/Screens/Menu/IntroWelcome.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroCircles.cs b/osu.Game/Screens/Menu/IntroCircles.cs index 113d496855..d4cd073b7a 100644 --- a/osu.Game/Screens/Menu/IntroCircles.cs +++ b/osu.Game/Screens/Menu/IntroCircles.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Menu private void load(AudioManager audio) { if (MenuVoice.Value) - welcome = audio.Samples.Get(@"Intro/lazer/welcome"); + welcome = audio.Samples.Get(@"Intro/welcome"); } protected override void LogoArriving(OsuLogo logo, bool resuming) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index abc7a3c7ee..2f9d43bed6 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Menu MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); - Seeya = audio.Samples.Get(@"Intro/lazer/seeya"); + Seeya = audio.Samples.Get(@"Intro/seeya"); BeatmapSetInfo setInfo = null; diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index fb84ccffd0..9be74a0fd9 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Menu private void load() { if (MenuVoice.Value && !UsingThemedIntro) - welcome = audio.Samples.Get(@"Intro/lazer/welcome"); + welcome = audio.Samples.Get(@"Intro/welcome"); } protected override void LogoArriving(OsuLogo logo, bool resuming) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 38405fab6a..dec3af5ac9 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Menu { public class IntroWelcome : IntroScreen { - protected override string BeatmapHash => "64E00D7022195959BFA3109D09C2E2276C8F12F486B91FCF6175583E973B48F2"; + protected override string BeatmapHash => "64e00d7022195959bfa3109d09c2e2276c8f12f486b91fcf6175583e973b48f2"; protected override string BeatmapFile => "welcome.osz"; private const double delay_step_two = 2142; private SampleChannel welcome; From 6000e0f86a48e89d6e64f14cb4f9b5046f04c486 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 12 Jun 2020 23:01:22 +0900 Subject: [PATCH 407/871] Increase size to match timing distribution graph --- osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs index 8386ee5992..b605ddcc35 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual.Ranking /// /// Full size of the heatmap. /// - private const float size = 100; + private const float size = 130; /// /// Size of the inner circle containing the "hit" points, relative to . From 8a9d01119753923a0230e0c5bea40f93b4586cdf Mon Sep 17 00:00:00 2001 From: mcendu Date: Fri, 12 Jun 2020 22:09:58 +0800 Subject: [PATCH 408/871] add test files --- .../Resources/special-skin/mania/hit0@2x.png | Bin 0 -> 56621 bytes .../Resources/special-skin/mania/hit100@2x.png | Bin 0 -> 66535 bytes .../Resources/special-skin/mania/hit200@2x.png | Bin 0 -> 82190 bytes .../Resources/special-skin/mania/hit300@2x.png | Bin 0 -> 92906 bytes .../special-skin/mania/hit300g-0@2x.png | Bin 0 -> 56026 bytes .../special-skin/mania/hit300g-1@2x.png | Bin 0 -> 57038 bytes .../Resources/special-skin/mania/hit50@2x.png | Bin 0 -> 57215 bytes .../special-skin/mania/stage-bottom@2x.png | Bin 0 -> 1965 bytes .../Resources/special-skin/skin.ini | 11 +++++++++-- 9 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit0@2x.png create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit100@2x.png create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit200@2x.png create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300@2x.png create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300g-0@2x.png create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300g-1@2x.png create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit50@2x.png create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-bottom@2x.png diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit0@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit0@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..2e7b9bc34f3ea66e5d4e687adb312f36d7c5a4bd GIT binary patch literal 56621 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

~<2&bFepI2$u~| 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 FV1mC)D$D=? literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit200@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit200@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..24ad926375e8c88df664bdd9555ddddc1a748be9 GIT binary patch literal 82190 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 IM6N<$f=61fdH?_b literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..098561f980ddc8a17308c29a62a249fd2812e370 GIT binary patch literal 92906 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

E=Rmmw{ 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­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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300g-1@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/hit300g-1@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..f17b2b1e737a4d9b1c88247a738dadec3d9dfd1a GIT binary patch literal 57038 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 Ks#k-08_-AT>ABZ45&7U3%MLFgSfv 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

}+-2g^TWTo$yAMT?d2TCHI=II`n0mRKNHw#M7` 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 literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-bottom@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-bottom@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ca590eaf08e65dc1ae40bce9f9f74e69c3a0f8a4 GIT binary patch literal 1965 zcmV;e2U7TnP)3L?K*_rKGQL~x> z08mZsB?AB;00{&D5QGE*00=??0RRLcfdBx4kU#(cK}a9~fFL9g06-8D2ml}m2?PKT zgaiTr2ton@00ep2fXKN$&0jO!YxaDt*~2%@9=&BYwcLXAPMQr~G21k5w&6kJxjb8A z_D|IAZ~bOB28!G08;c&-tz|}co88}PcJ#Q}%BR*6wH~u$-xc>$EBeCeshNF!$ZXkE z{4@Pc`rGCr-6ySR6WyVQ-FPAY_E=?Chs?hHsuAqU`YZ}YpPY$;++^|Vo<)i3z{N(e z%4_H7h_!?Q{lQCS*LIoxvfXU+xV41Z-S{|4x@)q3->SZFltBJiXSV8T{Ikhd%-($* zp6p+HV)pyGz&!{aR!1JHReDt%hW5g z1K;zRuk~Pe(Cp;lMj{afL)Gwe%S|Ve0rQ)YM(Ha}HGe(sZ;8)r6ciOiZ}ulkWz);! zV4G#u@vkXPAZ6QC-xPgLGxO0G&FJ%#jMQEgnrBKU55PwW3zcakeCk+SS65)``!)Nu zs-X{Fj-OxMlN~U98rf^a?Aq5z_{3rBc&+M-vwN)+|NrxT#S0Gfo_*)y=MO6;A1nFH7I=uowik4k0!)k7a$v5vLSeW3((Z%h21DwFB;V?Aq$W35s$!_q+l zK`=jQWYyQ}lkPL4SOv73A6O*{ewqUi1+`6gtYa-kU+8n(nTy`@@i{9+$LiMDsEw{LB_v=Mv^2@D zpOsnbgQbVdcrlBv6w-umnnt|}B>sf?4^fHa%hTEItb7)KKOGP-B|T4|1QxogdNKKZ zQT5W_y!R=IG;6D#1dhe?A3AfqFr43q>B;kZTMWoW0>LZ_Hk(VlE^iIPd|K9_31l(p z3`BkA7K7^ln_eg?S(E2VP(3U)Mh;3?2=@6%W92jDjrOCU?>0azJDHxIJRjX*W-2aC<`i!vK1W|>Q0q$ktUI|pP`0cSxKJ0lB-ksec|RJs+vn*xXE-!7fSfYz~nhXkU$VjH#_pBS30K`7rs$(9fwB6 z{(Gu!2G~y0>pnvIV&z2qJftsl4DPH^$-}K?&zn34njwK8SUSioOO;p@m}!+!y2V_aS|uvyf{;@iFMEQPPv={(K2ggam?M=`zf8#+-+LJJy{Q_p(w+ zrP*rtPBPcN@V7zC8pJa@9nXoW_&wPpoYO-EPqieNo>|zCSrBU*!f@fI|YoEM>|n zPSIwHzK3s{ZT~a5YF%eAdh8g>e$U-)X1jk5--xkYwL@Ph;U763PoC?HgiCi(6(uZG zq9CJSb1EuXz5b$H@^Ccz-uNWB6-t)7Tg?V;H%98+mOLE4FN&^unyfTq&~sl z1tmf(yq*;`W3Npt3XQlm0(WyM3ZzOb-I_Ft&==@HH4+E_AViH4762d!2?PKTgaiTr z2ton@00bd{004rJKmY(iScwh*)v^2`>Vy9Q*Vy#8=R%=B00000NkvXXu0mjflsBGC literal 0 HcmV?d00001 diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini index 56564776b3..eaa50d1a61 100644 --- a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini +++ b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini @@ -1,6 +1,13 @@ [General] -Version: 2.4 +Version: 2.5 [Mania] Keys: 4 -ColumnLineWidth: 3,1,3,1,1 \ No newline at end of file +ColumnLineWidth: 3,1,3,1,1 +Hit0: mania/hit0 +Hit50: mania/hit50 +Hit100: mania/hit100 +Hit200: mania/hit200 +Hit300: mania/hit300 +Hit300g: mania/hit300g +BottomStageImage: mania/stage-bottom \ No newline at end of file From b663c940aea1da0d66b8cea0c86637edaacf81d3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 12 Jun 2020 23:46:46 +0900 Subject: [PATCH 409/871] Rename enum --- .../Visual/Ranking/TestSceneAccuracyHeatmap.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs index b605ddcc35..9f82287640 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs @@ -167,15 +167,15 @@ namespace osu.Game.Tests.Visual.Ranking for (int c = 0; c < cols; c++) { Vector2 pos = new Vector2(c * point_size, r * point_size); - HitType type = HitType.Hit; + HitPointType pointType = HitPointType.Hit; if (Vector2.Distance(pos, centre) > size * inner_portion / 2) - type = HitType.Miss; + pointType = HitPointType.Miss; - allPoints.Add(new HitPoint(pos, type) + allPoints.Add(new HitPoint(pos, pointType) { Size = new Vector2(point_size), - Colour = type == HitType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) + Colour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) }); } } @@ -216,11 +216,11 @@ namespace osu.Game.Tests.Visual.Ranking private class HitPoint : Circle { - private readonly HitType type; + private readonly HitPointType pointType; - public HitPoint(Vector2 position, HitType type) + public HitPoint(Vector2 position, HitPointType pointType) { - this.type = type; + this.pointType = pointType; Position = position; Alpha = 0; @@ -230,12 +230,12 @@ namespace osu.Game.Tests.Visual.Ranking { if (Alpha < 1) Alpha += 0.1f; - else if (type == HitType.Hit) + else if (pointType == HitPointType.Hit) Colour = ((Color4)Colour).Lighten(0.1f); } } - private enum HitType + private enum HitPointType { Hit, Miss From 586e3d405c8d7863137b542b914b044ffbe4fde2 Mon Sep 17 00:00:00 2001 From: mcendu Date: Fri, 12 Jun 2020 22:48:18 +0800 Subject: [PATCH 410/871] add proper decoding support? --- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index a988bd589f..cbd6aa17dc 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -111,11 +111,11 @@ namespace osu.Game.Skinning HandleColours(currentConfig, line); break; + // Custom sprite paths case string _ when pair.Key.StartsWith("NoteImage"): - currentConfig.ImageLookups[pair.Key] = pair.Value; - break; - case string _ when pair.Key.StartsWith("KeyImage"): + case string _ when pair.Key.StartsWith("Hit"): + case "BottomStageImage": currentConfig.ImageLookups[pair.Key] = pair.Value; break; } From 7def6a91812670529b671b5e89abf243d8a8c30b Mon Sep 17 00:00:00 2001 From: mcendu Date: Fri, 12 Jun 2020 23:06:19 +0800 Subject: [PATCH 411/871] fix tests incorrectly showing judgements not present in mania --- .../Skinning/TestSceneDrawableJudgement.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs index 497b80950a..540bf82e1f 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs @@ -6,6 +6,7 @@ using System.Linq; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Scoring; @@ -16,14 +17,17 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { public TestSceneDrawableJudgement() { + var HitWindows = new ManiaHitWindows(); + foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1)) { - AddStep("Show " + result.GetDescription(), () => SetContents(() => - new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - })); + if (HitWindows.IsHitResultAllowed(result)) + AddStep("Show " + result.GetDescription(), () => SetContents(() => + new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + })); } } } From c6e087b9947aa46a8bed3abe3928ebd1d2a0ba04 Mon Sep 17 00:00:00 2001 From: mcendu Date: Fri, 12 Jun 2020 23:11:50 +0800 Subject: [PATCH 412/871] remove incorrectly added key --- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index cbd6aa17dc..0806676fde 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -115,7 +115,6 @@ namespace osu.Game.Skinning case string _ when pair.Key.StartsWith("NoteImage"): case string _ when pair.Key.StartsWith("KeyImage"): case string _ when pair.Key.StartsWith("Hit"): - case "BottomStageImage": currentConfig.ImageLookups[pair.Key] = pair.Value; break; } From da46288ef09489d5d98a163f5ada7e292aed091b Mon Sep 17 00:00:00 2001 From: mcendu Date: Fri, 12 Jun 2020 23:20:04 +0800 Subject: [PATCH 413/871] remove stage bottom image --- .../special-skin/mania/stage-bottom@2x.png | Bin 1965 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-bottom@2x.png diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-bottom@2x.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-bottom@2x.png deleted file mode 100644 index ca590eaf08e65dc1ae40bce9f9f74e69c3a0f8a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1965 zcmV;e2U7TnP)3L?K*_rKGQL~x> z08mZsB?AB;00{&D5QGE*00=??0RRLcfdBx4kU#(cK}a9~fFL9g06-8D2ml}m2?PKT zgaiTr2ton@00ep2fXKN$&0jO!YxaDt*~2%@9=&BYwcLXAPMQr~G21k5w&6kJxjb8A z_D|IAZ~bOB28!G08;c&-tz|}co88}PcJ#Q}%BR*6wH~u$-xc>$EBeCeshNF!$ZXkE z{4@Pc`rGCr-6ySR6WyVQ-FPAY_E=?Chs?hHsuAqU`YZ}YpPY$;++^|Vo<)i3z{N(e z%4_H7h_!?Q{lQCS*LIoxvfXU+xV41Z-S{|4x@)q3->SZFltBJiXSV8T{Ikhd%-($* zp6p+HV)pyGz&!{aR!1JHReDt%hW5g z1K;zRuk~Pe(Cp;lMj{afL)Gwe%S|Ve0rQ)YM(Ha}HGe(sZ;8)r6ciOiZ}ulkWz);! zV4G#u@vkXPAZ6QC-xPgLGxO0G&FJ%#jMQEgnrBKU55PwW3zcakeCk+SS65)``!)Nu zs-X{Fj-OxMlN~U98rf^a?Aq5z_{3rBc&+M-vwN)+|NrxT#S0Gfo_*)y=MO6;A1nFH7I=uowik4k0!)k7a$v5vLSeW3((Z%h21DwFB;V?Aq$W35s$!_q+l zK`=jQWYyQ}lkPL4SOv73A6O*{ewqUi1+`6gtYa-kU+8n(nTy`@@i{9+$LiMDsEw{LB_v=Mv^2@D zpOsnbgQbVdcrlBv6w-umnnt|}B>sf?4^fHa%hTEItb7)KKOGP-B|T4|1QxogdNKKZ zQT5W_y!R=IG;6D#1dhe?A3AfqFr43q>B;kZTMWoW0>LZ_Hk(VlE^iIPd|K9_31l(p z3`BkA7K7^ln_eg?S(E2VP(3U)Mh;3?2=@6%W92jDjrOCU?>0azJDHxIJRjX*W-2aC<`i!vK1W|>Q0q$ktUI|pP`0cSxKJ0lB-ksec|RJs+vn*xXE-!7fSfYz~nhXkU$VjH#_pBS30K`7rs$(9fwB6 z{(Gu!2G~y0>pnvIV&z2qJftsl4DPH^$-}K?&zn34njwK8SUSioOO;p@m}!+!y2V_aS|uvyf{;@iFMEQPPv={(K2ggam?M=`zf8#+-+LJJy{Q_p(w+ zrP*rtPBPcN@V7zC8pJa@9nXoW_&wPpoYO-EPqieNo>|zCSrBU*!f@fI|YoEM>|n zPSIwHzK3s{ZT~a5YF%eAdh8g>e$U-)X1jk5--xkYwL@Ph;U763PoC?HgiCi(6(uZG zq9CJSb1EuXz5b$H@^Ccz-uNWB6-t)7Tg?V;H%98+mOLE4FN&^unyfTq&~sl z1tmf(yq*;`W3Npt3XQlm0(WyM3ZzOb-I_Ft&==@HH4+E_AViH4762d!2?PKTgaiTr z2ton@00bd{004rJKmY(iScwh*)v^2`>Vy9Q*Vy#8=R%=B00000NkvXXu0mjflsBGC From a42bfcb5aba1910cc975fed0d420864917d9069f Mon Sep 17 00:00:00 2001 From: mcendu Date: Fri, 12 Jun 2020 23:23:57 +0800 Subject: [PATCH 414/871] remove reference to stage bottom from skin ini --- osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini index eaa50d1a61..941abac1da 100644 --- a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini +++ b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini @@ -9,5 +9,4 @@ Hit50: mania/hit50 Hit100: mania/hit100 Hit200: mania/hit200 Hit300: mania/hit300 -Hit300g: mania/hit300g -BottomStageImage: mania/stage-bottom \ No newline at end of file +Hit300g: mania/hit300g \ No newline at end of file From aa476835e7b657b414093fb3dcf6b19c3d935d9d Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 13 Jun 2020 11:31:34 +0800 Subject: [PATCH 415/871] tidy up code --- .../Skinning/TestSceneDrawableJudgement.cs | 6 ++- .../Skinning/ManiaLegacySkinTransformer.cs | 40 +++++++++---------- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs index 540bf82e1f..a4d4ec50f8 100644 --- a/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Mania.Tests/Skinning/TestSceneDrawableJudgement.cs @@ -17,17 +17,19 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning { public TestSceneDrawableJudgement() { - var HitWindows = new ManiaHitWindows(); + var hitWindows = new ManiaHitWindows(); foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1)) { - if (HitWindows.IsHitResultAllowed(result)) + if (hitWindows.IsHitResultAllowed(result)) + { AddStep("Show " + result.GetDescription(), () => SetContents(() => new DrawableManiaJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null) { Anchor = Anchor.Centre, Origin = Anchor.Centre, })); + } } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 9ba544ed59..3304330233 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -24,31 +24,31 @@ namespace osu.Game.Rulesets.Mania.Skinning /// Mapping of to ther corresponding /// value. ///

- private static readonly IReadOnlyDictionary componentMapping + private static readonly IReadOnlyDictionary hitresult_mapping = new Dictionary - { - { HitResult.Perfect, LegacyManiaSkinConfigurationLookups.Hit300g }, - { HitResult.Great, LegacyManiaSkinConfigurationLookups.Hit300 }, - { HitResult.Good, LegacyManiaSkinConfigurationLookups.Hit200 }, - { HitResult.Ok, LegacyManiaSkinConfigurationLookups.Hit100 }, - { HitResult.Meh, LegacyManiaSkinConfigurationLookups.Hit50 }, - { HitResult.Miss, LegacyManiaSkinConfigurationLookups.Hit0 } - }; + { + { HitResult.Perfect, LegacyManiaSkinConfigurationLookups.Hit300g }, + { HitResult.Great, LegacyManiaSkinConfigurationLookups.Hit300 }, + { HitResult.Good, LegacyManiaSkinConfigurationLookups.Hit200 }, + { HitResult.Ok, LegacyManiaSkinConfigurationLookups.Hit100 }, + { HitResult.Meh, LegacyManiaSkinConfigurationLookups.Hit50 }, + { HitResult.Miss, LegacyManiaSkinConfigurationLookups.Hit0 } + }; /// /// Mapping of to their corresponding /// default filenames. /// - private static readonly IReadOnlyDictionary defaultName + private static readonly IReadOnlyDictionary default_hitresult_skin_filenames = new Dictionary - { - { HitResult.Perfect, "mania-hit300g" }, - { HitResult.Great, "mania-hit300" }, - { HitResult.Good, "mania-hit200" }, - { HitResult.Ok, "mania-hit100" }, - { HitResult.Meh, "mania-hit50" }, - { HitResult.Miss, "mania-hit0" } - }; + { + { HitResult.Perfect, "mania-hit300g" }, + { HitResult.Great, "mania-hit300" }, + { HitResult.Good, "mania-hit200" }, + { HitResult.Ok, "mania-hit100" }, + { HitResult.Meh, "mania-hit50" }, + { HitResult.Miss, "mania-hit0" } + }; private Lazy isLegacySkin; @@ -129,8 +129,8 @@ namespace osu.Game.Rulesets.Mania.Skinning private Drawable getResult(HitResult result) { string image = GetConfig( - new ManiaSkinConfigurationLookup(componentMapping[result]) - )?.Value ?? defaultName[result]; + new ManiaSkinConfigurationLookup(hitresult_mapping[result]) + )?.Value ?? default_hitresult_skin_filenames[result]; return this.GetAnimation(image, true, true); } From 7212ab3a1afe56a5ae0654811d8ce535cf1e24c6 Mon Sep 17 00:00:00 2001 From: clayton Date: Fri, 12 Jun 2020 23:48:30 -0700 Subject: [PATCH 416/871] Add new beatmap genres and languages --- osu.Game/Overlays/BeatmapListing/SearchGenre.cs | 6 +++++- osu.Game/Overlays/BeatmapListing/SearchLanguage.cs | 12 +++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/SearchGenre.cs b/osu.Game/Overlays/BeatmapListing/SearchGenre.cs index b12bba6249..de437fac3e 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchGenre.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchGenre.cs @@ -20,6 +20,10 @@ namespace osu.Game.Overlays.BeatmapListing [Description("Hip Hop")] HipHop = 9, - Electronic = 10 + Electronic = 10, + Metal = 11, + Classical = 12, + Folk = 13, + Jazz = 14 } } diff --git a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs index dac7e4f1a2..ef7576344a 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs @@ -11,7 +11,7 @@ namespace osu.Game.Overlays.BeatmapListing [Order(0)] Any, - [Order(11)] + [Order(13)] Other, [Order(1)] @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.BeatmapListing [Order(2)] Chinese, - [Order(10)] + [Order(12)] Instrumental, [Order(7)] @@ -42,6 +42,12 @@ namespace osu.Game.Overlays.BeatmapListing Spanish, [Order(5)] - Italian + Italian, + + [Order(10)] + Russian, + + [Order(11)] + Polish } } From 9d98adee1e0b0b806f4d678e3310a5fb3cc3edc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 13 Jun 2020 16:10:41 +0900 Subject: [PATCH 417/871] Update fastlane to fix upload failure for iOS releases --- Gemfile.lock | 72 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index e3954c2681..bf971d2c22 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,6 +5,22 @@ GEM addressable (2.7.0) public_suffix (>= 2.0.2, < 5.0) atomos (0.1.3) + aws-eventstream (1.1.0) + aws-partitions (1.329.0) + aws-sdk-core (3.99.2) + aws-eventstream (~> 1, >= 1.0.2) + aws-partitions (~> 1, >= 1.239.0) + aws-sigv4 (~> 1.1) + jmespath (~> 1.0) + aws-sdk-kms (1.34.1) + aws-sdk-core (~> 3, >= 3.99.0) + aws-sigv4 (~> 1.1) + aws-sdk-s3 (1.68.1) + aws-sdk-core (~> 3, >= 3.99.0) + aws-sdk-kms (~> 1) + aws-sigv4 (~> 1.1) + aws-sigv4 (1.1.4) + aws-eventstream (~> 1.0, >= 1.0.2) babosa (1.0.3) claide (1.0.3) colored (1.2) @@ -13,23 +29,24 @@ GEM highline (~> 1.7.2) declarative (0.0.10) declarative-option (0.1.0) - digest-crc (0.4.1) + digest-crc (0.5.1) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) dotenv (2.7.5) emoji_regex (1.0.1) - excon (0.71.1) - faraday (0.17.3) + excon (0.74.0) + faraday (1.0.1) multipart-post (>= 1.2, < 3) faraday-cookie_jar (0.0.6) faraday (>= 0.7.4) http-cookie (~> 1.0.0) - faraday_middleware (0.13.1) - faraday (>= 0.7.4, < 1.0) + faraday_middleware (1.0.0) + faraday (~> 1.0) fastimage (2.1.7) - fastlane (2.140.0) + fastlane (2.149.1) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.3, < 3.0.0) + aws-sdk-s3 (~> 1.0) babosa (>= 1.0.2, < 2.0.0) bundler (>= 1.12.0, < 3.0.0) colored @@ -37,12 +54,12 @@ GEM dotenv (>= 2.1.1, < 3.0.0) emoji_regex (>= 0.1, < 2.0) excon (>= 0.71.0, < 1.0.0) - faraday (~> 0.17) + faraday (>= 0.17, < 2.0) faraday-cookie_jar (~> 0.0.6) - faraday_middleware (~> 0.13.1) + faraday_middleware (>= 0.13.1, < 2.0) fastimage (>= 2.1.0, < 3.0.0) gh_inspector (>= 1.1.2, < 2.0.0) - google-api-client (>= 0.29.2, < 0.37.0) + google-api-client (>= 0.37.0, < 0.39.0) google-cloud-storage (>= 1.15.0, < 2.0.0) highline (>= 1.7.2, < 2.0.0) json (< 3.0.0) @@ -69,7 +86,7 @@ GEM souyuz (= 0.9.1) fastlane-plugin-xamarin (0.6.3) gh_inspector (1.1.3) - google-api-client (0.36.4) + google-api-client (0.38.0) addressable (~> 2.5, >= 2.5.1) googleauth (~> 0.9) httpclient (>= 2.8.1, < 3.0) @@ -80,27 +97,28 @@ GEM google-cloud-core (1.5.0) google-cloud-env (~> 1.0) google-cloud-errors (~> 1.0) - google-cloud-env (1.3.0) - faraday (~> 0.11) - google-cloud-errors (1.0.0) - google-cloud-storage (1.25.1) + google-cloud-env (1.3.2) + faraday (>= 0.17.3, < 2.0) + google-cloud-errors (1.0.1) + google-cloud-storage (1.26.2) addressable (~> 2.5) digest-crc (~> 0.4) google-api-client (~> 0.33) google-cloud-core (~> 1.2) googleauth (~> 0.9) mini_mime (~> 1.0) - googleauth (0.10.0) - faraday (~> 0.12) + googleauth (0.12.0) + faraday (>= 0.17.3, < 2.0) jwt (>= 1.4, < 3.0) memoist (~> 0.16) multi_json (~> 1.11) os (>= 0.9, < 2.0) - signet (~> 0.12) + signet (~> 0.14) highline (1.7.10) http-cookie (1.0.3) domain_name (~> 0.5) httpclient (2.8.3) + jmespath (1.4.0) json (2.3.0) jwt (2.1.0) memoist (0.16.2) @@ -114,7 +132,7 @@ GEM naturally (2.2.0) nokogiri (1.10.7) mini_portile2 (~> 2.4.0) - os (1.0.1) + os (1.1.0) plist (3.5.0) public_suffix (2.0.5) representable (3.0.4) @@ -125,12 +143,12 @@ GEM rouge (2.0.7) rubyzip (1.3.0) security (0.1.3) - signet (0.12.0) + signet (0.14.0) addressable (~> 2.3) - faraday (~> 0.9) + faraday (>= 0.17.3, < 2.0) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - simctl (1.6.7) + simctl (1.6.8) CFPropertyList naturally slack-notifier (2.3.2) @@ -141,17 +159,17 @@ GEM terminal-notifier (2.0.0) terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) - tty-cursor (0.7.0) - tty-screen (0.7.0) - tty-spinner (0.9.2) + tty-cursor (0.7.1) + tty-screen (0.8.0) + tty-spinner (0.9.3) tty-cursor (~> 0.7) uber (0.1.0) unf (0.1.4) unf_ext - unf_ext (0.0.7.6) - unicode-display_width (1.6.1) + unf_ext (0.0.7.7) + unicode-display_width (1.7.0) word_wrap (1.0.0) - xcodeproj (1.14.0) + xcodeproj (1.16.0) CFPropertyList (>= 2.3.3, < 4.0) atomos (~> 0.1.3) claide (>= 1.0.2, < 2.0) From 7bc70e644a56a76b9d2be063262e6de378853bc3 Mon Sep 17 00:00:00 2001 From: clayton Date: Sat, 13 Jun 2020 00:20:34 -0700 Subject: [PATCH 418/871] Add Unspecified language --- osu.Game/Overlays/BeatmapListing/SearchLanguage.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs index ef7576344a..43f16059e9 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs @@ -48,6 +48,9 @@ namespace osu.Game.Overlays.BeatmapListing Russian, [Order(11)] - Polish + Polish, + + [Order(14)] + Unspecified } } From c490dba7b3e0e22202ab861e0132a5779d818f0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 13 Jun 2020 18:18:46 +0900 Subject: [PATCH 419/871] Fix crash on local score display --- osu.Game/Scoring/ScoreStore.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Scoring/ScoreStore.cs b/osu.Game/Scoring/ScoreStore.cs index 9627481f4d..f5c5cd5dad 100644 --- a/osu.Game/Scoring/ScoreStore.cs +++ b/osu.Game/Scoring/ScoreStore.cs @@ -18,6 +18,8 @@ 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.Ruleset); } } From 9230c148c7f68ea6908568d72052ca7e165678e2 Mon Sep 17 00:00:00 2001 From: Power Maker Date: Sat, 13 Jun 2020 12:18:50 +0200 Subject: [PATCH 420/871] Add cursor rotation on middle mouse button --- osu.Game/Graphics/Cursor/MenuCursor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index 8305f33e25..ff28dddd40 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -126,7 +126,7 @@ namespace osu.Game.Graphics.Cursor activeCursor.ScaleTo(0.6f, 250, Easing.In); } - private bool shouldKeepRotating(MouseEvent e) => cursorRotate.Value && (e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Right)); + private bool shouldKeepRotating(MouseEvent e) => cursorRotate.Value && (anyMainButtonPressed(e)); private static bool anyMainButtonPressed(MouseEvent e) => e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Middle) || e.IsPressed(MouseButton.Right); From 619c541cf559f546c5718673c4acf279f98ce320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Jun 2020 12:41:00 +0200 Subject: [PATCH 421/871] Rewrite test to use dummy API --- .../Online/TestSceneCommentsContainer.cs | 117 +++++++++++++----- 1 file changed, 89 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index 42e6b9087c..26ad0b0d3f 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -1,52 +1,113 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; +using System.Linq; using NUnit.Framework; -using osu.Game.Online.API.Requests; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics; -using osu.Game.Overlays.Comments; using osu.Game.Overlays; using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Game.Users; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays.Comments; namespace osu.Game.Tests.Visual.Online { [TestFixture] public class TestSceneCommentsContainer : OsuTestScene { - protected override bool UseOnlineAPI => true; - [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - public TestSceneCommentsContainer() - { - BasicScrollContainer scroll; - TestCommentsContainer comments; + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; - Add(scroll = new BasicScrollContainer + private CommentsContainer commentsContainer; + + [SetUp] + public void SetUp() => Schedule(() => + Child = new BasicScrollContainer { RelativeSizeAxes = Axes.Both, - Child = comments = new TestCommentsContainer() + Child = commentsContainer = new CommentsContainer() }); - AddStep("Big Black comments", () => comments.ShowComments(CommentableType.Beatmapset, 41823)); - AddStep("Airman comments", () => comments.ShowComments(CommentableType.Beatmapset, 24313)); - AddStep("Lazer build comments", () => comments.ShowComments(CommentableType.Build, 4772)); - AddStep("News comments", () => comments.ShowComments(CommentableType.NewsPost, 715)); - AddStep("Trigger user change", comments.User.TriggerChange); - AddStep("Idle state", () => - { - scroll.Clear(); - scroll.Add(comments = new TestCommentsContainer()); - }); - } - - private class TestCommentsContainer : CommentsContainer + [Test] + public void TestIdleState() { - public new Bindable User => base.User; + AddUntilStep("loading spinner shown", + () => commentsContainer.ChildrenOfType().Single().IsLoading); } + + [Test] + public void TestSingleCommentsPage() + { + setUpCommentsResponse(exampleComments); + AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123)); + AddUntilStep("show more button hidden", + () => commentsContainer.ChildrenOfType().Single().Alpha == 0); + } + + [Test] + public void TestMultipleCommentPages() + { + var comments = exampleComments; + comments.HasMore = true; + comments.TopLevelCount = 10; + + setUpCommentsResponse(comments); + AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123)); + AddUntilStep("show more button visible", + () => commentsContainer.ChildrenOfType().Single().Alpha == 1); + } + + private void setUpCommentsResponse(CommentBundle commentBundle) + => AddStep("set up response", () => + { + dummyAPI.HandleRequest = request => + { + if (!(request is GetCommentsRequest getCommentsRequest)) + return; + + getCommentsRequest.TriggerSuccess(commentBundle); + }; + }); + + private CommentBundle exampleComments => new CommentBundle + { + Comments = new List + { + new Comment + { + Id = 1, + Message = "This is a comment", + LegacyName = "FirstUser", + CreatedAt = DateTimeOffset.Now, + VotesCount = 19, + RepliesCount = 1 + }, + new Comment + { + Id = 5, + ParentId = 1, + Message = "This is a child comment", + LegacyName = "SecondUser", + CreatedAt = DateTimeOffset.Now, + VotesCount = 4, + }, + new Comment + { + Id = 10, + Message = "This is another comment", + LegacyName = "ThirdUser", + CreatedAt = DateTimeOffset.Now, + VotesCount = 0 + }, + }, + IncludedComments = new List(), + }; } } From 5655e090d1e1102aea9c82b461ffec7402538c65 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 13 Jun 2020 18:45:06 +0800 Subject: [PATCH 422/871] revert movement of is mania skin check statements --- .../Skinning/ManiaLegacySkinTransformer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 3304330233..f386712222 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -78,8 +78,6 @@ namespace osu.Game.Rulesets.Mania.Skinning public Drawable GetDrawableComponent(ISkinComponent component) { - if (!isLegacySkin.Value || !hasKeyTexture.Value) - return null; switch (component) { @@ -87,6 +85,9 @@ namespace osu.Game.Rulesets.Mania.Skinning return getResult(resultComponent.Component); case ManiaSkinComponent maniaComponent: + if (!isLegacySkin.Value || !hasKeyTexture.Value) + return null; + switch (maniaComponent.Component) { case ManiaSkinComponents.ColumnBackground: From 4eeb22ca18e4598ab693c7beb0fd5309188f8ee8 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 13 Jun 2020 18:47:40 +0800 Subject: [PATCH 423/871] rename a few variables and fix typo --- .../Skinning/ManiaLegacySkinTransformer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index f386712222..07d0ce66e2 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Skinning private readonly ManiaBeatmap beatmap; /// - /// Mapping of to ther corresponding + /// Mapping of to their corresponding /// value. /// private static readonly IReadOnlyDictionary hitresult_mapping @@ -129,11 +129,11 @@ namespace osu.Game.Rulesets.Mania.Skinning private Drawable getResult(HitResult result) { - string image = GetConfig( + string filename = GetConfig( new ManiaSkinConfigurationLookup(hitresult_mapping[result]) )?.Value ?? default_hitresult_skin_filenames[result]; - return this.GetAnimation(image, true, true); + return this.GetAnimation(filename, true, true); } public Texture GetTexture(string componentName) => source.GetTexture(componentName); From e8046654c8da91496a72c341e8f2f68bd4be66da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Jun 2020 12:48:16 +0200 Subject: [PATCH 424/871] Add failing test case --- .../Visual/Online/TestSceneCommentsContainer.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index 26ad0b0d3f..08130e60db 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -64,6 +64,21 @@ namespace osu.Game.Tests.Visual.Online () => commentsContainer.ChildrenOfType().Single().Alpha == 1); } + [Test] + public void TestMultipleLoads() + { + var comments = exampleComments; + int topLevelCommentCount = exampleComments.Comments.Count(comment => comment.IsTopLevel); + + AddStep("hide container", () => commentsContainer.Hide()); + setUpCommentsResponse(comments); + AddRepeatStep("show comments multiple times", + () => commentsContainer.ShowComments(CommentableType.Beatmapset, 456), 2); + AddStep("show container", () => commentsContainer.Show()); + AddUntilStep("comment count is correct", + () => commentsContainer.ChildrenOfType().Count() == topLevelCommentCount); + } + private void setUpCommentsResponse(CommentBundle commentBundle) => AddStep("set up response", () => { From aab606b237953d1d9844fd36245fe8b7e42fca08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Jun 2020 13:00:05 +0200 Subject: [PATCH 425/871] Cancel scheduled asynchronous load of comments --- osu.Game/Overlays/Comments/CommentsContainer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index e7bfeaf968..f71808ba89 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -12,6 +12,7 @@ using osu.Game.Online.API.Requests.Responses; using System.Threading; using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Threading; using osu.Game.Users; namespace osu.Game.Overlays.Comments @@ -30,6 +31,7 @@ namespace osu.Game.Overlays.Comments private IAPIProvider api { get; set; } private GetCommentsRequest request; + private ScheduledDelegate scheduledCommentsLoad; private CancellationTokenSource loadCancellation; private int currentPage; @@ -152,8 +154,9 @@ namespace osu.Game.Overlays.Comments request?.Cancel(); loadCancellation?.Cancel(); + scheduledCommentsLoad?.Cancel(); request = new GetCommentsRequest(id.Value, type.Value, Sort.Value, currentPage++, 0); - request.Success += res => Schedule(() => onSuccess(res)); + request.Success += res => scheduledCommentsLoad = Schedule(() => onSuccess(res)); api.PerformAsync(request); } From 8402d4a5f3c2e2cac8e9b5c4948a961f0e5d1b50 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 13 Jun 2020 21:18:56 +0900 Subject: [PATCH 426/871] Remove newline --- osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 07d0ce66e2..74a983fac8 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -78,7 +78,6 @@ namespace osu.Game.Rulesets.Mania.Skinning public Drawable GetDrawableComponent(ISkinComponent component) { - switch (component) { case GameplaySkinComponent resultComponent: From b9e247da8f3d4eeccf3448bda6d0c4554666cd55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 13 Jun 2020 21:19:06 +0900 Subject: [PATCH 427/871] Simplify lookup code --- osu.Game/Skinning/LegacySkin.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 390dc871e4..0b2b723440 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -259,22 +259,12 @@ namespace osu.Game.Skinning return SkinUtils.As(new Bindable(existing.ColumnLineWidth[maniaLookup.TargetColumn.Value + 1])); case LegacyManiaSkinConfigurationLookups.Hit0: - return SkinUtils.As(getManiaImage(existing, "Hit0")); - case LegacyManiaSkinConfigurationLookups.Hit50: - return SkinUtils.As(getManiaImage(existing, "Hit50")); - case LegacyManiaSkinConfigurationLookups.Hit100: - return SkinUtils.As(getManiaImage(existing, "Hit100")); - case LegacyManiaSkinConfigurationLookups.Hit200: - return SkinUtils.As(getManiaImage(existing, "Hit200")); - case LegacyManiaSkinConfigurationLookups.Hit300: - return SkinUtils.As(getManiaImage(existing, "Hit300")); - case LegacyManiaSkinConfigurationLookups.Hit300g: - return SkinUtils.As(getManiaImage(existing, "Hit300g")); + return SkinUtils.As(getManiaImage(existing, maniaLookup.Lookup.ToString())); } return null; From 1cd96b80021c39e82e091808a89ac15c2426fc36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Jun 2020 15:05:52 +0200 Subject: [PATCH 428/871] Rework StableInfo into a DI'd data structure --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 48 ++++--------------- osu.Game.Tournament/Models/StableInfo.cs | 43 ++++++++++++++++- osu.Game.Tournament/Screens/SetupScreen.cs | 14 ++---- .../Screens/StablePathSelectScreen.cs | 20 ++++---- osu.Game.Tournament/TournamentGameBase.cs | 2 + 5 files changed, 67 insertions(+), 60 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 16f2b0b1fd..a9b39c7ba2 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -5,7 +5,6 @@ using System; using System.IO; using System.Linq; using System.Collections.Generic; -using Newtonsoft.Json; using Microsoft.Win32; using osu.Framework.Allocation; using osu.Framework.Logging; @@ -34,14 +33,13 @@ namespace osu.Game.Tournament.IPC [Resolved] private LadderInfo ladder { get; set; } + [Resolved] + private StableInfo stableInfo { get; set; } + private int lastBeatmapId; private ScheduledDelegate scheduled; private GetBeatmapRequest beatmapLookupRequest; - public StableInfo StableInfo { get; private set; } - - public const string STABLE_CONFIG = "tournament/stable.json"; - public Storage IPCStorage { get; private set; } [Resolved] @@ -165,8 +163,8 @@ namespace osu.Game.Tournament.IPC private string findStablePath() { - if (!string.IsNullOrEmpty(readStableConfig())) - return StableInfo.StablePath.Value; + if (!string.IsNullOrEmpty(stableInfo.StablePath)) + return stableInfo.StablePath; string stableInstallPath = string.Empty; @@ -204,43 +202,13 @@ namespace osu.Game.Tournament.IPC if (!ipcFileExistsInDirectory(path)) return false; - StableInfo.StablePath.Value = path; - - using (var stream = tournamentStorage.GetStream(STABLE_CONFIG, FileAccess.Write, FileMode.Create)) - using (var sw = new StreamWriter(stream)) - { - sw.Write(JsonConvert.SerializeObject(StableInfo, - new JsonSerializerSettings - { - Formatting = Formatting.Indented, - NullValueHandling = NullValueHandling.Ignore, - DefaultValueHandling = DefaultValueHandling.Ignore, - })); - } - + stableInfo.StablePath = path; LocateStableStorage(); + stableInfo.SaveChanges(); + return true; } - private string readStableConfig() - { - if (StableInfo == null) - StableInfo = new StableInfo(); - - if (tournamentStorage.Exists(FileBasedIPC.STABLE_CONFIG)) - { - using (Stream stream = tournamentStorage.GetStream(FileBasedIPC.STABLE_CONFIG, FileAccess.Read, FileMode.Open)) - using (var sr = new StreamReader(stream)) - { - StableInfo = JsonConvert.DeserializeObject(sr.ReadToEnd()); - } - - return StableInfo.StablePath.Value; - } - - return null; - } - private string findFromEnvVar() { try diff --git a/osu.Game.Tournament/Models/StableInfo.cs b/osu.Game.Tournament/Models/StableInfo.cs index 4818842151..1faf6beaff 100644 --- a/osu.Game.Tournament/Models/StableInfo.cs +++ b/osu.Game.Tournament/Models/StableInfo.cs @@ -2,7 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Bindables; +using System.IO; +using Newtonsoft.Json; +using osu.Framework.Platform; namespace osu.Game.Tournament.Models { @@ -12,6 +14,43 @@ namespace osu.Game.Tournament.Models [Serializable] public class StableInfo { - public Bindable StablePath = new Bindable(string.Empty); + public string StablePath { get; set; } + + public event Action OnStableInfoSaved; + + private const string config_path = "tournament/stable.json"; + + private readonly Storage storage; + + public StableInfo(Storage storage) + { + this.storage = storage; + + if (!storage.Exists(config_path)) + return; + + using (Stream stream = storage.GetStream(config_path, FileAccess.Read, FileMode.Open)) + using (var sr = new StreamReader(stream)) + { + JsonConvert.PopulateObject(sr.ReadToEnd(), this); + } + } + + public void SaveChanges() + { + using (var stream = storage.GetStream(config_path, FileAccess.Write, FileMode.Create)) + using (var sw = new StreamWriter(stream)) + { + sw.Write(JsonConvert.SerializeObject(this, + new JsonSerializerSettings + { + Formatting = Formatting.Indented, + NullValueHandling = NullValueHandling.Ignore, + DefaultValueHandling = DefaultValueHandling.Ignore, + })); + } + + OnStableInfoSaved?.Invoke(); + } } } diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 503a2487da..98bc292901 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -31,6 +31,9 @@ namespace osu.Game.Tournament.Screens [Resolved] private MatchIPCInfo ipc { get; set; } + [Resolved] + private StableInfo stableInfo { get; set; } + [Resolved] private IAPIProvider api { get; set; } @@ -57,6 +60,7 @@ namespace osu.Game.Tournament.Screens }; api.LocalUser.BindValueChanged(_ => Schedule(reload)); + stableInfo.OnStableInfoSaved += () => Schedule(reload); reload(); } @@ -66,21 +70,13 @@ namespace osu.Game.Tournament.Screens private void reload() { var fileBasedIpc = ipc as FileBasedIPC; - StableInfo stableInfo = fileBasedIpc?.StableInfo; fillFlow.Children = new Drawable[] { new ActionableInfo { Label = "Current IPC source", ButtonText = "Change source", - Action = () => - { - stableInfo?.StablePath.BindValueChanged(_ => - { - Schedule(reload); - }); - sceneManager?.SetScreen(new StablePathSelectScreen()); - }, + Action = () => sceneManager?.SetScreen(new StablePathSelectScreen()), Value = fileBasedIpc?.IPCStorage?.GetFullPath(string.Empty) ?? "Not found", Failing = fileBasedIpc?.IPCStorage == null, Description = "The osu!stable installation which is currently being used as a data source. If a source is not found, make sure you have created an empty ipc.txt in your stable cutting-edge installation." diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index ad0c06e4f9..816f0ed4b8 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -15,34 +15,36 @@ using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Components; +using osu.Game.Tournament.Models; using osuTK; namespace osu.Game.Tournament.Screens { public class StablePathSelectScreen : TournamentScreen { - private DirectorySelector directorySelector; - [Resolved] - private MatchIPCInfo ipc { get; set; } - - private DialogOverlay overlay; + private GameHost host { get; set; } [Resolved(canBeNull: true)] private TournamentSceneManager sceneManager { get; set; } [Resolved] - private GameHost host { get; set; } + private MatchIPCInfo ipc { get; set; } + + [Resolved] + private StableInfo stableInfo { get; set; } + + private DirectorySelector directorySelector; + private DialogOverlay overlay; [BackgroundDependencyLoader(true)] private void load(Storage storage, OsuColour colours) { - var fileBasedIpc = ipc as FileBasedIPC; var initialPath = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent?.FullName; - if (!string.IsNullOrEmpty(fileBasedIpc?.StableInfo.StablePath.Value)) + if (!string.IsNullOrEmpty(stableInfo.StablePath)) { - initialPath = new DirectoryInfo(host.GetStorage(fileBasedIpc.StableInfo.StablePath.Value).GetFullPath(string.Empty)).Parent?.FullName; + initialPath = new DirectoryInfo(host.GetStorage(stableInfo.StablePath).GetFullPath(string.Empty)).Parent?.FullName; } AddRangeInternal(new Drawable[] diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 718c8ee644..5fc1d03f6d 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -53,6 +53,8 @@ namespace osu.Game.Tournament ladder.CurrentMatch.Value = ladder.Matches.FirstOrDefault(p => p.Current.Value); + dependencies.CacheAs(new StableInfo(storage)); + dependencies.CacheAs(ipc = new FileBasedIPC()); Add(ipc); } From 586d5791e029d2de9a779fdbf9d9c12af3cfab28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Jun 2020 15:07:21 +0200 Subject: [PATCH 429/871] Remove unused argument --- .../Screens/TestSceneStablePathSelectScreen.cs | 3 +-- osu.Game.Tournament/Screens/StablePathSelectScreen.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreen.cs index ce0626dd0f..6e63b2d799 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneStablePathSelectScreen.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using osu.Game.Tournament.Screens; -using osu.Framework.Platform; namespace osu.Game.Tournament.Tests.Screens { @@ -15,7 +14,7 @@ namespace osu.Game.Tournament.Tests.Screens private class StablePathSelectTestScreen : StablePathSelectScreen { - protected override void ChangePath(Storage storage) + protected override void ChangePath() { Expire(); } diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index 816f0ed4b8..a830cbe4b9 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -108,7 +108,7 @@ namespace osu.Game.Tournament.Screens Origin = Anchor.Centre, Width = 300, Text = "Select stable path", - Action = () => ChangePath(storage) + Action = ChangePath }, new TriangleButton { @@ -135,7 +135,7 @@ namespace osu.Game.Tournament.Screens }); } - protected virtual void ChangePath(Storage storage) + protected virtual void ChangePath() { var target = directorySelector.CurrentDirectory.Value.FullName; var fileBasedIpc = ipc as FileBasedIPC; From 992aa0041e3933dcf8ded8b9778cb76bc5c02ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Jun 2020 15:27:46 +0200 Subject: [PATCH 430/871] Allow auto-detect to work after choosing manually --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 64 +++++++++++-------- .../Screens/StablePathSelectScreen.cs | 3 +- 2 files changed, 38 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index a9b39c7ba2..01466231a6 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -5,6 +5,7 @@ using System; using System.IO; using System.Linq; using System.Collections.Generic; +using JetBrains.Annotations; using Microsoft.Win32; using osu.Framework.Allocation; using osu.Framework.Logging; @@ -21,6 +22,8 @@ namespace osu.Game.Tournament.IPC { public class FileBasedIPC : MatchIPCInfo { + public Storage IPCStorage { get; private set; } + [Resolved] protected IAPIProvider API { get; private set; } @@ -36,22 +39,22 @@ namespace osu.Game.Tournament.IPC [Resolved] private StableInfo stableInfo { get; set; } + [Resolved] + private Storage tournamentStorage { get; set; } + private int lastBeatmapId; private ScheduledDelegate scheduled; private GetBeatmapRequest beatmapLookupRequest; - public Storage IPCStorage { get; private set; } - - [Resolved] - private Storage tournamentStorage { get; set; } - [BackgroundDependencyLoader] private void load() { - LocateStableStorage(); + var stablePath = stableInfo.StablePath ?? findStablePath(); + initialiseIPCStorage(stablePath); } - public Storage LocateStableStorage() + [CanBeNull] + private Storage initialiseIPCStorage(string path) { scheduled?.Cancel(); @@ -59,8 +62,6 @@ namespace osu.Game.Tournament.IPC try { - var path = findStablePath(); - if (string.IsNullOrEmpty(path)) return null; @@ -159,13 +160,37 @@ namespace osu.Game.Tournament.IPC return IPCStorage; } + public bool SetIPCLocation(string path) + { + if (!ipcFileExistsInDirectory(path)) + return false; + + var newStorage = initialiseIPCStorage(stableInfo.StablePath = path); + if (newStorage == null) + return false; + + stableInfo.SaveChanges(); + return true; + } + + public bool AutoDetectIPCLocation() + { + var autoDetectedPath = findStablePath(); + if (string.IsNullOrEmpty(autoDetectedPath)) + return false; + + var newStorage = initialiseIPCStorage(stableInfo.StablePath = autoDetectedPath); + if (newStorage == null) + return false; + + stableInfo.SaveChanges(); + return true; + } + private static bool ipcFileExistsInDirectory(string p) => File.Exists(Path.Combine(p, "ipc.txt")); private string findStablePath() { - if (!string.IsNullOrEmpty(stableInfo.StablePath)) - return stableInfo.StablePath; - string stableInstallPath = string.Empty; try @@ -183,10 +208,7 @@ namespace osu.Game.Tournament.IPC stableInstallPath = r.Invoke(); if (stableInstallPath != null) - { - SetIPCLocation(stableInstallPath); return stableInstallPath; - } } return null; @@ -197,18 +219,6 @@ namespace osu.Game.Tournament.IPC } } - public bool SetIPCLocation(string path) - { - if (!ipcFileExistsInDirectory(path)) - return false; - - stableInfo.StablePath = path; - LocateStableStorage(); - stableInfo.SaveChanges(); - - return true; - } - private string findFromEnvVar() { try diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index a830cbe4b9..2a54dffc7c 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -156,9 +156,8 @@ namespace osu.Game.Tournament.Screens protected virtual void AutoDetect() { var fileBasedIpc = ipc as FileBasedIPC; - fileBasedIpc?.LocateStableStorage(); - if (fileBasedIpc?.IPCStorage == null) + if (!fileBasedIpc?.AutoDetectIPCLocation() ?? true) { overlay = new DialogOverlay(); overlay.Push(new IPCErrorDialog("Failed to auto detect", "An osu! stable cutting-edge installation could not be auto detected.\nPlease try and manually point to the directory.")); From 34cd9f7a699d62dd339c9a18bb95fefeddab0de1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Jun 2020 15:32:30 +0200 Subject: [PATCH 431/871] Streamline autodetect & manual set path --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 01466231a6..de9df3ca35 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -162,7 +162,7 @@ namespace osu.Game.Tournament.IPC public bool SetIPCLocation(string path) { - if (!ipcFileExistsInDirectory(path)) + if (path == null || !ipcFileExistsInDirectory(path)) return false; var newStorage = initialiseIPCStorage(stableInfo.StablePath = path); @@ -173,22 +173,11 @@ namespace osu.Game.Tournament.IPC return true; } - public bool AutoDetectIPCLocation() - { - var autoDetectedPath = findStablePath(); - if (string.IsNullOrEmpty(autoDetectedPath)) - return false; - - var newStorage = initialiseIPCStorage(stableInfo.StablePath = autoDetectedPath); - if (newStorage == null) - return false; - - stableInfo.SaveChanges(); - return true; - } + public bool AutoDetectIPCLocation() => SetIPCLocation(findStablePath()); private static bool ipcFileExistsInDirectory(string p) => File.Exists(Path.Combine(p, "ipc.txt")); + [CanBeNull] private string findStablePath() { string stableInstallPath = string.Empty; From e0518fd451ac9aebd3b4506ada28eb202e55cf25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Jun 2020 15:38:29 +0200 Subject: [PATCH 432/871] Fix silent failure --- osu.Game.Tournament/Screens/StablePathSelectScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index 2a54dffc7c..0b9900c0d4 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -141,7 +141,7 @@ namespace osu.Game.Tournament.Screens var fileBasedIpc = ipc as FileBasedIPC; Logger.Log($"Changing Stable CE location to {target}"); - if (!fileBasedIpc?.SetIPCLocation(target) ?? false) + if (!fileBasedIpc?.SetIPCLocation(target) ?? true) { overlay = new DialogOverlay(); overlay.Push(new IPCErrorDialog("This is an invalid IPC Directory", "Select a directory that contains an osu! stable cutting edge installation and make sure it has an empty ipc.txt file in it.")); From 5dd47bf393b5116b9077ef4aeea3942ca5a0d766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Jun 2020 16:01:00 +0200 Subject: [PATCH 433/871] Remove unnecessary members --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 3 --- osu.Game.Tournament/Screens/StablePathSelectScreen.cs | 9 --------- 2 files changed, 12 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index de9df3ca35..d52a2b6445 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -39,9 +39,6 @@ namespace osu.Game.Tournament.IPC [Resolved] private StableInfo stableInfo { get; set; } - [Resolved] - private Storage tournamentStorage { get; set; } - private int lastBeatmapId; private ScheduledDelegate scheduled; private GetBeatmapRequest beatmapLookupRequest; diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index 0b9900c0d4..958c3ef822 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -15,7 +15,6 @@ using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Components; -using osu.Game.Tournament.Models; using osuTK; namespace osu.Game.Tournament.Screens @@ -31,9 +30,6 @@ namespace osu.Game.Tournament.Screens [Resolved] private MatchIPCInfo ipc { get; set; } - [Resolved] - private StableInfo stableInfo { get; set; } - private DirectorySelector directorySelector; private DialogOverlay overlay; @@ -42,11 +38,6 @@ namespace osu.Game.Tournament.Screens { var initialPath = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent?.FullName; - if (!string.IsNullOrEmpty(stableInfo.StablePath)) - { - initialPath = new DirectoryInfo(host.GetStorage(stableInfo.StablePath).GetFullPath(string.Empty)).Parent?.FullName; - } - AddRangeInternal(new Drawable[] { new Container From 201bfda3382931077cc8acc26082ce740004f123 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Sat, 13 Jun 2020 15:16:27 +0100 Subject: [PATCH 434/871] Give ModTimeRamp an adjust pitch setting. Implement in ModWindDown and ModWindUp --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 16 +++++++++++++++- osu.Game/Rulesets/Mods/ModWindDown.cs | 7 +++++++ osu.Game/Rulesets/Mods/ModWindUp.cs | 7 +++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index c1f3e357a1..a38aa2bac6 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -26,6 +26,9 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Final rate", "The final speed to ramp to")] public abstract BindableNumber FinalRate { get; } + [SettingSource("Adjust Pitch", "Should pitch be adjusted with speed")] + public abstract BindableBool AdjustPitch { get; } + public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x"; private double finalRateTime; @@ -44,12 +47,15 @@ namespace osu.Game.Rulesets.Mods { // for preview purpose at song select. eventually we'll want to be able to update every frame. FinalRate.BindValueChanged(val => applyAdjustment(1), true); + + AdjustPitch.BindValueChanged(updatePitchAdjustment, false); } public void ApplyToTrack(Track track) { this.track = track; - track.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); + + track.AddAdjustment(AdjustPitch.Value ? AdjustableProperty.Frequency : AdjustableProperty.Tempo, SpeedChange); FinalRate.TriggerChange(); } @@ -75,5 +81,13 @@ namespace osu.Game.Rulesets.Mods /// The amount of adjustment to apply (from 0..1). private void applyAdjustment(double amount) => SpeedChange.Value = InitialRate.Value + (FinalRate.Value - InitialRate.Value) * Math.Clamp(amount, 0, 1); + + private void updatePitchAdjustment(ValueChangedEvent value) + { + // remove existing old adjustment + track.RemoveAdjustment(value.OldValue ? AdjustableProperty.Frequency : AdjustableProperty.Tempo, SpeedChange); + + track.AddAdjustment(value.NewValue ? AdjustableProperty.Frequency : AdjustableProperty.Tempo, SpeedChange); + } } } diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index 5e634ac434..e46b4eff2e 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -37,6 +37,13 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; + [SettingSource("Adjust Pitch", "Should pitch be adjusted with speed")] + public override BindableBool AdjustPitch { get; } = new BindableBool + { + Default = true, + Value = true + }; + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindUp)).ToArray(); } } diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index 74c6fc22d3..02203a474d 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -37,6 +37,13 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; + [SettingSource("Adjust Pitch", "Should pitch be adjusted with speed")] + public override BindableBool AdjustPitch { get; } = new BindableBool + { + Default = true, + Value = true + }; + public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(ModWindDown)).ToArray(); } } From 2cadab8d29a3e5f30fbc9676f1a23d3fdd6df682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Jun 2020 16:20:59 +0200 Subject: [PATCH 435/871] Add xmldoc --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 10 ++++++++++ osu.Game.Tournament/Models/StableInfo.cs | 6 ++++++ 2 files changed, 16 insertions(+) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index d52a2b6445..681839ebc4 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -157,6 +157,11 @@ namespace osu.Game.Tournament.IPC return IPCStorage; } + /// + /// Manually sets the path to the directory used for inter-process communication with a cutting-edge install. + /// + /// Path to the IPC directory + /// Whether the supplied path was a valid IPC directory. public bool SetIPCLocation(string path) { if (path == null || !ipcFileExistsInDirectory(path)) @@ -170,6 +175,11 @@ namespace osu.Game.Tournament.IPC return true; } + /// + /// Tries to automatically detect the path to the directory used for inter-process communication + /// with a cutting-edge install. + /// + /// Whether an IPC directory was successfully auto-detected. public bool AutoDetectIPCLocation() => SetIPCLocation(findStablePath()); private static bool ipcFileExistsInDirectory(string p) => File.Exists(Path.Combine(p, "ipc.txt")); diff --git a/osu.Game.Tournament/Models/StableInfo.cs b/osu.Game.Tournament/Models/StableInfo.cs index 1faf6beaff..0b0050a245 100644 --- a/osu.Game.Tournament/Models/StableInfo.cs +++ b/osu.Game.Tournament/Models/StableInfo.cs @@ -14,8 +14,14 @@ namespace osu.Game.Tournament.Models [Serializable] public class StableInfo { + /// + /// Path to the IPC directory used by the stable (cutting-edge) install. + /// public string StablePath { get; set; } + /// + /// Fired whenever stable info is successfully saved to file. + /// public event Action OnStableInfoSaved; private const string config_path = "tournament/stable.json"; From 308ec6a491a051e029e9fd3d2ee30fa03cd080bf Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 13 Jun 2020 23:05:57 +0800 Subject: [PATCH 436/871] add extension method for mania skin config retrieval --- .../Skinning/ManiaSkinConfigExtensions.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs new file mode 100644 index 0000000000..2e17a6bef1 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaSkinConfigExtensions.cs @@ -0,0 +1,21 @@ +// 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.Game.Skinning; + +namespace osu.Game.Rulesets.Mania.Skinning +{ + public static class ManiaSkinConfigExtensions + { + /// + /// Retrieve a per-column-count skin configuration. + /// + /// The skin from which configuration is retrieved. + /// The value to retrieve. + /// If not null, denotes the index of the column to which the entry applies. + public static IBindable GetManiaSkinConfig(this ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null) + => skin.GetConfig( + new ManiaSkinConfigurationLookup(lookup, index)); + } +} From bd7b7b50176c37cd799a560a060adf5a9f74daab Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 13 Jun 2020 23:06:25 +0800 Subject: [PATCH 437/871] make all former LegacyManiaElement subclasses use extension method Remove LegacyManiaElement --- .../Skinning/LegacyHitTarget.cs | 8 +++--- .../Skinning/LegacyManiaColumnElement.cs | 6 ++--- .../Skinning/LegacyManiaElement.cs | 25 ------------------- .../Skinning/LegacyStageBackground.cs | 7 +++--- .../Skinning/LegacyStageForeground.cs | 5 ++-- 5 files changed, 14 insertions(+), 37 deletions(-) delete mode 100644 osu.Game.Rulesets.Mania/Skinning/LegacyManiaElement.cs diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs index 40752d3f4b..d055ef3480 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitTarget.cs @@ -14,7 +14,7 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Mania.Skinning { - public class LegacyHitTarget : LegacyManiaElement + public class LegacyHitTarget : CompositeDrawable { private readonly IBindable direction = new Bindable(); @@ -28,13 +28,13 @@ namespace osu.Game.Rulesets.Mania.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { - string targetImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HitTargetImage)?.Value + string targetImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.HitTargetImage)?.Value ?? "mania-stage-hint"; - bool showJudgementLine = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ShowJudgementLine)?.Value + bool showJudgementLine = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.ShowJudgementLine)?.Value ?? true; - Color4 lineColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.JudgementLineColour)?.Value + Color4 lineColour = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.JudgementLineColour)?.Value ?? Color4.White; InternalChild = directionContainer = new Container diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs index 05b731ec5d..0c46a00bed 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs @@ -13,7 +13,7 @@ namespace osu.Game.Rulesets.Mania.Skinning /// /// A which is placed somewhere within a . /// - public class LegacyManiaColumnElement : LegacyManiaElement + public class LegacyManiaColumnElement : CompositeDrawable { [Resolved] protected Column Column { get; private set; } @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Skinning } } - protected override IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null) - => base.GetManiaSkinConfig(skin, lookup, index ?? Column.Index); + protected IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null) + => skin.GetManiaSkinConfig(lookup, index ?? Column.Index); } } diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaElement.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaElement.cs deleted file mode 100644 index 11fdd663a1..0000000000 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaElement.cs +++ /dev/null @@ -1,25 +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 osu.Framework.Bindables; -using osu.Framework.Graphics.Containers; -using osu.Game.Skinning; - -namespace osu.Game.Rulesets.Mania.Skinning -{ - /// - /// A mania legacy skin element. - /// - public class LegacyManiaElement : CompositeDrawable - { - /// - /// Retrieve a per-column-count skin configuration. - /// - /// The skin from which configuration is retrieved. - /// The value to retrieve. - /// If not null, denotes the index of the column to which the entry applies. - protected virtual IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null) - => skin.GetConfig( - new ManiaSkinConfigurationLookup(lookup, index)); - } -} diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs index f177284399..7f5de601ca 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyStageBackground.cs @@ -3,13 +3,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Mania.Skinning { - public class LegacyStageBackground : LegacyManiaElement + public class LegacyStageBackground : CompositeDrawable { private Drawable leftSprite; private Drawable rightSprite; @@ -22,10 +23,10 @@ namespace osu.Game.Rulesets.Mania.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin) { - string leftImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LeftStageImage)?.Value + string leftImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LeftStageImage)?.Value ?? "mania-stage-left"; - string rightImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.RightStageImage)?.Value + string rightImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.RightStageImage)?.Value ?? "mania-stage-right"; InternalChildren = new[] diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyStageForeground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyStageForeground.cs index 9719005d54..4609fcc849 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyStageForeground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyStageForeground.cs @@ -4,13 +4,14 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Mania.Skinning { - public class LegacyStageForeground : LegacyManiaElement + public class LegacyStageForeground : CompositeDrawable { private readonly IBindable direction = new Bindable(); @@ -24,7 +25,7 @@ namespace osu.Game.Rulesets.Mania.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { - string bottomImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.BottomStageImage)?.Value + string bottomImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.BottomStageImage)?.Value ?? "mania-stage-bottom"; sprite = skin.GetAnimation(bottomImage, true, true)?.With(d => From ffae73a966c8afcc131c9de8b082e44950211487 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 13 Jun 2020 23:07:04 +0800 Subject: [PATCH 438/871] let retrievals outside mania skin components use extension https://github.com/ppy/osu/pull/9264#discussion_r439730321 --- .../Skinning/ManiaLegacySkinTransformer.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 74a983fac8..19a107eb0d 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -71,8 +71,7 @@ namespace osu.Game.Rulesets.Mania.Skinning { isLegacySkin = new Lazy(() => source.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null); hasKeyTexture = new Lazy(() => source.GetAnimation( - GetConfig( - new ManiaSkinConfigurationLookup(LegacyManiaSkinConfigurationLookups.KeyImage, 0))?.Value + this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage)?.Value ?? "mania-key1", true, true) != null); } @@ -128,9 +127,8 @@ namespace osu.Game.Rulesets.Mania.Skinning private Drawable getResult(HitResult result) { - string filename = GetConfig( - new ManiaSkinConfigurationLookup(hitresult_mapping[result]) - )?.Value ?? default_hitresult_skin_filenames[result]; + string filename = this.GetManiaSkinConfig(hitresult_mapping[result])?.Value + ?? default_hitresult_skin_filenames[result]; return this.GetAnimation(filename, true, true); } From 9a0a1ba0df10b87baa552e9d5869892d4cbfe3d9 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 13 Jun 2020 23:12:15 +0800 Subject: [PATCH 439/871] correct logic of hasKeyTexture determination --- osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 19a107eb0d..7a2fa711e3 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -71,7 +71,7 @@ namespace osu.Game.Rulesets.Mania.Skinning { isLegacySkin = new Lazy(() => source.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null); hasKeyTexture = new Lazy(() => source.GetAnimation( - this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage)?.Value + this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value ?? "mania-key1", true, true) != null); } From eb92c3390d6b4299a2c23f0ade181ae8e4b575b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Jun 2020 17:17:58 +0200 Subject: [PATCH 440/871] Check for nulls when looking for ipc.txt --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 681839ebc4..a17491bf2d 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -182,7 +182,7 @@ namespace osu.Game.Tournament.IPC /// Whether an IPC directory was successfully auto-detected. public bool AutoDetectIPCLocation() => SetIPCLocation(findStablePath()); - private static bool ipcFileExistsInDirectory(string p) => File.Exists(Path.Combine(p, "ipc.txt")); + private static bool ipcFileExistsInDirectory(string p) => p != null && File.Exists(Path.Combine(p, "ipc.txt")); [CanBeNull] private string findStablePath() From 77eb428184473b09e0cb370adaf11e45e0b9ed9e Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Sat, 13 Jun 2020 16:30:21 +0100 Subject: [PATCH 441/871] Use consistent setting casing --- osu.Game/Rulesets/Mods/ModWindDown.cs | 2 +- osu.Game/Rulesets/Mods/ModWindUp.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModWindDown.cs b/osu.Game/Rulesets/Mods/ModWindDown.cs index e46b4eff2e..679b50057b 100644 --- a/osu.Game/Rulesets/Mods/ModWindDown.cs +++ b/osu.Game/Rulesets/Mods/ModWindDown.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - [SettingSource("Adjust Pitch", "Should pitch be adjusted with speed")] + [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] public override BindableBool AdjustPitch { get; } = new BindableBool { Default = true, diff --git a/osu.Game/Rulesets/Mods/ModWindUp.cs b/osu.Game/Rulesets/Mods/ModWindUp.cs index 02203a474d..b733bf423e 100644 --- a/osu.Game/Rulesets/Mods/ModWindUp.cs +++ b/osu.Game/Rulesets/Mods/ModWindUp.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mods Precision = 0.01, }; - [SettingSource("Adjust Pitch", "Should pitch be adjusted with speed")] + [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] public override BindableBool AdjustPitch { get; } = new BindableBool { Default = true, From dc5bb12fa8e9f6f47c14c57b8242b24f24aa37c3 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Sat, 13 Jun 2020 16:32:43 +0100 Subject: [PATCH 442/871] Use local helper for selecting adjusted property --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index a38aa2bac6..9f30f340fd 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Mods [SettingSource("Final rate", "The final speed to ramp to")] public abstract BindableNumber FinalRate { get; } - [SettingSource("Adjust Pitch", "Should pitch be adjusted with speed")] + [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] public abstract BindableBool AdjustPitch { get; } public override string SettingDescription => $"{InitialRate.Value:N2}x to {FinalRate.Value:N2}x"; @@ -55,9 +55,8 @@ namespace osu.Game.Rulesets.Mods { this.track = track; - track.AddAdjustment(AdjustPitch.Value ? AdjustableProperty.Frequency : AdjustableProperty.Tempo, SpeedChange); - FinalRate.TriggerChange(); + AdjustPitch.TriggerChange(); } public virtual void ApplyToBeatmap(IBeatmap beatmap) @@ -85,9 +84,12 @@ namespace osu.Game.Rulesets.Mods private void updatePitchAdjustment(ValueChangedEvent value) { // remove existing old adjustment - track.RemoveAdjustment(value.OldValue ? AdjustableProperty.Frequency : AdjustableProperty.Tempo, SpeedChange); + track.RemoveAdjustment(adjustmentForPitchSetting(value.OldValue), SpeedChange); - track.AddAdjustment(value.NewValue ? AdjustableProperty.Frequency : AdjustableProperty.Tempo, SpeedChange); + track.AddAdjustment(adjustmentForPitchSetting(value.NewValue), SpeedChange); } + + private AdjustableProperty adjustmentForPitchSetting(bool value) + => value ? AdjustableProperty.Frequency : AdjustableProperty.Tempo; } } From 4bfc16b4ce73ad2a5ee48282ee5636896e48ae95 Mon Sep 17 00:00:00 2001 From: Shivam Date: Sat, 13 Jun 2020 17:48:15 +0200 Subject: [PATCH 443/871] Implement changes from review Moves seeya back to the introscreen and uses a virtual string to change whenever it's needed and removed remainingTime() --- osu.Game/Screens/Menu/IntroScreen.cs | 8 +++++--- osu.Game/Screens/Menu/IntroWelcome.cs | 18 +++++++----------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 2f9d43bed6..88d18d0073 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -49,7 +49,9 @@ namespace osu.Game.Screens.Menu private const int exit_delay = 3000; - protected SampleChannel Seeya { get; set; } + private SampleChannel seeya; + + protected virtual string SeeyaSampleName => "Intro/seeya"; private LeasedBindable beatmap; @@ -72,7 +74,7 @@ namespace osu.Game.Screens.Menu MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); - Seeya = audio.Samples.Get(@"Intro/seeya"); + seeya = audio.Samples.Get(SeeyaSampleName); BeatmapSetInfo setInfo = null; @@ -124,7 +126,7 @@ namespace osu.Game.Screens.Menu double fadeOutTime = exit_delay; // we also handle the exit transition. if (MenuVoice.Value) - Seeya.Play(); + seeya.Play(); else fadeOutTime = 500; diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index dec3af5ac9..a431752369 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -22,17 +22,15 @@ namespace osu.Game.Screens.Menu private const double delay_step_two = 2142; private SampleChannel welcome; private SampleChannel pianoReverb; + protected override string SeeyaSampleName => "Intro/Welcome/seeya"; [BackgroundDependencyLoader] private void load(AudioManager audio) { - Seeya = audio.Samples.Get(@"Intro/welcome/seeya"); - if (MenuVoice.Value) - { - welcome = audio.Samples.Get(@"Intro/welcome/welcome"); - pianoReverb = audio.Samples.Get(@"Intro/welcome/welcome_piano"); - } + welcome = audio.Samples.Get(@"Intro/Welcome/welcome"); + + pianoReverb = audio.Samples.Get(@"Intro/Welcome/welcome_piano"); } protected override void LogoArriving(OsuLogo logo, bool resuming) @@ -126,7 +124,7 @@ namespace osu.Game.Screens.Menu Width = 750, Height = 78, Alpha = 0, - Texture = textures.Get(@"Welcome/welcome_text@2x") + Texture = textures.Get(@"Welcome/welcome_text") }, }; } @@ -135,13 +133,11 @@ namespace osu.Game.Screens.Menu { base.LoadComplete(); - double remainingTime() => delay_step_two - TransformDelay; - using (BeginDelayedSequence(0, true)) { welcomeText.ResizeHeightTo(welcomeText.Height * 2, 500, Easing.In); - welcomeText.FadeIn(remainingTime()); - welcomeText.ScaleTo(welcomeText.Scale + new Vector2(0.1f), remainingTime(), Easing.Out).OnComplete(_ => Expire()); + welcomeText.FadeIn(delay_step_two); + welcomeText.ScaleTo(welcomeText.Scale + new Vector2(0.1f), delay_step_two, Easing.Out).OnComplete(_ => Expire()); } } } From 51bbd91373a54b7cfbd24151d97345e4d193cfb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 13 Jun 2020 19:28:21 +0200 Subject: [PATCH 444/871] Bring back initial directory behaviour --- osu.Game.Tournament/Screens/StablePathSelectScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs index 958c3ef822..b4d56f60c7 100644 --- a/osu.Game.Tournament/Screens/StablePathSelectScreen.cs +++ b/osu.Game.Tournament/Screens/StablePathSelectScreen.cs @@ -36,7 +36,8 @@ namespace osu.Game.Tournament.Screens [BackgroundDependencyLoader(true)] private void load(Storage storage, OsuColour colours) { - var initialPath = new DirectoryInfo(storage.GetFullPath(string.Empty)).Parent?.FullName; + var initialStorage = (ipc as FileBasedIPC)?.IPCStorage ?? storage; + var initialPath = new DirectoryInfo(initialStorage.GetFullPath(string.Empty)).Parent?.FullName; AddRangeInternal(new Drawable[] { From 7b95c55afb17429a8eb1b253c62767c11f3792c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 14 Jun 2020 11:33:59 +0900 Subject: [PATCH 445/871] Fix HardwareCorrectionOffsetClock breaking ElapsedTime readings --- osu.Game/Screens/Play/GameplayClockContainer.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 2f85d6ad1e..fe1d22e987 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -251,8 +251,9 @@ namespace osu.Game.Screens.Play private class HardwareCorrectionOffsetClock : FramedOffsetClock { - // we always want to apply the same real-time offset, so it should be adjusted by the playback rate to achieve this. - public override double CurrentTime => SourceTime + Offset * Rate; + // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this. + // base implementation already adds offset at 1.0 rate, so we only add the difference from that here. + public override double CurrentTime => base.CurrentTime + Offset * (1 - Rate); public HardwareCorrectionOffsetClock(IClock source, bool processSource = true) : base(source, processSource) From 1164a1048330211410a9f050176bf868c56a023d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 14 Jun 2020 11:34:07 +0900 Subject: [PATCH 446/871] Add test coverage --- .../TestSceneGameplayClockContainer.cs | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs diff --git a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs new file mode 100644 index 0000000000..a97566ba7b --- /dev/null +++ b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.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; +using NUnit.Framework; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play; +using osu.Game.Tests.Visual; + +namespace osu.Game.Tests.Gameplay +{ + public class TestSceneGameplayClockContainer : OsuTestScene + { + [Test] + public void TestStartThenElapsedTime() + { + GameplayClockContainer gcc = null; + + AddStep("create container", () => Add(gcc = new GameplayClockContainer(CreateWorkingBeatmap(new OsuRuleset().RulesetInfo), Array.Empty(), 0))); + AddStep("start track", () => gcc.Start()); + AddUntilStep("elapsed greater than zero", () => gcc.GameplayClock.ElapsedFrameTime > 0); + } + } +} From abe07b742ebb16190fe0a4601f1fbbc8094748c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 14 Jun 2020 13:20:58 +0900 Subject: [PATCH 447/871] Fix drag scroll in editor timeline no longer working correctly --- osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs | 2 -- .../Edit/Compose/Components/ComposeBlueprintContainer.cs | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index d07cffff0c..cc417bbb10 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -44,8 +44,6 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly BindableList selectedHitObjects = new BindableList(); - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; - [Resolved(canBeNull: true)] private IPositionSnapProvider snapProvider { get; set; } diff --git a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs index 0b5d8262fd..e1f311f1b8 100644 --- a/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/ComposeBlueprintContainer.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; +using osuTK; namespace osu.Game.Screens.Edit.Compose.Components { @@ -26,6 +27,8 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly Container placementBlueprintContainer; + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true; + private InputManager inputManager; private readonly IEnumerable drawableHitObjects; From 0d53d0ffc8f361576a60ebda3ae1e5dece2c6144 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jun 2020 00:46:20 +0900 Subject: [PATCH 448/871] Fix back-to-front math --- osu.Game/Screens/Play/GameplayClockContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index fe1d22e987..0653373c91 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -253,7 +253,7 @@ namespace osu.Game.Screens.Play { // we always want to apply the same real-time offset, so it should be adjusted by the difference in playback rate (from realtime) to achieve this. // base implementation already adds offset at 1.0 rate, so we only add the difference from that here. - public override double CurrentTime => base.CurrentTime + Offset * (1 - Rate); + public override double CurrentTime => base.CurrentTime + Offset * (Rate - 1); public HardwareCorrectionOffsetClock(IClock source, bool processSource = true) : base(source, processSource) From 9907b4763bb76963eee9d652640559f07e5fce1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 14 Jun 2020 18:39:41 +0200 Subject: [PATCH 449/871] Remove redundant default argument value --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 9f30f340fd..09c50ce115 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Mods // for preview purpose at song select. eventually we'll want to be able to update every frame. FinalRate.BindValueChanged(val => applyAdjustment(1), true); - AdjustPitch.BindValueChanged(updatePitchAdjustment, false); + AdjustPitch.BindValueChanged(updatePitchAdjustment); } public void ApplyToTrack(Track track) From 5f0a345eebd0410364a79b69aa0465490b48712a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 14 Jun 2020 18:48:49 +0200 Subject: [PATCH 450/871] Unify method naming --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 09c50ce115..edca3edf46 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -46,9 +46,8 @@ namespace osu.Game.Rulesets.Mods protected ModTimeRamp() { // for preview purpose at song select. eventually we'll want to be able to update every frame. - FinalRate.BindValueChanged(val => applyAdjustment(1), true); - - AdjustPitch.BindValueChanged(updatePitchAdjustment); + FinalRate.BindValueChanged(val => applyRateAdjustment(1), true); + AdjustPitch.BindValueChanged(applyPitchAdjustment); } public void ApplyToTrack(Track track) @@ -71,17 +70,17 @@ namespace osu.Game.Rulesets.Mods public virtual void Update(Playfield playfield) { - applyAdjustment((track.CurrentTime - beginRampTime) / finalRateTime); + applyRateAdjustment((track.CurrentTime - beginRampTime) / finalRateTime); } /// /// Adjust the rate along the specified ramp /// /// The amount of adjustment to apply (from 0..1). - private void applyAdjustment(double amount) => + private void applyRateAdjustment(double amount) => SpeedChange.Value = InitialRate.Value + (FinalRate.Value - InitialRate.Value) * Math.Clamp(amount, 0, 1); - private void updatePitchAdjustment(ValueChangedEvent value) + private void applyPitchAdjustment(ValueChangedEvent value) { // remove existing old adjustment track.RemoveAdjustment(adjustmentForPitchSetting(value.OldValue), SpeedChange); From e6ddd0380e2ee3b12aacd5a2fdf009ede4605672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 14 Jun 2020 18:50:07 +0200 Subject: [PATCH 451/871] Rename bool arguments for readability --- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index edca3edf46..df059eef7d 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -80,15 +80,15 @@ namespace osu.Game.Rulesets.Mods private void applyRateAdjustment(double amount) => SpeedChange.Value = InitialRate.Value + (FinalRate.Value - InitialRate.Value) * Math.Clamp(amount, 0, 1); - private void applyPitchAdjustment(ValueChangedEvent value) + private void applyPitchAdjustment(ValueChangedEvent adjustPitchSetting) { // remove existing old adjustment - track.RemoveAdjustment(adjustmentForPitchSetting(value.OldValue), SpeedChange); + track.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange); - track.AddAdjustment(adjustmentForPitchSetting(value.NewValue), SpeedChange); + track.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange); } - private AdjustableProperty adjustmentForPitchSetting(bool value) - => value ? AdjustableProperty.Frequency : AdjustableProperty.Tempo; + private AdjustableProperty adjustmentForPitchSetting(bool adjustPitchSettingValue) + => adjustPitchSettingValue ? AdjustableProperty.Frequency : AdjustableProperty.Tempo; } } From b8fa1a2c41445264c0835d0600ddf378bd42948a Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 14 Jun 2020 11:22:38 -0700 Subject: [PATCH 452/871] Add shortcut to go home --- .../Input/Bindings/GlobalActionContainer.cs | 5 +++++ .../Overlays/Toolbar/ToolbarHomeButton.cs | 19 ++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 71771abede..618798a6d8 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -39,6 +39,8 @@ namespace osu.Game.Input.Bindings new KeyBinding(InputKey.Escape, GlobalAction.Back), new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back), + new KeyBinding(new[] { InputKey.Alt, InputKey.Home }, GlobalAction.Home), + new KeyBinding(InputKey.Up, GlobalAction.SelectPrevious), new KeyBinding(InputKey.Down, GlobalAction.SelectNext), @@ -152,5 +154,8 @@ namespace osu.Game.Input.Bindings [Description("Next Selection")] SelectNext, + + [Description("Home")] + Home, } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs index 6f5e703a66..e642f0c453 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarHomeButton.cs @@ -2,10 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; +using osu.Game.Input.Bindings; namespace osu.Game.Overlays.Toolbar { - public class ToolbarHomeButton : ToolbarButton + public class ToolbarHomeButton : ToolbarButton, IKeyBindingHandler { public ToolbarHomeButton() { @@ -13,5 +15,20 @@ namespace osu.Game.Overlays.Toolbar TooltipMain = "Home"; TooltipSub = "Return to the main menu"; } + + public bool OnPressed(GlobalAction action) + { + if (action == GlobalAction.Home) + { + Click(); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + } } } From 1f7679e829bb39c4bbc88a6599faa527e93a805d Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 14 Jun 2020 11:24:23 -0700 Subject: [PATCH 453/871] Fix home button not flashing when pressing shortcut --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index 3d66d3c28e..e0ea88fcf3 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -78,9 +78,8 @@ namespace osu.Game.Overlays.Toolbar HoverBackground = new Box { RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(80).Opacity(180), + Colour = OsuColour.Gray(80).Opacity(0), Blending = BlendingParameters.Additive, - Alpha = 0, }, Flow = new FillFlowContainer { @@ -146,14 +145,14 @@ namespace osu.Game.Overlays.Toolbar protected override bool OnHover(HoverEvent e) { - HoverBackground.FadeIn(200); + HoverBackground.FadeColour(OsuColour.Gray(80).Opacity(180), 200); tooltipContainer.FadeIn(100); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - HoverBackground.FadeOut(200); + HoverBackground.FadeColour(OsuColour.Gray(80).Opacity(0), 200); tooltipContainer.FadeOut(100); } } From 978636b90c5f1f1ff943cbe4027543c595e52201 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jun 2020 09:38:33 +0900 Subject: [PATCH 454/871] Fix storyboard sample playback failing when expected to play at 0ms --- osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index f3f8308964..8292b02068 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -51,7 +51,7 @@ namespace osu.Game.Storyboards.Drawables LifetimeStart = sampleInfo.StartTime; LifetimeEnd = double.MaxValue; } - else if (Time.Current - Time.Elapsed < sampleInfo.StartTime) + else if (Time.Current - Time.Elapsed <= sampleInfo.StartTime) { // We've passed the start time of the sample. We only play the sample if we're within an allowable range // from the sample's start, to reduce layering if we've been fast-forwarded far into the future From fdf7c56ba28db7165d1651dcdfb2e69520866e35 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jun 2020 11:18:12 +0900 Subject: [PATCH 455/871] Add test coverage --- .../Gameplay/TestSceneStoryboardSamples.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 84506739ab..2c85c4809b 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.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.IO; using System.Threading.Tasks; @@ -10,7 +11,12 @@ using osu.Framework.Audio.Sample; using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Audio; +using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play; using osu.Game.Skinning; +using osu.Game.Storyboards; +using osu.Game.Storyboards.Drawables; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual; @@ -43,6 +49,27 @@ namespace osu.Game.Tests.Gameplay AddAssert("sample is non-null", () => channel != null); } + [Test] + public void TestSamplePlaybackAtZero() + { + GameplayClockContainer gameplayContainer = null; + DrawableStoryboardSample sample = null; + + AddStep("create container", () => + { + Add(gameplayContainer = new GameplayClockContainer(CreateWorkingBeatmap(new OsuRuleset().RulesetInfo), Array.Empty(), 0)); + + gameplayContainer.Add(sample = new DrawableStoryboardSample(new StoryboardSampleInfo(string.Empty, 0, 1)) + { + Clock = gameplayContainer.GameplayClock + }); + }); + + AddStep("start time", () => gameplayContainer.Start()); + + AddUntilStep("sample playback succeeded", () => sample.LifetimeEnd < double.MaxValue); + } + private class TestSkin : LegacySkin { public TestSkin(string resourceName, AudioManager audioManager) From b41567c66c48f2fc679cb91518e70e7ee7799b88 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 14 Jun 2020 22:02:21 -0700 Subject: [PATCH 456/871] Split hover and flash to separate boxes --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index e0ea88fcf3..cbcb4060a3 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -62,6 +62,7 @@ namespace osu.Game.Overlays.Toolbar protected ConstrainedIconContainer IconContainer; protected SpriteText DrawableText; protected Box HoverBackground; + private readonly Box FlashBackground; private readonly FillFlowContainer tooltipContainer; private readonly SpriteText tooltip1; private readonly SpriteText tooltip2; @@ -78,7 +79,14 @@ namespace osu.Game.Overlays.Toolbar HoverBackground = new Box { RelativeSizeAxes = Axes.Both, - Colour = OsuColour.Gray(80).Opacity(0), + Colour = OsuColour.Gray(80).Opacity(180), + Blending = BlendingParameters.Additive, + Alpha = 0, + }, + FlashBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Transparent, Blending = BlendingParameters.Additive, }, Flow = new FillFlowContainer @@ -138,21 +146,21 @@ namespace osu.Game.Overlays.Toolbar protected override bool OnClick(ClickEvent e) { - HoverBackground.FlashColour(Color4.White.Opacity(100), 500, Easing.OutQuint); + FlashBackground.FlashColour(Color4.White.Opacity(100), 500, Easing.OutQuint); tooltipContainer.FadeOut(100); return base.OnClick(e); } protected override bool OnHover(HoverEvent e) { - HoverBackground.FadeColour(OsuColour.Gray(80).Opacity(180), 200); + HoverBackground.FadeIn(200); tooltipContainer.FadeIn(100); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - HoverBackground.FadeColour(OsuColour.Gray(80).Opacity(0), 200); + HoverBackground.FadeOut(200); tooltipContainer.FadeOut(100); } } From 941fdf5e76a40d6d3400ab3b75a4869414c55aa6 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 14 Jun 2020 22:16:17 -0700 Subject: [PATCH 457/871] Fix flash background naming --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index cbcb4060a3..e752516baf 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -62,7 +62,7 @@ namespace osu.Game.Overlays.Toolbar protected ConstrainedIconContainer IconContainer; protected SpriteText DrawableText; protected Box HoverBackground; - private readonly Box FlashBackground; + private readonly Box flashBackground; private readonly FillFlowContainer tooltipContainer; private readonly SpriteText tooltip1; private readonly SpriteText tooltip2; @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Toolbar Blending = BlendingParameters.Additive, Alpha = 0, }, - FlashBackground = new Box + flashBackground = new Box { RelativeSizeAxes = Axes.Both, Colour = Color4.Transparent, @@ -146,7 +146,7 @@ namespace osu.Game.Overlays.Toolbar protected override bool OnClick(ClickEvent e) { - FlashBackground.FlashColour(Color4.White.Opacity(100), 500, Easing.OutQuint); + flashBackground.FlashColour(Color4.White.Opacity(100), 500, Easing.OutQuint); tooltipContainer.FadeOut(100); return base.OnClick(e); } From 1770b70b8164442d21f79ea4b0e9116a91552403 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jun 2020 16:14:35 +0900 Subject: [PATCH 458/871] Change implementation to ensure flashBackground is not present by default --- osu.Game/Overlays/Toolbar/ToolbarButton.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/ToolbarButton.cs b/osu.Game/Overlays/Toolbar/ToolbarButton.cs index e752516baf..86a3f5d8aa 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarButton.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarButton.cs @@ -86,7 +86,8 @@ namespace osu.Game.Overlays.Toolbar flashBackground = new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Transparent, + Alpha = 0, + Colour = Color4.White.Opacity(100), Blending = BlendingParameters.Additive, }, Flow = new FillFlowContainer @@ -146,7 +147,7 @@ namespace osu.Game.Overlays.Toolbar protected override bool OnClick(ClickEvent e) { - flashBackground.FlashColour(Color4.White.Opacity(100), 500, Easing.OutQuint); + flashBackground.FadeOutFromOne(800, Easing.OutQuint); tooltipContainer.FadeOut(100); return base.OnClick(e); } From 60381d581718dad2d92dfef6b0da3ff8026fa9fe Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 17 Apr 2020 03:38:12 +0300 Subject: [PATCH 459/871] Remove IRulesetTestScene and use OsuTestScene.CreateRuleset() instead --- .../Rulesets/Testing/IRulesetTestScene.cs | 20 ------------------- osu.Game/Tests/Visual/OsuTestScene.cs | 6 +++--- 2 files changed, 3 insertions(+), 23 deletions(-) delete mode 100644 osu.Game/Rulesets/Testing/IRulesetTestScene.cs diff --git a/osu.Game/Rulesets/Testing/IRulesetTestScene.cs b/osu.Game/Rulesets/Testing/IRulesetTestScene.cs deleted file mode 100644 index e8b8a79eb5..0000000000 --- a/osu.Game/Rulesets/Testing/IRulesetTestScene.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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.Rulesets.Testing -{ - /// - /// An interface that can be assigned to test scenes to indicate - /// that the test scene is testing ruleset-specific components. - /// This is to cache required ruleset dependencies for the components. - /// - public interface IRulesetTestScene - { - /// - /// Retrieves the ruleset that is going - /// to be tested by this test scene. - /// - /// The . - Ruleset CreateRuleset(); - } -} diff --git a/osu.Game/Tests/Visual/OsuTestScene.cs b/osu.Game/Tests/Visual/OsuTestScene.cs index eb1905cbe1..82ba989306 100644 --- a/osu.Game/Tests/Visual/OsuTestScene.cs +++ b/osu.Game/Tests/Visual/OsuTestScene.cs @@ -20,7 +20,6 @@ using osu.Game.Database; using osu.Game.Online.API; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Testing; using osu.Game.Rulesets.UI; using osu.Game.Screens; using osu.Game.Storyboards; @@ -70,8 +69,9 @@ namespace osu.Game.Tests.Visual { var baseDependencies = base.CreateChildDependencies(parent); - if (this is IRulesetTestScene rts) - baseDependencies = rulesetDependencies = new DrawableRulesetDependencies(rts.CreateRuleset(), baseDependencies); + var providedRuleset = CreateRuleset(); + if (providedRuleset != null) + baseDependencies = rulesetDependencies = new DrawableRulesetDependencies(providedRuleset, baseDependencies); Dependencies = new OsuScreenDependencies(false, baseDependencies); From f14774e795ab68f593008346a43c1c70401681e7 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Mon, 15 Jun 2020 11:47:31 +0300 Subject: [PATCH 460/871] Rename to TestSceneRulesetDependencies, mark as headless and avoid throwing Avoid throw not implemented exceptions from TestRuleset since it's now set as the global ruleset bindable automatically by base, throwing to innocent components is probably not needed. --- ...ene.cs => TestSceneRulesetDependencies.cs} | 41 +++++++++++++------ 1 file changed, 28 insertions(+), 13 deletions(-) rename osu.Game.Tests/Testing/{TestSceneRulesetTestScene.cs => TestSceneRulesetDependencies.cs} (73%) diff --git a/osu.Game.Tests/Testing/TestSceneRulesetTestScene.cs b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs similarity index 73% rename from osu.Game.Tests/Testing/TestSceneRulesetTestScene.cs rename to osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs index 6d8502e651..80f1b02794 100644 --- a/osu.Game.Tests/Testing/TestSceneRulesetTestScene.cs +++ b/osu.Game.Tests/Testing/TestSceneRulesetDependencies.cs @@ -9,21 +9,28 @@ using osu.Framework.Audio.Track; using osu.Framework.Configuration.Tracking; using osu.Framework.Graphics.Textures; using osu.Framework.IO.Stores; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets; using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mods; -using osu.Game.Rulesets.Testing; using osu.Game.Rulesets.UI; using osu.Game.Tests.Resources; using osu.Game.Tests.Visual; namespace osu.Game.Tests.Testing { - public class TestSceneRulesetTestScene : OsuTestScene, IRulesetTestScene + /// + /// A test scene ensuring the dependencies for the + /// provided ruleset below are cached at the base implementation. + /// + [HeadlessTest] + public class TestSceneRulesetDependencies : OsuTestScene { + protected override Ruleset CreateRuleset() => new TestRuleset(); + [Test] public void TestRetrieveTexture() { @@ -45,8 +52,6 @@ namespace osu.Game.Tests.Testing Dependencies.Get() != null); } - public Ruleset CreateRuleset() => new TestRuleset(); - private class TestRuleset : Ruleset { public override string Description => string.Empty; @@ -62,19 +67,29 @@ namespace osu.Game.Tests.Testing public override IResourceStore CreateResourceStore() => new NamespacedResourceStore(TestResources.GetStore(), @"Resources"); public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new TestRulesetConfigManager(); - public override IEnumerable GetModsFor(ModType type) => throw new NotImplementedException(); - public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new NotImplementedException(); - public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => throw new NotImplementedException(); - public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException(); + public override IEnumerable GetModsFor(ModType type) => Array.Empty(); + public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => null; + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => null; + public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => null; } private class TestRulesetConfigManager : IRulesetConfigManager { - public void Load() => throw new NotImplementedException(); - public bool Save() => throw new NotImplementedException(); - public TrackedSettings CreateTrackedSettings() => throw new NotImplementedException(); - public void LoadInto(TrackedSettings settings) => throw new NotImplementedException(); - public void Dispose() => throw new NotImplementedException(); + public void Load() + { + } + + public bool Save() => true; + + public TrackedSettings CreateTrackedSettings() => new TrackedSettings(); + + public void LoadInto(TrackedSettings settings) + { + } + + public void Dispose() + { + } } } } From f4b57933c347b99eb4f6238f6dbb9729fae51176 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 15 Jun 2020 08:54:34 +0000 Subject: [PATCH 461/871] Bump Microsoft.Build.Traversal from 2.0.48 to 2.0.50 Bumps [Microsoft.Build.Traversal](https://github.com/Microsoft/MSBuildSdks) from 2.0.48 to 2.0.50. - [Release notes](https://github.com/Microsoft/MSBuildSdks/releases) - [Changelog](https://github.com/microsoft/MSBuildSdks/blob/master/RELEASE.md) - [Commits](https://github.com/Microsoft/MSBuildSdks/compare/Microsoft.Build.Traversal.2.0.48...Microsoft.Build.Traversal.2.0.50) Signed-off-by: dependabot-preview[bot] --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index bdb90eb0e9..9aa5b6192b 100644 --- a/global.json +++ b/global.json @@ -5,6 +5,6 @@ "version": "3.1.100" }, "msbuild-sdks": { - "Microsoft.Build.Traversal": "2.0.48" + "Microsoft.Build.Traversal": "2.0.50" } } \ No newline at end of file From ad5bd1f0c00ae10c0c857759e38b1f588d2f4b45 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jun 2020 18:45:50 +0900 Subject: [PATCH 462/871] Update in line with other/unspecified switch See https://github.com/ppy/osu-web/commit/289f0f0a209f1f840270db07794a7bfd52439db1. --- osu.Game/Overlays/BeatmapListing/SearchLanguage.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs index 43f16059e9..eee5d8f7e1 100644 --- a/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs +++ b/osu.Game/Overlays/BeatmapListing/SearchLanguage.cs @@ -11,8 +11,8 @@ namespace osu.Game.Overlays.BeatmapListing [Order(0)] Any, - [Order(13)] - Other, + [Order(14)] + Unspecified, [Order(1)] English, @@ -50,7 +50,7 @@ namespace osu.Game.Overlays.BeatmapListing [Order(11)] Polish, - [Order(14)] - Unspecified + [Order(13)] + Other } } From d57b58a7dd6a8429cd684cf8d6e2fbd0f2f643ad Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 15 Jun 2020 18:42:16 +0900 Subject: [PATCH 463/871] Add temporary fix for tournament song bar disappearance --- osu.Game.Tournament/Components/SongBar.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index e86fd890c1..fc7fcef892 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -77,6 +77,8 @@ namespace osu.Game.Tournament.Components flow = new FillFlowContainer { RelativeSizeAxes = Axes.X, + // Todo: This is a hack for https://github.com/ppy/osu-framework/issues/3617 since this container is at the very edge of the screen and potentially initially masked away. + Height = 1, AutoSizeAxes = Axes.Y, LayoutDuration = 500, LayoutEasing = Easing.OutQuint, From c3c5a99a2288819f7c95bbf64069cd21838d54c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jun 2020 20:23:35 +0900 Subject: [PATCH 464/871] Load imported scores to results screen rather than gameplay --- osu.Game/OsuGame.cs | 23 ++++++++++++++++--- .../Screens/Ranking/ReplayDownloadButton.cs | 2 +- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 7ecd7851d7..7c5e4b8d94 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -35,7 +35,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Input; using osu.Game.Overlays.Notifications; -using osu.Game.Screens.Play; using osu.Game.Input.Bindings; using osu.Game.Online.Chat; using osu.Game.Skinning; @@ -43,6 +42,8 @@ using osuTK.Graphics; using osu.Game.Overlays.Volume; using osu.Game.Rulesets.Mods; using osu.Game.Scoring; +using osu.Game.Screens.Play; +using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; using osu.Game.Updater; using osu.Game.Utils; @@ -360,7 +361,7 @@ namespace osu.Game /// Present a score's replay immediately. /// The user should have already requested this interactively. ///
- public void PresentScore(ScoreInfo score) + public void PresentScore(ScoreInfo score, ScorePresentType presentType = ScorePresentType.Results) { // The given ScoreInfo may have missing properties if it was retrieved from online data. Re-retrieve it from the database // to ensure all the required data for presenting a replay are present. @@ -392,9 +393,19 @@ namespace osu.Game PerformFromScreen(screen => { + Ruleset.Value = databasedScore.ScoreInfo.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); - screen.Push(new ReplayPlayerLoader(databasedScore)); + switch (presentType) + { + case ScorePresentType.Gameplay: + screen.Push(new ReplayPlayerLoader(databasedScore)); + break; + + case ScorePresentType.Results: + screen.Push(new SoloResultsScreen(databasedScore.ScoreInfo)); + break; + } }, validScreens: new[] { typeof(PlaySongSelect) }); } @@ -1000,4 +1011,10 @@ namespace osu.Game Exit(); } } + + public enum ScorePresentType + { + Results, + Gameplay + } } diff --git a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs index 9d4e3af230..d0142e57fe 100644 --- a/osu.Game/Screens/Ranking/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/ReplayDownloadButton.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Ranking switch (State.Value) { case DownloadState.LocallyAvailable: - game?.PresentScore(Model.Value); + game?.PresentScore(Model.Value, ScorePresentType.Gameplay); break; case DownloadState.NotDownloaded: From 90d69c121625ddc69e88b5a232e7c4f6afa51076 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jun 2020 20:31:47 +0900 Subject: [PATCH 465/871] Allow legacy score to be constructed even if replay file is missing --- osu.Game/Scoring/LegacyDatabasedScore.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Scoring/LegacyDatabasedScore.cs b/osu.Game/Scoring/LegacyDatabasedScore.cs index bd673eaa29..8908775472 100644 --- a/osu.Game/Scoring/LegacyDatabasedScore.cs +++ b/osu.Game/Scoring/LegacyDatabasedScore.cs @@ -16,7 +16,10 @@ namespace osu.Game.Scoring { ScoreInfo = score; - var replayFilename = score.Files.First(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase)).FileInfo.StoragePath; + var replayFilename = score.Files.FirstOrDefault(f => f.Filename.EndsWith(".osr", StringComparison.InvariantCultureIgnoreCase))?.FileInfo.StoragePath; + + if (replayFilename == null) + return; using (var stream = store.GetStream(replayFilename)) Replay = new DatabasedLegacyScoreDecoder(rulesets, beatmaps).Parse(stream).Replay; From 17a70bf6ee241a0e26b064de1bc51cb04b0140a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jun 2020 20:32:27 +0900 Subject: [PATCH 466/871] Add test coverage --- .../Visual/Navigation/OsuGameTestScene.cs | 3 + .../Navigation/TestScenePresentScore.cs | 155 ++++++++++++++++++ osu.Game/Screens/Play/ReplayPlayerLoader.cs | 8 +- 3 files changed, 162 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs diff --git a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs index 31afce86ae..c4acf4f7da 100644 --- a/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs +++ b/osu.Game.Tests/Visual/Navigation/OsuGameTestScene.cs @@ -17,6 +17,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Rulesets; +using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Menu; using osuTK.Graphics; @@ -100,6 +101,8 @@ namespace osu.Game.Tests.Visual.Navigation public new BeatmapManager BeatmapManager => base.BeatmapManager; + public new ScoreManager ScoreManager => base.ScoreManager; + public new SettingsPanel Settings => base.Settings; public new MusicController MusicController => base.MusicController; diff --git a/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs new file mode 100644 index 0000000000..b2e18849c9 --- /dev/null +++ b/osu.Game.Tests/Visual/Navigation/TestScenePresentScore.cs @@ -0,0 +1,155 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Osu; +using osu.Game.Scoring; +using osu.Game.Screens.Menu; +using osu.Game.Screens.Play; +using osu.Game.Screens.Ranking; + +namespace osu.Game.Tests.Visual.Navigation +{ + public class TestScenePresentScore : OsuGameTestScene + { + private BeatmapSetInfo beatmap; + + [SetUpSteps] + public new void SetUpSteps() + { + AddStep("import beatmap", () => + { + var difficulty = new BeatmapDifficulty(); + var metadata = new BeatmapMetadata + { + Artist = "SomeArtist", + AuthorString = "SomeAuthor", + Title = "import" + }; + + beatmap = Game.BeatmapManager.Import(new BeatmapSetInfo + { + Hash = Guid.NewGuid().ToString(), + OnlineBeatmapSetID = 1, + Metadata = metadata, + Beatmaps = new List + { + new BeatmapInfo + { + OnlineBeatmapID = 1 * 1024, + Metadata = metadata, + BaseDifficulty = difficulty, + Ruleset = new OsuRuleset().RulesetInfo + }, + new BeatmapInfo + { + OnlineBeatmapID = 1 * 2048, + Metadata = metadata, + BaseDifficulty = difficulty, + Ruleset = new OsuRuleset().RulesetInfo + }, + } + }).Result; + }); + } + + [Test] + public void TestFromMainMenu([Values] ScorePresentType type) + { + var firstImport = importScore(1); + var secondimport = importScore(3); + + presentAndConfirm(firstImport, type); + returnToMenu(); + presentAndConfirm(secondimport, type); + returnToMenu(); + returnToMenu(); + } + + [Test] + public void TestFromMainMenuDifferentRuleset([Values] ScorePresentType type) + { + var firstImport = importScore(1); + var secondimport = importScore(3, new ManiaRuleset().RulesetInfo); + + presentAndConfirm(firstImport, type); + returnToMenu(); + presentAndConfirm(secondimport, type); + returnToMenu(); + returnToMenu(); + } + + [Test] + public void TestFromSongSelect([Values] ScorePresentType type) + { + var firstImport = importScore(1); + presentAndConfirm(firstImport, type); + + var secondimport = importScore(3); + presentAndConfirm(secondimport, type); + } + + [Test] + public void TestFromSongSelectDifferentRuleset([Values] ScorePresentType type) + { + var firstImport = importScore(1); + presentAndConfirm(firstImport, type); + + var secondimport = importScore(3, new ManiaRuleset().RulesetInfo); + presentAndConfirm(secondimport, type); + } + + private void returnToMenu() + { + AddStep("return to menu", () => Game.ScreenStack.CurrentScreen.Exit()); + AddUntilStep("wait for menu", () => Game.ScreenStack.CurrentScreen is MainMenu); + } + + private Func importScore(int i, RulesetInfo ruleset = null) + { + ScoreInfo imported = null; + AddStep($"import score {i}", () => + { + imported = Game.ScoreManager.Import(new ScoreInfo + { + Hash = Guid.NewGuid().ToString(), + OnlineScoreID = i, + Beatmap = beatmap.Beatmaps.First(), + Ruleset = ruleset ?? new OsuRuleset().RulesetInfo + }).Result; + }); + + AddAssert($"import {i} succeeded", () => imported != null); + + return () => imported; + } + + private void presentAndConfirm(Func getImport, ScorePresentType type) + { + AddStep("present score", () => Game.PresentScore(getImport(), type)); + + switch (type) + { + case ScorePresentType.Results: + AddUntilStep("wait for results", () => Game.ScreenStack.CurrentScreen is ResultsScreen); + AddUntilStep("correct score displayed", () => ((ResultsScreen)Game.ScreenStack.CurrentScreen).Score.ID == getImport().ID); + AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Ruleset.ID); + break; + + case ScorePresentType.Gameplay: + AddUntilStep("wait for player loader", () => Game.ScreenStack.CurrentScreen is ReplayPlayerLoader); + AddUntilStep("correct score displayed", () => ((ReplayPlayerLoader)Game.ScreenStack.CurrentScreen).Score.ID == getImport().ID); + AddAssert("correct ruleset selected", () => Game.Ruleset.Value.ID == getImport().Ruleset.ID); + break; + } + } + } +} diff --git a/osu.Game/Screens/Play/ReplayPlayerLoader.cs b/osu.Game/Screens/Play/ReplayPlayerLoader.cs index 4572570437..9eff4cb8fc 100644 --- a/osu.Game/Screens/Play/ReplayPlayerLoader.cs +++ b/osu.Game/Screens/Play/ReplayPlayerLoader.cs @@ -9,7 +9,7 @@ namespace osu.Game.Screens.Play { public class ReplayPlayerLoader : PlayerLoader { - private readonly ScoreInfo scoreInfo; + public readonly ScoreInfo Score; public ReplayPlayerLoader(Score score) : base(() => new ReplayPlayer(score)) @@ -17,14 +17,14 @@ namespace osu.Game.Screens.Play if (score.Replay == null) throw new ArgumentException($"{nameof(score)} must have a non-null {nameof(score.Replay)}.", nameof(score)); - scoreInfo = score.ScoreInfo; + Score = score.ScoreInfo; } public override void OnEntering(IScreen last) { // these will be reverted thanks to PlayerLoader's lease. - Mods.Value = scoreInfo.Mods; - Ruleset.Value = scoreInfo.Ruleset; + Mods.Value = Score.Mods; + Ruleset.Value = Score.Ruleset; base.OnEntering(last); } From 1ce374ae2f640c7adcfb16146769e4ad7d030ba3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jun 2020 20:34:46 +0900 Subject: [PATCH 467/871] 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 596e5bfa8b..6387356686 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@
- + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 1d3bafbfd6..8c098b79c6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index ad7850599b..373ad09597 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From f9db37a1de9c425661df474f2d56fbf2d9ddfa27 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 15 Jun 2020 21:48:59 +0900 Subject: [PATCH 468/871] Split out types --- .../Scoring/OsuScoreProcessor.cs | 18 -- .../Scoring/TimingDistribution.cs | 17 ++ osu.Game.Rulesets.Osu/Statistics/Heatmap.cs | 186 ++++++++++++++++++ .../Statistics/TimingDistributionGraph.cs | 139 +++++++++++++ .../Ranking/TestSceneAccuracyHeatmap.cs | 175 +--------------- .../TestSceneTimingDistributionGraph.cs | 130 +----------- 6 files changed, 344 insertions(+), 321 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Scoring/TimingDistribution.cs create mode 100644 osu.Game.Rulesets.Osu/Statistics/Heatmap.cs create mode 100644 osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 83339bd061..a9d48df52d 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -7,7 +7,6 @@ using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu.Scoring { @@ -62,11 +61,6 @@ namespace osu.Game.Rulesets.Osu.Scoring } } - public override void PopulateScore(ScoreInfo score) - { - base.PopulateScore(score); - } - protected override void Reset(bool storeResults) { base.Reset(storeResults); @@ -78,16 +72,4 @@ namespace osu.Game.Rulesets.Osu.Scoring public override HitWindows CreateHitWindows() => new OsuHitWindows(); } - - public class TimingDistribution - { - public readonly int[] Bins; - public readonly double BinSize; - - public TimingDistribution(int binCount, double binSize) - { - Bins = new int[binCount]; - BinSize = binSize; - } - } } diff --git a/osu.Game.Rulesets.Osu/Scoring/TimingDistribution.cs b/osu.Game.Rulesets.Osu/Scoring/TimingDistribution.cs new file mode 100644 index 0000000000..46f259f3d8 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Scoring/TimingDistribution.cs @@ -0,0 +1,17 @@ +// 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.Rulesets.Osu.Scoring +{ + public class TimingDistribution + { + public readonly int[] Bins; + public readonly double BinSize; + + public TimingDistribution(int binCount, double binSize) + { + Bins = new int[binCount]; + BinSize = binSize; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs new file mode 100644 index 0000000000..8105d12991 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs @@ -0,0 +1,186 @@ +// 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 osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Statistics +{ + public class Heatmap : CompositeDrawable + { + /// + /// Full size of the heatmap. + /// + private const float size = 130; + + /// + /// Size of the inner circle containing the "hit" points, relative to . + /// All other points outside of the inner circle are "miss" points. + /// + private const float inner_portion = 0.8f; + + private const float rotation = 45; + private const float point_size = 4; + + private Container allPoints; + + public Heatmap() + { + Size = new Vector2(size); + } + + [BackgroundDependencyLoader] + private void load() + { + InternalChildren = new Drawable[] + { + new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(inner_portion), + Masking = true, + BorderThickness = 2f, + BorderColour = Color4.White, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#202624") + } + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 2, // We're rotating along a diagonal - we don't really care how big this is. + Width = 1f, + Rotation = -rotation, + Alpha = 0.3f, + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 2, // We're rotating along a diagonal - we don't really care how big this is. + Width = 1f, + Rotation = rotation + }, + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Width = 10, + Height = 2f, + }, + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Y = -1, + Width = 2f, + Height = 10, + } + } + }, + allPoints = new Container { RelativeSizeAxes = Axes.Both } + }; + + Vector2 centre = new Vector2(size / 2); + int rows = (int)Math.Ceiling(size / point_size); + int cols = (int)Math.Ceiling(size / point_size); + + for (int r = 0; r < rows; r++) + { + for (int c = 0; c < cols; c++) + { + Vector2 pos = new Vector2(c * point_size, r * point_size); + HitPointType pointType = HitPointType.Hit; + + if (Vector2.Distance(pos, centre) > size * inner_portion / 2) + pointType = HitPointType.Miss; + + allPoints.Add(new HitPoint(pos, pointType) + { + Size = new Vector2(point_size), + Colour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) + }); + } + } + } + + public void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) + { + double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point. + double angle2 = Math.Atan2(end.Y - start.Y, start.X - end.X); // Angle between the end point and the start point. + double finalAngle = angle2 - angle1; // Angle between start, end, and hit points. + + float normalisedDistance = Vector2.Distance(hitPoint, end) / radius; + + // Find the most relevant hit point. + double minDist = double.PositiveInfinity; + HitPoint point = null; + + foreach (var p in allPoints) + { + Vector2 localCentre = new Vector2(size / 2); + float localRadius = localCentre.X * inner_portion * normalisedDistance; + double localAngle = finalAngle + 3 * Math.PI / 4; + Vector2 localPoint = localCentre + localRadius * new Vector2((float)Math.Cos(localAngle), (float)Math.Sin(localAngle)); + + float dist = Vector2.Distance(p.DrawPosition + p.DrawSize / 2, localPoint); + + if (dist < minDist) + { + minDist = dist; + point = p; + } + } + + Debug.Assert(point != null); + point.Increment(); + } + + private class HitPoint : Circle + { + private readonly HitPointType pointType; + + public HitPoint(Vector2 position, HitPointType pointType) + { + this.pointType = pointType; + + Position = position; + Alpha = 0; + } + + public void Increment() + { + if (Alpha < 1) + Alpha += 0.1f; + else if (pointType == HitPointType.Hit) + Colour = ((Color4)Colour).Lighten(0.1f); + } + } + + private enum HitPointType + { + Hit, + Miss + } + } +} diff --git a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs new file mode 100644 index 0000000000..a47d726988 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs @@ -0,0 +1,139 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Osu.Scoring; + +namespace osu.Game.Rulesets.Osu.Statistics +{ + public class TimingDistributionGraph : CompositeDrawable + { + /// + /// The number of data points shown on the axis below the graph. + /// + private const float axis_points = 5; + + /// + /// An amount to adjust the value of the axis points by, effectively insetting the axis in the graph. + /// Without an inset, the final data point will be placed halfway outside the graph. + /// + private const float axis_value_inset = 0.2f; + + private readonly TimingDistribution distribution; + + public TimingDistributionGraph(TimingDistribution distribution) + { + this.distribution = distribution; + } + + [BackgroundDependencyLoader] + private void load() + { + int maxCount = distribution.Bins.Max(); + + var bars = new Drawable[distribution.Bins.Length]; + for (int i = 0; i < bars.Length; i++) + bars[i] = new Bar { Height = (float)distribution.Bins[i] / maxCount }; + + Container axisFlow; + + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] { bars } + } + }, + new Drawable[] + { + axisFlow = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + }, + }, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + } + }; + + // We know the total number of bins on each side of the centre ((n - 1) / 2), and the size of each bin. + // So our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. + int sideBins = (distribution.Bins.Length - 1) / 2; + double maxValue = sideBins * distribution.BinSize; + double axisValueStep = maxValue / axis_points * (1 - axis_value_inset); + + axisFlow.Add(new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = "0", + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) + }); + + for (int i = 1; i <= axis_points; i++) + { + double axisValue = i * axisValueStep; + float position = (float)(axisValue / maxValue); + float alpha = 1f - position * 0.8f; + + axisFlow.Add(new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + X = -position / 2, + Alpha = alpha, + Text = axisValue.ToString("-0"), + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) + }); + + axisFlow.Add(new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativePositionAxes = Axes.X, + X = position / 2, + Alpha = alpha, + Text = axisValue.ToString("+0"), + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) + }); + } + } + + private class Bar : CompositeDrawable + { + public Bar() + { + Anchor = Anchor.BottomCentre; + Origin = Anchor.BottomCentre; + + RelativeSizeAxes = Axes.Both; + + Padding = new MarginPadding { Horizontal = 1 }; + + InternalChild = new Circle + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#66FFCC") + }; + } + } + } +} diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs index 9f82287640..9e5fda0ae6 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs @@ -1,15 +1,13 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Diagnostics; -using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Rulesets.Osu.Statistics; using osuTK; using osuTK.Graphics; @@ -70,177 +68,6 @@ namespace osu.Game.Tests.Visual.Ranking return true; } - private class Heatmap : CompositeDrawable - { - /// - /// Full size of the heatmap. - /// - private const float size = 130; - - /// - /// Size of the inner circle containing the "hit" points, relative to . - /// All other points outside of the inner circle are "miss" points. - /// - private const float inner_portion = 0.8f; - - private const float rotation = 45; - private const float point_size = 4; - - private Container allPoints; - - public Heatmap() - { - Size = new Vector2(size); - } - - [BackgroundDependencyLoader] - private void load() - { - InternalChildren = new Drawable[] - { - new CircularContainer - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(inner_portion), - Masking = true, - BorderThickness = 2f, - BorderColour = Color4.White, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#202624") - } - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new Drawable[] - { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = 1f, - Rotation = -rotation, - Alpha = 0.3f, - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = 1f, - Rotation = rotation - }, - new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Width = 10, - Height = 2f, - }, - new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Y = -1, - Width = 2f, - Height = 10, - } - } - }, - allPoints = new Container { RelativeSizeAxes = Axes.Both } - }; - - Vector2 centre = new Vector2(size / 2); - int rows = (int)Math.Ceiling(size / point_size); - int cols = (int)Math.Ceiling(size / point_size); - - for (int r = 0; r < rows; r++) - { - for (int c = 0; c < cols; c++) - { - Vector2 pos = new Vector2(c * point_size, r * point_size); - HitPointType pointType = HitPointType.Hit; - - if (Vector2.Distance(pos, centre) > size * inner_portion / 2) - pointType = HitPointType.Miss; - - allPoints.Add(new HitPoint(pos, pointType) - { - Size = new Vector2(point_size), - Colour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) - }); - } - } - } - - public void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) - { - double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point. - double angle2 = Math.Atan2(end.Y - start.Y, start.X - end.X); // Angle between the end point and the start point. - double finalAngle = angle2 - angle1; // Angle between start, end, and hit points. - - float normalisedDistance = Vector2.Distance(hitPoint, end) / radius; - - // Find the most relevant hit point. - double minDist = double.PositiveInfinity; - HitPoint point = null; - - foreach (var p in allPoints) - { - Vector2 localCentre = new Vector2(size / 2); - float localRadius = localCentre.X * inner_portion * normalisedDistance; - double localAngle = finalAngle + 3 * Math.PI / 4; - Vector2 localPoint = localCentre + localRadius * new Vector2((float)Math.Cos(localAngle), (float)Math.Sin(localAngle)); - - float dist = Vector2.Distance(p.DrawPosition + p.DrawSize / 2, localPoint); - - if (dist < minDist) - { - minDist = dist; - point = p; - } - } - - Debug.Assert(point != null); - point.Increment(); - } - } - - private class HitPoint : Circle - { - private readonly HitPointType pointType; - - public HitPoint(Vector2 position, HitPointType pointType) - { - this.pointType = pointType; - - Position = position; - Alpha = 0; - } - - public void Increment() - { - if (Alpha < 1) - Alpha += 0.1f; - else if (pointType == HitPointType.Hit) - Colour = ((Color4)Colour).Lighten(0.1f); - } - } - - private enum HitPointType - { - Hit, - Miss - } - private class BorderCircle : CircularContainer { public BorderCircle() diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs index 73225ff599..7530fc42b8 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs @@ -2,15 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Rulesets.Osu.Statistics; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -65,128 +61,4 @@ namespace osu.Game.Tests.Visual.Ranking return distribution; } } - - public class TimingDistributionGraph : CompositeDrawable - { - /// - /// The number of data points shown on the axis below the graph. - /// - private const float axis_points = 5; - - /// - /// An amount to adjust the value of the axis points by, effectively insetting the axis in the graph. - /// Without an inset, the final data point will be placed halfway outside the graph. - /// - private const float axis_value_inset = 0.2f; - - private readonly TimingDistribution distribution; - - public TimingDistributionGraph(TimingDistribution distribution) - { - this.distribution = distribution; - } - - [BackgroundDependencyLoader] - private void load() - { - int maxCount = distribution.Bins.Max(); - - var bars = new Drawable[distribution.Bins.Length]; - for (int i = 0; i < bars.Length; i++) - bars[i] = new Bar { Height = (float)distribution.Bins[i] / maxCount }; - - Container axisFlow; - - InternalChild = new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] { bars } - } - }, - new Drawable[] - { - axisFlow = new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y - } - }, - }, - RowDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - } - }; - - // We know the total number of bins on each side of the centre ((n - 1) / 2), and the size of each bin. - // So our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. - int sideBins = (distribution.Bins.Length - 1) / 2; - double maxValue = sideBins * distribution.BinSize; - double axisValueStep = maxValue / axis_points * (1 - axis_value_inset); - - axisFlow.Add(new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Text = "0", - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) - }); - - for (int i = 1; i <= axis_points; i++) - { - double axisValue = i * axisValueStep; - float position = (float)(axisValue / maxValue); - float alpha = 1f - position * 0.8f; - - axisFlow.Add(new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - X = -position / 2, - Alpha = alpha, - Text = axisValue.ToString("-0"), - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) - }); - - axisFlow.Add(new OsuSpriteText - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativePositionAxes = Axes.X, - X = position / 2, - Alpha = alpha, - Text = axisValue.ToString("+0"), - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) - }); - } - } - - private class Bar : CompositeDrawable - { - public Bar() - { - Anchor = Anchor.BottomCentre; - Origin = Anchor.BottomCentre; - - RelativeSizeAxes = Axes.Both; - - Padding = new MarginPadding { Horizontal = 1 }; - - InternalChild = new Circle - { - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#66FFCC") - }; - } - } - } } From d2155c3da3c01e54636a5ce4876618f68acb855f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jun 2020 22:19:02 +0900 Subject: [PATCH 469/871] Fix thread safety --- osu.Game/Updater/UpdateManager.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Updater/UpdateManager.cs b/osu.Game/Updater/UpdateManager.cs index 5da366bde9..61775a26b7 100644 --- a/osu.Game/Updater/UpdateManager.cs +++ b/osu.Game/Updater/UpdateManager.cs @@ -64,10 +64,12 @@ namespace osu.Game.Updater if (!CanCheckForUpdate) return; - lock (updateTaskLock) - updateCheckTask ??= PerformUpdateCheck(); + Task waitTask; - await updateCheckTask; + lock (updateTaskLock) + waitTask = (updateCheckTask ??= PerformUpdateCheck()); + + await waitTask; lock (updateTaskLock) updateCheckTask = null; From 53b7057ee05a3551c07ac7e0d2a00f15f13b4c29 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jun 2020 22:19:11 +0900 Subject: [PATCH 470/871] Don't show update button when updates are not feasible --- .../Sections/General/UpdateSettings.cs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 869e6c9c51..9fca820cac 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -30,16 +30,18 @@ namespace osu.Game.Overlays.Settings.Sections.General Bindable = config.GetBindable(OsuSetting.ReleaseStream), }); - // We should only display the button for UpdateManagers that do check for updates - Add(checkForUpdatesButton = new SettingsButton + if (updateManager.CanCheckForUpdate) { - Text = "Check for updates", - Action = () => + Add(checkForUpdatesButton = new SettingsButton { - checkForUpdatesButton.Enabled.Value = false; - Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(t => Schedule(() => checkForUpdatesButton.Enabled.Value = true)); - } - }); + Text = "Check for updates", + Action = () => + { + checkForUpdatesButton.Enabled.Value = false; + Task.Run(updateManager.CheckForUpdateAsync).ContinueWith(t => Schedule(() => checkForUpdatesButton.Enabled.Value = true)); + } + }); + } if (RuntimeInfo.IsDesktop) { From 97067976f75a416dd8adc290aed8841ab51740ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 15 Jun 2020 22:23:06 +0900 Subject: [PATCH 471/871] Add null check --- osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs index 9fca820cac..9c7d0b0be4 100644 --- a/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/UpdateSettings.cs @@ -30,7 +30,7 @@ namespace osu.Game.Overlays.Settings.Sections.General Bindable = config.GetBindable(OsuSetting.ReleaseStream), }); - if (updateManager.CanCheckForUpdate) + if (updateManager?.CanCheckForUpdate == true) { Add(checkForUpdatesButton = new SettingsButton { From 900da8849866bc13ddc2e71c4065d24c4ed4a74c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 15 Jun 2020 22:44:55 +0900 Subject: [PATCH 472/871] Populate hit offsets from score processor --- .../Judgements/OsuHitCircleJudgementResult.cs | 23 +++++++ .../Objects/Drawables/DrawableHitCircle.cs | 28 ++++++++- osu.Game.Rulesets.Osu/Scoring/HitOffset.cs | 23 +++++++ .../Scoring/OsuScoreProcessor.cs | 61 ++++++++++++++++++- osu.Game/Scoring/ScoreInfo.cs | 2 + 5 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Judgements/OsuHitCircleJudgementResult.cs create mode 100644 osu.Game.Rulesets.Osu/Scoring/HitOffset.cs diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuHitCircleJudgementResult.cs b/osu.Game.Rulesets.Osu/Judgements/OsuHitCircleJudgementResult.cs new file mode 100644 index 0000000000..103d02958d --- /dev/null +++ b/osu.Game.Rulesets.Osu/Judgements/OsuHitCircleJudgementResult.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. + +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Judgements +{ + public class OsuHitCircleJudgementResult : OsuJudgementResult + { + public HitCircle HitCircle => (HitCircle)HitObject; + + public Vector2? HitPosition; + public float? Radius; + + public OsuHitCircleJudgementResult(HitObject hitObject, Judgement judgement) + : base(hitObject, judgement) + { + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index d73ad888f4..2f86400b25 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -7,8 +7,11 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Rulesets.Scoring; using osuTK; @@ -32,6 +35,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables protected virtual OsuSkinComponents CirclePieceComponent => OsuSkinComponents.HitCircle; + private InputManager inputManager; + public DrawableHitCircle(HitCircle h) : base(h) { @@ -86,6 +91,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AccentColour.BindValueChanged(accent => ApproachCircle.Colour = accent.NewValue, true); } + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + public override double LifetimeStart { get => base.LifetimeStart; @@ -126,7 +138,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables return; } - ApplyResult(r => r.Type = result); + ApplyResult(r => + { + var circleResult = (OsuHitCircleJudgementResult)r; + + if (result != HitResult.Miss) + { + var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position); + circleResult.HitPosition = HitObject.StackedPosition + (localMousePosition - DrawSize / 2); + circleResult.Radius = (float)HitObject.Radius; + } + + circleResult.Type = result; + }); } protected override void UpdateInitialTransforms() @@ -172,6 +196,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public Drawable ProxiedLayer => ApproachCircle; + protected override JudgementResult CreateResult(Judgement judgement) => new OsuHitCircleJudgementResult(HitObject, judgement); + public class HitReceptor : CompositeDrawable, IKeyBindingHandler { // IsHovered is used diff --git a/osu.Game.Rulesets.Osu/Scoring/HitOffset.cs b/osu.Game.Rulesets.Osu/Scoring/HitOffset.cs new file mode 100644 index 0000000000..e6a5a01b48 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Scoring/HitOffset.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. + +using osuTK; + +namespace osu.Game.Rulesets.Osu.Scoring +{ + public class HitOffset + { + public readonly Vector2 Position1; + public readonly Vector2 Position2; + public readonly Vector2 HitPosition; + public readonly float Radius; + + public HitOffset(Vector2 position1, Vector2 position2, Vector2 hitPosition, float radius) + { + Position1 = position1; + Position2 = position2; + HitPosition = hitPosition; + Radius = radius; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index a9d48df52d..97be372e37 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -2,11 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Diagnostics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu.Scoring { @@ -28,6 +32,7 @@ namespace osu.Game.Rulesets.Osu.Scoring private const int timing_distribution_centre_bin_index = timing_distribution_bins; private TimingDistribution timingDistribution; + private readonly List hitOffsets = new List(); public override void ApplyBeatmap(IBeatmap beatmap) { @@ -39,6 +44,8 @@ namespace osu.Game.Rulesets.Osu.Scoring base.ApplyBeatmap(beatmap); } + private OsuHitCircleJudgementResult lastCircleResult; + protected override void OnResultApplied(JudgementResult result) { base.OnResultApplied(result); @@ -47,6 +54,8 @@ namespace osu.Game.Rulesets.Osu.Scoring { int binOffset = (int)(result.TimeOffset / timingDistribution.BinSize); timingDistribution.Bins[timing_distribution_centre_bin_index + binOffset]++; + + addHitOffset(result); } } @@ -58,17 +67,67 @@ namespace osu.Game.Rulesets.Osu.Scoring { int binOffset = (int)(result.TimeOffset / timingDistribution.BinSize); timingDistribution.Bins[timing_distribution_centre_bin_index + binOffset]--; + + removeHitOffset(result); } } + private void addHitOffset(JudgementResult result) + { + if (!(result is OsuHitCircleJudgementResult circleResult)) + return; + + if (lastCircleResult == null) + { + lastCircleResult = circleResult; + return; + } + + if (circleResult.HitPosition != null) + { + Debug.Assert(circleResult.Radius != null); + hitOffsets.Add(new HitOffset(lastCircleResult.HitCircle.StackedEndPosition, circleResult.HitCircle.StackedEndPosition, circleResult.HitPosition.Value, circleResult.Radius.Value)); + } + + lastCircleResult = circleResult; + } + + private void removeHitOffset(JudgementResult result) + { + if (!(result is OsuHitCircleJudgementResult circleResult)) + return; + + if (hitOffsets.Count > 0 && circleResult.HitPosition != null) + hitOffsets.RemoveAt(hitOffsets.Count - 1); + } + protected override void Reset(bool storeResults) { base.Reset(storeResults); timingDistribution.Bins.AsSpan().Clear(); + hitOffsets.Clear(); } - protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) => new OsuJudgementResult(hitObject, judgement); + public override void PopulateScore(ScoreInfo score) + { + base.PopulateScore(score); + + score.ExtraStatistics["timing_distribution"] = timingDistribution; + score.ExtraStatistics["hit_offsets"] = hitOffsets; + } + + protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) + { + switch (hitObject) + { + case HitCircle _: + return new OsuHitCircleJudgementResult(hitObject, judgement); + + default: + return new OsuJudgementResult(hitObject, judgement); + } + } public override HitWindows CreateHitWindows() => new OsuHitWindows(); } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 7b37c267bc..38b37afc55 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -166,6 +166,8 @@ namespace osu.Game.Scoring } } + public Dictionary ExtraStatistics = new Dictionary(); + [JsonIgnore] public List Files { get; set; } From 89b54be67395cfdaa6ef6b37862e899545e54c72 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 15 Jun 2020 22:45:18 +0900 Subject: [PATCH 473/871] Add initial implementation of the statistics panel --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 14 ++++ osu.Game.Rulesets.Osu/Statistics/Heatmap.cs | 10 ++- .../Ranking/TestSceneAccuracyHeatmap.cs | 4 +- .../Ranking/TestScreenStatisticsPanel.cs | 24 +++++++ osu.Game/Rulesets/Ruleset.cs | 3 + .../Ranking/Statistics/StatisticContainer.cs | 70 +++++++++++++++++++ .../Ranking/Statistics/StatisticsPanel.cs | 56 +++++++++++++++ 7 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Visual/Ranking/TestScreenStatisticsPanel.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs create mode 100644 osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 689a7b35ea..a76413480d 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -29,6 +29,8 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; using System; +using osu.Game.Rulesets.Osu.Statistics; +using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Rulesets.Osu { @@ -186,5 +188,17 @@ namespace osu.Game.Rulesets.Osu public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new OsuReplayFrame(); public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); + + public override IEnumerable CreateStatistics(ScoreInfo score) => new[] + { + new StatisticContainer("Timing Distribution") + { + Child = new TimingDistributionGraph((TimingDistribution)score.ExtraStatistics.GetValueOrDefault("timing_distribution")) + }, + new StatisticContainer("Accuracy Heatmap") + { + Child = new Heatmap((List)score.ExtraStatistics.GetValueOrDefault("hit_offsets")) + }, + }; } } diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs index 8105d12991..89d861a6d1 100644 --- a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs @@ -2,12 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Osu.Scoring; using osuTK; using osuTK.Graphics; @@ -29,10 +31,13 @@ namespace osu.Game.Rulesets.Osu.Statistics private const float rotation = 45; private const float point_size = 4; + private readonly IReadOnlyList offsets; private Container allPoints; - public Heatmap() + public Heatmap(IReadOnlyList offsets) { + this.offsets = offsets; + Size = new Vector2(size); } @@ -122,6 +127,9 @@ namespace osu.Game.Rulesets.Osu.Statistics }); } } + + foreach (var o in offsets) + AddPoint(o.Position1, o.Position2, o.HitPosition, o.Radius); } public void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs index 9e5fda0ae6..a1b2dccea3 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs @@ -1,12 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Statistics; using osuTK; using osuTK.Graphics; @@ -38,7 +40,7 @@ namespace osu.Game.Tests.Visual.Ranking { Position = new Vector2(500, 300), }, - heatmap = new Heatmap + heatmap = new Heatmap(new List()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Ranking/TestScreenStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestScreenStatisticsPanel.cs new file mode 100644 index 0000000000..e61cf9568d --- /dev/null +++ b/osu.Game.Tests/Visual/Ranking/TestScreenStatisticsPanel.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Rulesets.Osu; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Statistics; + +namespace osu.Game.Tests.Visual.Ranking +{ + public class TestScreenStatisticsPanel : OsuTestScene + { + [Test] + public void TestScore() + { + loadPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + } + + private void loadPanel(ScoreInfo score) => AddStep("load panel", () => + { + Child = new StatisticsPanel(score); + }); + } +} diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 4f28607733..5c349ca557 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -23,6 +23,7 @@ using osu.Game.Scoring; using osu.Game.Skinning; using osu.Game.Users; using JetBrains.Annotations; +using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Rulesets { @@ -208,5 +209,7 @@ namespace osu.Game.Rulesets ///
/// An empty frame for the current ruleset, or null if unsupported. public virtual IConvertibleReplayFrame CreateConvertibleReplayFrame() => null; + + public virtual IEnumerable CreateStatistics(ScoreInfo score) => Enumerable.Empty(); } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs new file mode 100644 index 0000000000..8d10529496 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.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 osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Screens.Ranking.Statistics +{ + public class StatisticContainer : Container + { + protected override Container Content => content; + + private readonly Container content; + + public StatisticContainer(string name) + { + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.X, + Content = new[] + { + new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new Circle + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 9, + Width = 4, + Colour = Color4Extensions.FromHex("#00FFAA") + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = name, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), + } + } + } + }, + new Drawable[] + { + content = new Container + { + RelativeSizeAxes = Axes.Both + } + }, + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + } + }; + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs new file mode 100644 index 0000000000..8ab85527db --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -0,0 +1,56 @@ +// 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.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Scoring; +using osuTK; + +namespace osu.Game.Screens.Ranking.Statistics +{ + public class StatisticsPanel : CompositeDrawable + { + public StatisticsPanel(ScoreInfo score) + { + // Todo: Not correct. + RelativeSizeAxes = Axes.Both; + + Container statistics; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#333") + }, + new ScorePanel(score) // Todo: Temporary + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + State = PanelState.Expanded, + X = 30 + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Left = ScorePanel.EXPANDED_WIDTH + 30 + 50, + Right = 50 + }, + Child = statistics = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Spacing = new Vector2(30, 15), + } + } + }; + + foreach (var s in score.Ruleset.CreateInstance().CreateStatistics(score)) + statistics.Add(s); + } + } +} From c79d8a425178de9326d3bdb19d96e93bf002ca44 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jun 2020 00:12:32 +0900 Subject: [PATCH 474/871] Update ChannelTabControl in line with TabControl changes --- osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index cb6abb7cc6..19c6f437b6 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -78,19 +78,10 @@ namespace osu.Game.Overlays.Chat.Tabs /// The channel that is going to be removed. public void RemoveChannel(Channel channel) { - if (Current.Value == channel) - { - var allChannels = TabContainer.AllTabItems.Select(tab => tab.Value).ToList(); - var isNextTabSelector = allChannels[allChannels.IndexOf(channel) + 1] == selectorTab.Value; - - // selectorTab is not switchable, so we have to explicitly select it if it's the only tab left - if (isNextTabSelector && allChannels.Count == 2) - SelectTab(selectorTab); - else - SwitchTab(isNextTabSelector ? -1 : 1); - } - RemoveItem(channel); + + if (SelectedTab == null) + SelectTab(selectorTab); } protected override void SelectTab(TabItem tab) From a65c1a9abdb358f69065e72b1ebe00ab2e1ee95a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jun 2020 16:08:41 +0900 Subject: [PATCH 475/871] Fix test name --- ...TestScreenStatisticsPanel.cs => TestSceneStatisticsPanel.cs} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename osu.Game.Tests/Visual/Ranking/{TestScreenStatisticsPanel.cs => TestSceneStatisticsPanel.cs} (91%) diff --git a/osu.Game.Tests/Visual/Ranking/TestScreenStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs similarity index 91% rename from osu.Game.Tests/Visual/Ranking/TestScreenStatisticsPanel.cs rename to osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index e61cf9568d..22ee6077cb 100644 --- a/osu.Game.Tests/Visual/Ranking/TestScreenStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -8,7 +8,7 @@ using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Tests.Visual.Ranking { - public class TestScreenStatisticsPanel : OsuTestScene + public class TestSceneStatisticsPanel : OsuTestScene { [Test] public void TestScore() From 9ea7c3dc90474e56bdf07c30988f1cd5007ef919 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jun 2020 16:31:02 +0900 Subject: [PATCH 476/871] Make heatmap support dynamic sizing --- osu.Game.Rulesets.Osu/Statistics/Heatmap.cs | 155 +++++++++++------- .../Ranking/TestSceneAccuracyHeatmap.cs | 16 +- 2 files changed, 108 insertions(+), 63 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs index 89d861a6d1..7e140e6fd2 100644 --- a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Layout; using osu.Game.Rulesets.Osu.Scoring; using osuTK; using osuTK.Graphics; @@ -18,12 +19,7 @@ namespace osu.Game.Rulesets.Osu.Statistics public class Heatmap : CompositeDrawable { /// - /// Full size of the heatmap. - /// - private const float size = 130; - - /// - /// Size of the inner circle containing the "hit" points, relative to . + /// Size of the inner circle containing the "hit" points, relative to the size of this . /// All other points outside of the inner circle are "miss" points. /// private const float inner_portion = 0.8f; @@ -34,77 +30,106 @@ namespace osu.Game.Rulesets.Osu.Statistics private readonly IReadOnlyList offsets; private Container allPoints; + private readonly LayoutValue sizeLayout = new LayoutValue(Invalidation.DrawSize); + public Heatmap(IReadOnlyList offsets) { this.offsets = offsets; - Size = new Vector2(size); + AddLayout(sizeLayout); } [BackgroundDependencyLoader] private void load() { - InternalChildren = new Drawable[] + InternalChild = new Container { - new CircularContainer + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Size = new Vector2(inner_portion), - Masking = true, - BorderThickness = 2f, - BorderColour = Color4.White, - Child = new Box + new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Size = new Vector2(inner_portion), + Masking = true, + BorderThickness = 2f, + BorderColour = Color4.White, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex("#202624") + } + }, + new Container { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#202624") - } - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Children = new Drawable[] - { - new Box + Masking = true, + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = 1f, - Rotation = -rotation, - Alpha = 0.3f, - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = 1f, - Rotation = rotation - }, - new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Width = 10, - Height = 2f, - }, - new Box - { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Y = -1, - Width = 2f, - Height = 10, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 2, // We're rotating along a diagonal - we don't really care how big this is. + Width = 1f, + Rotation = -rotation, + Alpha = 0.3f, + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 2, // We're rotating along a diagonal - we don't really care how big this is. + Width = 1f, + Rotation = rotation + }, + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Width = 10, + Height = 2f, + }, + new Box + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Y = -1, + Width = 2f, + Height = 10, + } } + }, + allPoints = new Container + { + RelativeSizeAxes = Axes.Both } - }, - allPoints = new Container { RelativeSizeAxes = Axes.Both } + } }; + } + + protected override void Update() + { + base.Update(); + validateHitPoints(); + } + + private void validateHitPoints() + { + if (sizeLayout.IsValid) + return; + + allPoints.Clear(); + + // Since the content is fit, both dimensions should have the same size. + float size = allPoints.DrawSize.X; Vector2 centre = new Vector2(size / 2); int rows = (int)Math.Ceiling(size / point_size); @@ -130,16 +155,24 @@ namespace osu.Game.Rulesets.Osu.Statistics foreach (var o in offsets) AddPoint(o.Position1, o.Position2, o.HitPosition, o.Radius); + + sizeLayout.Validate(); } - public void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) + protected void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) { + if (allPoints.Count == 0) + return; + double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point. double angle2 = Math.Atan2(end.Y - start.Y, start.X - end.X); // Angle between the end point and the start point. double finalAngle = angle2 - angle1; // Angle between start, end, and hit points. float normalisedDistance = Vector2.Distance(hitPoint, end) / radius; + // Since the content is fit, both dimensions should have the same size. + float size = allPoints.DrawSize.X; + // Find the most relevant hit point. double minDist = double.PositiveInfinity; HitPoint point = null; diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs index a1b2dccea3..53c8e56f53 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs @@ -20,7 +20,7 @@ namespace osu.Game.Tests.Visual.Ranking private readonly Box background; private readonly Drawable object1; private readonly Drawable object2; - private readonly Heatmap heatmap; + private readonly TestHeatmap heatmap; public TestSceneAccuracyHeatmap() { @@ -40,10 +40,11 @@ namespace osu.Game.Tests.Visual.Ranking { Position = new Vector2(500, 300), }, - heatmap = new Heatmap(new List()) + heatmap = new TestHeatmap(new List()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, + Size = new Vector2(130) } }; } @@ -70,6 +71,17 @@ namespace osu.Game.Tests.Visual.Ranking return true; } + private class TestHeatmap : Heatmap + { + public TestHeatmap(IReadOnlyList offsets) + : base(offsets) + { + } + + public new void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) + => base.AddPoint(start, end, hitPoint, radius); + } + private class BorderCircle : CircularContainer { public BorderCircle() From c3d4ffed00655912d207ba0606bbeec4f7106cb1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jun 2020 16:46:33 +0900 Subject: [PATCH 477/871] 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 6387356686..b95b794004 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 8c098b79c6..6ec57e5100 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 373ad09597..0bfff24805 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 3dbe164b2c4da72b25c26d25156bc5a8de3ce28b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jun 2020 17:20:38 +0900 Subject: [PATCH 478/871] Add some very basic safety checks around non-existent data --- osu.Game.Rulesets.Osu/Statistics/Heatmap.cs | 7 +++++-- .../Statistics/TimingDistributionGraph.cs | 3 +++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs index 7e140e6fd2..51508a5e8b 100644 --- a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs @@ -153,8 +153,11 @@ namespace osu.Game.Rulesets.Osu.Statistics } } - foreach (var o in offsets) - AddPoint(o.Position1, o.Position2, o.HitPosition, o.Radius); + if (offsets?.Count > 0) + { + foreach (var o in offsets) + AddPoint(o.Position1, o.Position2, o.HitPosition, o.Radius); + } sizeLayout.Validate(); } diff --git a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs index a47d726988..0ba94b7101 100644 --- a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs +++ b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs @@ -36,6 +36,9 @@ namespace osu.Game.Rulesets.Osu.Statistics [BackgroundDependencyLoader] private void load() { + if (distribution?.Bins == null || distribution.Bins.Length == 0) + return; + int maxCount = distribution.Bins.Max(); var bars = new Drawable[distribution.Bins.Length]; From 076eac2362480a80749a0b42d1caaf7295f25ae3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jun 2020 17:46:34 +0900 Subject: [PATCH 479/871] Inset entire graph rather than just the axis --- .../Statistics/TimingDistributionGraph.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs index 0ba94b7101..1f9f38bf3b 100644 --- a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs +++ b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs @@ -20,12 +20,6 @@ namespace osu.Game.Rulesets.Osu.Statistics ///
private const float axis_points = 5; - /// - /// An amount to adjust the value of the axis points by, effectively insetting the axis in the graph. - /// Without an inset, the final data point will be placed halfway outside the graph. - /// - private const float axis_value_inset = 0.2f; - private readonly TimingDistribution distribution; public TimingDistributionGraph(TimingDistribution distribution) @@ -50,6 +44,7 @@ namespace osu.Game.Rulesets.Osu.Statistics InternalChild = new GridContainer { RelativeSizeAxes = Axes.Both, + Width = 0.8f, Content = new[] { new Drawable[] @@ -80,7 +75,7 @@ namespace osu.Game.Rulesets.Osu.Statistics // So our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. int sideBins = (distribution.Bins.Length - 1) / 2; double maxValue = sideBins * distribution.BinSize; - double axisValueStep = maxValue / axis_points * (1 - axis_value_inset); + double axisValueStep = maxValue / axis_points; axisFlow.Add(new OsuSpriteText { From 9442fc00ac408c314ca078bbfa5d3c426adf6aa2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jun 2020 17:48:59 +0900 Subject: [PATCH 480/871] Temporary hack to make replay player populate scores --- osu.Game/Screens/Play/ReplayPlayer.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index b443603128..d7580ea271 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -27,11 +27,14 @@ namespace osu.Game.Screens.Play protected override void GotoRanking() { - this.Push(CreateResults(DrawableRuleset.ReplayScore.ScoreInfo)); + this.Push(CreateResults(CreateScore())); } protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, false); - protected override ScoreInfo CreateScore() => score.ScoreInfo; + // protected override ScoreInfo CreateScore() + // { + // return score.ScoreInfo; + // } } } From a2ddb4edb456ed7b0c78ff4af1f67e4e3a312289 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jun 2020 17:49:28 +0900 Subject: [PATCH 481/871] Change interface for creating statistic rows --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 37 +++++++++++++++---- osu.Game/Rulesets/Ruleset.cs | 2 +- .../Ranking/Statistics/StatisticContainer.cs | 2 +- .../Ranking/Statistics/StatisticRow.cs | 15 ++++++++ .../Ranking/Statistics/StatisticsPanel.cs | 16 ++++++-- 5 files changed, 59 insertions(+), 13 deletions(-) create mode 100644 osu.Game/Screens/Ranking/Statistics/StatisticRow.cs diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index a76413480d..67a9bda1a9 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -29,6 +29,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; using System; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Screens.Ranking.Statistics; @@ -189,16 +190,36 @@ namespace osu.Game.Rulesets.Osu public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); - public override IEnumerable CreateStatistics(ScoreInfo score) => new[] + public override StatisticRow[] CreateStatistics(ScoreInfo score) => new[] { - new StatisticContainer("Timing Distribution") + new StatisticRow { - Child = new TimingDistributionGraph((TimingDistribution)score.ExtraStatistics.GetValueOrDefault("timing_distribution")) - }, - new StatisticContainer("Accuracy Heatmap") - { - Child = new Heatmap((List)score.ExtraStatistics.GetValueOrDefault("hit_offsets")) - }, + Content = new Drawable[] + { + new StatisticContainer("Timing Distribution") + { + RelativeSizeAxes = Axes.X, + Height = 130, + Child = new TimingDistributionGraph((TimingDistribution)score.ExtraStatistics.GetValueOrDefault("timing_distribution")) + { + RelativeSizeAxes = Axes.Both + } + }, + new StatisticContainer("Accuracy Heatmap") + { + RelativeSizeAxes = Axes.Both, + Child = new Heatmap((List)score.ExtraStatistics.GetValueOrDefault("hit_offsets")) + { + RelativeSizeAxes = Axes.Both + } + }, + }, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 130), + } + } }; } } diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 5c349ca557..f05685b6e9 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -210,6 +210,6 @@ namespace osu.Game.Rulesets /// An empty frame for the current ruleset, or null if unsupported. public virtual IConvertibleReplayFrame CreateConvertibleReplayFrame() => null; - public virtual IEnumerable CreateStatistics(ScoreInfo score) => Enumerable.Empty(); + public virtual StatisticRow[] CreateStatistics(ScoreInfo score) => Array.Empty(); } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs index 8d10529496..d7b42c1c2f 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Ranking.Statistics { InternalChild = new GridContainer { - RelativeSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Both, Content = new[] { new Drawable[] diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs new file mode 100644 index 0000000000..5d39ef57b2 --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/StatisticRow.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. + +using System; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Screens.Ranking.Statistics +{ + public class StatisticRow + { + public Drawable[] Content = Array.Empty(); + public Dimension[] ColumnDimensions = Array.Empty(); + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 8ab85527db..6c5fa1837a 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -17,7 +17,7 @@ namespace osu.Game.Screens.Ranking.Statistics // Todo: Not correct. RelativeSizeAxes = Axes.Both; - Container statistics; + FillFlowContainer statisticRows; InternalChildren = new Drawable[] { @@ -41,16 +41,26 @@ namespace osu.Game.Screens.Ranking.Statistics Left = ScorePanel.EXPANDED_WIDTH + 30 + 50, Right = 50 }, - Child = statistics = new FillFlowContainer + Child = statisticRows = new FillFlowContainer { RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, Spacing = new Vector2(30, 15), } } }; foreach (var s in score.Ruleset.CreateInstance().CreateStatistics(score)) - statistics.Add(s); + { + statisticRows.Add(new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] { s.Content }, + ColumnDimensions = s.ColumnDimensions, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }); + } } } } From 808e216059e00b52cf7c02a62ea1ca94bd5aaccd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jun 2020 17:49:37 +0900 Subject: [PATCH 482/871] Improve test scene --- .../Visual/Ranking/TestSceneStatisticsPanel.cs | 13 ++++++++++++- .../Ranking/TestSceneTimingDistributionGraph.cs | 4 ++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index 22ee6077cb..c02be9ab5d 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.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.Collections.Generic; using NUnit.Framework; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; @@ -13,7 +15,16 @@ namespace osu.Game.Tests.Visual.Ranking [Test] public void TestScore() { - loadPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) + { + ExtraStatistics = + { + ["timing_distribution"] = TestSceneTimingDistributionGraph.CreateNormalDistribution(), + ["hit_offsets"] = new List() + } + }; + + loadPanel(score); } private void loadPanel(ScoreInfo score) => AddStep("load panel", () => diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs index 7530fc42b8..2249655093 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333") }, - new TimingDistributionGraph(createNormalDistribution()) + new TimingDistributionGraph(CreateNormalDistribution()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -31,7 +31,7 @@ namespace osu.Game.Tests.Visual.Ranking }; } - private TimingDistribution createNormalDistribution() + public static TimingDistribution CreateNormalDistribution() { var distribution = new TimingDistribution(51, 5); From e7687a09271d12f5cadf93b00b7ff798733d9340 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 16 Jun 2020 17:49:43 +0900 Subject: [PATCH 483/871] Temporary placement inside results screen --- osu.Game/Screens/Ranking/ResultsScreen.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index fbb9b95478..4d589b4527 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -16,6 +16,7 @@ using osu.Game.Online.API; using osu.Game.Scoring; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Play; +using osu.Game.Screens.Ranking.Statistics; using osuTK; namespace osu.Game.Screens.Ranking @@ -134,6 +135,11 @@ namespace osu.Game.Screens.Ranking }, }); } + + AddInternal(new StatisticsPanel(Score) + { + RelativeSizeAxes = Axes.Both + }); } protected override void LoadComplete() From 3f1b9edabe20478578e05f85476d8acaf06eb62d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jun 2020 20:16:04 +0900 Subject: [PATCH 484/871] Fix regression in android build parsing behaviour --- osu.Android/OsuGameAndroid.cs | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 19ed7ffcf5..5f936ffce4 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -3,6 +3,7 @@ using System; using Android.App; +using Android.OS; using osu.Game; using osu.Game.Updater; @@ -18,9 +19,32 @@ namespace osu.Android try { - // todo: needs checking before play store redeploy. - string versionName = packageInfo.VersionName; - // undo play store version garbling + // We store the osu! build number in the "VersionCode" field to better support google play releases. + // If we were to use the main build number, it would require a new submission each time (similar to TestFlight). + // In order to do this, we should split it up and pad the numbers to still ensure sequential increase over time. + // + // We also need to be aware that older SDK versions store this as a 32bit int. + // + // Basic conversion format (as done in Fastfile): 2020.606.0 -> 202006060 + + // https://stackoverflow.com/questions/52977079/android-sdk-28-versioncode-in-packageinfo-has-been-deprecated + string versionName = string.Empty; + + if (Build.VERSION.SdkInt >= BuildVersionCodes.P) + { + versionName = packageInfo.LongVersionCode.ToString(); + versionName = versionName.Substring(versionName.Length - 9); + } + else + { + +#pragma warning disable CS0618 // Type or member is obsolete + // this is required else older SDKs will report missing method exception. + versionName = packageInfo.VersionCode.ToString(); +#pragma warning restore CS0618 // Type or member is obsolete + } + + // undo play store version garbling (as mentioned above). return new Version(int.Parse(versionName.Substring(0, 4)), int.Parse(versionName.Substring(4, 4)), int.Parse(versionName.Substring(8, 1))); } catch From 115ea244beb9953c4e189e1cfa426c0f705e1c79 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 16 Jun 2020 20:20:46 +0900 Subject: [PATCH 485/871] Add note about substring usage --- osu.Android/OsuGameAndroid.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 5f936ffce4..136b85699a 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -33,6 +33,7 @@ namespace osu.Android if (Build.VERSION.SdkInt >= BuildVersionCodes.P) { versionName = packageInfo.LongVersionCode.ToString(); + // ensure we only read the trailing portion of long (the part we are interested in). versionName = versionName.Substring(versionName.Length - 9); } else From c5358cbb6b22d3167efe2a21868d23fc91b523b0 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 16 Jun 2020 21:01:10 +0900 Subject: [PATCH 486/871] Remove blank line --- osu.Android/OsuGameAndroid.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Android/OsuGameAndroid.cs b/osu.Android/OsuGameAndroid.cs index 136b85699a..7542a2b997 100644 --- a/osu.Android/OsuGameAndroid.cs +++ b/osu.Android/OsuGameAndroid.cs @@ -38,7 +38,6 @@ namespace osu.Android } else { - #pragma warning disable CS0618 // Type or member is obsolete // this is required else older SDKs will report missing method exception. versionName = packageInfo.VersionCode.ToString(); @@ -58,4 +57,4 @@ namespace osu.Android protected override UpdateManager CreateUpdateManager() => new SimpleUpdateManager(); } -} \ No newline at end of file +} From 693a760a193e8521d7db350318bd9a1e93d4f9db Mon Sep 17 00:00:00 2001 From: Shivam Date: Tue, 16 Jun 2020 15:44:59 +0200 Subject: [PATCH 487/871] Use RelativeSizeAxes for width --- osu.Game/Screens/Menu/IntroWelcome.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index a431752369..711c7b64e4 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -120,9 +120,9 @@ namespace osu.Game.Screens.Menu { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Scale = new Vector2(0.3f), - Width = 750, - Height = 78, + RelativeSizeAxes = Axes.X, + Scale = new Vector2(0.1f), + Height = 156, Alpha = 0, Texture = textures.Get(@"Welcome/welcome_text") }, From 1cf16038a708f1c586b6405b58bb3d70082cf2c5 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Tue, 16 Jun 2020 14:54:05 +0100 Subject: [PATCH 488/871] Create IApplicableToSample --- osu.Game/Rulesets/Mods/IApplicableToSample.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 osu.Game/Rulesets/Mods/IApplicableToSample.cs diff --git a/osu.Game/Rulesets/Mods/IApplicableToSample.cs b/osu.Game/Rulesets/Mods/IApplicableToSample.cs new file mode 100644 index 0000000000..559d127cfc --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToSample.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. + +using osu.Framework.Audio.Sample; + +namespace osu.Game.Rulesets.Mods +{ + /// + /// An interface for mods that make adjustments to a sample. + /// + public interface IApplicableToSample : IApplicableMod + { + void ApplyToSample(SampleChannel sample); + } +} From 9f4f3ce2cc055867cbfe58f723c334f7b08b1448 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Tue, 16 Jun 2020 14:54:50 +0100 Subject: [PATCH 489/871] Handle IApplicableToSample mods --- .../Storyboards/Drawables/DrawableStoryboardSample.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 8292b02068..2b9c66d2e6 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -1,11 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Mods; namespace osu.Game.Storyboards.Drawables { @@ -28,12 +31,17 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap) + private void load(IBindable beatmap, IBindable> mods) { channel = beatmap.Value.Skin.GetSample(sampleInfo); if (channel != null) + { channel.Volume.Value = sampleInfo.Volume / 100.0; + + foreach (var mod in mods.Value.OfType()) + mod.ApplyToSample(channel); + } } protected override void Update() From 4138f6119f24ce50991a64c89cd91e4c5fa28a23 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Tue, 16 Jun 2020 14:58:23 +0100 Subject: [PATCH 490/871] Update rate adjust mods to also use IApplicableToSample --- osu.Game/Rulesets/Mods/ModRateAdjust.cs | 8 +++++++- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index cb2ff149f1..ecd625c3b4 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -2,12 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Audio; +using osu.Framework.Audio.Sample; using osu.Framework.Audio.Track; using osu.Framework.Bindables; namespace osu.Game.Rulesets.Mods { - public abstract class ModRateAdjust : Mod, IApplicableToTrack + public abstract class ModRateAdjust : Mod, IApplicableToTrack, IApplicableToSample { public abstract BindableNumber SpeedChange { get; } @@ -16,6 +17,11 @@ namespace osu.Game.Rulesets.Mods track.AddAdjustment(AdjustableProperty.Tempo, SpeedChange); } + public virtual void ApplyToSample(SampleChannel sample) + { + sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); + } + public override string SettingDescription => SpeedChange.IsDefault ? string.Empty : $"{SpeedChange.Value:N2}x"; } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index df059eef7d..352e3ae915 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -10,10 +10,11 @@ using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Objects; +using osu.Framework.Audio.Sample; namespace osu.Game.Rulesets.Mods { - public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToBeatmap, IApplicableToTrack + public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToBeatmap, IApplicableToTrack, IApplicableToSample { /// /// The point in the beatmap at which the final ramping rate should be reached. @@ -58,6 +59,11 @@ namespace osu.Game.Rulesets.Mods AdjustPitch.TriggerChange(); } + public void ApplyToSample(SampleChannel sample) + { + sample.AddAdjustment(AdjustableProperty.Frequency, SpeedChange); + } + public virtual void ApplyToBeatmap(IBeatmap beatmap) { HitObject lastObject = beatmap.HitObjects.LastOrDefault(); From 5e74985edafa034306c4559d32e99aa495697d80 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Jun 2020 19:28:40 +0900 Subject: [PATCH 491/871] Add scrolling capability to results screen --- osu.Game/Screens/Ranking/ResultsScreen.cs | 61 +++++++++++++++-------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 4d589b4527..11ed9fb5b7 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -24,6 +24,7 @@ namespace osu.Game.Screens.Ranking public abstract class ResultsScreen : OsuScreen { protected const float BACKGROUND_BLUR = 20; + private static readonly float screen_height = 768 - TwoLayerButton.SIZE_EXTENDED.Y; public override bool DisallowExternalBeatmapRulesetChanges => true; @@ -68,10 +69,24 @@ namespace osu.Game.Screens.Ranking { new ResultsScrollContainer { - Child = panels = new ScorePanelList + Child = new FillFlowContainer { - RelativeSizeAxes = Axes.Both, - SelectedScore = { BindTarget = SelectedScore } + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + panels = new ScorePanelList + { + RelativeSizeAxes = Axes.X, + Height = screen_height, + SelectedScore = { BindTarget = SelectedScore } + }, + new StatisticsPanel(Score) + { + RelativeSizeAxes = Axes.X, + Height = screen_height, + } + } } } }, @@ -135,11 +150,6 @@ namespace osu.Game.Screens.Ranking }, }); } - - AddInternal(new StatisticsPanel(Score) - { - RelativeSizeAxes = Axes.Both - }); } protected override void LoadComplete() @@ -180,27 +190,38 @@ namespace osu.Game.Screens.Ranking return base.OnExiting(next); } + [Cached] private class ResultsScrollContainer : OsuScrollContainer { - private readonly Container content; - - protected override Container Content => content; - public ResultsScrollContainer() { - base.Content.Add(content = new Container - { - RelativeSizeAxes = Axes.X - }); - RelativeSizeAxes = Axes.Both; ScrollbarVisible = false; } - protected override void Update() + protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default) { - base.Update(); - content.Height = Math.Max(768 - TwoLayerButton.SIZE_EXTENDED.Y, DrawHeight); + if (!animated) + { + // If the user is scrolling via mouse drag, follow the mouse 1:1. + base.OnUserScroll(value, false, distanceDecay); + } + else + { + float direction = Math.Sign(value - Target); + float target = Target + direction * screen_height; + + if (target <= -screen_height / 2 || target >= ScrollableExtent + screen_height / 2) + { + // If the user is already at either extent and scrolling in the clamped direction, we want to follow the default scroll exactly so that the bounces aren't too harsh. + base.OnUserScroll(value, true, distanceDecay); + } + else + { + // Otherwise, scroll one screen in the target direction. + base.OnUserScroll(target, true, distanceDecay); + } + } } } } From c3e268616fdd1b44cc146f0a07b7e05cd788866f Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Wed, 17 Jun 2020 11:43:32 +0100 Subject: [PATCH 492/871] Implement grouping interface IApplicableToAudio --- osu.Game/Rulesets/Mods/IApplicableToAudio.cs | 10 ++++++++++ osu.Game/Rulesets/Mods/ModRateAdjust.cs | 2 +- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Rulesets/Mods/IApplicableToAudio.cs diff --git a/osu.Game/Rulesets/Mods/IApplicableToAudio.cs b/osu.Game/Rulesets/Mods/IApplicableToAudio.cs new file mode 100644 index 0000000000..40e13764c6 --- /dev/null +++ b/osu.Game/Rulesets/Mods/IApplicableToAudio.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace osu.Game.Rulesets.Mods +{ + public interface IApplicableToAudio : IApplicableToTrack, IApplicableToSample + { + } +} diff --git a/osu.Game/Rulesets/Mods/ModRateAdjust.cs b/osu.Game/Rulesets/Mods/ModRateAdjust.cs index ecd625c3b4..874384686f 100644 --- a/osu.Game/Rulesets/Mods/ModRateAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModRateAdjust.cs @@ -8,7 +8,7 @@ using osu.Framework.Bindables; namespace osu.Game.Rulesets.Mods { - public abstract class ModRateAdjust : Mod, IApplicableToTrack, IApplicableToSample + public abstract class ModRateAdjust : Mod, IApplicableToAudio { public abstract BindableNumber SpeedChange { get; } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index 352e3ae915..cbd07efa97 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -14,7 +14,7 @@ using osu.Framework.Audio.Sample; namespace osu.Game.Rulesets.Mods { - public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToBeatmap, IApplicableToTrack, IApplicableToSample + public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToBeatmap, IApplicableToAudio { /// /// The point in the beatmap at which the final ramping rate should be reached. From 725b2e540bdfd86d836c6345f6e6ea1daead0865 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 17 Jun 2020 22:29:00 +0900 Subject: [PATCH 493/871] wip --- .../Visual/Ranking/TestSceneScorePanel.cs | 38 ++++++++ osu.Game/Screens/Ranking/ResultsScreen.cs | 89 +++++++++++++------ osu.Game/Screens/Ranking/ScorePanel.cs | 60 +++++++++++++ osu.Game/Screens/Ranking/ScorePanelList.cs | 59 +++++++++--- .../Ranking/Statistics/StatisticsPanel.cs | 7 -- 5 files changed, 205 insertions(+), 48 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index 250fdc5ebd..1c5087ee94 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Utils; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -101,6 +102,39 @@ namespace osu.Game.Tests.Visual.Ranking AddWaitStep("wait for transition", 10); } + [Test] + public void TestSceneTrackingScorePanel() + { + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; + + addPanelStep(score, PanelState.Contracted); + + AddStep("enable tracking", () => + { + panel.Anchor = Anchor.CentreLeft; + panel.Origin = Anchor.CentreLeft; + panel.Tracking = true; + + Add(panel.CreateTrackingComponent().With(d => + { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; + })); + }); + + assertTracking(true); + + AddStep("expand panel", () => panel.State = PanelState.Expanded); + AddWaitStep("wait for transition", 2); + assertTracking(true); + + AddStep("stop tracking", () => panel.Tracking = false); + assertTracking(false); + + AddStep("start tracking", () => panel.Tracking = true); + assertTracking(true); + } + private void addPanelStep(ScoreInfo score, PanelState state = PanelState.Expanded) => AddStep("add panel", () => { Child = panel = new ScorePanel(score) @@ -110,5 +144,9 @@ namespace osu.Game.Tests.Visual.Ranking State = state }; }); + + private void assertTracking(bool tracking) => AddAssert($"{(tracking ? "is" : "is not")} tracking", () => + Precision.AlmostEquals(panel.ScreenSpaceDrawQuad.TopLeft, panel.CreateTrackingComponent().ScreenSpaceDrawQuad.TopLeft) == tracking + && Precision.AlmostEquals(panel.ScreenSpaceDrawQuad.BottomRight, panel.CreateTrackingComponent().ScreenSpaceDrawQuad.BottomRight) == tracking); } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 11ed9fb5b7..4ef012f6f2 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -10,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; +using osu.Framework.Utils; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -44,6 +46,9 @@ namespace osu.Game.Screens.Ranking [Resolved] private IAPIProvider api { get; set; } + private Container scorePanelContainer; + private ResultsScrollContainer scrollContainer; + private Container expandedPanelProxyContainer; private Drawable bottomPanel; private ScorePanelList panels; @@ -58,6 +63,13 @@ namespace osu.Game.Screens.Ranking [BackgroundDependencyLoader] private void load() { + scorePanelContainer = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + }; + FillFlowContainer buttons; InternalChild = new GridContainer @@ -67,26 +79,35 @@ namespace osu.Game.Screens.Ranking { new Drawable[] { - new ResultsScrollContainer + new Container { - Child = new FillFlowContainer + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + scorePanelContainer, + scrollContainer = new ResultsScrollContainer { - panels = new ScorePanelList + Child = new FillFlowContainer { RelativeSizeAxes = Axes.X, - Height = screen_height, - SelectedScore = { BindTarget = SelectedScore } - }, - new StatisticsPanel(Score) - { - RelativeSizeAxes = Axes.X, - Height = screen_height, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + panels = new ScorePanelList(scorePanelContainer) + { + RelativeSizeAxes = Axes.X, + Height = screen_height, + SelectedScore = { BindTarget = SelectedScore } + }, + new StatisticsPanel(Score) + { + RelativeSizeAxes = Axes.X, + Height = screen_height, + } + } } - } + }, + expandedPanelProxyContainer = new Container { RelativeSizeAxes = Axes.Both } } } }, @@ -173,6 +194,21 @@ namespace osu.Game.Screens.Ranking /// An responsible for the fetch operation. This will be queued and performed automatically. protected virtual APIRequest FetchScores(Action> scoresCallback) => null; + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + ScorePanel expandedPanel = scorePanelContainer.Single(p => p.State == PanelState.Expanded); + expandedPanel.Tracking = false; + expandedPanel.Anchor = Anchor.Centre; + expandedPanel.Origin = Anchor.Centre; + + scorePanelContainer.X = (float)Interpolation.Lerp(0, -DrawWidth / 2 + ScorePanel.EXPANDED_WIDTH / 2f, Math.Clamp(scrollContainer.Current / (screen_height * 0.8f), 0, 1)); + + if (expandedPanelProxyContainer.Count == 0) + expandedPanelProxyContainer.Add(expandedPanel.CreateProxy()); + } + public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -205,22 +241,21 @@ namespace osu.Game.Screens.Ranking { // If the user is scrolling via mouse drag, follow the mouse 1:1. base.OnUserScroll(value, false, distanceDecay); + return; + } + + float direction = Math.Sign(value - Target); + float target = Target + direction * screen_height; + + if (target <= -screen_height / 2 || target >= ScrollableExtent + screen_height / 2) + { + // If the user is already at either extent and scrolling in the clamped direction, we want to follow the default scroll exactly so that the bounces aren't too harsh. + base.OnUserScroll(value, true, distanceDecay); } else { - float direction = Math.Sign(value - Target); - float target = Target + direction * screen_height; - - if (target <= -screen_height / 2 || target >= ScrollableExtent + screen_height / 2) - { - // If the user is already at either extent and scrolling in the clamped direction, we want to follow the default scroll exactly so that the bounces aren't too harsh. - base.OnUserScroll(value, true, distanceDecay); - } - else - { - // Otherwise, scroll one screen in the target direction. - base.OnUserScroll(target, true, distanceDecay); - } + // Otherwise, scroll one screen in the target direction. + base.OnUserScroll(target, true, distanceDecay); } } } diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 65fb901c89..7ca96a9a58 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -182,6 +182,40 @@ namespace osu.Game.Screens.Ranking } } + private bool tracking; + private Vector2 lastNonTrackingPosition; + + /// + /// Whether this should track the position of the tracking component created via . + /// + public bool Tracking + { + get => tracking; + set + { + if (tracking == value) + return; + + tracking = value; + + if (tracking) + lastNonTrackingPosition = Position; + else + Position = lastNonTrackingPosition; + } + } + + protected override void Update() + { + base.Update(); + + if (Tracking && trackingComponent != null) + { + Vector2 topLeftPos = Parent.ToLocalSpace(trackingComponent.ScreenSpaceDrawQuad.TopLeft); + Position = topLeftPos - AnchorPosition + OriginPosition; + } + } + private void updateState() { topLayerContent?.FadeOut(content_fade_duration).Expire(); @@ -248,5 +282,31 @@ namespace osu.Game.Screens.Ranking => base.ReceivePositionalInputAt(screenSpacePos) || topLayerContainer.ReceivePositionalInputAt(screenSpacePos) || middleLayerContainer.ReceivePositionalInputAt(screenSpacePos); + + private TrackingComponent trackingComponent; + + public TrackingComponent CreateTrackingComponent() => trackingComponent ??= new TrackingComponent(this); + + public class TrackingComponent : Drawable + { + public readonly ScorePanel Panel; + + public TrackingComponent(ScorePanel panel) + { + Panel = panel; + } + + protected override void Update() + { + base.Update(); + Size = Panel.DrawSize; + } + + // In ScorePanelList, score panels are added _before_ the flow, but this means that input will be blocked by the scroll container. + // So by forwarding input events, we remove the need to consider the order in which input is handled. + protected override bool OnClick(ClickEvent e) => Panel.TriggerEvent(e); + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Panel.ReceivePositionalInputAt(screenSpacePos); + } } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 1142297274..d49085bc96 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -27,11 +28,20 @@ namespace osu.Game.Screens.Ranking public readonly Bindable SelectedScore = new Bindable(); + private readonly Container panels; private readonly Flow flow; private readonly Scroll scroll; private ScorePanel expandedPanel; - public ScorePanelList() + /// + /// Creates a new . + /// + /// The target container in which s should reside. + /// s are set to track by default, but this allows + /// This should be placed _before_ the in the hierarchy. + /// + /// + public ScorePanelList(Container panelTarget = null) { RelativeSizeAxes = Axes.Both; @@ -47,6 +57,18 @@ namespace osu.Game.Screens.Ranking AutoSizeAxes = Axes.Both, } }; + + if (panelTarget == null) + { + // To prevent 1-frame sizing issues, the panel container is added _before_ the scroll + flow containers + AddInternal(panels = new Container + { + RelativeSizeAxes = Axes.Both, + Depth = 1 + }); + } + else + panels = panelTarget; } protected override void LoadComplete() @@ -62,10 +84,9 @@ namespace osu.Game.Screens.Ranking /// The to add. public void AddScore(ScoreInfo score) { - flow.Add(new ScorePanel(score) + var panel = new ScorePanel(score) { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Tracking = true }.With(p => { p.StateChanged += s => @@ -73,6 +94,13 @@ namespace osu.Game.Screens.Ranking if (s == PanelState.Expanded) SelectedScore.Value = p.Score; }; + }); + + panels.Add(panel); + flow.Add(panel.CreateTrackingComponent().With(d => + { + d.Anchor = Anchor.Centre; + d.Origin = Anchor.Centre; })); if (SelectedScore.Value == score) @@ -99,14 +127,15 @@ namespace osu.Game.Screens.Ranking private void selectedScoreChanged(ValueChangedEvent score) { // Contract the old panel. - foreach (var p in flow.Where(p => p.Score == score.OldValue)) + foreach (var t in flow.Where(t => t.Panel.Score == score.OldValue)) { - p.State = PanelState.Contracted; - p.Margin = new MarginPadding(); + t.Panel.State = PanelState.Contracted; + t.Margin = new MarginPadding(); } // Find the panel corresponding to the new score. - expandedPanel = flow.SingleOrDefault(p => p.Score == score.NewValue); + var expandedTrackingComponent = flow.SingleOrDefault(t => t.Panel.Score == score.NewValue); + expandedPanel = expandedTrackingComponent?.Panel; // handle horizontal scroll only when not hovering the expanded panel. scroll.HandleScroll = () => expandedPanel?.IsHovered != true; @@ -114,9 +143,11 @@ namespace osu.Game.Screens.Ranking if (expandedPanel == null) return; + Debug.Assert(expandedTrackingComponent != null); + // Expand the new panel. + expandedTrackingComponent.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; expandedPanel.State = PanelState.Expanded; - expandedPanel.Margin = new MarginPadding { Horizontal = expanded_panel_spacing }; // Scroll to the new panel. This is done manually since we need: // 1) To scroll after the scroll container's visible range is updated. @@ -145,15 +176,15 @@ namespace osu.Game.Screens.Ranking flow.Padding = new MarginPadding { Horizontal = offset }; } - private class Flow : FillFlowContainer + private class Flow : FillFlowContainer { public override IEnumerable FlowingChildren => applySorting(AliveInternalChildren); - public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Score != score).Count(); + public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).Count(); - private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() - .OrderByDescending(s => s.Score.TotalScore) - .ThenBy(s => s.Score.OnlineScoreID); + private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() + .OrderByDescending(s => s.Panel.Score.TotalScore) + .ThenBy(s => s.Panel.Score.OnlineScoreID); } private class Scroll : OsuScrollContainer diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 6c5fa1837a..bae6d0ffbb 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -26,13 +26,6 @@ namespace osu.Game.Screens.Ranking.Statistics RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333") }, - new ScorePanel(score) // Todo: Temporary - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - State = PanelState.Expanded, - X = 30 - }, new Container { RelativeSizeAxes = Axes.Both, From bed5e857df7d2af44ae5f4bbfa304f04be741da1 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Wed, 17 Jun 2020 14:49:55 +0100 Subject: [PATCH 494/871] Add missing license header and remove unused usings --- osu.Game/Rulesets/Mods/IApplicableToAudio.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/Mods/IApplicableToAudio.cs b/osu.Game/Rulesets/Mods/IApplicableToAudio.cs index 40e13764c6..901da7af55 100644 --- a/osu.Game/Rulesets/Mods/IApplicableToAudio.cs +++ b/osu.Game/Rulesets/Mods/IApplicableToAudio.cs @@ -1,6 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Text; +// 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.Rulesets.Mods { From 69d85ca3aeab18758ad644e1592d99a83fe35506 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Jun 2020 13:20:16 +0900 Subject: [PATCH 495/871] Add more cards to results screen test --- .../Visual/Ranking/TestSceneResultsScreen.cs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 125aa0a1e7..ea33aa62e3 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.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 System.Collections.Generic; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; @@ -8,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Online.API; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens; @@ -113,6 +116,22 @@ namespace osu.Game.Tests.Visual.Ranking RetryOverlay = InternalChildren.OfType().SingleOrDefault(); } + + protected override APIRequest FetchScores(Action> scoresCallback) + { + var scores = new List(); + + for (int i = 0; i < 20; i++) + { + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + score.TotalScore += 10 - i; + scores.Add(score); + } + + scoresCallback?.Invoke(scores); + + return null; + } } private class UnrankedSoloResultsScreen : SoloResultsScreen From c31a05977d7b62a81e146de50ab374c8e2cca0d1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Jun 2020 16:50:45 +0900 Subject: [PATCH 496/871] Re-implement statistics as a click-in panel --- osu.Game/Screens/Ranking/ResultsScreen.cs | 109 +++++++----------- osu.Game/Screens/Ranking/ScorePanel.cs | 74 ++++++------ osu.Game/Screens/Ranking/ScorePanelList.cs | 67 ++++++----- .../Ranking/Statistics/StatisticsPanel.cs | 34 +++--- 4 files changed, 135 insertions(+), 149 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 4ef012f6f2..927628a811 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -11,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Screens; -using osu.Framework.Utils; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -46,11 +44,9 @@ namespace osu.Game.Screens.Ranking [Resolved] private IAPIProvider api { get; set; } - private Container scorePanelContainer; - private ResultsScrollContainer scrollContainer; - private Container expandedPanelProxyContainer; + private StatisticsPanel statisticsPanel; private Drawable bottomPanel; - private ScorePanelList panels; + private ScorePanelList scorePanelList; protected ResultsScreen(ScoreInfo score, bool allowRetry = true) { @@ -63,13 +59,6 @@ namespace osu.Game.Screens.Ranking [BackgroundDependencyLoader] private void load() { - scorePanelContainer = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - }; - FillFlowContainer buttons; InternalChild = new GridContainer @@ -84,30 +73,26 @@ namespace osu.Game.Screens.Ranking RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - scorePanelContainer, - scrollContainer = new ResultsScrollContainer + new OsuScrollContainer { - Child = new FillFlowContainer + RelativeSizeAxes = Axes.Both, + ScrollbarVisible = false, + Child = new Container { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + Height = screen_height, Children = new Drawable[] { - panels = new ScorePanelList(scorePanelContainer) + scorePanelList = new ScorePanelList { - RelativeSizeAxes = Axes.X, - Height = screen_height, - SelectedScore = { BindTarget = SelectedScore } + RelativeSizeAxes = Axes.Both, + SelectedScore = { BindTarget = SelectedScore }, + PostExpandAction = onExpandedPanelClicked }, - new StatisticsPanel(Score) - { - RelativeSizeAxes = Axes.X, - Height = screen_height, - } + statisticsPanel = new StatisticsPanel(Score) { RelativeSizeAxes = Axes.Both } } } }, - expandedPanelProxyContainer = new Container { RelativeSizeAxes = Axes.Both } } } }, @@ -155,7 +140,7 @@ namespace osu.Game.Screens.Ranking }; if (Score != null) - panels.AddScore(Score); + scorePanelList.AddScore(Score); if (player != null && allowRetry) { @@ -180,7 +165,7 @@ namespace osu.Game.Screens.Ranking var req = FetchScores(scores => Schedule(() => { foreach (var s in scores) - panels.AddScore(s); + scorePanelList.AddScore(s); })); if (req != null) @@ -194,21 +179,6 @@ namespace osu.Game.Screens.Ranking /// An responsible for the fetch operation. This will be queued and performed automatically. protected virtual APIRequest FetchScores(Action> scoresCallback) => null; - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - ScorePanel expandedPanel = scorePanelContainer.Single(p => p.State == PanelState.Expanded); - expandedPanel.Tracking = false; - expandedPanel.Anchor = Anchor.Centre; - expandedPanel.Origin = Anchor.Centre; - - scorePanelContainer.X = (float)Interpolation.Lerp(0, -DrawWidth / 2 + ScorePanel.EXPANDED_WIDTH / 2f, Math.Clamp(scrollContainer.Current / (screen_height * 0.8f), 0, 1)); - - if (expandedPanelProxyContainer.Count == 0) - expandedPanelProxyContainer.Add(expandedPanel.CreateProxy()); - } - public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -226,36 +196,39 @@ namespace osu.Game.Screens.Ranking return base.OnExiting(next); } - [Cached] - private class ResultsScrollContainer : OsuScrollContainer + private void onExpandedPanelClicked() { - public ResultsScrollContainer() + statisticsPanel.ToggleVisibility(); + + if (statisticsPanel.State.Value == Visibility.Hidden) { - RelativeSizeAxes = Axes.Both; - ScrollbarVisible = false; + foreach (var panel in scorePanelList.Panels) + { + if (panel.State == PanelState.Contracted) + panel.FadeIn(150); + else + { + panel.MoveTo(panel.GetTrackingPosition(), 150, Easing.OutQuint).OnComplete(p => + { + scorePanelList.HandleScroll = true; + p.Tracking = true; + }); + } + } } - - protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default) + else { - if (!animated) + foreach (var panel in scorePanelList.Panels) { - // If the user is scrolling via mouse drag, follow the mouse 1:1. - base.OnUserScroll(value, false, distanceDecay); - return; - } + if (panel.State == PanelState.Contracted) + panel.FadeOut(150, Easing.OutQuint); + else + { + scorePanelList.HandleScroll = false; - float direction = Math.Sign(value - Target); - float target = Target + direction * screen_height; - - if (target <= -screen_height / 2 || target >= ScrollableExtent + screen_height / 2) - { - // If the user is already at either extent and scrolling in the clamped direction, we want to follow the default scroll exactly so that the bounces aren't too harsh. - base.OnUserScroll(value, true, distanceDecay); - } - else - { - // Otherwise, scroll one screen in the target direction. - base.OnUserScroll(target, true, distanceDecay); + panel.Tracking = false; + panel.MoveTo(new Vector2(scorePanelList.CurrentScrollPosition, panel.GetTrackingPosition().Y), 150, Easing.OutQuint); + } } } } diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 7ca96a9a58..31b2796c13 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -76,6 +76,18 @@ namespace osu.Game.Screens.Ranking private static readonly Color4 contracted_middle_layer_colour = Color4Extensions.FromHex("#353535"); public event Action StateChanged; + public Action PostExpandAction; + + /// + /// Whether this should track the position of the tracking component created via . + /// + public bool Tracking; + + /// + /// Whether this can enter into an state. + /// + public bool CanExpand = true; + public readonly ScoreInfo Score; private Container content; @@ -182,38 +194,18 @@ namespace osu.Game.Screens.Ranking } } - private bool tracking; - private Vector2 lastNonTrackingPosition; - - /// - /// Whether this should track the position of the tracking component created via . - /// - public bool Tracking - { - get => tracking; - set - { - if (tracking == value) - return; - - tracking = value; - - if (tracking) - lastNonTrackingPosition = Position; - else - Position = lastNonTrackingPosition; - } - } - protected override void Update() { base.Update(); if (Tracking && trackingComponent != null) - { - Vector2 topLeftPos = Parent.ToLocalSpace(trackingComponent.ScreenSpaceDrawQuad.TopLeft); - Position = topLeftPos - AnchorPosition + OriginPosition; - } + Position = GetTrackingPosition(); + } + + public Vector2 GetTrackingPosition() + { + Vector2 topLeftPos = Parent.ToLocalSpace(trackingComponent.ScreenSpaceDrawQuad.TopLeft); + return topLeftPos - AnchorPosition + OriginPosition; } private void updateState() @@ -270,10 +262,28 @@ namespace osu.Game.Screens.Ranking } } + public override Vector2 Size + { + get => base.Size; + set + { + base.Size = value; + + if (trackingComponent != null) + trackingComponent.Size = value; + } + } + protected override bool OnClick(ClickEvent e) { if (State == PanelState.Contracted) - State = PanelState.Expanded; + { + if (CanExpand) + State = PanelState.Expanded; + return true; + } + + PostExpandAction?.Invoke(); return true; } @@ -296,17 +306,13 @@ namespace osu.Game.Screens.Ranking Panel = panel; } - protected override void Update() - { - base.Update(); - Size = Panel.DrawSize; - } - // In ScorePanelList, score panels are added _before_ the flow, but this means that input will be blocked by the scroll container. // So by forwarding input events, we remove the need to consider the order in which input is handled. protected override bool OnClick(ClickEvent e) => Panel.TriggerEvent(e); public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Panel.ReceivePositionalInputAt(screenSpacePos); + + public override bool IsPresent => Panel.IsPresent; } } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index d49085bc96..e332f462bb 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -26,9 +26,15 @@ namespace osu.Game.Screens.Ranking /// private const float expanded_panel_spacing = 15; + public Action PostExpandAction; + public readonly Bindable SelectedScore = new Bindable(); + public float CurrentScrollPosition => scroll.Current; + + public IReadOnlyList Panels => panels; private readonly Container panels; + private readonly Flow flow; private readonly Scroll scroll; private ScorePanel expandedPanel; @@ -36,39 +42,27 @@ namespace osu.Game.Screens.Ranking /// /// Creates a new . /// - /// The target container in which s should reside. - /// s are set to track by default, but this allows - /// This should be placed _before_ the in the hierarchy. - /// - /// - public ScorePanelList(Container panelTarget = null) + public ScorePanelList() { RelativeSizeAxes = Axes.Both; InternalChild = scroll = new Scroll { RelativeSizeAxes = Axes.Both, - Child = flow = new Flow + HandleScroll = () => HandleScroll && expandedPanel?.IsHovered != true, // handle horizontal scroll only when not hovering the expanded panel. + Children = new Drawable[] { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(panel_spacing, 0), - AutoSizeAxes = Axes.Both, + panels = new Container { RelativeSizeAxes = Axes.Both }, + flow = new Flow + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(panel_spacing, 0), + AutoSizeAxes = Axes.Both, + }, } }; - - if (panelTarget == null) - { - // To prevent 1-frame sizing issues, the panel container is added _before_ the scroll + flow containers - AddInternal(panels = new Container - { - RelativeSizeAxes = Axes.Both, - Depth = 1 - }); - } - else - panels = panelTarget; } protected override void LoadComplete() @@ -78,6 +72,25 @@ namespace osu.Game.Screens.Ranking SelectedScore.BindValueChanged(selectedScoreChanged, true); } + private bool handleScroll = true; + + public bool HandleScroll + { + get => handleScroll; + set + { + handleScroll = value; + + foreach (var p in panels) + p.CanExpand = value; + + scroll.ScrollbarVisible = value; + + if (!value) + scroll.ScrollTo(CurrentScrollPosition, false); + } + } + /// /// Adds a to this list. /// @@ -86,7 +99,8 @@ namespace osu.Game.Screens.Ranking { var panel = new ScorePanel(score) { - Tracking = true + Tracking = true, + PostExpandAction = () => PostExpandAction?.Invoke() }.With(p => { p.StateChanged += s => @@ -137,9 +151,6 @@ namespace osu.Game.Screens.Ranking var expandedTrackingComponent = flow.SingleOrDefault(t => t.Panel.Score == score.NewValue); expandedPanel = expandedTrackingComponent?.Panel; - // handle horizontal scroll only when not hovering the expanded panel. - scroll.HandleScroll = () => expandedPanel?.IsHovered != true; - if (expandedPanel == null) return; diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index bae6d0ffbb..cc9007f527 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -1,17 +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 osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Game.Scoring; using osuTK; namespace osu.Game.Screens.Ranking.Statistics { - public class StatisticsPanel : CompositeDrawable + public class StatisticsPanel : VisibilityContainer { + protected override bool StartHidden => true; + public StatisticsPanel(ScoreInfo score) { // Todo: Not correct. @@ -19,27 +19,19 @@ namespace osu.Game.Screens.Ranking.Statistics FillFlowContainer statisticRows; - InternalChildren = new Drawable[] + InternalChild = new Container { - new Box + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { - RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex("#333") + Left = ScorePanel.EXPANDED_WIDTH + 30 + 50, + Right = 50 }, - new Container + Child = statisticRows = new FillFlowContainer { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Left = ScorePanel.EXPANDED_WIDTH + 30 + 50, - Right = 50 - }, - Child = statisticRows = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(30, 15), - } + Direction = FillDirection.Vertical, + Spacing = new Vector2(30, 15), } }; @@ -55,5 +47,9 @@ namespace osu.Game.Screens.Ranking.Statistics }); } } + + protected override void PopIn() => this.FadeIn(); + + protected override void PopOut() => this.FadeOut(); } } From 6c8a24260bd4edd511b9284db59fe70e12f97347 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Jun 2020 17:06:05 +0900 Subject: [PATCH 497/871] Add padding --- osu.Game/Screens/Ranking/ResultsScreen.cs | 2 +- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 927628a811..4a7cb6679a 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -227,7 +227,7 @@ namespace osu.Game.Screens.Ranking scorePanelList.HandleScroll = false; panel.Tracking = false; - panel.MoveTo(new Vector2(scorePanelList.CurrentScrollPosition, panel.GetTrackingPosition().Y), 150, Easing.OutQuint); + panel.MoveTo(new Vector2(scorePanelList.CurrentScrollPosition + StatisticsPanel.SIDE_PADDING, panel.GetTrackingPosition().Y), 150, Easing.OutQuint); } } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index cc9007f527..733c855426 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -10,6 +10,8 @@ namespace osu.Game.Screens.Ranking.Statistics { public class StatisticsPanel : VisibilityContainer { + public const float SIDE_PADDING = 30; + protected override bool StartHidden => true; public StatisticsPanel(ScoreInfo score) @@ -24,8 +26,10 @@ namespace osu.Game.Screens.Ranking.Statistics RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { - Left = ScorePanel.EXPANDED_WIDTH + 30 + 50, - Right = 50 + Left = ScorePanel.EXPANDED_WIDTH + SIDE_PADDING * 3, + Right = SIDE_PADDING, + Top = SIDE_PADDING, + Bottom = 50 // Approximate padding to the bottom of the score panel. }, Child = statisticRows = new FillFlowContainer { From 20db5b33abc952390f58a9110266f55f5377fc51 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Jun 2020 22:11:03 +0900 Subject: [PATCH 498/871] Rework score processor to provide more generic events --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 5 +- .../Scoring/OsuScoreProcessor.cs | 128 +++++++----------- osu.Game.Rulesets.Osu/Statistics/Heatmap.cs | 24 +++- .../Statistics/TimingDistributionGraph.cs | 45 ++++-- .../Ranking/TestSceneAccuracyHeatmap.cs | 9 +- .../Ranking/TestSceneStatisticsPanel.cs | 9 +- .../TestSceneTimingDistributionGraph.cs | 34 ++--- osu.Game/Scoring/ScoreInfo.cs | 4 +- 8 files changed, 126 insertions(+), 132 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 67a9bda1a9..c7003deed2 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -29,6 +29,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; using System; +using System.Linq; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Screens.Ranking.Statistics; @@ -200,7 +201,7 @@ namespace osu.Game.Rulesets.Osu { RelativeSizeAxes = Axes.X, Height = 130, - Child = new TimingDistributionGraph((TimingDistribution)score.ExtraStatistics.GetValueOrDefault("timing_distribution")) + Child = new TimingDistributionGraph(score.HitEvents.Cast().ToList()) { RelativeSizeAxes = Axes.Both } @@ -208,7 +209,7 @@ namespace osu.Game.Rulesets.Osu new StatisticContainer("Accuracy Heatmap") { RelativeSizeAxes = Axes.Both, - Child = new Heatmap((List)score.ExtraStatistics.GetValueOrDefault("hit_offsets")) + Child = new Heatmap(score.Beatmap, score.HitEvents.Cast().ToList()) { RelativeSizeAxes = Axes.Both } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 97be372e37..9694367210 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -1,120 +1,52 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; -using System.Diagnostics; -using osu.Game.Beatmaps; +using System.Linq; +using JetBrains.Annotations; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osuTK; namespace osu.Game.Rulesets.Osu.Scoring { public class OsuScoreProcessor : ScoreProcessor { - /// - /// The number of bins on each side of the timing distribution. - /// - private const int timing_distribution_bins = 25; - - /// - /// The total number of bins in the timing distribution, including bins on both sides and the centre bin at 0. - /// - private const int total_timing_distribution_bins = timing_distribution_bins * 2 + 1; - - /// - /// The centre bin, with a timing distribution very close to/at 0. - /// - private const int timing_distribution_centre_bin_index = timing_distribution_bins; - - private TimingDistribution timingDistribution; - private readonly List hitOffsets = new List(); - - public override void ApplyBeatmap(IBeatmap beatmap) - { - var hitWindows = CreateHitWindows(); - hitWindows.SetDifficulty(beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty); - - timingDistribution = new TimingDistribution(total_timing_distribution_bins, hitWindows.WindowFor(hitWindows.LowestSuccessfulHitResult()) / timing_distribution_bins); - - base.ApplyBeatmap(beatmap); - } - - private OsuHitCircleJudgementResult lastCircleResult; + private readonly List hitEvents = new List(); + private HitObject lastHitObject; protected override void OnResultApplied(JudgementResult result) { base.OnResultApplied(result); - if (result.IsHit) - { - int binOffset = (int)(result.TimeOffset / timingDistribution.BinSize); - timingDistribution.Bins[timing_distribution_centre_bin_index + binOffset]++; - - addHitOffset(result); - } + hitEvents.Add(new HitEvent(result.TimeOffset, result.Type, result.HitObject, lastHitObject, (result as OsuHitCircleJudgementResult)?.HitPosition)); + lastHitObject = result.HitObject; } protected override void OnResultReverted(JudgementResult result) { base.OnResultReverted(result); - if (result.IsHit) - { - int binOffset = (int)(result.TimeOffset / timingDistribution.BinSize); - timingDistribution.Bins[timing_distribution_centre_bin_index + binOffset]--; - - removeHitOffset(result); - } - } - - private void addHitOffset(JudgementResult result) - { - if (!(result is OsuHitCircleJudgementResult circleResult)) - return; - - if (lastCircleResult == null) - { - lastCircleResult = circleResult; - return; - } - - if (circleResult.HitPosition != null) - { - Debug.Assert(circleResult.Radius != null); - hitOffsets.Add(new HitOffset(lastCircleResult.HitCircle.StackedEndPosition, circleResult.HitCircle.StackedEndPosition, circleResult.HitPosition.Value, circleResult.Radius.Value)); - } - - lastCircleResult = circleResult; - } - - private void removeHitOffset(JudgementResult result) - { - if (!(result is OsuHitCircleJudgementResult circleResult)) - return; - - if (hitOffsets.Count > 0 && circleResult.HitPosition != null) - hitOffsets.RemoveAt(hitOffsets.Count - 1); + hitEvents.RemoveAt(hitEvents.Count - 1); } protected override void Reset(bool storeResults) { base.Reset(storeResults); - timingDistribution.Bins.AsSpan().Clear(); - hitOffsets.Clear(); + hitEvents.Clear(); + lastHitObject = null; } public override void PopulateScore(ScoreInfo score) { base.PopulateScore(score); - score.ExtraStatistics["timing_distribution"] = timingDistribution; - score.ExtraStatistics["hit_offsets"] = hitOffsets; + score.HitEvents.AddRange(hitEvents.Select(e => e).Cast()); } protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) @@ -131,4 +63,42 @@ namespace osu.Game.Rulesets.Osu.Scoring public override HitWindows CreateHitWindows() => new OsuHitWindows(); } + + public readonly struct HitEvent + { + /// + /// The time offset from the end of at which the event occurred. + /// + public readonly double TimeOffset; + + /// + /// The hit result. + /// + public readonly HitResult Result; + + /// + /// The on which the result occurred. + /// + public readonly HitObject HitObject; + + /// + /// The occurring prior to . + /// + [CanBeNull] + public readonly HitObject LastHitObject; + + /// + /// The player's cursor position, if available, at the time of the event. + /// + public readonly Vector2? CursorPosition; + + public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, Vector2? cursorPosition) + { + TimeOffset = timeOffset; + Result = result; + HitObject = hitObject; + LastHitObject = lastHitObject; + CursorPosition = cursorPosition; + } + } } diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs index 51508a5e8b..95cfc5b768 100644 --- a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs @@ -10,6 +10,8 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Layout; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Scoring; using osuTK; using osuTK.Graphics; @@ -27,14 +29,16 @@ namespace osu.Game.Rulesets.Osu.Statistics private const float rotation = 45; private const float point_size = 4; - private readonly IReadOnlyList offsets; private Container allPoints; + private readonly BeatmapInfo beatmap; + private readonly IReadOnlyList hitEvents; private readonly LayoutValue sizeLayout = new LayoutValue(Invalidation.DrawSize); - public Heatmap(IReadOnlyList offsets) + public Heatmap(BeatmapInfo beatmap, IReadOnlyList hitEvents) { - this.offsets = offsets; + this.beatmap = beatmap; + this.hitEvents = hitEvents; AddLayout(sizeLayout); } @@ -153,10 +157,18 @@ namespace osu.Game.Rulesets.Osu.Statistics } } - if (offsets?.Count > 0) + if (hitEvents.Count > 0) { - foreach (var o in offsets) - AddPoint(o.Position1, o.Position2, o.HitPosition, o.Radius); + // Todo: This should probably not be done like this. + float radius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (beatmap.BaseDifficulty.CircleSize - 5) / 5) / 2; + + foreach (var e in hitEvents) + { + if (e.LastHitObject == null || e.CursorPosition == null) + continue; + + AddPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.CursorPosition.Value, radius); + } } sizeLayout.Validate(); diff --git a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs index 1f9f38bf3b..b319cc5aa9 100644 --- a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs +++ b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.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 System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -15,29 +17,52 @@ namespace osu.Game.Rulesets.Osu.Statistics { public class TimingDistributionGraph : CompositeDrawable { + /// + /// The number of bins on each side of the timing distribution. + /// + private const int timing_distribution_bins = 25; + + /// + /// The total number of bins in the timing distribution, including bins on both sides and the centre bin at 0. + /// + private const int total_timing_distribution_bins = timing_distribution_bins * 2 + 1; + + /// + /// The centre bin, with a timing distribution very close to/at 0. + /// + private const int timing_distribution_centre_bin_index = timing_distribution_bins; + /// /// The number of data points shown on the axis below the graph. /// private const float axis_points = 5; - private readonly TimingDistribution distribution; + private readonly List hitEvents; - public TimingDistributionGraph(TimingDistribution distribution) + public TimingDistributionGraph(List hitEvents) { - this.distribution = distribution; + this.hitEvents = hitEvents; } [BackgroundDependencyLoader] private void load() { - if (distribution?.Bins == null || distribution.Bins.Length == 0) + if (hitEvents.Count == 0) return; - int maxCount = distribution.Bins.Max(); + int[] bins = new int[total_timing_distribution_bins]; + double binSize = hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins; - var bars = new Drawable[distribution.Bins.Length]; + foreach (var e in hitEvents) + { + int binOffset = (int)(e.TimeOffset / binSize); + bins[timing_distribution_centre_bin_index + binOffset]++; + } + + int maxCount = bins.Max(); + var bars = new Drawable[total_timing_distribution_bins]; for (int i = 0; i < bars.Length; i++) - bars[i] = new Bar { Height = (float)distribution.Bins[i] / maxCount }; + bars[i] = new Bar { Height = (float)bins[i] / maxCount }; Container axisFlow; @@ -71,10 +96,8 @@ namespace osu.Game.Rulesets.Osu.Statistics } }; - // We know the total number of bins on each side of the centre ((n - 1) / 2), and the size of each bin. - // So our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. - int sideBins = (distribution.Bins.Length - 1) / 2; - double maxValue = sideBins * distribution.BinSize; + // Our axis will contain one centre element + 5 points on each side, each with a value depending on the number of bins * bin size. + double maxValue = timing_distribution_bins * binSize; double axisValueStep = maxValue / axis_points; axisFlow.Add(new OsuSpriteText diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs index 53c8e56f53..ba6a0e42c2 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs @@ -8,8 +8,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Statistics; +using osu.Game.Tests.Beatmaps; using osuTK; using osuTK.Graphics; @@ -40,7 +43,7 @@ namespace osu.Game.Tests.Visual.Ranking { Position = new Vector2(500, 300), }, - heatmap = new TestHeatmap(new List()) + heatmap = new TestHeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, new List()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -73,8 +76,8 @@ namespace osu.Game.Tests.Visual.Ranking private class TestHeatmap : Heatmap { - public TestHeatmap(IReadOnlyList offsets) - : base(offsets) + public TestHeatmap(BeatmapInfo beatmap, List events) + : base(beatmap, events) { } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index c02be9ab5d..faabdf2cb6 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -1,10 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; @@ -17,11 +16,7 @@ namespace osu.Game.Tests.Visual.Ranking { var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { - ExtraStatistics = - { - ["timing_distribution"] = TestSceneTimingDistributionGraph.CreateNormalDistribution(), - ["hit_offsets"] = new List() - } + HitEvents = TestSceneTimingDistributionGraph.CreateDistributedHitEvents().Cast().ToList(), }; loadPanel(score); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs index 2249655093..178d6d95b5 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs @@ -1,12 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Statistics; +using osu.Game.Rulesets.Scoring; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -22,7 +25,7 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333") }, - new TimingDistributionGraph(CreateNormalDistribution()) + new TimingDistributionGraph(CreateDistributedHitEvents()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -31,34 +34,19 @@ namespace osu.Game.Tests.Visual.Ranking }; } - public static TimingDistribution CreateNormalDistribution() + public static List CreateDistributedHitEvents() { - var distribution = new TimingDistribution(51, 5); + var hitEvents = new List(); - // We create an approximately-normal distribution of 51 elements by using the 13th binomial row (14 initial elements) and subdividing the inner values twice. - var row = new List { 1 }; - for (int i = 0; i < 13; i++) - row.Add(row[i] * (13 - i) / (i + 1)); - - // Each subdivision yields 2n-1 total elements, so first subdivision will contain 27 elements, and the second will contain 53 elements. - for (int div = 0; div < 2; div++) + for (int i = 0; i < 50; i++) { - var newRow = new List { 1 }; + int count = (int)(Math.Pow(25 - Math.Abs(i - 25), 2)); - for (int i = 0; i < row.Count - 1; i++) - { - newRow.Add((row[i] + row[i + 1]) / 2); - newRow.Add(row[i + 1]); - } - - row = newRow; + for (int j = 0; j < count; j++) + hitEvents.Add(new HitEvent(i - 25, HitResult.Perfect, new HitCircle(), new HitCircle(), null)); } - // After the subdivisions take place, we're left with 53 values which we use the inner 51 of. - for (int i = 1; i < row.Count - 1; i++) - distribution.Bins[i - 1] = row[i]; - - return distribution; + return hitEvents; } } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 38b37afc55..6fc5892b3c 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -166,7 +166,9 @@ namespace osu.Game.Scoring } } - public Dictionary ExtraStatistics = new Dictionary(); + [NotMapped] + [JsonIgnore] + public List HitEvents = new List(); [JsonIgnore] public List Files { get; set; } From ecdfcb1955f4929bc11fe1e3d1e8e1ddadfbd119 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Jun 2020 22:21:30 +0900 Subject: [PATCH 499/871] Display placeholder if no statistics available --- .../Ranking/TestSceneStatisticsPanel.cs | 17 +++++- osu.Game/Screens/Ranking/ResultsScreen.cs | 6 +- .../Ranking/Statistics/StatisticsPanel.cs | 61 +++++++++++++------ 3 files changed, 63 insertions(+), 21 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index faabdf2cb6..cc3415a530 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -3,6 +3,8 @@ using System.Linq; using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; @@ -12,7 +14,7 @@ namespace osu.Game.Tests.Visual.Ranking public class TestSceneStatisticsPanel : OsuTestScene { [Test] - public void TestScore() + public void TestScoreWithStatistics() { var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { @@ -22,9 +24,20 @@ namespace osu.Game.Tests.Visual.Ranking loadPanel(score); } + [Test] + public void TestScoreWithoutStatistics() + { + loadPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)); + } + private void loadPanel(ScoreInfo score) => AddStep("load panel", () => { - Child = new StatisticsPanel(score); + Child = new StatisticsPanel + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible }, + Score = { Value = score } + }; }); } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 4a7cb6679a..c02a120a73 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -89,7 +89,11 @@ namespace osu.Game.Screens.Ranking SelectedScore = { BindTarget = SelectedScore }, PostExpandAction = onExpandedPanelClicked }, - statisticsPanel = new StatisticsPanel(Score) { RelativeSizeAxes = Axes.Both } + statisticsPanel = new StatisticsPanel + { + RelativeSizeAxes = Axes.Both, + Score = { BindTarget = SelectedScore } + } } } }, diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 733c855426..28a8bc460e 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -1,8 +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.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Online.Placeholders; using osu.Game.Scoring; using osuTK; @@ -12,15 +15,14 @@ namespace osu.Game.Screens.Ranking.Statistics { public const float SIDE_PADDING = 30; + public readonly Bindable Score = new Bindable(); + protected override bool StartHidden => true; - public StatisticsPanel(ScoreInfo score) + private readonly Container content; + + public StatisticsPanel() { - // Todo: Not correct. - RelativeSizeAxes = Axes.Both; - - FillFlowContainer statisticRows; - InternalChild = new Container { RelativeSizeAxes = Axes.Both, @@ -31,24 +33,47 @@ namespace osu.Game.Screens.Ranking.Statistics Top = SIDE_PADDING, Bottom = 50 // Approximate padding to the bottom of the score panel. }, - Child = statisticRows = new FillFlowContainer + Child = content = new Container { RelativeSizeAxes = Axes.Both }, + }; + } + + [BackgroundDependencyLoader] + private void load() + { + Score.BindValueChanged(populateStatistics, true); + } + + private void populateStatistics(ValueChangedEvent score) + { + foreach (var child in content) + child.FadeOut(150).Expire(); + + var newScore = score.NewValue; + + if (newScore.HitEvents == null || newScore.HitEvents.Count == 0) + content.Add(new MessagePlaceholder("Score has no statistics :(")); + else + { + var rows = new FillFlowContainer { RelativeSizeAxes = Axes.Both, Direction = FillDirection.Vertical, Spacing = new Vector2(30, 15), - } - }; + }; - foreach (var s in score.Ruleset.CreateInstance().CreateStatistics(score)) - { - statisticRows.Add(new GridContainer + foreach (var row in newScore.Ruleset.CreateInstance().CreateStatistics(newScore)) { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] { s.Content }, - ColumnDimensions = s.ColumnDimensions, - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } - }); + rows.Add(new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] { row.Content }, + ColumnDimensions = row.ColumnDimensions, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }); + } + + content.Add(rows); } } From 53f507f51af7adc2e48200281fc8ff1598489142 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Jun 2020 22:27:10 +0900 Subject: [PATCH 500/871] Fade background --- osu.Game/Screens/Ranking/ResultsScreen.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index c02a120a73..5073adcc50 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -219,6 +219,8 @@ namespace osu.Game.Screens.Ranking }); } } + + Background.FadeTo(0.5f, 150); } else { @@ -234,6 +236,8 @@ namespace osu.Game.Screens.Ranking panel.MoveTo(new Vector2(scorePanelList.CurrentScrollPosition + StatisticsPanel.SIDE_PADDING, panel.GetTrackingPosition().Y), 150, Easing.OutQuint); } } + + Background.FadeTo(0.1f, 150); } } } From 85a0f78600e97866c1726eefeed64113fef5d76c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 18 Jun 2020 22:27:27 +0900 Subject: [PATCH 501/871] Hide statistics panel on first exit --- osu.Game/Screens/Ranking/ResultsScreen.cs | 24 ++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 5073adcc50..de1939352f 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -87,7 +87,7 @@ namespace osu.Game.Screens.Ranking { RelativeSizeAxes = Axes.Both, SelectedScore = { BindTarget = SelectedScore }, - PostExpandAction = onExpandedPanelClicked + PostExpandAction = () => statisticsPanel.ToggleVisibility() }, statisticsPanel = new StatisticsPanel { @@ -174,6 +174,8 @@ namespace osu.Game.Screens.Ranking if (req != null) api.Queue(req); + + statisticsPanel.State.BindValueChanged(onStatisticsStateChanged, true); } /// @@ -195,17 +197,23 @@ namespace osu.Game.Screens.Ranking public override bool OnExiting(IScreen next) { + if (statisticsPanel.State.Value == Visibility.Visible) + { + statisticsPanel.Hide(); + return true; + } + Background.FadeTo(1, 250); return base.OnExiting(next); } - private void onExpandedPanelClicked() + private void onStatisticsStateChanged(ValueChangedEvent state) { - statisticsPanel.ToggleVisibility(); - - if (statisticsPanel.State.Value == Visibility.Hidden) + if (state.NewValue == Visibility.Hidden) { + Background.FadeTo(0.5f, 150); + foreach (var panel in scorePanelList.Panels) { if (panel.State == PanelState.Contracted) @@ -219,11 +227,11 @@ namespace osu.Game.Screens.Ranking }); } } - - Background.FadeTo(0.5f, 150); } else { + Background.FadeTo(0.1f, 150); + foreach (var panel in scorePanelList.Panels) { if (panel.State == PanelState.Contracted) @@ -236,8 +244,6 @@ namespace osu.Game.Screens.Ranking panel.MoveTo(new Vector2(scorePanelList.CurrentScrollPosition + StatisticsPanel.SIDE_PADDING, panel.GetTrackingPosition().Y), 150, Easing.OutQuint); } } - - Background.FadeTo(0.1f, 150); } } } From add1265d5354b8ead7644b3a6389fc287fc3d7b5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jun 2020 23:35:03 +0900 Subject: [PATCH 502/871] Block screen suspend while gameplay is active --- osu.Game/Screens/Play/Player.cs | 7 ++++ .../Screens/Play/ScreenSuspensionHandler.cs | 42 +++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 osu.Game/Screens/Play/ScreenSuspensionHandler.cs diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 83991ad027..d3b88e56ae 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -125,6 +125,8 @@ namespace osu.Game.Screens.Play private GameplayBeatmap gameplayBeatmap; + private ScreenSuspensionHandler screenSuspension; + private DependencyContainer dependencies; protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) @@ -179,6 +181,7 @@ namespace osu.Game.Screens.Play InternalChild = GameplayClockContainer = new GameplayClockContainer(Beatmap.Value, Mods.Value, DrawableRuleset.GameplayStartTime); AddInternal(gameplayBeatmap = new GameplayBeatmap(playableBeatmap)); + AddInternal(screenSuspension = new ScreenSuspensionHandler(GameplayClockContainer)); dependencies.CacheAs(gameplayBeatmap); @@ -628,12 +631,16 @@ namespace osu.Game.Screens.Play public override void OnSuspending(IScreen next) { + screenSuspension?.Expire(); + fadeOut(); base.OnSuspending(next); } public override bool OnExiting(IScreen next) { + screenSuspension?.Expire(); + if (completionProgressDelegate != null && !completionProgressDelegate.Cancelled && !completionProgressDelegate.Completed) { // proceed to result screen if beatmap already finished playing diff --git a/osu.Game/Screens/Play/ScreenSuspensionHandler.cs b/osu.Game/Screens/Play/ScreenSuspensionHandler.cs new file mode 100644 index 0000000000..948276f03f --- /dev/null +++ b/osu.Game/Screens/Play/ScreenSuspensionHandler.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 osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Platform; + +namespace osu.Game.Screens.Play +{ + internal class ScreenSuspensionHandler : Component + { + private readonly GameplayClockContainer gameplayClockContainer; + private Bindable isPaused; + + [Resolved] + private GameHost host { get; set; } + + public ScreenSuspensionHandler(GameplayClockContainer gameplayClockContainer) + { + this.gameplayClockContainer = gameplayClockContainer; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + isPaused = gameplayClockContainer.IsPaused.GetBoundCopy(); + isPaused.BindValueChanged(paused => host.AllowScreenSuspension.Value = paused.NewValue, true); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + isPaused?.UnbindAll(); + + if (host != null) + host.AllowScreenSuspension.Value = true; + } + } +} From 7da56ec7fd27ebddc7253b6151b50f93ad289dd4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jun 2020 23:52:35 +0900 Subject: [PATCH 503/871] Add null check and xmldoc --- osu.Game/Screens/Play/ScreenSuspensionHandler.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/ScreenSuspensionHandler.cs b/osu.Game/Screens/Play/ScreenSuspensionHandler.cs index 948276f03f..59ad74d81a 100644 --- a/osu.Game/Screens/Play/ScreenSuspensionHandler.cs +++ b/osu.Game/Screens/Play/ScreenSuspensionHandler.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.Bindables; using osu.Framework.Graphics; @@ -8,7 +10,10 @@ using osu.Framework.Platform; namespace osu.Game.Screens.Play { - internal class ScreenSuspensionHandler : Component + /// + /// Ensures screen is not suspended / dimmed while gameplay is active. + /// + public class ScreenSuspensionHandler : Component { private readonly GameplayClockContainer gameplayClockContainer; private Bindable isPaused; @@ -16,9 +21,9 @@ namespace osu.Game.Screens.Play [Resolved] private GameHost host { get; set; } - public ScreenSuspensionHandler(GameplayClockContainer gameplayClockContainer) + public ScreenSuspensionHandler([NotNull] GameplayClockContainer gameplayClockContainer) { - this.gameplayClockContainer = gameplayClockContainer; + this.gameplayClockContainer = gameplayClockContainer ?? throw new ArgumentNullException(nameof(gameplayClockContainer)); } protected override void LoadComplete() From 290ae373469bd43d66c6c965edb7e0c016ff797a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 18 Jun 2020 23:54:20 +0900 Subject: [PATCH 504/871] Add assertion of only usage game-wide --- osu.Game/Screens/Play/ScreenSuspensionHandler.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Play/ScreenSuspensionHandler.cs b/osu.Game/Screens/Play/ScreenSuspensionHandler.cs index 59ad74d81a..8585a5c309 100644 --- a/osu.Game/Screens/Play/ScreenSuspensionHandler.cs +++ b/osu.Game/Screens/Play/ScreenSuspensionHandler.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -30,6 +31,10 @@ namespace osu.Game.Screens.Play { base.LoadComplete(); + // This is the only usage game-wide of suspension changes. + // Assert to ensure we don't accidentally forget this in the future. + Debug.Assert(host.AllowScreenSuspension.Value); + isPaused = gameplayClockContainer.IsPaused.GetBoundCopy(); isPaused.BindValueChanged(paused => host.AllowScreenSuspension.Value = paused.NewValue, true); } From f04f2d21755103041272a66484730a5ae8687cfc Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Thu, 18 Jun 2020 21:46:32 +0100 Subject: [PATCH 505/871] Add test scene --- .../Gameplay/TestSceneStoryboardSamples.cs | 57 +++++++++++++++++++ .../Drawables/DrawableStoryboardSample.cs | 17 +++--- 2 files changed, 66 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 552d163b2f..60911d6792 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -10,9 +10,12 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.IO.Stores; using osu.Framework.Testing; +using osu.Framework.Timing; using osu.Game.Audio; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; +using osu.Game.Rulesets.Osu.Mods; using osu.Game.Screens.Play; using osu.Game.Skinning; using osu.Game.Storyboards; @@ -70,6 +73,37 @@ namespace osu.Game.Tests.Gameplay AddUntilStep("sample playback succeeded", () => sample.LifetimeEnd < double.MaxValue); } + [Test] + public void TestSamplePlaybackWithRateMods() + { + GameplayClockContainer gameplayContainer = null; + TestDrawableStoryboardSample sample = null; + + OsuModDoubleTime doubleTimeMod = null; + + AddStep("create container", () => + { + var beatmap = Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + + Add(gameplayContainer = new GameplayClockContainer(beatmap, new[] { doubleTimeMod = new OsuModDoubleTime() }, 0)); + + SelectedMods.Value = new[] { doubleTimeMod }; + Beatmap.Value = new TestCustomSkinWorkingBeatmap(beatmap.Beatmap, gameplayContainer.GameplayClock, Audio); + }); + + AddStep("create storyboard sample", () => + { + gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1)) + { + Clock = gameplayContainer.GameplayClock + }); + }); + + AddStep("start", () => gameplayContainer.Start()); + + AddAssert("sample playback rate matches mod rates", () => sample.TestChannel.AggregateFrequency.Value == doubleTimeMod.SpeedChange.Value); + } + private class TestSkin : LegacySkin { public TestSkin(string resourceName, AudioManager audioManager) @@ -99,5 +133,28 @@ namespace osu.Game.Tests.Gameplay { } } + + private class TestCustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap + { + private readonly AudioManager audio; + + public TestCustomSkinWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock referenceClock, AudioManager audio) + : base(beatmap, null, referenceClock, audio) + { + this.audio = audio; + } + + protected override ISkin GetSkin() => new TestSkin("test-sample", audio); + } + + private class TestDrawableStoryboardSample : DrawableStoryboardSample + { + public TestDrawableStoryboardSample(StoryboardSampleInfo sampleInfo) + : base(sampleInfo) + { + } + + public SampleChannel TestChannel => Channel; + } } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 2b9c66d2e6..04df46410e 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -20,7 +20,8 @@ namespace osu.Game.Storyboards.Drawables private const double allowable_late_start = 100; private readonly StoryboardSampleInfo sampleInfo; - private SampleChannel channel; + + protected SampleChannel Channel; public override bool RemoveWhenNotAlive => false; @@ -33,14 +34,14 @@ namespace osu.Game.Storyboards.Drawables [BackgroundDependencyLoader] private void load(IBindable beatmap, IBindable> mods) { - channel = beatmap.Value.Skin.GetSample(sampleInfo); + Channel = beatmap.Value.Skin.GetSample(sampleInfo); - if (channel != null) + if (Channel != null) { - channel.Volume.Value = sampleInfo.Volume / 100.0; + Channel.Volume.Value = sampleInfo.Volume / 100.0; foreach (var mod in mods.Value.OfType()) - mod.ApplyToSample(channel); + mod.ApplyToSample(Channel); } } @@ -52,7 +53,7 @@ namespace osu.Game.Storyboards.Drawables if (Time.Current < sampleInfo.StartTime) { // We've rewound before the start time of the sample - channel?.Stop(); + Channel?.Stop(); // In the case that the user fast-forwards to a point far beyond the start time of the sample, // we want to be able to fall into the if-conditional below (therefore we must not have a life time end) @@ -64,7 +65,7 @@ namespace osu.Game.Storyboards.Drawables // We've passed the start time of the sample. We only play the sample if we're within an allowable range // from the sample's start, to reduce layering if we've been fast-forwarded far into the future if (Time.Current - sampleInfo.StartTime < allowable_late_start) - channel?.Play(); + Channel?.Play(); // In the case that the user rewinds to a point far behind the start time of the sample, // we want to be able to fall into the if-conditional above (therefore we must not have a life time start) @@ -75,7 +76,7 @@ namespace osu.Game.Storyboards.Drawables protected override void Dispose(bool isDisposing) { - channel?.Stop(); + Channel?.Stop(); base.Dispose(isDisposing); } } From 5530e2a1dbaa413a4383348ca7b2cf042d3c1c60 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 15:35:39 +0900 Subject: [PATCH 506/871] Add test for delayed score fetch --- .../Visual/Ranking/TestSceneResultsScreen.cs | 63 ++++++++++++++++++- 1 file changed, 60 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index ea33aa62e3..9d3c22d87c 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -4,11 +4,13 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Rulesets.Osu; @@ -16,6 +18,7 @@ using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; +using osuTK.Input; namespace osu.Game.Tests.Visual.Ranking { @@ -44,7 +47,7 @@ namespace osu.Game.Tests.Visual.Ranking private UnrankedSoloResultsScreen createUnrankedSoloResultsScreen() => new UnrankedSoloResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo)); [Test] - public void ResultsWithoutPlayer() + public void TestResultsWithoutPlayer() { TestResultsScreen screen = null; OsuScreenStack stack; @@ -63,7 +66,7 @@ namespace osu.Game.Tests.Visual.Ranking } [Test] - public void ResultsWithPlayer() + public void TestResultsWithPlayer() { TestResultsScreen screen = null; @@ -73,7 +76,7 @@ namespace osu.Game.Tests.Visual.Ranking } [Test] - public void ResultsForUnranked() + public void TestResultsForUnranked() { UnrankedSoloResultsScreen screen = null; @@ -82,6 +85,24 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("retry overlay present", () => screen.RetryOverlay != null); } + [Test] + public void TestFetchScoresAfterShowingStatistics() + { + DelayedFetchResultsScreen screen = null; + + AddStep("load results", () => Child = new TestResultsContainer(screen = new DelayedFetchResultsScreen(new TestScoreInfo(new OsuRuleset().RulesetInfo), 3000))); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddStep("click expanded panel", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + InputManager.MoveMouseTo(expandedPanel); + InputManager.Click(MouseButton.Left); + }); + + AddUntilStep("wait for fetch", () => screen.FetchCompleted); + AddAssert("expanded panel still on screen", () => this.ChildrenOfType().Single(p => p.State == PanelState.Expanded).ScreenSpaceDrawQuad.TopLeft.X > 0); + } + private class TestResultsContainer : Container { [Cached(typeof(Player))] @@ -134,6 +155,42 @@ namespace osu.Game.Tests.Visual.Ranking } } + private class DelayedFetchResultsScreen : TestResultsScreen + { + public bool FetchCompleted { get; private set; } + + private readonly double delay; + + public DelayedFetchResultsScreen(ScoreInfo score, double delay) + : base(score) + { + this.delay = delay; + } + + protected override APIRequest FetchScores(Action> scoresCallback) + { + Task.Run(async () => + { + await Task.Delay(TimeSpan.FromMilliseconds(delay)); + + var scores = new List(); + + for (int i = 0; i < 20; i++) + { + var score = new TestScoreInfo(new OsuRuleset().RulesetInfo); + score.TotalScore += 10 - i; + scores.Add(score); + } + + scoresCallback?.Invoke(scores); + + Schedule(() => FetchCompleted = true); + }); + + return null; + } + } + private class UnrankedSoloResultsScreen : SoloResultsScreen { public HotkeyRetryOverlay RetryOverlay; From ec16b0fc5a3888c65b198da0640b563accba5803 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 17:28:35 +0900 Subject: [PATCH 507/871] Rework score panel tracking to fix visual edge cases --- .../Visual/Ranking/TestSceneScorePanel.cs | 38 -------- osu.Game/Screens/Ranking/ResultsScreen.cs | 97 +++++++++++++------ osu.Game/Screens/Ranking/ScorePanel.cs | 45 ++------- osu.Game/Screens/Ranking/ScorePanelList.cs | 79 +++++++++------ .../Ranking/ScorePanelTrackingContainer.cs | 35 +++++++ 5 files changed, 155 insertions(+), 139 deletions(-) create mode 100644 osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs index 1c5087ee94..250fdc5ebd 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneScorePanel.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Utils; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; @@ -102,39 +101,6 @@ namespace osu.Game.Tests.Visual.Ranking AddWaitStep("wait for transition", 10); } - [Test] - public void TestSceneTrackingScorePanel() - { - var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { Accuracy = 0.925, Rank = ScoreRank.A }; - - addPanelStep(score, PanelState.Contracted); - - AddStep("enable tracking", () => - { - panel.Anchor = Anchor.CentreLeft; - panel.Origin = Anchor.CentreLeft; - panel.Tracking = true; - - Add(panel.CreateTrackingComponent().With(d => - { - d.Anchor = Anchor.Centre; - d.Origin = Anchor.Centre; - })); - }); - - assertTracking(true); - - AddStep("expand panel", () => panel.State = PanelState.Expanded); - AddWaitStep("wait for transition", 2); - assertTracking(true); - - AddStep("stop tracking", () => panel.Tracking = false); - assertTracking(false); - - AddStep("start tracking", () => panel.Tracking = true); - assertTracking(true); - } - private void addPanelStep(ScoreInfo score, PanelState state = PanelState.Expanded) => AddStep("add panel", () => { Child = panel = new ScorePanel(score) @@ -144,9 +110,5 @@ namespace osu.Game.Tests.Visual.Ranking State = state }; }); - - private void assertTracking(bool tracking) => AddAssert($"{(tracking ? "is" : "is not")} tracking", () => - Precision.AlmostEquals(panel.ScreenSpaceDrawQuad.TopLeft, panel.CreateTrackingComponent().ScreenSpaceDrawQuad.TopLeft) == tracking - && Precision.AlmostEquals(panel.ScreenSpaceDrawQuad.BottomRight, panel.CreateTrackingComponent().ScreenSpaceDrawQuad.BottomRight) == tracking); } } diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index de1939352f..133efd6e7b 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; @@ -47,6 +48,7 @@ namespace osu.Game.Screens.Ranking private StatisticsPanel statisticsPanel; private Drawable bottomPanel; private ScorePanelList scorePanelList; + private Container detachedPanelContainer; protected ResultsScreen(ScoreInfo score, bool allowRetry = true) { @@ -89,11 +91,15 @@ namespace osu.Game.Screens.Ranking SelectedScore = { BindTarget = SelectedScore }, PostExpandAction = () => statisticsPanel.ToggleVisibility() }, + detachedPanelContainer = new Container + { + RelativeSizeAxes = Axes.Both + }, statisticsPanel = new StatisticsPanel { RelativeSizeAxes = Axes.Both, Score = { BindTarget = SelectedScore } - } + }, } } }, @@ -169,7 +175,7 @@ namespace osu.Game.Screens.Ranking var req = FetchScores(scores => Schedule(() => { foreach (var s in scores) - scorePanelList.AddScore(s); + addScore(s); })); if (req != null) @@ -208,42 +214,71 @@ namespace osu.Game.Screens.Ranking return base.OnExiting(next); } + private void addScore(ScoreInfo score) + { + var panel = scorePanelList.AddScore(score); + + if (detachedPanel != null) + panel.Alpha = 0; + } + + private ScorePanel detachedPanel; + private void onStatisticsStateChanged(ValueChangedEvent state) { - if (state.NewValue == Visibility.Hidden) + if (state.NewValue == Visibility.Visible) { - Background.FadeTo(0.5f, 150); + // Detach the panel in its original location, and move into the desired location in the local container. + var expandedPanel = scorePanelList.GetPanelForScore(SelectedScore.Value); + var screenSpacePos = expandedPanel.ScreenSpaceDrawQuad.TopLeft; - foreach (var panel in scorePanelList.Panels) - { - if (panel.State == PanelState.Contracted) - panel.FadeIn(150); - else - { - panel.MoveTo(panel.GetTrackingPosition(), 150, Easing.OutQuint).OnComplete(p => - { - scorePanelList.HandleScroll = true; - p.Tracking = true; - }); - } - } - } - else - { + // Detach and move into the local container. + scorePanelList.Detach(expandedPanel); + detachedPanelContainer.Add(expandedPanel); + + // Move into its original location in the local container. + var origLocation = detachedPanelContainer.ToLocalSpace(screenSpacePos); + expandedPanel.MoveTo(origLocation); + expandedPanel.MoveToX(origLocation.X); + + // Move into the final location. + expandedPanel.MoveToX(StatisticsPanel.SIDE_PADDING, 150, Easing.OutQuint); + + // Hide contracted panels. + foreach (var contracted in scorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) + contracted.FadeOut(150, Easing.OutQuint); + scorePanelList.HandleInput = false; + + // Dim background. Background.FadeTo(0.1f, 150); - foreach (var panel in scorePanelList.Panels) - { - if (panel.State == PanelState.Contracted) - panel.FadeOut(150, Easing.OutQuint); - else - { - scorePanelList.HandleScroll = false; + detachedPanel = expandedPanel; + } + else if (detachedPanel != null) + { + var screenSpacePos = detachedPanel.ScreenSpaceDrawQuad.TopLeft; - panel.Tracking = false; - panel.MoveTo(new Vector2(scorePanelList.CurrentScrollPosition + StatisticsPanel.SIDE_PADDING, panel.GetTrackingPosition().Y), 150, Easing.OutQuint); - } - } + // Remove from the local container and re-attach. + detachedPanelContainer.Remove(detachedPanel); + scorePanelList.Attach(detachedPanel); + + // Move into its original location in the attached container. + var origLocation = detachedPanel.Parent.ToLocalSpace(screenSpacePos); + detachedPanel.MoveTo(origLocation); + detachedPanel.MoveToX(origLocation.X); + + // Move into the final location. + detachedPanel.MoveToX(0, 150, Easing.OutQuint); + + // Show contracted panels. + foreach (var contracted in scorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) + contracted.FadeIn(150, Easing.OutQuint); + scorePanelList.HandleInput = true; + + // Un-dim background. + Background.FadeTo(0.5f, 150); + + detachedPanel = null; } } } diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 31b2796c13..257279bdc9 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -78,11 +78,6 @@ namespace osu.Game.Screens.Ranking public event Action StateChanged; public Action PostExpandAction; - /// - /// Whether this should track the position of the tracking component created via . - /// - public bool Tracking; - /// /// Whether this can enter into an state. /// @@ -194,20 +189,6 @@ namespace osu.Game.Screens.Ranking } } - protected override void Update() - { - base.Update(); - - if (Tracking && trackingComponent != null) - Position = GetTrackingPosition(); - } - - public Vector2 GetTrackingPosition() - { - Vector2 topLeftPos = Parent.ToLocalSpace(trackingComponent.ScreenSpaceDrawQuad.TopLeft); - return topLeftPos - AnchorPosition + OriginPosition; - } - private void updateState() { topLayerContent?.FadeOut(content_fade_duration).Expire(); @@ -269,8 +250,8 @@ namespace osu.Game.Screens.Ranking { base.Size = value; - if (trackingComponent != null) - trackingComponent.Size = value; + if (trackingContainer != null) + trackingContainer.Size = value; } } @@ -293,26 +274,14 @@ namespace osu.Game.Screens.Ranking || topLayerContainer.ReceivePositionalInputAt(screenSpacePos) || middleLayerContainer.ReceivePositionalInputAt(screenSpacePos); - private TrackingComponent trackingComponent; + private ScorePanelTrackingContainer trackingContainer; - public TrackingComponent CreateTrackingComponent() => trackingComponent ??= new TrackingComponent(this); - - public class TrackingComponent : Drawable + public ScorePanelTrackingContainer CreateTrackingContainer() { - public readonly ScorePanel Panel; + if (trackingContainer != null) + throw new InvalidOperationException("A score panel container has already been created."); - public TrackingComponent(ScorePanel panel) - { - Panel = panel; - } - - // In ScorePanelList, score panels are added _before_ the flow, but this means that input will be blocked by the scroll container. - // So by forwarding input events, we remove the need to consider the order in which input is handled. - protected override bool OnClick(ClickEvent e) => Panel.TriggerEvent(e); - - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => Panel.ReceivePositionalInputAt(screenSpacePos); - - public override bool IsPresent => Panel.IsPresent; + return trackingContainer = new ScorePanelTrackingContainer(this); } } } diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index e332f462bb..32903860ec 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -32,9 +32,6 @@ namespace osu.Game.Screens.Ranking public float CurrentScrollPosition => scroll.Current; - public IReadOnlyList Panels => panels; - private readonly Container panels; - private readonly Flow flow; private readonly Scroll scroll; private ScorePanel expandedPanel; @@ -49,10 +46,9 @@ namespace osu.Game.Screens.Ranking InternalChild = scroll = new Scroll { RelativeSizeAxes = Axes.Both, - HandleScroll = () => HandleScroll && expandedPanel?.IsHovered != true, // handle horizontal scroll only when not hovering the expanded panel. + HandleScroll = () => expandedPanel?.IsHovered != true, // handle horizontal scroll only when not hovering the expanded panel. Children = new Drawable[] { - panels = new Container { RelativeSizeAxes = Axes.Both }, flow = new Flow { Anchor = Anchor.Centre, @@ -72,34 +68,14 @@ namespace osu.Game.Screens.Ranking SelectedScore.BindValueChanged(selectedScoreChanged, true); } - private bool handleScroll = true; - - public bool HandleScroll - { - get => handleScroll; - set - { - handleScroll = value; - - foreach (var p in panels) - p.CanExpand = value; - - scroll.ScrollbarVisible = value; - - if (!value) - scroll.ScrollTo(CurrentScrollPosition, false); - } - } - /// /// Adds a to this list. /// /// The to add. - public void AddScore(ScoreInfo score) + public ScorePanel AddScore(ScoreInfo score) { var panel = new ScorePanel(score) { - Tracking = true, PostExpandAction = () => PostExpandAction?.Invoke() }.With(p => { @@ -110,8 +86,7 @@ namespace osu.Game.Screens.Ranking }; }); - panels.Add(panel); - flow.Add(panel.CreateTrackingComponent().With(d => + flow.Add(panel.CreateTrackingContainer().With(d => { d.Anchor = Anchor.Centre; d.Origin = Anchor.Centre; @@ -132,6 +107,8 @@ namespace osu.Game.Screens.Ranking scroll.InstantScrollTarget = (scroll.InstantScrollTarget ?? scroll.Target) + ScorePanel.CONTRACTED_WIDTH + panel_spacing; } } + + return panel; } /// @@ -187,15 +164,53 @@ namespace osu.Game.Screens.Ranking flow.Padding = new MarginPadding { Horizontal = offset }; } - private class Flow : FillFlowContainer + private bool handleInput = true; + + public bool HandleInput + { + get => handleInput; + set + { + handleInput = value; + scroll.ScrollbarVisible = value; + } + } + + public override bool PropagatePositionalInputSubTree => HandleInput && base.PropagatePositionalInputSubTree; + + public override bool PropagateNonPositionalInputSubTree => HandleInput && base.PropagateNonPositionalInputSubTree; + + public IEnumerable GetScorePanels() => flow.Select(t => t.Panel); + + public ScorePanel GetPanelForScore(ScoreInfo score) => flow.Single(t => t.Panel.Score == score).Panel; + + public void Detach(ScorePanel panel) + { + var container = flow.FirstOrDefault(t => t.Panel == panel); + if (container == null) + throw new InvalidOperationException("Panel is not contained by the score panel list."); + + container.Detach(); + } + + public void Attach(ScorePanel panel) + { + var container = flow.FirstOrDefault(t => t.Panel == panel); + if (container == null) + throw new InvalidOperationException("Panel is not contained by the score panel list."); + + container.Attach(); + } + + private class Flow : FillFlowContainer { public override IEnumerable FlowingChildren => applySorting(AliveInternalChildren); public int GetPanelIndex(ScoreInfo score) => applySorting(Children).TakeWhile(s => s.Panel.Score != score).Count(); - private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() - .OrderByDescending(s => s.Panel.Score.TotalScore) - .ThenBy(s => s.Panel.Score.OnlineScoreID); + private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() + .OrderByDescending(s => s.Panel.Score.TotalScore) + .ThenBy(s => s.Panel.Score.OnlineScoreID); } private class Scroll : OsuScrollContainer diff --git a/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs b/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs new file mode 100644 index 0000000000..f6f26d0f8a --- /dev/null +++ b/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs @@ -0,0 +1,35 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Screens.Ranking +{ + public class ScorePanelTrackingContainer : CompositeDrawable + { + public readonly ScorePanel Panel; + + public ScorePanelTrackingContainer(ScorePanel panel) + { + Panel = panel; + Attach(); + } + + public void Detach() + { + if (InternalChildren.Count == 0) + throw new InvalidOperationException("Score panel container is not attached."); + + RemoveInternal(Panel); + } + + public void Attach() + { + if (InternalChildren.Count > 0) + throw new InvalidOperationException("Score panel container is already attached."); + + AddInternal(Panel); + } + } +} From 55196efe6e4ae03efe09e7dbc2b79d7d90f8e4c5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 18:02:54 +0900 Subject: [PATCH 508/871] Fix panel depth ordering --- osu.Game/Screens/Ranking/ScorePanelList.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 32903860ec..8f9064c2d1 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -211,6 +211,22 @@ namespace osu.Game.Screens.Ranking private IEnumerable applySorting(IEnumerable drawables) => drawables.OfType() .OrderByDescending(s => s.Panel.Score.TotalScore) .ThenBy(s => s.Panel.Score.OnlineScoreID); + + protected override int Compare(Drawable x, Drawable y) + { + var tX = (ScorePanelTrackingContainer)x; + var tY = (ScorePanelTrackingContainer)y; + + int result = tY.Panel.Score.TotalScore.CompareTo(tX.Panel.Score.TotalScore); + + if (result != 0) + return result; + + if (tX.Panel.Score.OnlineScoreID == null || tY.Panel.Score.OnlineScoreID == null) + return base.Compare(x, y); + + return tX.Panel.Score.OnlineScoreID.Value.CompareTo(tY.Panel.Score.OnlineScoreID.Value); + } } private class Scroll : OsuScrollContainer From c9ad3192b02ae4a9a2cc4d6b19adb9b84d54d4ef Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 18:02:57 +0900 Subject: [PATCH 509/871] Add more tests --- .../Visual/Ranking/TestSceneResultsScreen.cs | 69 +++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index 9d3c22d87c..ac364b5233 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Rulesets.Osu; @@ -18,6 +19,7 @@ using osu.Game.Scoring; using osu.Game.Screens; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; +using osu.Game.Screens.Ranking.Statistics; using osuTK.Input; namespace osu.Game.Tests.Visual.Ranking @@ -85,6 +87,73 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("retry overlay present", () => screen.RetryOverlay != null); } + [Test] + public void TestShowHideStatistics() + { + TestResultsScreen screen = null; + + AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + + AddStep("click expanded panel", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + InputManager.MoveMouseTo(expandedPanel); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("statistics shown", () => this.ChildrenOfType().Single().State.Value == Visibility.Visible); + + AddUntilStep("expanded panel at the left of the screen", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + return expandedPanel.ScreenSpaceDrawQuad.TopLeft.X - screen.ScreenSpaceDrawQuad.TopLeft.X < 150; + }); + + AddStep("click expanded panel", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + InputManager.MoveMouseTo(expandedPanel); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("statistics hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); + + AddUntilStep("expanded panel in centre of screen", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + return Precision.AlmostEquals(expandedPanel.ScreenSpaceDrawQuad.Centre.X, screen.ScreenSpaceDrawQuad.Centre.X, 1); + }); + } + + [Test] + public void TestShowStatisticsAndClickOtherPanel() + { + TestResultsScreen screen = null; + + AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + + ScorePanel expandedPanel = null; + ScorePanel contractedPanel = null; + + AddStep("click expanded panel then contracted panel", () => + { + expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + InputManager.MoveMouseTo(expandedPanel); + InputManager.Click(MouseButton.Left); + + contractedPanel = this.ChildrenOfType().First(p => p.State == PanelState.Contracted && p.ScreenSpaceDrawQuad.TopLeft.X > screen.ScreenSpaceDrawQuad.TopLeft.X); + InputManager.MoveMouseTo(contractedPanel); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("statistics shown", () => this.ChildrenOfType().Single().State.Value == Visibility.Visible); + + AddAssert("contracted panel still contracted", () => contractedPanel.State == PanelState.Contracted); + AddAssert("expanded panel still expanded", () => expandedPanel.State == PanelState.Expanded); + } + [Test] public void TestFetchScoresAfterShowingStatistics() { From cae3a5f447e166b57bfed30a52e4a51964e4e2a6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 19:08:36 +0900 Subject: [PATCH 510/871] Rework heatmap for more consistent performance --- osu.Game.Rulesets.Osu/Statistics/Heatmap.cs | 94 +++++++++---------- .../Ranking/TestSceneAccuracyHeatmap.cs | 55 +++++++---- 2 files changed, 78 insertions(+), 71 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs index 95cfc5b768..8ebc8e9001 100644 --- a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs @@ -26,10 +26,15 @@ namespace osu.Game.Rulesets.Osu.Statistics /// private const float inner_portion = 0.8f; - private const float rotation = 45; - private const float point_size = 4; + /// + /// Number of rows/columns of points. + /// 4px per point @ 128x128 size (the contents of the are always square). 1024 total points. + /// + private const int points_per_dimension = 32; - private Container allPoints; + private const float rotation = 45; + + private GridContainer pointGrid; private readonly BeatmapInfo beatmap; private readonly IReadOnlyList hitEvents; @@ -111,52 +116,39 @@ namespace osu.Game.Rulesets.Osu.Statistics } } }, - allPoints = new Container + pointGrid = new GridContainer { RelativeSizeAxes = Axes.Both } } }; - } - protected override void Update() - { - base.Update(); - validateHitPoints(); - } + Vector2 centre = new Vector2(points_per_dimension) / 2; + float innerRadius = centre.X * inner_portion; - private void validateHitPoints() - { - if (sizeLayout.IsValid) - return; + Drawable[][] points = new Drawable[points_per_dimension][]; - allPoints.Clear(); - - // Since the content is fit, both dimensions should have the same size. - float size = allPoints.DrawSize.X; - - Vector2 centre = new Vector2(size / 2); - int rows = (int)Math.Ceiling(size / point_size); - int cols = (int)Math.Ceiling(size / point_size); - - for (int r = 0; r < rows; r++) + for (int r = 0; r < points_per_dimension; r++) { - for (int c = 0; c < cols; c++) + points[r] = new Drawable[points_per_dimension]; + + for (int c = 0; c < points_per_dimension; c++) { - Vector2 pos = new Vector2(c * point_size, r * point_size); - HitPointType pointType = HitPointType.Hit; + HitPointType pointType = Vector2.Distance(new Vector2(c, r), centre) <= innerRadius + ? HitPointType.Hit + : HitPointType.Miss; - if (Vector2.Distance(pos, centre) > size * inner_portion / 2) - pointType = HitPointType.Miss; - - allPoints.Add(new HitPoint(pos, pointType) + var point = new HitPoint(pointType) { - Size = new Vector2(point_size), Colour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) - }); + }; + + points[r][c] = point; } } + pointGrid.Content = points; + if (hitEvents.Count > 0) { // Todo: This should probably not be done like this. @@ -170,41 +162,39 @@ namespace osu.Game.Rulesets.Osu.Statistics AddPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.CursorPosition.Value, radius); } } - - sizeLayout.Validate(); } protected void AddPoint(Vector2 start, Vector2 end, Vector2 hitPoint, float radius) { - if (allPoints.Count == 0) + if (pointGrid.Content.Length == 0) return; double angle1 = Math.Atan2(end.Y - hitPoint.Y, hitPoint.X - end.X); // Angle between the end point and the hit point. double angle2 = Math.Atan2(end.Y - start.Y, start.X - end.X); // Angle between the end point and the start point. double finalAngle = angle2 - angle1; // Angle between start, end, and hit points. - float normalisedDistance = Vector2.Distance(hitPoint, end) / radius; - // Since the content is fit, both dimensions should have the same size. - float size = allPoints.DrawSize.X; + // Convert the above into the local search space. + Vector2 localCentre = new Vector2(points_per_dimension) / 2; + float localRadius = localCentre.X * inner_portion * normalisedDistance; // The radius inside the inner portion which of the heatmap which the closest point lies. + double localAngle = finalAngle + 3 * Math.PI / 4; // The angle inside the heatmap on which the closest point lies. + Vector2 localPoint = localCentre + localRadius * new Vector2((float)Math.Cos(localAngle), (float)Math.Sin(localAngle)); // Find the most relevant hit point. double minDist = double.PositiveInfinity; HitPoint point = null; - foreach (var p in allPoints) + for (int r = 0; r < points_per_dimension; r++) { - Vector2 localCentre = new Vector2(size / 2); - float localRadius = localCentre.X * inner_portion * normalisedDistance; - double localAngle = finalAngle + 3 * Math.PI / 4; - Vector2 localPoint = localCentre + localRadius * new Vector2((float)Math.Cos(localAngle), (float)Math.Sin(localAngle)); - - float dist = Vector2.Distance(p.DrawPosition + p.DrawSize / 2, localPoint); - - if (dist < minDist) + for (int c = 0; c < points_per_dimension; c++) { - minDist = dist; - point = p; + float dist = Vector2.Distance(new Vector2(c, r), localPoint); + + if (dist < minDist) + { + minDist = dist; + point = (HitPoint)pointGrid.Content[r][c]; + } } } @@ -216,11 +206,11 @@ namespace osu.Game.Rulesets.Osu.Statistics { private readonly HitPointType pointType; - public HitPoint(Vector2 position, HitPointType pointType) + public HitPoint(HitPointType pointType) { this.pointType = pointType; - Position = position; + RelativeSizeAxes = Axes.Both; Alpha = 0; } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs index ba6a0e42c2..52cc41fbd8 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using NUnit.Framework; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Threading; using osu.Framework.Utils; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu; @@ -20,13 +22,18 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneAccuracyHeatmap : OsuManualInputManagerTestScene { - private readonly Box background; - private readonly Drawable object1; - private readonly Drawable object2; - private readonly TestHeatmap heatmap; + private Box background; + private Drawable object1; + private Drawable object2; + private TestHeatmap heatmap; + private ScheduledDelegate automaticAdditionDelegate; - public TestSceneAccuracyHeatmap() + [SetUp] + public void Setup() => Schedule(() => { + automaticAdditionDelegate?.Cancel(); + automaticAdditionDelegate = null; + Children = new[] { background = new Box @@ -41,7 +48,7 @@ namespace osu.Game.Tests.Visual.Ranking }, object2 = new BorderCircle { - Position = new Vector2(500, 300), + Position = new Vector2(100, 300), }, heatmap = new TestHeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, new List()) { @@ -50,22 +57,32 @@ namespace osu.Game.Tests.Visual.Ranking Size = new Vector2(130) } }; + }); + + [Test] + public void TestManyHitPointsAutomatic() + { + AddStep("add scheduled delegate", () => + { + automaticAdditionDelegate = Scheduler.AddDelayed(() => + { + var randomPos = new Vector2( + RNG.NextSingle(object1.DrawPosition.X - object1.DrawSize.X / 2, object1.DrawPosition.X + object1.DrawSize.X / 2), + RNG.NextSingle(object1.DrawPosition.Y - object1.DrawSize.Y / 2, object1.DrawPosition.Y + object1.DrawSize.Y / 2)); + + // The background is used for ToLocalSpace() since we need to go _inside_ the DrawSizePreservingContainer (Content of TestScene). + heatmap.AddPoint(object2.Position, object1.Position, randomPos, RNG.NextSingle(10, 500)); + InputManager.MoveMouseTo(background.ToScreenSpace(randomPos)); + }, 1, true); + }); + + AddWaitStep("wait for some hit points", 10); } - protected override void LoadComplete() + [Test] + public void TestManualPlacement() { - base.LoadComplete(); - - Scheduler.AddDelayed(() => - { - var randomPos = new Vector2( - RNG.NextSingle(object1.DrawPosition.X - object1.DrawSize.X / 2, object1.DrawPosition.X + object1.DrawSize.X / 2), - RNG.NextSingle(object1.DrawPosition.Y - object1.DrawSize.Y / 2, object1.DrawPosition.Y + object1.DrawSize.Y / 2)); - - // The background is used for ToLocalSpace() since we need to go _inside_ the DrawSizePreservingContainer (Content of TestScene). - heatmap.AddPoint(object2.Position, object1.Position, randomPos, RNG.NextSingle(10, 500)); - InputManager.MoveMouseTo(background.ToScreenSpace(randomPos)); - }, 1, true); + AddStep("return user input", () => InputManager.UseParentInput = true); } protected override bool OnMouseDown(MouseDownEvent e) From d3e4e6325884a2ac753d3c0c2c2601accb7a4d2f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 19:12:48 +0900 Subject: [PATCH 511/871] Remove unnecessary class --- .../Scoring/TimingDistribution.cs | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Scoring/TimingDistribution.cs diff --git a/osu.Game.Rulesets.Osu/Scoring/TimingDistribution.cs b/osu.Game.Rulesets.Osu/Scoring/TimingDistribution.cs deleted file mode 100644 index 46f259f3d8..0000000000 --- a/osu.Game.Rulesets.Osu/Scoring/TimingDistribution.cs +++ /dev/null @@ -1,17 +0,0 @@ -// 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.Rulesets.Osu.Scoring -{ - public class TimingDistribution - { - public readonly int[] Bins; - public readonly double BinSize; - - public TimingDistribution(int binCount, double binSize) - { - Bins = new int[binCount]; - BinSize = binSize; - } - } -} From a3ff25177ad782e562732315a74be1557ca19ffc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 19:12:55 +0900 Subject: [PATCH 512/871] Asyncify statistics load --- .../Ranking/Statistics/StatisticsPanel.cs | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 28a8bc460e..acaf91246d 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.Placeholders; using osu.Game.Scoring; using osuTK; @@ -20,6 +22,7 @@ namespace osu.Game.Screens.Ranking.Statistics protected override bool StartHidden => true; private readonly Container content; + private readonly LoadingSpinner spinner; public StatisticsPanel() { @@ -33,7 +36,11 @@ namespace osu.Game.Screens.Ranking.Statistics Top = SIDE_PADDING, Bottom = 50 // Approximate padding to the bottom of the score panel. }, - Child = content = new Container { RelativeSizeAxes = Axes.Both }, + Children = new Drawable[] + { + content = new Container { RelativeSizeAxes = Axes.Both }, + spinner = new LoadingSpinner() + } }; } @@ -43,8 +50,12 @@ namespace osu.Game.Screens.Ranking.Statistics Score.BindValueChanged(populateStatistics, true); } + private CancellationTokenSource loadCancellation; + private void populateStatistics(ValueChangedEvent score) { + loadCancellation?.Cancel(); + foreach (var child in content) child.FadeOut(150).Expire(); @@ -54,6 +65,8 @@ namespace osu.Game.Screens.Ranking.Statistics content.Add(new MessagePlaceholder("Score has no statistics :(")); else { + spinner.Show(); + var rows = new FillFlowContainer { RelativeSizeAxes = Axes.Both, @@ -73,7 +86,14 @@ namespace osu.Game.Screens.Ranking.Statistics }); } - content.Add(rows); + LoadComponentAsync(rows, d => + { + if (Score.Value != newScore) + return; + + spinner.Hide(); + content.Add(d); + }, (loadCancellation = new CancellationTokenSource()).Token); } } From 8c9506197d30b1635bb51541959978221d7f0d94 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 19:41:36 +0900 Subject: [PATCH 513/871] Increase the number of bins in the timing distribution --- osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs index b319cc5aa9..30d25f581f 100644 --- a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs +++ b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.Statistics /// /// The number of bins on each side of the timing distribution. /// - private const int timing_distribution_bins = 25; + private const int timing_distribution_bins = 50; /// /// The total number of bins in the timing distribution, including bins on both sides and the centre bin at 0. From ef56225d9adfda9bd45038746cd03edfb244a7b0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 19:43:46 +0900 Subject: [PATCH 514/871] Rename CursorPosition -< PositionOffset --- osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs | 8 ++++---- osu.Game.Rulesets.Osu/Statistics/Heatmap.cs | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 9694367210..0a9ce83912 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -88,17 +88,17 @@ namespace osu.Game.Rulesets.Osu.Scoring public readonly HitObject LastHitObject; /// - /// The player's cursor position, if available, at the time of the event. + /// The player's position offset, if available, at the time of the event. /// - public readonly Vector2? CursorPosition; + public readonly Vector2? PositionOffset; - public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, Vector2? cursorPosition) + public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, Vector2? positionOffset) { TimeOffset = timeOffset; Result = result; HitObject = hitObject; LastHitObject = lastHitObject; - CursorPosition = cursorPosition; + PositionOffset = positionOffset; } } } diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs index 8ebc8e9001..b648dd5e47 100644 --- a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs @@ -156,10 +156,10 @@ namespace osu.Game.Rulesets.Osu.Statistics foreach (var e in hitEvents) { - if (e.LastHitObject == null || e.CursorPosition == null) + if (e.LastHitObject == null || e.PositionOffset == null) continue; - AddPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.CursorPosition.Value, radius); + AddPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.PositionOffset.Value, radius); } } } From eab00ec9d9644f32e72268c819fad8cf5801e17c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 19:58:35 +0900 Subject: [PATCH 515/871] Move hit events to the ScoreProcessor --- .../Judgements/OsuHitCircleJudgementResult.cs | 9 ++- .../Objects/Drawables/DrawableHitCircle.cs | 4 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 5 +- .../Scoring/OsuScoreProcessor.cs | 76 ------------------- osu.Game.Rulesets.Osu/Statistics/Heatmap.cs | 36 ++++----- .../Statistics/TimingDistributionGraph.cs | 15 ++-- .../Ranking/TestSceneAccuracyHeatmap.cs | 10 +-- .../Ranking/TestSceneStatisticsPanel.cs | 3 +- .../TestSceneTimingDistributionGraph.cs | 4 +- osu.Game/Rulesets/Scoring/HitEvent.cs | 48 ++++++++++++ osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 18 +++++ osu.Game/Scoring/ScoreInfo.cs | 2 +- 12 files changed, 106 insertions(+), 124 deletions(-) create mode 100644 osu.Game/Rulesets/Scoring/HitEvent.cs diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuHitCircleJudgementResult.cs b/osu.Game.Rulesets.Osu/Judgements/OsuHitCircleJudgementResult.cs index 103d02958d..9b33e746b3 100644 --- a/osu.Game.Rulesets.Osu/Judgements/OsuHitCircleJudgementResult.cs +++ b/osu.Game.Rulesets.Osu/Judgements/OsuHitCircleJudgementResult.cs @@ -10,10 +10,15 @@ namespace osu.Game.Rulesets.Osu.Judgements { public class OsuHitCircleJudgementResult : OsuJudgementResult { + /// + /// The . + /// public HitCircle HitCircle => (HitCircle)HitObject; - public Vector2? HitPosition; - public float? Radius; + /// + /// The position of the player's cursor when was hit. + /// + public Vector2? CursorPositionAtHit; public OsuHitCircleJudgementResult(HitObject hitObject, Judgement judgement) : base(hitObject, judgement) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 2f86400b25..854fc4c91c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -142,11 +142,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { var circleResult = (OsuHitCircleJudgementResult)r; + // Todo: This should also consider misses, but they're a little more interesting to handle, since we don't necessarily know the position at the time of a miss. if (result != HitResult.Miss) { var localMousePosition = ToLocalSpace(inputManager.CurrentState.Mouse.Position); - circleResult.HitPosition = HitObject.StackedPosition + (localMousePosition - DrawSize / 2); - circleResult.Radius = (float)HitObject.Radius; + circleResult.CursorPositionAtHit = HitObject.StackedPosition + (localMousePosition - DrawSize / 2); } circleResult.Type = result; diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index c7003deed2..45980cb3d5 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -29,7 +29,6 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; using System; -using System.Linq; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Screens.Ranking.Statistics; @@ -201,7 +200,7 @@ namespace osu.Game.Rulesets.Osu { RelativeSizeAxes = Axes.X, Height = 130, - Child = new TimingDistributionGraph(score.HitEvents.Cast().ToList()) + Child = new TimingDistributionGraph(score) { RelativeSizeAxes = Axes.Both } @@ -209,7 +208,7 @@ namespace osu.Game.Rulesets.Osu new StatisticContainer("Accuracy Heatmap") { RelativeSizeAxes = Axes.Both, - Child = new Heatmap(score.Beatmap, score.HitEvents.Cast().ToList()) + Child = new Heatmap(score) { RelativeSizeAxes = Axes.Both } diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 0a9ce83912..231a24cac5 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -1,54 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; -using System.Linq; -using JetBrains.Annotations; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; -using osuTK; namespace osu.Game.Rulesets.Osu.Scoring { public class OsuScoreProcessor : ScoreProcessor { - private readonly List hitEvents = new List(); - private HitObject lastHitObject; - - protected override void OnResultApplied(JudgementResult result) - { - base.OnResultApplied(result); - - hitEvents.Add(new HitEvent(result.TimeOffset, result.Type, result.HitObject, lastHitObject, (result as OsuHitCircleJudgementResult)?.HitPosition)); - lastHitObject = result.HitObject; - } - - protected override void OnResultReverted(JudgementResult result) - { - base.OnResultReverted(result); - - hitEvents.RemoveAt(hitEvents.Count - 1); - } - - protected override void Reset(bool storeResults) - { - base.Reset(storeResults); - - hitEvents.Clear(); - lastHitObject = null; - } - - public override void PopulateScore(ScoreInfo score) - { - base.PopulateScore(score); - - score.HitEvents.AddRange(hitEvents.Select(e => e).Cast()); - } - protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) { switch (hitObject) @@ -63,42 +25,4 @@ namespace osu.Game.Rulesets.Osu.Scoring public override HitWindows CreateHitWindows() => new OsuHitWindows(); } - - public readonly struct HitEvent - { - /// - /// The time offset from the end of at which the event occurred. - /// - public readonly double TimeOffset; - - /// - /// The hit result. - /// - public readonly HitResult Result; - - /// - /// The on which the result occurred. - /// - public readonly HitObject HitObject; - - /// - /// The occurring prior to . - /// - [CanBeNull] - public readonly HitObject LastHitObject; - - /// - /// The player's position offset, if available, at the time of the event. - /// - public readonly Vector2? PositionOffset; - - public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, Vector2? positionOffset) - { - TimeOffset = timeOffset; - Result = result; - HitObject = hitObject; - LastHitObject = lastHitObject; - PositionOffset = positionOffset; - } - } } diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs index b648dd5e47..49d7f67b7f 100644 --- a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs @@ -2,17 +2,14 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Layout; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Scoring; using osuTK; using osuTK.Graphics; @@ -36,16 +33,11 @@ namespace osu.Game.Rulesets.Osu.Statistics private GridContainer pointGrid; - private readonly BeatmapInfo beatmap; - private readonly IReadOnlyList hitEvents; - private readonly LayoutValue sizeLayout = new LayoutValue(Invalidation.DrawSize); + private readonly ScoreInfo score; - public Heatmap(BeatmapInfo beatmap, IReadOnlyList hitEvents) + public Heatmap(ScoreInfo score) { - this.beatmap = beatmap; - this.hitEvents = hitEvents; - - AddLayout(sizeLayout); + this.score = score; } [BackgroundDependencyLoader] @@ -149,18 +141,18 @@ namespace osu.Game.Rulesets.Osu.Statistics pointGrid.Content = points; - if (hitEvents.Count > 0) + if (score.HitEvents == null || score.HitEvents.Count == 0) + return; + + // Todo: This should probably not be done like this. + float radius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (score.Beatmap.BaseDifficulty.CircleSize - 5) / 5) / 2; + + foreach (var e in score.HitEvents) { - // Todo: This should probably not be done like this. - float radius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (beatmap.BaseDifficulty.CircleSize - 5) / 5) / 2; + if (e.LastHitObject == null || e.PositionOffset == null) + continue; - foreach (var e in hitEvents) - { - if (e.LastHitObject == null || e.PositionOffset == null) - continue; - - AddPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.PositionOffset.Value, radius); - } + AddPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.PositionOffset.Value, radius); } } diff --git a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs index 30d25f581f..f3ccb0630e 100644 --- a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs +++ b/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.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.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -11,7 +10,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Rulesets.Osu.Scoring; +using osu.Game.Scoring; namespace osu.Game.Rulesets.Osu.Statistics { @@ -37,23 +36,23 @@ namespace osu.Game.Rulesets.Osu.Statistics /// private const float axis_points = 5; - private readonly List hitEvents; + private readonly ScoreInfo score; - public TimingDistributionGraph(List hitEvents) + public TimingDistributionGraph(ScoreInfo score) { - this.hitEvents = hitEvents; + this.score = score; } [BackgroundDependencyLoader] private void load() { - if (hitEvents.Count == 0) + if (score.HitEvents == null || score.HitEvents.Count == 0) return; int[] bins = new int[total_timing_distribution_bins]; - double binSize = hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins; + double binSize = score.HitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins; - foreach (var e in hitEvents) + foreach (var e in score.HitEvents) { int binOffset = (int)(e.TimeOffset / binSize); bins[timing_distribution_centre_bin_index + binOffset]++; diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs index 52cc41fbd8..d8b0594803 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -10,10 +9,9 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Framework.Utils; -using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu; -using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Statistics; +using osu.Game.Scoring; using osu.Game.Tests.Beatmaps; using osuTK; using osuTK.Graphics; @@ -50,7 +48,7 @@ namespace osu.Game.Tests.Visual.Ranking { Position = new Vector2(100, 300), }, - heatmap = new TestHeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo, new List()) + heatmap = new TestHeatmap(new ScoreInfo { Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -93,8 +91,8 @@ namespace osu.Game.Tests.Visual.Ranking private class TestHeatmap : Heatmap { - public TestHeatmap(BeatmapInfo beatmap, List events) - : base(beatmap, events) + public TestHeatmap(ScoreInfo score) + : base(score) { } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index cc3415a530..bcf8a19c61 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -18,7 +17,7 @@ namespace osu.Game.Tests.Visual.Ranking { var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { - HitEvents = TestSceneTimingDistributionGraph.CreateDistributedHitEvents().Cast().ToList(), + HitEvents = TestSceneTimingDistributionGraph.CreateDistributedHitEvents() }; loadPanel(score); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs index 178d6d95b5..d5ee50e636 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs @@ -7,9 +7,9 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Rulesets.Scoring; +using osu.Game.Scoring; using osuTK; namespace osu.Game.Tests.Visual.Ranking @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333") }, - new TimingDistributionGraph(CreateDistributedHitEvents()) + new TimingDistributionGraph(new ScoreInfo { HitEvents = CreateDistributedHitEvents() }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Rulesets/Scoring/HitEvent.cs b/osu.Game/Rulesets/Scoring/HitEvent.cs new file mode 100644 index 0000000000..908ac0c171 --- /dev/null +++ b/osu.Game/Rulesets/Scoring/HitEvent.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using JetBrains.Annotations; +using osu.Game.Rulesets.Objects; +using osuTK; + +namespace osu.Game.Rulesets.Scoring +{ + public readonly struct HitEvent + { + /// + /// The time offset from the end of at which the event occurred. + /// + public readonly double TimeOffset; + + /// + /// The hit result. + /// + public readonly HitResult Result; + + /// + /// The on which the result occurred. + /// + public readonly HitObject HitObject; + + /// + /// The occurring prior to . + /// + [CanBeNull] + public readonly HitObject LastHitObject; + + /// + /// The player's position offset, if available, at the time of the event. + /// + [CanBeNull] + public readonly Vector2? PositionOffset; + + public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, [CanBeNull] Vector2? positionOffset) + { + TimeOffset = timeOffset; + Result = result; + HitObject = hitObject; + LastHitObject = lastHitObject; + PositionOffset = positionOffset; + } + } +} diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 619547aef4..b9f51dfad3 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -9,6 +9,7 @@ using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; +using osu.Game.Rulesets.Objects; using osu.Game.Scoring; namespace osu.Game.Rulesets.Scoring @@ -61,6 +62,9 @@ namespace osu.Game.Rulesets.Scoring private double baseScore; private double bonusScore; + private readonly List hitEvents = new List(); + private HitObject lastHitObject; + private double scoreMultiplier = 1; public ScoreProcessor() @@ -128,6 +132,9 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore += result.Judgement.MaxNumericResult; } + hitEvents.Add(CreateHitEvent(result)); + lastHitObject = result.HitObject; + updateScore(); OnResultApplied(result); @@ -137,6 +144,9 @@ namespace osu.Game.Rulesets.Scoring { } + protected virtual HitEvent CreateHitEvent(JudgementResult result) + => new HitEvent(result.TimeOffset, result.Type, result.HitObject, lastHitObject, null); + protected sealed override void RevertResultInternal(JudgementResult result) { Combo.Value = result.ComboAtJudgement; @@ -159,6 +169,10 @@ namespace osu.Game.Rulesets.Scoring rollingMaxBaseScore -= result.Judgement.MaxNumericResult; } + Debug.Assert(hitEvents.Count > 0); + lastHitObject = hitEvents[^1].LastHitObject; + hitEvents.RemoveAt(hitEvents.Count - 1); + updateScore(); OnResultReverted(result); @@ -219,6 +233,8 @@ namespace osu.Game.Rulesets.Scoring base.Reset(storeResults); scoreResultCounts.Clear(); + hitEvents.Clear(); + lastHitObject = null; if (storeResults) { @@ -259,6 +275,8 @@ namespace osu.Game.Rulesets.Scoring foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) score.Statistics[result] = GetStatistic(result); + + score.HitEvents = new List(hitEvents); } /// diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 6fc5892b3c..84c0d5b54e 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -168,7 +168,7 @@ namespace osu.Game.Scoring [NotMapped] [JsonIgnore] - public List HitEvents = new List(); + public List HitEvents { get; set; } [JsonIgnore] public List Files { get; set; } From 1cbbd6b4427130159832eade1e91ae557c4181e5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 20:03:18 +0900 Subject: [PATCH 516/871] Move timing distribution graph to osu.Game --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs | 2 +- .../Visual/Ranking/TestSceneTimingDistributionGraph.cs | 8 ++++---- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) rename osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs => osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs (96%) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 45980cb3d5..d99fee3b15 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -200,7 +200,7 @@ namespace osu.Game.Rulesets.Osu { RelativeSizeAxes = Axes.X, Height = 130, - Child = new TimingDistributionGraph(score) + Child = new HitEventTimingDistributionGraph(score) { RelativeSizeAxes = Axes.Both } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index bcf8a19c61..210abaef4e 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Ranking { var score = new TestScoreInfo(new OsuRuleset().RulesetInfo) { - HitEvents = TestSceneTimingDistributionGraph.CreateDistributedHitEvents() + HitEvents = TestSceneHitEventTimingDistributionGraph.CreateDistributedHitEvents() }; loadPanel(score); diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs index d5ee50e636..bfdc216aa1 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs @@ -7,16 +7,16 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Statistics; using osuTK; namespace osu.Game.Tests.Visual.Ranking { - public class TestSceneTimingDistributionGraph : OsuTestScene + public class TestSceneHitEventTimingDistributionGraph : OsuTestScene { - public TestSceneTimingDistributionGraph() + public TestSceneHitEventTimingDistributionGraph() { Children = new Drawable[] { @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333") }, - new TimingDistributionGraph(new ScoreInfo { HitEvents = CreateDistributedHitEvents() }) + new HitEventTimingDistributionGraph(new ScoreInfo { HitEvents = CreateDistributedHitEvents() }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs similarity index 96% rename from osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs rename to osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index f3ccb0630e..b258e92aeb 100644 --- a/osu.Game.Rulesets.Osu/Statistics/TimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -12,9 +12,9 @@ using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Scoring; -namespace osu.Game.Rulesets.Osu.Statistics +namespace osu.Game.Screens.Ranking.Statistics { - public class TimingDistributionGraph : CompositeDrawable + public class HitEventTimingDistributionGraph : CompositeDrawable { /// /// The number of bins on each side of the timing distribution. @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Osu.Statistics private readonly ScoreInfo score; - public TimingDistributionGraph(ScoreInfo score) + public HitEventTimingDistributionGraph(ScoreInfo score) { this.score = score; } From 83e6c3efdb32c23f2af26fb2c4661ae49f62275c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 20:31:52 +0900 Subject: [PATCH 517/871] Adjust API for returning statistics --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 43 ++++++++----------- osu.Game.Rulesets.Osu/Statistics/Heatmap.cs | 3 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 18 ++++++++ .../TestSceneTimingDistributionGraph.cs | 3 +- osu.Game/Rulesets/Ruleset.cs | 1 + .../HitEventTimingDistributionGraph.cs | 26 +++++++---- .../Ranking/Statistics/StatisticContainer.cs | 2 +- .../Ranking/Statistics/StatisticItem.cs | 23 ++++++++++ .../Ranking/Statistics/StatisticRow.cs | 11 ++--- .../Ranking/Statistics/StatisticsPanel.cs | 5 ++- 10 files changed, 92 insertions(+), 43 deletions(-) create mode 100644 osu.Game/Screens/Ranking/Statistics/StatisticItem.cs diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index d99fee3b15..aa313c92b3 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -29,7 +29,9 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using osu.Game.Skinning; using System; +using System.Linq; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Screens.Ranking.Statistics; @@ -190,36 +192,29 @@ namespace osu.Game.Rulesets.Osu public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); - public override StatisticRow[] CreateStatistics(ScoreInfo score) => new[] + public override StatisticRow[] CreateStatistics(ScoreInfo score) { - new StatisticRow + var hitCircleEvents = score.HitEvents.Where(e => e.HitObject is HitCircle).ToList(); + + return new[] { - Content = new Drawable[] + new StatisticRow { - new StatisticContainer("Timing Distribution") + Columns = new[] { - RelativeSizeAxes = Axes.X, - Height = 130, - Child = new HitEventTimingDistributionGraph(score) + new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(hitCircleEvents) { - RelativeSizeAxes = Axes.Both - } - }, - new StatisticContainer("Accuracy Heatmap") - { - RelativeSizeAxes = Axes.Both, - Child = new Heatmap(score) + RelativeSizeAxes = Axes.X, + Height = 130 + }), + new StatisticItem("Accuracy Heatmap", new Heatmap(score) { - RelativeSizeAxes = Axes.Both - } - }, - }, - ColumnDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 130), + RelativeSizeAxes = Axes.X, + Height = 130 + }, new Dimension(GridSizeMode.Absolute, 130)), + } } - } - }; + }; + } } } diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs index 49d7f67b7f..86cb8e682f 100644 --- a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs @@ -3,6 +3,7 @@ using System; using System.Diagnostics; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -147,7 +148,7 @@ namespace osu.Game.Rulesets.Osu.Statistics // Todo: This should probably not be done like this. float radius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (score.Beatmap.BaseDifficulty.CircleSize - 5) / 5) / 2; - foreach (var e in score.HitEvents) + foreach (var e in score.HitEvents.Where(e => e.HitObject is HitCircle)) { if (e.LastHitObject == null || e.PositionOffset == null) continue; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 4cdd1fbc24..cd4e699262 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -21,9 +21,12 @@ using osu.Game.Rulesets.Taiko.Difficulty; using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Scoring; using System; +using System.Linq; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Taiko.Edit; +using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Skinning; +using osu.Game.Screens.Ranking.Statistics; using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko @@ -155,5 +158,20 @@ namespace osu.Game.Rulesets.Taiko public int LegacyID => 1; public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); + + public override StatisticRow[] CreateStatistics(ScoreInfo score) => new[] + { + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is Hit).ToList()) + { + RelativeSizeAxes = Axes.X, + Height = 130 + }), + } + } + }; } } diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs index bfdc216aa1..b34529cca7 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Scoring; -using osu.Game.Scoring; using osu.Game.Screens.Ranking.Statistics; using osuTK; @@ -25,7 +24,7 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333") }, - new HitEventTimingDistributionGraph(new ScoreInfo { HitEvents = CreateDistributedHitEvents() }) + new HitEventTimingDistributionGraph(CreateDistributedHitEvents()) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index f05685b6e9..52784e354f 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -210,6 +210,7 @@ namespace osu.Game.Rulesets /// An empty frame for the current ruleset, or null if unsupported. public virtual IConvertibleReplayFrame CreateConvertibleReplayFrame() => null; + [NotNull] public virtual StatisticRow[] CreateStatistics(ScoreInfo score) => Array.Empty(); } } diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index b258e92aeb..4acbc7da3c 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -10,10 +11,13 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Scoring; +using osu.Game.Rulesets.Scoring; namespace osu.Game.Screens.Ranking.Statistics { + /// + /// A graph which displays the distribution of hit timing in a series of s. + /// public class HitEventTimingDistributionGraph : CompositeDrawable { /// @@ -32,27 +36,31 @@ namespace osu.Game.Screens.Ranking.Statistics private const int timing_distribution_centre_bin_index = timing_distribution_bins; /// - /// The number of data points shown on the axis below the graph. + /// The number of data points shown on each side of the axis below the graph. /// private const float axis_points = 5; - private readonly ScoreInfo score; + private readonly IReadOnlyList hitEvents; - public HitEventTimingDistributionGraph(ScoreInfo score) + /// + /// Creates a new . + /// + /// The s to display the timing distribution of. + public HitEventTimingDistributionGraph(IReadOnlyList hitEvents) { - this.score = score; + this.hitEvents = hitEvents; } [BackgroundDependencyLoader] private void load() { - if (score.HitEvents == null || score.HitEvents.Count == 0) + if (hitEvents == null || hitEvents.Count == 0) return; int[] bins = new int[total_timing_distribution_bins]; - double binSize = score.HitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins; + double binSize = hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins; - foreach (var e in score.HitEvents) + foreach (var e in hitEvents) { int binOffset = (int)(e.TimeOffset / binSize); bins[timing_distribution_centre_bin_index + binOffset]++; @@ -67,6 +75,8 @@ namespace osu.Game.Screens.Ranking.Statistics InternalChild = new GridContainer { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, Width = 0.8f, Content = new[] diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs index d7b42c1c2f..b8dde8f85e 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs @@ -11,7 +11,7 @@ using osuTK; namespace osu.Game.Screens.Ranking.Statistics { - public class StatisticContainer : Container + internal class StatisticContainer : Container { protected override Container Content => content; diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs new file mode 100644 index 0000000000..2605ae9f1b --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/StatisticItem.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. + +using JetBrains.Annotations; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; + +namespace osu.Game.Screens.Ranking.Statistics +{ + public class StatisticItem + { + public readonly string Name; + public readonly Drawable Content; + public readonly Dimension Dimension; + + public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null) + { + Name = name; + Content = content; + Dimension = dimension; + } + } +} diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs index 5d39ef57b2..ebab148fc2 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs @@ -1,15 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; +using JetBrains.Annotations; namespace osu.Game.Screens.Ranking.Statistics { public class StatisticRow { - public Drawable[] Content = Array.Empty(); - public Dimension[] ColumnDimensions = Array.Empty(); + /// + /// The columns of this . + /// + [ItemCanBeNull] + public StatisticItem[] Columns; } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index acaf91246d..3d81229ac3 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.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 System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -80,8 +81,8 @@ namespace osu.Game.Screens.Ranking.Statistics { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Content = new[] { row.Content }, - ColumnDimensions = row.ColumnDimensions, + Content = new[] { row.Columns?.Select(c => c?.Content).ToArray() }, + ColumnDimensions = Enumerable.Range(0, row.Columns?.Length ?? 0).Select(i => row.Columns[i]?.Dimension ?? new Dimension()).ToArray(), RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } }); } From ad3bc99e7c9bf6d39acb5f7595d97e1369ad4c56 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 20:48:53 +0900 Subject: [PATCH 518/871] Fix hit event position offset not being set --- osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs | 3 +++ osu.Game/Rulesets/Scoring/HitEvent.cs | 2 ++ 2 files changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs index 231a24cac5..86ec76e373 100644 --- a/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs +++ b/osu.Game.Rulesets.Osu/Scoring/OsuScoreProcessor.cs @@ -11,6 +11,9 @@ namespace osu.Game.Rulesets.Osu.Scoring { public class OsuScoreProcessor : ScoreProcessor { + protected override HitEvent CreateHitEvent(JudgementResult result) + => base.CreateHitEvent(result).With((result as OsuHitCircleJudgementResult)?.CursorPositionAtHit); + protected override JudgementResult CreateResult(HitObject hitObject, Judgement judgement) { switch (hitObject) diff --git a/osu.Game/Rulesets/Scoring/HitEvent.cs b/osu.Game/Rulesets/Scoring/HitEvent.cs index 908ac0c171..a2770ec580 100644 --- a/osu.Game/Rulesets/Scoring/HitEvent.cs +++ b/osu.Game/Rulesets/Scoring/HitEvent.cs @@ -44,5 +44,7 @@ namespace osu.Game.Rulesets.Scoring LastHitObject = lastHitObject; PositionOffset = positionOffset; } + + public HitEvent With(Vector2? positionOffset) => new HitEvent(TimeOffset, Result, HitObject, LastHitObject, positionOffset); } } From 34a8fcfd2f6a3cd1d380400801c95e02930016c2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 20:53:24 +0900 Subject: [PATCH 519/871] Fix potential off-by-one --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 4acbc7da3c..43de862007 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -58,7 +58,7 @@ namespace osu.Game.Screens.Ranking.Statistics return; int[] bins = new int[total_timing_distribution_bins]; - double binSize = hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins; + double binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins); foreach (var e in hitEvents) { From 5ce2c712d343e46e939f62d36f9ad39e047e414d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 20:53:43 +0900 Subject: [PATCH 520/871] Fix statistics not being wrapped by containers --- osu.Game/Rulesets/Ruleset.cs | 5 +++++ .../Ranking/Statistics/StatisticContainer.cs | 10 ++++++++-- .../Ranking/Statistics/StatisticItem.cs | 20 +++++++++++++++++++ .../Ranking/Statistics/StatisticRow.cs | 5 ++++- .../Ranking/Statistics/StatisticsPanel.cs | 11 ++++++++-- 5 files changed, 46 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index 52784e354f..a325e641a4 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -210,6 +210,11 @@ namespace osu.Game.Rulesets /// An empty frame for the current ruleset, or null if unsupported. public virtual IConvertibleReplayFrame CreateConvertibleReplayFrame() => null; + /// + /// Creates the statistics for a to be displayed in the results screen. + /// + /// The to create the statistics for. The score is guaranteed to have populated. + /// The s to display. Each may contain 0 or more . [NotNull] public virtual StatisticRow[] CreateStatistics(ScoreInfo score) => Array.Empty(); } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs index b8dde8f85e..b063893633 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs @@ -19,9 +19,13 @@ namespace osu.Game.Screens.Ranking.Statistics public StatisticContainer(string name) { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + InternalChild = new GridContainer { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Content = new[] { new Drawable[] @@ -56,13 +60,15 @@ namespace osu.Game.Screens.Ranking.Statistics { content = new Container { - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, } }, }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), } }; } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs index 2605ae9f1b..a3ef5bf99e 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs @@ -7,12 +7,32 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Screens.Ranking.Statistics { + /// + /// An item to be displayed in a row of statistics inside the results screen. + /// public class StatisticItem { + /// + /// The name of this item. + /// public readonly string Name; + + /// + /// The content to be displayed. + /// public readonly Drawable Content; + + /// + /// The of this row. This can be thought of as the column dimension of an encompassing . + /// public readonly Dimension Dimension; + /// + /// Creates a new , to be displayed inside a in the results screen. + /// + /// The name of this item. + /// The content to be displayed. + /// The of this row. This can be thought of as the column dimension of an encompassing . public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null) { Name = name; diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs b/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs index ebab148fc2..e1ca9799a3 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticRow.cs @@ -5,12 +5,15 @@ using JetBrains.Annotations; namespace osu.Game.Screens.Ranking.Statistics { + /// + /// A row of statistics to be displayed in the results screen. + /// public class StatisticRow { /// /// The columns of this . /// - [ItemCanBeNull] + [ItemNotNull] public StatisticItem[] Columns; } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 3d81229ac3..328b6933a0 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -81,8 +81,15 @@ namespace osu.Game.Screens.Ranking.Statistics { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Content = new[] { row.Columns?.Select(c => c?.Content).ToArray() }, - ColumnDimensions = Enumerable.Range(0, row.Columns?.Length ?? 0).Select(i => row.Columns[i]?.Dimension ?? new Dimension()).ToArray(), + Content = new[] + { + row.Columns?.Select(c => new StatisticContainer(c.Name) + { + Child = c.Content + }).Cast().ToArray() + }, + ColumnDimensions = Enumerable.Range(0, row.Columns?.Length ?? 0) + .Select(i => row.Columns[i].Dimension ?? new Dimension()).ToArray(), RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } }); } From 8aea8267fb989172c535a331aacddcfbe048e3b7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 20:58:05 +0900 Subject: [PATCH 521/871] Add some padding --- osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs index b063893633..d9e5e1294a 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs @@ -62,6 +62,7 @@ namespace osu.Game.Screens.Ranking.Statistics { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 15 } } }, }, From 89a863a3379262c600f9774728e63117c8037567 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 21:02:20 +0900 Subject: [PATCH 522/871] Refactor OsuRuleset --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 33 ++++++++++++----------------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index aa313c92b3..3fb8f574b3 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -192,29 +192,24 @@ namespace osu.Game.Rulesets.Osu public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); - public override StatisticRow[] CreateStatistics(ScoreInfo score) + public override StatisticRow[] CreateStatistics(ScoreInfo score) => new[] { - var hitCircleEvents = score.HitEvents.Where(e => e.HitObject is HitCircle).ToList(); - - return new[] + new StatisticRow { - new StatisticRow + Columns = new[] { - Columns = new[] + new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is HitCircle).ToList()) { - new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(hitCircleEvents) - { - RelativeSizeAxes = Axes.X, - Height = 130 - }), - new StatisticItem("Accuracy Heatmap", new Heatmap(score) - { - RelativeSizeAxes = Axes.X, - Height = 130 - }, new Dimension(GridSizeMode.Absolute, 130)), - } + RelativeSizeAxes = Axes.X, + Height = 130 + }), + new StatisticItem("Accuracy Heatmap", new Heatmap(score) + { + RelativeSizeAxes = Axes.X, + Height = 130 + }, new Dimension(GridSizeMode.Absolute, 130)), } - }; - } + } + }; } } From 49997c54d01be791bcc1bdfe4d2ee52abf063edb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 21:14:17 +0900 Subject: [PATCH 523/871] Remove unused class --- osu.Game.Rulesets.Osu/Scoring/HitOffset.cs | 23 ---------------------- 1 file changed, 23 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Scoring/HitOffset.cs diff --git a/osu.Game.Rulesets.Osu/Scoring/HitOffset.cs b/osu.Game.Rulesets.Osu/Scoring/HitOffset.cs deleted file mode 100644 index e6a5a01b48..0000000000 --- a/osu.Game.Rulesets.Osu/Scoring/HitOffset.cs +++ /dev/null @@ -1,23 +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 osuTK; - -namespace osu.Game.Rulesets.Osu.Scoring -{ - public class HitOffset - { - public readonly Vector2 Position1; - public readonly Vector2 Position2; - public readonly Vector2 HitPosition; - public readonly float Radius; - - public HitOffset(Vector2 position1, Vector2 position2, Vector2 hitPosition, float radius) - { - Position1 = position1; - Position2 = position2; - HitPosition = hitPosition; - Radius = radius; - } - } -} From 863666f7c483d83dda88a3acf9b1bb1b33f70bce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 21:14:31 +0900 Subject: [PATCH 524/871] Move accuracy heatmap to osu! ruleset, rename, remove magic number --- .../TestSceneAccuracyHeatmap.cs | 16 ++++++++-------- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- .../{Heatmap.cs => AccuracyHeatmap.cs} | 11 ++++++----- 3 files changed, 15 insertions(+), 14 deletions(-) rename {osu.Game.Tests/Visual/Ranking => osu.Game.Rulesets.Osu.Tests}/TestSceneAccuracyHeatmap.cs (86%) rename osu.Game.Rulesets.Osu/Statistics/{Heatmap.cs => AccuracyHeatmap.cs} (95%) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs similarity index 86% rename from osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs rename to osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs index d8b0594803..f2a36ea017 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs @@ -9,21 +9,21 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Framework.Threading; using osu.Framework.Utils; -using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Scoring; using osu.Game.Tests.Beatmaps; +using osu.Game.Tests.Visual; using osuTK; using osuTK.Graphics; -namespace osu.Game.Tests.Visual.Ranking +namespace osu.Game.Rulesets.Osu.Tests { public class TestSceneAccuracyHeatmap : OsuManualInputManagerTestScene { private Box background; private Drawable object1; private Drawable object2; - private TestHeatmap heatmap; + private TestAccuracyHeatmap accuracyHeatmap; private ScheduledDelegate automaticAdditionDelegate; [SetUp] @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Ranking { Position = new Vector2(100, 300), }, - heatmap = new TestHeatmap(new ScoreInfo { Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }) + accuracyHeatmap = new TestAccuracyHeatmap(new ScoreInfo { Beatmap = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -69,7 +69,7 @@ namespace osu.Game.Tests.Visual.Ranking RNG.NextSingle(object1.DrawPosition.Y - object1.DrawSize.Y / 2, object1.DrawPosition.Y + object1.DrawSize.Y / 2)); // The background is used for ToLocalSpace() since we need to go _inside_ the DrawSizePreservingContainer (Content of TestScene). - heatmap.AddPoint(object2.Position, object1.Position, randomPos, RNG.NextSingle(10, 500)); + accuracyHeatmap.AddPoint(object2.Position, object1.Position, randomPos, RNG.NextSingle(10, 500)); InputManager.MoveMouseTo(background.ToScreenSpace(randomPos)); }, 1, true); }); @@ -85,13 +85,13 @@ namespace osu.Game.Tests.Visual.Ranking protected override bool OnMouseDown(MouseDownEvent e) { - heatmap.AddPoint(object2.Position, object1.Position, background.ToLocalSpace(e.ScreenSpaceMouseDownPosition), 50); + accuracyHeatmap.AddPoint(object2.Position, object1.Position, background.ToLocalSpace(e.ScreenSpaceMouseDownPosition), 50); return true; } - private class TestHeatmap : Heatmap + private class TestAccuracyHeatmap : AccuracyHeatmap { - public TestHeatmap(ScoreInfo score) + public TestAccuracyHeatmap(ScoreInfo score) : base(score) { } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 3fb8f574b3..65f26c0647 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -203,7 +203,7 @@ namespace osu.Game.Rulesets.Osu RelativeSizeAxes = Axes.X, Height = 130 }), - new StatisticItem("Accuracy Heatmap", new Heatmap(score) + new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score) { RelativeSizeAxes = Axes.X, Height = 130 diff --git a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs similarity index 95% rename from osu.Game.Rulesets.Osu/Statistics/Heatmap.cs rename to osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 86cb8e682f..10ca3eb9be 100644 --- a/osu.Game.Rulesets.Osu/Statistics/Heatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Utils; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Scoring; using osuTK; @@ -16,17 +17,17 @@ using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Statistics { - public class Heatmap : CompositeDrawable + public class AccuracyHeatmap : CompositeDrawable { /// - /// Size of the inner circle containing the "hit" points, relative to the size of this . + /// Size of the inner circle containing the "hit" points, relative to the size of this . /// All other points outside of the inner circle are "miss" points. /// private const float inner_portion = 0.8f; /// /// Number of rows/columns of points. - /// 4px per point @ 128x128 size (the contents of the are always square). 1024 total points. + /// 4px per point @ 128x128 size (the contents of the are always square). 1024 total points. /// private const int points_per_dimension = 32; @@ -36,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Statistics private readonly ScoreInfo score; - public Heatmap(ScoreInfo score) + public AccuracyHeatmap(ScoreInfo score) { this.score = score; } @@ -170,7 +171,7 @@ namespace osu.Game.Rulesets.Osu.Statistics // Convert the above into the local search space. Vector2 localCentre = new Vector2(points_per_dimension) / 2; float localRadius = localCentre.X * inner_portion * normalisedDistance; // The radius inside the inner portion which of the heatmap which the closest point lies. - double localAngle = finalAngle + 3 * Math.PI / 4; // The angle inside the heatmap on which the closest point lies. + double localAngle = finalAngle + Math.PI - MathUtils.DegreesToRadians(rotation); // The angle inside the heatmap on which the closest point lies. Vector2 localPoint = localCentre + localRadius * new Vector2((float)Math.Cos(localAngle), (float)Math.Sin(localAngle)); // Find the most relevant hit point. From 81ad257a17ae2bab2df18ab9ceddb1e77202c149 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 21:18:58 +0900 Subject: [PATCH 525/871] Add timing distribution to mania ruleset --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index a37aaa8cc4..b8725af856 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -30,6 +30,7 @@ using osu.Game.Rulesets.Mania.Skinning; using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; using osu.Game.Scoring; +using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Rulesets.Mania { @@ -307,6 +308,21 @@ namespace osu.Game.Rulesets.Mania { return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast().OrderByDescending(i => i).First(v => variant >= v); } + + public override StatisticRow[] CreateStatistics(ScoreInfo score) => new[] + { + new StatisticRow + { + Columns = new[] + { + new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents) + { + RelativeSizeAxes = Axes.X, + Height = 130 + }), + } + } + }; } public enum PlayfieldType From 25abdc290331e6d2ffea212259771d25cbf5b3b7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 21:41:48 +0900 Subject: [PATCH 526/871] General cleanups --- osu.Game/Rulesets/Scoring/HitEvent.cs | 18 +++++++- osu.Game/Rulesets/Scoring/HitWindows.cs | 2 +- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 12 ------ osu.Game/Screens/Ranking/ScorePanel.cs | 18 +++++--- osu.Game/Screens/Ranking/ScorePanelList.cs | 43 ++++++++++++++----- .../Ranking/ScorePanelTrackingContainer.cs | 17 +++++++- .../Ranking/Statistics/StatisticContainer.cs | 23 ++++++---- .../Ranking/Statistics/StatisticItem.cs | 4 +- .../Ranking/Statistics/StatisticsPanel.cs | 9 ++-- 9 files changed, 98 insertions(+), 48 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/HitEvent.cs b/osu.Game/Rulesets/Scoring/HitEvent.cs index a2770ec580..ea2975a6c4 100644 --- a/osu.Game/Rulesets/Scoring/HitEvent.cs +++ b/osu.Game/Rulesets/Scoring/HitEvent.cs @@ -7,6 +7,9 @@ using osuTK; namespace osu.Game.Rulesets.Scoring { + /// + /// A generated by the containing extra statistics around a . + /// public readonly struct HitEvent { /// @@ -31,11 +34,19 @@ namespace osu.Game.Rulesets.Scoring public readonly HitObject LastHitObject; /// - /// The player's position offset, if available, at the time of the event. + /// A position offset, if available, at the time of the event. /// [CanBeNull] public readonly Vector2? PositionOffset; + /// + /// Creates a new . + /// + /// The time offset from the end of at which the event occurs. + /// The . + /// The that triggered the event. + /// The previous . + /// A positional offset. public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, [CanBeNull] Vector2? positionOffset) { TimeOffset = timeOffset; @@ -45,6 +56,11 @@ namespace osu.Game.Rulesets.Scoring PositionOffset = positionOffset; } + /// + /// Creates a new with an optional positional offset. + /// + /// The positional offset. + /// The new . public HitEvent With(Vector2? positionOffset) => new HitEvent(TimeOffset, Result, HitObject, LastHitObject, positionOffset); } } diff --git a/osu.Game/Rulesets/Scoring/HitWindows.cs b/osu.Game/Rulesets/Scoring/HitWindows.cs index 77acbd4137..018b50bd3d 100644 --- a/osu.Game/Rulesets/Scoring/HitWindows.cs +++ b/osu.Game/Rulesets/Scoring/HitWindows.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Scoring /// Retrieves the with the largest hit window that produces a successful hit. /// /// The lowest allowed successful . - public HitResult LowestSuccessfulHitResult() + protected HitResult LowestSuccessfulHitResult() { for (var result = HitResult.Meh; result <= HitResult.Perfect; ++result) { diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index b9f51dfad3..22ec023f58 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -136,12 +136,6 @@ namespace osu.Game.Rulesets.Scoring lastHitObject = result.HitObject; updateScore(); - - OnResultApplied(result); - } - - protected virtual void OnResultApplied(JudgementResult result) - { } protected virtual HitEvent CreateHitEvent(JudgementResult result) @@ -174,12 +168,6 @@ namespace osu.Game.Rulesets.Scoring hitEvents.RemoveAt(hitEvents.Count - 1); updateScore(); - - OnResultReverted(result); - } - - protected virtual void OnResultReverted(JudgementResult result) - { } private void updateScore() diff --git a/osu.Game/Screens/Ranking/ScorePanel.cs b/osu.Game/Screens/Ranking/ScorePanel.cs index 257279bdc9..9633f5c533 100644 --- a/osu.Game/Screens/Ranking/ScorePanel.cs +++ b/osu.Game/Screens/Ranking/ScorePanel.cs @@ -76,12 +76,11 @@ namespace osu.Game.Screens.Ranking private static readonly Color4 contracted_middle_layer_colour = Color4Extensions.FromHex("#353535"); public event Action StateChanged; - public Action PostExpandAction; /// - /// Whether this can enter into an state. + /// An action to be invoked if this is clicked while in an expanded state. /// - public bool CanExpand = true; + public Action PostExpandAction; public readonly ScoreInfo Score; @@ -250,6 +249,7 @@ namespace osu.Game.Screens.Ranking { base.Size = value; + // Auto-size isn't used to avoid 1-frame issues and because the score panel is removed/re-added to the container. if (trackingContainer != null) trackingContainer.Size = value; } @@ -259,8 +259,7 @@ namespace osu.Game.Screens.Ranking { if (State == PanelState.Contracted) { - if (CanExpand) - State = PanelState.Expanded; + State = PanelState.Expanded; return true; } @@ -276,6 +275,15 @@ namespace osu.Game.Screens.Ranking private ScorePanelTrackingContainer trackingContainer; + /// + /// Creates a which this can reside inside. + /// The will track the size of this . + /// + /// + /// This is immediately added as a child of the . + /// + /// The . + /// If a already exists. public ScorePanelTrackingContainer CreateTrackingContainer() { if (trackingContainer != null) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 8f9064c2d1..9ebd7822c0 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -26,12 +26,13 @@ namespace osu.Game.Screens.Ranking /// private const float expanded_panel_spacing = 15; + /// + /// An action to be invoked if a is clicked while in an expanded state. + /// public Action PostExpandAction; public readonly Bindable SelectedScore = new Bindable(); - public float CurrentScrollPosition => scroll.Current; - private readonly Flow flow; private readonly Scroll scroll; private ScorePanel expandedPanel; @@ -47,16 +48,13 @@ namespace osu.Game.Screens.Ranking { RelativeSizeAxes = Axes.Both, HandleScroll = () => expandedPanel?.IsHovered != true, // handle horizontal scroll only when not hovering the expanded panel. - Children = new Drawable[] + Child = flow = new Flow { - flow = new Flow - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(panel_spacing, 0), - AutoSizeAxes = Axes.Both, - }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(panel_spacing, 0), + AutoSizeAxes = Axes.Both, } }; } @@ -166,6 +164,10 @@ namespace osu.Game.Screens.Ranking private bool handleInput = true; + /// + /// Whether this or any of the s contained should handle scroll or click input. + /// Setting to false will also hide the scrollbar. + /// public bool HandleInput { get => handleInput; @@ -180,10 +182,24 @@ namespace osu.Game.Screens.Ranking public override bool PropagateNonPositionalInputSubTree => HandleInput && base.PropagateNonPositionalInputSubTree; + /// + /// Enumerates all s contained in this . + /// + /// public IEnumerable GetScorePanels() => flow.Select(t => t.Panel); + /// + /// Finds the corresponding to a . + /// + /// The to find the corresponding for. + /// The . public ScorePanel GetPanelForScore(ScoreInfo score) => flow.Single(t => t.Panel.Score == score).Panel; + /// + /// Detaches a from its , allowing the panel to be moved elsewhere in the hierarchy. + /// + /// The to detach. + /// If is not a part of this . public void Detach(ScorePanel panel) { var container = flow.FirstOrDefault(t => t.Panel == panel); @@ -193,6 +209,11 @@ namespace osu.Game.Screens.Ranking container.Detach(); } + /// + /// Attaches a to its in this . + /// + /// The to attach. + /// If is not a part of this . public void Attach(ScorePanel panel) { var container = flow.FirstOrDefault(t => t.Panel == panel); diff --git a/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs b/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs index f6f26d0f8a..c8010d1c32 100644 --- a/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs +++ b/osu.Game/Screens/Ranking/ScorePanelTrackingContainer.cs @@ -6,16 +6,27 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Screens.Ranking { + /// + /// A which tracks the size of a , to which the can be added or removed. + /// public class ScorePanelTrackingContainer : CompositeDrawable { + /// + /// The that created this . + /// public readonly ScorePanel Panel; - public ScorePanelTrackingContainer(ScorePanel panel) + internal ScorePanelTrackingContainer(ScorePanel panel) { Panel = panel; Attach(); } + /// + /// Detaches the from this , removing it as a child. + /// This will continue tracking any size changes. + /// + /// If the is already detached. public void Detach() { if (InternalChildren.Count == 0) @@ -24,6 +35,10 @@ namespace osu.Game.Screens.Ranking RemoveInternal(Panel); } + /// + /// Attaches the to this , adding it as a child. + /// + /// If the is already attached. public void Attach() { if (InternalChildren.Count > 0) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs index d9e5e1294a..ed98698411 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticContainer.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticContainer.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.CodeAnalysis; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -11,13 +12,16 @@ using osuTK; namespace osu.Game.Screens.Ranking.Statistics { - internal class StatisticContainer : Container + /// + /// Wraps a to add a header and suitable layout for use in . + /// + internal class StatisticContainer : CompositeDrawable { - protected override Container Content => content; - - private readonly Container content; - - public StatisticContainer(string name) + /// + /// Creates a new . + /// + /// The to display. + public StatisticContainer([NotNull] StatisticItem item) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -50,7 +54,7 @@ namespace osu.Game.Screens.Ranking.Statistics { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Text = name, + Text = item.Name, Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), } } @@ -58,11 +62,12 @@ namespace osu.Game.Screens.Ranking.Statistics }, new Drawable[] { - content = new Container + new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 15 } + Margin = new MarginPadding { Top = 15 }, + Child = item.Content } }, }, diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs index a3ef5bf99e..e959ed24fc 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticItem.cs @@ -30,9 +30,9 @@ namespace osu.Game.Screens.Ranking.Statistics /// /// Creates a new , to be displayed inside a in the results screen. /// - /// The name of this item. + /// The name of the item. /// The content to be displayed. - /// The of this row. This can be thought of as the column dimension of an encompassing . + /// The of this item. This can be thought of as the column dimension of an encompassing . public StatisticItem([NotNull] string name, [NotNull] Drawable content, [CanBeNull] Dimension dimension = null) { Name = name; diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 328b6933a0..c560cc9852 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -83,10 +83,7 @@ namespace osu.Game.Screens.Ranking.Statistics AutoSizeAxes = Axes.Y, Content = new[] { - row.Columns?.Select(c => new StatisticContainer(c.Name) - { - Child = c.Content - }).Cast().ToArray() + row.Columns?.Select(c => new StatisticContainer(c)).Cast().ToArray() }, ColumnDimensions = Enumerable.Range(0, row.Columns?.Length ?? 0) .Select(i => row.Columns[i].Dimension ?? new Dimension()).ToArray(), @@ -105,8 +102,8 @@ namespace osu.Game.Screens.Ranking.Statistics } } - protected override void PopIn() => this.FadeIn(); + protected override void PopIn() => this.FadeIn(150, Easing.OutQuint); - protected override void PopOut() => this.FadeOut(); + protected override void PopOut() => this.FadeOut(150, Easing.OutQuint); } } From 49bdd897758bf918224e8fafa8a0e02e9d0af70a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 21:54:09 +0900 Subject: [PATCH 527/871] Cleanup ReplayPlayer adjustments --- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Play/ReplayPlayer.cs | 21 +++++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 83991ad027..cfcef5155d 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -460,7 +460,7 @@ namespace osu.Game.Screens.Play { var score = new ScoreInfo { - Beatmap = Beatmap.Value.BeatmapInfo, + Beatmap = gameplayBeatmap.BeatmapInfo, Ruleset = rulesetInfo, Mods = Mods.Value.ToArray(), }; diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index d7580ea271..8a925958fd 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Screens; using osu.Game.Scoring; using osu.Game.Screens.Ranking; @@ -25,16 +24,18 @@ namespace osu.Game.Screens.Play DrawableRuleset?.SetReplayScore(score); } - protected override void GotoRanking() - { - this.Push(CreateResults(CreateScore())); - } - protected override ResultsScreen CreateResults(ScoreInfo score) => new SoloResultsScreen(score, false); - // protected override ScoreInfo CreateScore() - // { - // return score.ScoreInfo; - // } + protected override ScoreInfo CreateScore() + { + var baseScore = base.CreateScore(); + + // Since the replay score doesn't contain statistics, we'll pass them through here. + // We also have to pass in the beatmap to get the post-mod-application version. + score.ScoreInfo.Beatmap = baseScore.Beatmap; + score.ScoreInfo.HitEvents = baseScore.HitEvents; + + return score.ScoreInfo; + } } } From 740b01c049592483adb0dbc6f8e0b2d4cbf9e9d5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 22:05:58 +0900 Subject: [PATCH 528/871] Add xmldoc --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 22ec023f58..9c1bc35169 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -138,6 +138,11 @@ namespace osu.Game.Rulesets.Scoring updateScore(); } + /// + /// Creates the that describes a . + /// + /// The to describe. + /// The . protected virtual HitEvent CreateHitEvent(JudgementResult result) => new HitEvent(result.TimeOffset, result.Type, result.HitObject, lastHitObject, null); From 486b899e8f31c262b5ab911792bb5269c0edc880 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 22:11:29 +0900 Subject: [PATCH 529/871] Rename method --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 2 +- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index b8725af856..44e8f343d5 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -309,7 +309,7 @@ namespace osu.Game.Rulesets.Mania return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast().OrderByDescending(i => i).First(v => variant >= v); } - public override StatisticRow[] CreateStatistics(ScoreInfo score) => new[] + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score) => new[] { new StatisticRow { diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 65f26c0647..8222eba339 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); - public override StatisticRow[] CreateStatistics(ScoreInfo score) => new[] + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score) => new[] { new StatisticRow { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index cd4e699262..92b04e8397 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -159,7 +159,7 @@ namespace osu.Game.Rulesets.Taiko public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); - public override StatisticRow[] CreateStatistics(ScoreInfo score) => new[] + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score) => new[] { new StatisticRow { diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index a325e641a4..f9c2b09be9 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -216,6 +216,6 @@ namespace osu.Game.Rulesets /// The to create the statistics for. The score is guaranteed to have populated. /// The s to display. Each may contain 0 or more . [NotNull] - public virtual StatisticRow[] CreateStatistics(ScoreInfo score) => Array.Empty(); + public virtual StatisticRow[] CreateStatisticsForScore(ScoreInfo score) => Array.Empty(); } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index c560cc9852..efb9397a23 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -75,7 +75,7 @@ namespace osu.Game.Screens.Ranking.Statistics Spacing = new Vector2(30, 15), }; - foreach (var row in newScore.Ruleset.CreateInstance().CreateStatistics(newScore)) + foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore)) { rows.Add(new GridContainer { From 4cb49cd606a57ec3aa4770ad8ab1caf5b11eeaee Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 22:21:34 +0900 Subject: [PATCH 530/871] Add minimum height to the timing distribution graph --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 43de862007..9b46bea2cb 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -69,7 +69,7 @@ namespace osu.Game.Screens.Ranking.Statistics int maxCount = bins.Max(); var bars = new Drawable[total_timing_distribution_bins]; for (int i = 0; i < bars.Length; i++) - bars[i] = new Bar { Height = (float)bins[i] / maxCount }; + bars[i] = new Bar { Height = Math.Max(0.05f, (float)bins[i] / maxCount) }; Container axisFlow; From 2814433d7cf572a78218db8da6c055e7139e1962 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 22:22:07 +0900 Subject: [PATCH 531/871] Rename test file --- ...butionGraph.cs => TestSceneHitEventTimingDistributionGraph.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename osu.Game.Tests/Visual/Ranking/{TestSceneTimingDistributionGraph.cs => TestSceneHitEventTimingDistributionGraph.cs} (100%) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs similarity index 100% rename from osu.Game.Tests/Visual/Ranking/TestSceneTimingDistributionGraph.cs rename to osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs From 22f3fd487b9cb7f346f068200b02f4fdf611e677 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 22:43:25 +0900 Subject: [PATCH 532/871] Mark test as headless --- osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs index a97566ba7b..cd3669f160 100644 --- a/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs +++ b/osu.Game.Tests/Gameplay/TestSceneGameplayClockContainer.cs @@ -3,6 +3,7 @@ using System; using NUnit.Framework; +using osu.Framework.Testing; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Play; @@ -10,6 +11,7 @@ using osu.Game.Tests.Visual; namespace osu.Game.Tests.Gameplay { + [HeadlessTest] public class TestSceneGameplayClockContainer : OsuTestScene { [Test] From 037bd3b46330bc2f2942bf761f0e883455b02d5c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 19 Jun 2020 22:47:55 +0900 Subject: [PATCH 533/871] Fix possible nullref --- osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs | 6 ++++++ osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 3 +++ 2 files changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index 210abaef4e..8700fbeb42 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -29,6 +29,12 @@ namespace osu.Game.Tests.Visual.Ranking loadPanel(new TestScoreInfo(new OsuRuleset().RulesetInfo)); } + [Test] + public void TestNullScore() + { + loadPanel(null); + } + private void loadPanel(ScoreInfo score) => AddStep("load panel", () => { Child = new StatisticsPanel diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index efb9397a23..cac2bf866b 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -62,6 +62,9 @@ namespace osu.Game.Screens.Ranking.Statistics var newScore = score.NewValue; + if (newScore == null) + return; + if (newScore.HitEvents == null || newScore.HitEvents.Count == 0) content.Add(new MessagePlaceholder("Score has no statistics :(")); else From 3021bdf3059fc5d9ce2165d3594828a65b5ca4fc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 20 Jun 2020 00:34:01 +0900 Subject: [PATCH 534/871] 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 b95b794004..119c309675 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 6ec57e5100..bec3bc9d39 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 0bfff24805..de5130b66a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 0046cc08e913572866146cebdb512aed9f2ec725 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Fri, 19 Jun 2020 18:40:36 +0100 Subject: [PATCH 535/871] Add test cases for different mods and rates. Cleanup test scene. --- .../Gameplay/TestSceneStoryboardSamples.cs | 44 ++++++++++++------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 60911d6792..0803da6678 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -10,9 +10,8 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.IO.Stores; using osu.Framework.Testing; -using osu.Framework.Timing; using osu.Game.Audio; -using osu.Game.Beatmaps; +using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -73,26 +72,39 @@ namespace osu.Game.Tests.Gameplay AddUntilStep("sample playback succeeded", () => sample.LifetimeEnd < double.MaxValue); } - [Test] - public void TestSamplePlaybackWithRateMods() + [TestCase(typeof(OsuModDoubleTime), 1.5)] + [TestCase(typeof(OsuModHalfTime), 0.75)] + [TestCase(typeof(ModWindUp), 1.5)] + [TestCase(typeof(ModWindDown), 0.75)] + [TestCase(typeof(OsuModDoubleTime), 2)] + [TestCase(typeof(OsuModHalfTime), 0.5)] + [TestCase(typeof(ModWindUp), 2)] + [TestCase(typeof(ModWindDown), 0.5)] + public void TestSamplePlaybackWithRateMods(Type expectedMod, double expectedRate) { GameplayClockContainer gameplayContainer = null; TestDrawableStoryboardSample sample = null; - OsuModDoubleTime doubleTimeMod = null; + Mod testedMod = Activator.CreateInstance(expectedMod) as Mod; - AddStep("create container", () => + switch (testedMod) { - var beatmap = Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + case ModRateAdjust m: + m.SpeedChange.Value = expectedRate; + break; - Add(gameplayContainer = new GameplayClockContainer(beatmap, new[] { doubleTimeMod = new OsuModDoubleTime() }, 0)); + case ModTimeRamp m: + m.SpeedChange.Value = expectedRate; + break; + } - SelectedMods.Value = new[] { doubleTimeMod }; - Beatmap.Value = new TestCustomSkinWorkingBeatmap(beatmap.Beatmap, gameplayContainer.GameplayClock, Audio); - }); - - AddStep("create storyboard sample", () => + AddStep("setup storyboard sample", () => { + Beatmap.Value = new TestCustomSkinWorkingBeatmap(new OsuRuleset().RulesetInfo, Audio); + SelectedMods.Value = new[] { testedMod }; + + Add(gameplayContainer = new GameplayClockContainer(Beatmap.Value, SelectedMods.Value, 0)); + gameplayContainer.Add(sample = new TestDrawableStoryboardSample(new StoryboardSampleInfo("test-sample", 1, 1)) { Clock = gameplayContainer.GameplayClock @@ -101,7 +113,7 @@ namespace osu.Game.Tests.Gameplay AddStep("start", () => gameplayContainer.Start()); - AddAssert("sample playback rate matches mod rates", () => sample.TestChannel.AggregateFrequency.Value == doubleTimeMod.SpeedChange.Value); + AddAssert("sample playback rate matches mod rates", () => sample.TestChannel.AggregateFrequency.Value == expectedRate); } private class TestSkin : LegacySkin @@ -138,8 +150,8 @@ namespace osu.Game.Tests.Gameplay { private readonly AudioManager audio; - public TestCustomSkinWorkingBeatmap(IBeatmap beatmap, IFrameBasedClock referenceClock, AudioManager audio) - : base(beatmap, null, referenceClock, audio) + public TestCustomSkinWorkingBeatmap(RulesetInfo ruleset, AudioManager audio) + : base(ruleset, null, audio) { this.audio = audio; } From 1d5084c35554939390b33db0c8d8191e0382724a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Jun 2020 20:11:12 +0200 Subject: [PATCH 536/871] Use {Initial,Final}Rate instead of SpeedChange --- osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 0803da6678..295fcc5b58 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Gameplay break; case ModTimeRamp m: - m.SpeedChange.Value = expectedRate; + m.InitialRate.Value = m.FinalRate.Value = expectedRate; break; } From 34476f6c2fa0d1e3ad922e46980fa9133302ee29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Jun 2020 20:12:17 +0200 Subject: [PATCH 537/871] Delegate to base in a more consistent manner --- osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs index 295fcc5b58..b30870d057 100644 --- a/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneStoryboardSamples.cs @@ -113,7 +113,7 @@ namespace osu.Game.Tests.Gameplay AddStep("start", () => gameplayContainer.Start()); - AddAssert("sample playback rate matches mod rates", () => sample.TestChannel.AggregateFrequency.Value == expectedRate); + AddAssert("sample playback rate matches mod rates", () => sample.Channel.AggregateFrequency.Value == expectedRate); } private class TestSkin : LegacySkin @@ -166,7 +166,7 @@ namespace osu.Game.Tests.Gameplay { } - public SampleChannel TestChannel => Channel; + public new SampleChannel Channel => base.Channel; } } } From 53861cdde81dec2aba2edfad6c92d89208a477ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Jun 2020 20:13:43 +0200 Subject: [PATCH 538/871] Privatise setter --- osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 04df46410e..60cb9b94a6 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -21,7 +21,7 @@ namespace osu.Game.Storyboards.Drawables private readonly StoryboardSampleInfo sampleInfo; - protected SampleChannel Channel; + protected SampleChannel Channel { get; private set; } public override bool RemoveWhenNotAlive => false; From 470d5bfce3497c2c2a7048ee5db8a4a65249b153 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 19 Jun 2020 20:15:14 +0200 Subject: [PATCH 539/871] Invert if to reduce nesting --- .../Storyboards/Drawables/DrawableStoryboardSample.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 60cb9b94a6..8eaf9ac652 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -35,14 +35,13 @@ namespace osu.Game.Storyboards.Drawables private void load(IBindable beatmap, IBindable> mods) { Channel = beatmap.Value.Skin.GetSample(sampleInfo); + if (Channel == null) + return; - if (Channel != null) - { - Channel.Volume.Value = sampleInfo.Volume / 100.0; + Channel.Volume.Value = sampleInfo.Volume / 100.0; - foreach (var mod in mods.Value.OfType()) - mod.ApplyToSample(Channel); - } + foreach (var mod in mods.Value.OfType()) + mod.ApplyToSample(Channel); } protected override void Update() From 8298a2c8a936561714233e53e2111881dff91ba4 Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 20 Jun 2020 14:53:25 +0800 Subject: [PATCH 540/871] inline stage light lookup and clarify behavior --- osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs | 2 +- osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index 1a097405ac..b69827e2d9 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Mania.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { - string lightImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LightImage, 0)?.Value + string lightImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LightImage)?.Value ?? "mania-stage-light"; float leftLineWidth = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LeftLineWidth) diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 7a2fa711e3..4114bf5628 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -128,7 +128,7 @@ namespace osu.Game.Rulesets.Mania.Skinning private Drawable getResult(HitResult result) { string filename = this.GetManiaSkinConfig(hitresult_mapping[result])?.Value - ?? default_hitresult_skin_filenames[result]; + ?? default_hitresult_skin_filenames[result]; return this.GetAnimation(filename, true, true); } From ca555a6a5271124ff0ff55d83185ba4f6e3a034b Mon Sep 17 00:00:00 2001 From: mcendu Date: Sat, 20 Jun 2020 14:56:39 +0800 Subject: [PATCH 541/871] rename per-column skin config retrieval to GetColumnSkinConfig Removed parameter "index"; all these cases should use extension instead --- osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs | 2 +- .../Skinning/LegacyColumnBackground.cs | 12 ++++++------ .../Skinning/LegacyHitExplosion.cs | 4 ++-- osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs | 4 ++-- .../Skinning/LegacyManiaColumnElement.cs | 4 ++-- osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs index 0c9bc97ba9..a749f80855 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyBodyPiece.cs @@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, IScrollingInfo scrollingInfo, DrawableHitObject drawableObject) { - string imageName = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value + string imageName = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value ?? $"mania-note{FallbackColumnIndex}L"; sprite = skin.GetAnimation(imageName, true, true).With(d => diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs index b69827e2d9..64a7641421 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyColumnBackground.cs @@ -35,25 +35,25 @@ namespace osu.Game.Rulesets.Mania.Skinning string lightImage = skin.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.LightImage)?.Value ?? "mania-stage-light"; - float leftLineWidth = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LeftLineWidth) + float leftLineWidth = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LeftLineWidth) ?.Value ?? 1; - float rightLineWidth = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.RightLineWidth) + float rightLineWidth = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.RightLineWidth) ?.Value ?? 1; bool hasLeftLine = leftLineWidth > 0; bool hasRightLine = rightLineWidth > 0 && skin.GetConfig(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.4m || isLastColumn; - float lightPosition = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LightPosition)?.Value + float lightPosition = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.LightPosition)?.Value ?? 0; - Color4 lineColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLineColour)?.Value + Color4 lineColour = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLineColour)?.Value ?? Color4.White; - Color4 backgroundColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour)?.Value + Color4 backgroundColour = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnBackgroundColour)?.Value ?? Color4.Black; - Color4 lightColour = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value + Color4 lightColour = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ColumnLightColour)?.Value ?? Color4.White; InternalChildren = new Drawable[] diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs index ce0b9fe4b6..bc93bb2615 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyHitExplosion.cs @@ -26,10 +26,10 @@ namespace osu.Game.Rulesets.Mania.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { - string imageName = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ExplosionImage)?.Value + string imageName = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ExplosionImage)?.Value ?? "lightingN"; - float explosionScale = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ExplosionScale)?.Value + float explosionScale = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.ExplosionScale)?.Value ?? 1; // Create a temporary animation to retrieve the number of frames, in an effort to calculate the intended frame length. diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs index 7c8d1cd303..44f3e7d7b3 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyKeyArea.cs @@ -33,10 +33,10 @@ namespace osu.Game.Rulesets.Mania.Skinning [BackgroundDependencyLoader] private void load(ISkinSource skin, IScrollingInfo scrollingInfo) { - string upImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.KeyImage)?.Value + string upImage = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.KeyImage)?.Value ?? $"mania-key{FallbackColumnIndex}"; - string downImage = GetManiaSkinConfig(skin, LegacyManiaSkinConfigurationLookups.KeyImageDown)?.Value + string downImage = GetColumnSkinConfig(skin, LegacyManiaSkinConfigurationLookups.KeyImageDown)?.Value ?? $"mania-key{FallbackColumnIndex}D"; InternalChild = directionContainer = new Container diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs index 0c46a00bed..3c0c632c14 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyManiaColumnElement.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Mania.Skinning } } - protected IBindable GetManiaSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup, int? index = null) - => skin.GetManiaSkinConfig(lookup, index ?? Column.Index); + protected IBindable GetColumnSkinConfig(ISkin skin, LegacyManiaSkinConfigurationLookups lookup) + => skin.GetManiaSkinConfig(lookup, Column.Index); } } diff --git a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs index 85523ae3c0..515c941d65 100644 --- a/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/LegacyNotePiece.cs @@ -89,7 +89,7 @@ namespace osu.Game.Rulesets.Mania.Skinning break; } - string noteImage = GetManiaSkinConfig(skin, lookup)?.Value + string noteImage = GetColumnSkinConfig(skin, lookup)?.Value ?? $"mania-note{FallbackColumnIndex}{suffix}"; return skin.GetTexture(noteImage); From 19eb6fad7fca29af8f163ace52ba95e70383f542 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sun, 21 Jun 2020 17:42:17 +0900 Subject: [PATCH 542/871] Make hold note ticks affect combo score rather than bonus --- osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs index 00b839f8ec..294aab1e4e 100644 --- a/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/HoldNoteTickJudgement.cs @@ -7,8 +7,6 @@ namespace osu.Game.Rulesets.Mania.Judgements { public class HoldNoteTickJudgement : ManiaJudgement { - public override bool AffectsCombo => false; - protected override int NumericResultFor(HitResult result) => 20; protected override double HealthIncreaseFor(HitResult result) From 44925b3951655b7a2659ac4b641da911f16461e0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sun, 21 Jun 2020 18:05:26 +0900 Subject: [PATCH 543/871] Reduce mania's HP drain by 20% --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 ++ osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs | 10 +++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index a37aaa8cc4..6ddb052585 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -44,6 +44,8 @@ namespace osu.Game.Rulesets.Mania public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(); + public override HealthProcessor CreateHealthProcessor(double drainStartTime) => new DrainingHealthProcessor(drainStartTime, 0.2); + public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new ManiaBeatmapConverter(beatmap, this); public override PerformanceCalculator CreatePerformanceCalculator(WorkingBeatmap beatmap, ScoreInfo score) => new ManiaPerformanceCalculator(this, beatmap, score); diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index 982f527517..2d3754841b 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -44,6 +44,7 @@ namespace osu.Game.Rulesets.Scoring private double gameplayEndTime; private readonly double drainStartTime; + private readonly double drainLenience; private readonly List<(double time, double health)> healthIncreases = new List<(double, double)>(); private double targetMinimumHealth; @@ -55,9 +56,14 @@ namespace osu.Game.Rulesets.Scoring /// Creates a new . /// /// The time after which draining should begin. - public DrainingHealthProcessor(double drainStartTime) + /// A lenience to apply to the default drain rate.
+ /// A value of 0 uses the default drain rate.
+ /// A value of 0.5 halves the drain rate.
+ /// A value of 1 completely removes drain. + public DrainingHealthProcessor(double drainStartTime, double drainLenience = 0) { this.drainStartTime = drainStartTime; + this.drainLenience = drainLenience; } protected override void Update() @@ -95,6 +101,8 @@ namespace osu.Game.Rulesets.Scoring ))); targetMinimumHealth = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.DrainRate, min_health_target, mid_health_target, max_health_target); + targetMinimumHealth += drainLenience * (1 - targetMinimumHealth); + targetMinimumHealth = Math.Min(1, targetMinimumHealth); base.ApplyBeatmap(beatmap); } From 9fbe2fa80a140eef3a8babf39c02bd003db56bb9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sun, 21 Jun 2020 19:31:00 +0900 Subject: [PATCH 544/871] Add comments, change to clamp --- osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index 2d3754841b..ef341575fa 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -101,8 +101,12 @@ namespace osu.Game.Rulesets.Scoring ))); targetMinimumHealth = BeatmapDifficulty.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); - targetMinimumHealth = Math.Min(1, targetMinimumHealth); + + // Ensure the target HP is within an acceptable range. + targetMinimumHealth = Math.Clamp(targetMinimumHealth, 0, 1); base.ApplyBeatmap(beatmap); } From 599543acb6cf7fa44ff518d7f43eba47ec2e53b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Jun 2020 18:02:09 +0200 Subject: [PATCH 545/871] Extract abstract hitobject sample test class --- .../Gameplay/HitObjectSampleTest.cs | 183 ++++++++++++++ .../Gameplay/TestSceneHitObjectSamples.cs | 239 +++--------------- 2 files changed, 216 insertions(+), 206 deletions(-) create mode 100644 osu.Game.Tests/Gameplay/HitObjectSampleTest.cs diff --git a/osu.Game.Tests/Gameplay/HitObjectSampleTest.cs b/osu.Game.Tests/Gameplay/HitObjectSampleTest.cs new file mode 100644 index 0000000000..6621344b6e --- /dev/null +++ b/osu.Game.Tests/Gameplay/HitObjectSampleTest.cs @@ -0,0 +1,183 @@ +// 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 System.Threading.Tasks; +using osu.Framework.Allocation; +using osu.Framework.Audio; +using osu.Framework.IO.Stores; +using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Formats; +using osu.Game.IO; +using osu.Game.Rulesets; +using osu.Game.Skinning; +using osu.Game.Storyboards; +using osu.Game.Tests.Resources; +using osu.Game.Tests.Visual; +using osu.Game.Users; + +namespace osu.Game.Tests.Gameplay +{ + public abstract class HitObjectSampleTest : PlayerTestScene + { + private readonly SkinInfo userSkinInfo = new SkinInfo(); + + private readonly BeatmapInfo beatmapInfo = new BeatmapInfo + { + BeatmapSet = new BeatmapSetInfo(), + Metadata = new BeatmapMetadata + { + Author = User.SYSTEM_USER + } + }; + + private readonly TestResourceStore userSkinResourceStore = new TestResourceStore(); + private readonly TestResourceStore beatmapSkinResourceStore = new TestResourceStore(); + private SkinSourceDependencyContainer dependencies; + private IBeatmap currentTestBeatmap; + protected override bool HasCustomSteps => true; + + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + => new DependencyContainer(dependencies = new SkinSourceDependencyContainer(base.CreateChildDependencies(parent))); + + protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestBeatmap; + + protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + => new TestWorkingBeatmap(beatmapInfo, beatmapSkinResourceStore, beatmap, storyboard, Clock, Audio); + + protected void CreateTestWithBeatmap(string filename) + { + CreateTest(() => + { + AddStep("clear performed lookups", () => + { + userSkinResourceStore.PerformedLookups.Clear(); + beatmapSkinResourceStore.PerformedLookups.Clear(); + }); + + AddStep($"load {filename}", () => + { + using (var reader = new LineBufferedReader(TestResources.OpenResource($"SampleLookups/{filename}"))) + currentTestBeatmap = Decoder.GetDecoder(reader).Decode(reader); + }); + }); + } + + protected void SetupSkins(string beatmapFile, string userFile) + { + AddStep("setup skins", () => + { + userSkinInfo.Files = new List + { + new SkinFileInfo + { + Filename = userFile, + FileInfo = new IO.FileInfo { Hash = userFile } + } + }; + + beatmapInfo.BeatmapSet.Files = new List + { + new BeatmapSetFileInfo + { + Filename = beatmapFile, + FileInfo = new IO.FileInfo { Hash = beatmapFile } + } + }; + + // Need to refresh the cached skin source to refresh the skin resource store. + dependencies.SkinSource = new SkinProvidingContainer(new LegacySkin(userSkinInfo, userSkinResourceStore, Audio)); + }); + } + + protected void AssertBeatmapLookup(string name) => AddAssert($"\"{name}\" looked up from beatmap skin", + () => !userSkinResourceStore.PerformedLookups.Contains(name) && beatmapSkinResourceStore.PerformedLookups.Contains(name)); + + protected void AssertUserLookup(string name) => AddAssert($"\"{name}\" looked up from user skin", + () => !beatmapSkinResourceStore.PerformedLookups.Contains(name) && userSkinResourceStore.PerformedLookups.Contains(name)); + + protected class SkinSourceDependencyContainer : IReadOnlyDependencyContainer + { + public ISkinSource SkinSource; + + private readonly IReadOnlyDependencyContainer fallback; + + public SkinSourceDependencyContainer(IReadOnlyDependencyContainer fallback) + { + this.fallback = fallback; + } + + public object Get(Type type) + { + if (type == typeof(ISkinSource)) + return SkinSource; + + return fallback.Get(type); + } + + public object Get(Type type, CacheInfo info) + { + if (type == typeof(ISkinSource)) + return SkinSource; + + return fallback.Get(type, info); + } + + public void Inject(T instance) where T : class + { + // Never used directly + } + } + + protected class TestResourceStore : IResourceStore + { + public readonly List PerformedLookups = new List(); + + public byte[] Get(string name) + { + markLookup(name); + return Array.Empty(); + } + + public Task GetAsync(string name) + { + markLookup(name); + return Task.FromResult(Array.Empty()); + } + + public Stream GetStream(string name) + { + markLookup(name); + return new MemoryStream(); + } + + private void markLookup(string name) => PerformedLookups.Add(name.Substring(name.LastIndexOf(Path.DirectorySeparatorChar) + 1)); + + public IEnumerable GetAvailableResources() => Enumerable.Empty(); + + public void Dispose() + { + } + } + + private class TestWorkingBeatmap : ClockBackedTestWorkingBeatmap + { + private readonly BeatmapInfo skinBeatmapInfo; + private readonly IResourceStore resourceStore; + + public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore resourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, + double length = 60000) + : base(beatmap, storyboard, referenceClock, audio, length) + { + this.skinBeatmapInfo = skinBeatmapInfo; + this.resourceStore = resourceStore; + } + + protected override ISkin GetSkin() => new LegacyBeatmapSkin(skinBeatmapInfo, resourceStore, AudioManager); + } + } +} diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index acefaa006a..6144179d31 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -1,52 +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.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Audio; -using osu.Framework.IO.Stores; using osu.Framework.Testing; -using osu.Framework.Timing; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Formats; -using osu.Game.IO; using osu.Game.Rulesets; -using osu.Game.Skinning; -using osu.Game.Storyboards; -using osu.Game.Tests.Resources; -using osu.Game.Tests.Visual.Gameplay; -using osu.Game.Users; +using osu.Game.Rulesets.Osu; namespace osu.Game.Tests.Gameplay { [HeadlessTest] - public class TestSceneHitObjectSamples : OsuPlayerTestScene + public class TestSceneHitObjectSamples : HitObjectSampleTest { - private readonly SkinInfo userSkinInfo = new SkinInfo(); - - private readonly BeatmapInfo beatmapInfo = new BeatmapInfo - { - BeatmapSet = new BeatmapSetInfo(), - Metadata = new BeatmapMetadata - { - Author = User.SYSTEM_USER - } - }; - - private readonly TestResourceStore userSkinResourceStore = new TestResourceStore(); - private readonly TestResourceStore beatmapSkinResourceStore = new TestResourceStore(); - - protected override bool HasCustomSteps => true; - - private SkinSourceDependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - => new DependencyContainer(dependencies = new SkinSourceDependencyContainer(base.CreateChildDependencies(parent))); + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); /// /// Tests that a hitobject which provides no custom sample set retrieves samples from the user skin. @@ -56,11 +21,11 @@ namespace osu.Game.Tests.Gameplay { const string expected_sample = "normal-hitnormal"; - setupSkins(expected_sample, expected_sample); + SetupSkins(expected_sample, expected_sample); - createTestWithBeatmap("hitobject-skin-sample.osu"); + CreateTestWithBeatmap("hitobject-skin-sample.osu"); - assertUserLookup(expected_sample); + AssertUserLookup(expected_sample); } /// @@ -71,11 +36,11 @@ namespace osu.Game.Tests.Gameplay { const string expected_sample = "normal-hitnormal"; - setupSkins(expected_sample, expected_sample); + SetupSkins(expected_sample, expected_sample); - createTestWithBeatmap("hitobject-beatmap-sample.osu"); + CreateTestWithBeatmap("hitobject-beatmap-sample.osu"); - assertBeatmapLookup(expected_sample); + AssertBeatmapLookup(expected_sample); } /// @@ -86,11 +51,11 @@ namespace osu.Game.Tests.Gameplay { const string expected_sample = "normal-hitnormal"; - setupSkins(null, expected_sample); + SetupSkins(null, expected_sample); - createTestWithBeatmap("hitobject-beatmap-sample.osu"); + CreateTestWithBeatmap("hitobject-beatmap-sample.osu"); - assertUserLookup(expected_sample); + AssertUserLookup(expected_sample); } /// @@ -102,11 +67,11 @@ namespace osu.Game.Tests.Gameplay [TestCase("normal-hitnormal")] public void TestDefaultCustomSampleFromBeatmap(string expectedSample) { - setupSkins(expectedSample, expectedSample); + SetupSkins(expectedSample, expectedSample); - createTestWithBeatmap("hitobject-beatmap-custom-sample.osu"); + CreateTestWithBeatmap("hitobject-beatmap-custom-sample.osu"); - assertBeatmapLookup(expectedSample); + AssertBeatmapLookup(expectedSample); } /// @@ -118,11 +83,11 @@ namespace osu.Game.Tests.Gameplay [TestCase("normal-hitnormal")] public void TestDefaultCustomSampleFromUserSkinFallback(string expectedSample) { - setupSkins(string.Empty, expectedSample); + SetupSkins(string.Empty, expectedSample); - createTestWithBeatmap("hitobject-beatmap-custom-sample.osu"); + CreateTestWithBeatmap("hitobject-beatmap-custom-sample.osu"); - assertUserLookup(expectedSample); + AssertUserLookup(expectedSample); } /// @@ -133,11 +98,11 @@ namespace osu.Game.Tests.Gameplay { const string expected_sample = "hit_1.wav"; - setupSkins(expected_sample, expected_sample); + SetupSkins(expected_sample, expected_sample); - createTestWithBeatmap("file-beatmap-sample.osu"); + CreateTestWithBeatmap("file-beatmap-sample.osu"); - assertBeatmapLookup(expected_sample); + AssertBeatmapLookup(expected_sample); } /// @@ -148,11 +113,11 @@ namespace osu.Game.Tests.Gameplay { const string expected_sample = "normal-hitnormal"; - setupSkins(expected_sample, expected_sample); + SetupSkins(expected_sample, expected_sample); - createTestWithBeatmap("controlpoint-skin-sample.osu"); + CreateTestWithBeatmap("controlpoint-skin-sample.osu"); - assertUserLookup(expected_sample); + AssertUserLookup(expected_sample); } /// @@ -163,11 +128,11 @@ namespace osu.Game.Tests.Gameplay { const string expected_sample = "normal-hitnormal"; - setupSkins(expected_sample, expected_sample); + SetupSkins(expected_sample, expected_sample); - createTestWithBeatmap("controlpoint-beatmap-sample.osu"); + CreateTestWithBeatmap("controlpoint-beatmap-sample.osu"); - assertBeatmapLookup(expected_sample); + AssertBeatmapLookup(expected_sample); } /// @@ -177,11 +142,11 @@ namespace osu.Game.Tests.Gameplay [TestCase("normal-hitnormal")] public void TestControlPointCustomSampleFromBeatmap(string sampleName) { - setupSkins(sampleName, sampleName); + SetupSkins(sampleName, sampleName); - createTestWithBeatmap("controlpoint-beatmap-custom-sample.osu"); + CreateTestWithBeatmap("controlpoint-beatmap-custom-sample.osu"); - assertBeatmapLookup(sampleName); + AssertBeatmapLookup(sampleName); } /// @@ -192,149 +157,11 @@ namespace osu.Game.Tests.Gameplay { const string expected_sample = "normal-hitnormal3"; - setupSkins(expected_sample, expected_sample); + SetupSkins(expected_sample, expected_sample); - createTestWithBeatmap("hitobject-beatmap-custom-sample-override.osu"); + CreateTestWithBeatmap("hitobject-beatmap-custom-sample-override.osu"); - assertBeatmapLookup(expected_sample); - } - - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestBeatmap; - - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) - => new TestWorkingBeatmap(beatmapInfo, beatmapSkinResourceStore, beatmap, storyboard, Clock, Audio); - - private IBeatmap currentTestBeatmap; - - private void createTestWithBeatmap(string filename) - { - CreateTest(() => - { - AddStep("clear performed lookups", () => - { - userSkinResourceStore.PerformedLookups.Clear(); - beatmapSkinResourceStore.PerformedLookups.Clear(); - }); - - AddStep($"load {filename}", () => - { - using (var reader = new LineBufferedReader(TestResources.OpenResource($"SampleLookups/{filename}"))) - currentTestBeatmap = Decoder.GetDecoder(reader).Decode(reader); - }); - }); - } - - private void setupSkins(string beatmapFile, string userFile) - { - AddStep("setup skins", () => - { - userSkinInfo.Files = new List - { - new SkinFileInfo - { - Filename = userFile, - FileInfo = new IO.FileInfo { Hash = userFile } - } - }; - - beatmapInfo.BeatmapSet.Files = new List - { - new BeatmapSetFileInfo - { - Filename = beatmapFile, - FileInfo = new IO.FileInfo { Hash = beatmapFile } - } - }; - - // Need to refresh the cached skin source to refresh the skin resource store. - dependencies.SkinSource = new SkinProvidingContainer(new LegacySkin(userSkinInfo, userSkinResourceStore, Audio)); - }); - } - - private void assertBeatmapLookup(string name) => AddAssert($"\"{name}\" looked up from beatmap skin", - () => !userSkinResourceStore.PerformedLookups.Contains(name) && beatmapSkinResourceStore.PerformedLookups.Contains(name)); - - private void assertUserLookup(string name) => AddAssert($"\"{name}\" looked up from user skin", - () => !beatmapSkinResourceStore.PerformedLookups.Contains(name) && userSkinResourceStore.PerformedLookups.Contains(name)); - - private class SkinSourceDependencyContainer : IReadOnlyDependencyContainer - { - public ISkinSource SkinSource; - - private readonly IReadOnlyDependencyContainer fallback; - - public SkinSourceDependencyContainer(IReadOnlyDependencyContainer fallback) - { - this.fallback = fallback; - } - - public object Get(Type type) - { - if (type == typeof(ISkinSource)) - return SkinSource; - - return fallback.Get(type); - } - - public object Get(Type type, CacheInfo info) - { - if (type == typeof(ISkinSource)) - return SkinSource; - - return fallback.Get(type, info); - } - - public void Inject(T instance) where T : class - { - // Never used directly - } - } - - private class TestResourceStore : IResourceStore - { - public readonly List PerformedLookups = new List(); - - public byte[] Get(string name) - { - markLookup(name); - return Array.Empty(); - } - - public Task GetAsync(string name) - { - markLookup(name); - return Task.FromResult(Array.Empty()); - } - - public Stream GetStream(string name) - { - markLookup(name); - return new MemoryStream(); - } - - private void markLookup(string name) => PerformedLookups.Add(name.Substring(name.LastIndexOf(Path.DirectorySeparatorChar) + 1)); - - public IEnumerable GetAvailableResources() => Enumerable.Empty(); - - public void Dispose() - { - } - } - - private class TestWorkingBeatmap : ClockBackedTestWorkingBeatmap - { - private readonly BeatmapInfo skinBeatmapInfo; - private readonly IResourceStore resourceStore; - - public TestWorkingBeatmap(BeatmapInfo skinBeatmapInfo, IResourceStore resourceStore, IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, - double length = 60000) - : base(beatmap, storyboard, referenceClock, audio, length) - { - this.skinBeatmapInfo = skinBeatmapInfo; - this.resourceStore = resourceStore; - } - - protected override ISkin GetSkin() => new LegacyBeatmapSkin(skinBeatmapInfo, resourceStore, AudioManager); + AssertBeatmapLookup(expected_sample); } } } From 4a8a673d41a276a540cab16806bd80beb29308e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Jun 2020 18:19:33 +0200 Subject: [PATCH 546/871] Decouple abstract sample test from TestResources --- osu.Game.Tests/Gameplay/HitObjectSampleTest.cs | 5 +++-- osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Gameplay/HitObjectSampleTest.cs b/osu.Game.Tests/Gameplay/HitObjectSampleTest.cs index 6621344b6e..9c43690a95 100644 --- a/osu.Game.Tests/Gameplay/HitObjectSampleTest.cs +++ b/osu.Game.Tests/Gameplay/HitObjectSampleTest.cs @@ -16,7 +16,6 @@ using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Skinning; using osu.Game.Storyboards; -using osu.Game.Tests.Resources; using osu.Game.Tests.Visual; using osu.Game.Users; @@ -24,6 +23,8 @@ namespace osu.Game.Tests.Gameplay { public abstract class HitObjectSampleTest : PlayerTestScene { + protected abstract IResourceStore Resources { get; } + private readonly SkinInfo userSkinInfo = new SkinInfo(); private readonly BeatmapInfo beatmapInfo = new BeatmapInfo @@ -61,7 +62,7 @@ namespace osu.Game.Tests.Gameplay AddStep($"load {filename}", () => { - using (var reader = new LineBufferedReader(TestResources.OpenResource($"SampleLookups/{filename}"))) + using (var reader = new LineBufferedReader(Resources.GetStream($"Resources/SampleLookups/{filename}"))) currentTestBeatmap = Decoder.GetDecoder(reader).Decode(reader); }); }); diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index 6144179d31..78bdfeb80e 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Tests.Resources; namespace osu.Game.Tests.Gameplay { @@ -12,6 +14,7 @@ namespace osu.Game.Tests.Gameplay public class TestSceneHitObjectSamples : HitObjectSampleTest { protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); + protected override IResourceStore Resources => TestResources.GetStore(); /// /// Tests that a hitobject which provides no custom sample set retrieves samples from the user skin. From 4bba0c7359c857bc19d794c5b7b269e001d4d45e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Jun 2020 18:20:36 +0200 Subject: [PATCH 547/871] Move abstract sample test to main game project --- osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs | 1 + .../Gameplay => osu.Game/Tests/Beatmaps}/HitObjectSampleTest.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) rename {osu.Game.Tests/Gameplay => osu.Game/Tests/Beatmaps}/HitObjectSampleTest.cs (99%) diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index 78bdfeb80e..ef6efb7fec 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -6,6 +6,7 @@ using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; namespace osu.Game.Tests.Gameplay diff --git a/osu.Game.Tests/Gameplay/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs similarity index 99% rename from osu.Game.Tests/Gameplay/HitObjectSampleTest.cs rename to osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index 9c43690a95..91fca2c1bf 100644 --- a/osu.Game.Tests/Gameplay/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -19,7 +19,7 @@ using osu.Game.Storyboards; using osu.Game.Tests.Visual; using osu.Game.Users; -namespace osu.Game.Tests.Gameplay +namespace osu.Game.Tests.Beatmaps { public abstract class HitObjectSampleTest : PlayerTestScene { From 07cbc3e68343b83b17b4e43f8302f84833417f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Jun 2020 23:04:59 +0200 Subject: [PATCH 548/871] Privatise and seal whatever possible --- osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index 91fca2c1bf..b4ce322165 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -40,14 +40,14 @@ namespace osu.Game.Tests.Beatmaps private readonly TestResourceStore beatmapSkinResourceStore = new TestResourceStore(); private SkinSourceDependencyContainer dependencies; private IBeatmap currentTestBeatmap; - protected override bool HasCustomSteps => true; + protected sealed override bool HasCustomSteps => true; - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + protected sealed override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) => new DependencyContainer(dependencies = new SkinSourceDependencyContainer(base.CreateChildDependencies(parent))); - protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestBeatmap; + protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestBeatmap; - protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) + protected sealed override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null) => new TestWorkingBeatmap(beatmapInfo, beatmapSkinResourceStore, beatmap, storyboard, Clock, Audio); protected void CreateTestWithBeatmap(string filename) @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Beatmaps protected void AssertUserLookup(string name) => AddAssert($"\"{name}\" looked up from user skin", () => !beatmapSkinResourceStore.PerformedLookups.Contains(name) && userSkinResourceStore.PerformedLookups.Contains(name)); - protected class SkinSourceDependencyContainer : IReadOnlyDependencyContainer + private class SkinSourceDependencyContainer : IReadOnlyDependencyContainer { public ISkinSource SkinSource; @@ -134,7 +134,7 @@ namespace osu.Game.Tests.Beatmaps } } - protected class TestResourceStore : IResourceStore + private class TestResourceStore : IResourceStore { public readonly List PerformedLookups = new List(); From ad85c5f538a162ca9973301331db8baa8110b17d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Jun 2020 22:04:10 +0200 Subject: [PATCH 549/871] Add base legacy skin transformer --- .../Skinning/CatchLegacySkinTransformer.cs | 23 ++++------- .../Skinning/ManiaLegacySkinTransformer.cs | 26 +++++-------- .../Skinning/OsuLegacySkinTransformer.cs | 38 +++++++------------ .../Skinning/TaikoLegacySkinTransformer.cs | 17 +++------ osu.Game/Skinning/LegacySkinTransformer.cs | 35 +++++++++++++++++ 5 files changed, 71 insertions(+), 68 deletions(-) create mode 100644 osu.Game/Skinning/LegacySkinTransformer.cs diff --git a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs index 954f2dfc5f..d929da1a29 100644 --- a/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Catch/Skinning/CatchLegacySkinTransformer.cs @@ -2,26 +2,21 @@ // See the LICENCE file in the repository root for full licence text. using Humanizer; -using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Textures; -using osu.Game.Audio; using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Catch.Skinning { - public class CatchLegacySkinTransformer : ISkin + public class CatchLegacySkinTransformer : LegacySkinTransformer { - private readonly ISkin source; - - public CatchLegacySkinTransformer(ISkin source) + public CatchLegacySkinTransformer(ISkinSource source) + : base(source) { - this.source = source; } - public Drawable GetDrawableComponent(ISkinComponent component) + public override Drawable GetDrawableComponent(ISkinComponent component) { if (!(component is CatchSkinComponent catchSkinComponent)) return null; @@ -61,19 +56,15 @@ namespace osu.Game.Rulesets.Catch.Skinning return null; } - public Texture GetTexture(string componentName) => source.GetTexture(componentName); - - public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample); - - public IBindable GetConfig(TLookup lookup) + public override IBindable GetConfig(TLookup lookup) { switch (lookup) { case CatchSkinColour colour: - return source.GetConfig(new SkinCustomColourLookup(colour)); + return Source.GetConfig(new SkinCustomColourLookup(colour)); } - return source.GetConfig(lookup); + return Source.GetConfig(lookup); } } } diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 4114bf5628..84e88a10be 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -3,11 +3,8 @@ using System; using osu.Framework.Graphics; -using osu.Framework.Graphics.Textures; -using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Game.Rulesets.Scoring; -using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Skinning; @@ -15,9 +12,8 @@ using System.Collections.Generic; namespace osu.Game.Rulesets.Mania.Skinning { - public class ManiaLegacySkinTransformer : ISkin + public class ManiaLegacySkinTransformer : LegacySkinTransformer { - private readonly ISkin source; private readonly ManiaBeatmap beatmap; /// @@ -59,23 +55,23 @@ namespace osu.Game.Rulesets.Mania.Skinning private Lazy hasKeyTexture; public ManiaLegacySkinTransformer(ISkinSource source, IBeatmap beatmap) + : base(source) { - this.source = source; this.beatmap = (ManiaBeatmap)beatmap; - source.SourceChanged += sourceChanged; + Source.SourceChanged += sourceChanged; sourceChanged(); } private void sourceChanged() { - isLegacySkin = new Lazy(() => source.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null); - hasKeyTexture = new Lazy(() => source.GetAnimation( + isLegacySkin = new Lazy(() => Source.GetConfig(LegacySkinConfiguration.LegacySetting.Version) != null); + hasKeyTexture = new Lazy(() => Source.GetAnimation( this.GetManiaSkinConfig(LegacyManiaSkinConfigurationLookups.KeyImage, 0)?.Value ?? "mania-key1", true, true) != null); } - public Drawable GetDrawableComponent(ISkinComponent component) + public override Drawable GetDrawableComponent(ISkinComponent component) { switch (component) { @@ -133,16 +129,12 @@ namespace osu.Game.Rulesets.Mania.Skinning return this.GetAnimation(filename, true, true); } - public Texture GetTexture(string componentName) => source.GetTexture(componentName); - - public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample); - - public IBindable GetConfig(TLookup lookup) + public override IBindable GetConfig(TLookup lookup) { if (lookup is ManiaSkinConfigurationLookup maniaLookup) - return source.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn)); + return Source.GetConfig(new LegacyManiaSkinConfigurationLookup(beatmap.TotalColumns, maniaLookup.Lookup, maniaLookup.TargetColumn)); - return source.GetConfig(lookup); + return Source.GetConfig(lookup); } } } diff --git a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs index ba0003b5cd..3e5758ca01 100644 --- a/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Osu/Skinning/OsuLegacySkinTransformer.cs @@ -2,20 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Textures; -using osu.Game.Audio; using osu.Game.Skinning; using osuTK; namespace osu.Game.Rulesets.Osu.Skinning { - public class OsuLegacySkinTransformer : ISkin + public class OsuLegacySkinTransformer : LegacySkinTransformer { - private readonly ISkin source; - private Lazy hasHitCircle; /// @@ -26,19 +21,18 @@ namespace osu.Game.Rulesets.Osu.Skinning public const float LEGACY_CIRCLE_RADIUS = 64 - 5; public OsuLegacySkinTransformer(ISkinSource source) + : base(source) { - this.source = source; - - source.SourceChanged += sourceChanged; + Source.SourceChanged += sourceChanged; sourceChanged(); } private void sourceChanged() { - hasHitCircle = new Lazy(() => source.GetTexture("hitcircle") != null); + hasHitCircle = new Lazy(() => Source.GetTexture("hitcircle") != null); } - public Drawable GetDrawableComponent(ISkinComponent component) + public override Drawable GetDrawableComponent(ISkinComponent component) { if (!(component is OsuSkinComponent osuComponent)) return null; @@ -85,13 +79,13 @@ namespace osu.Game.Rulesets.Osu.Skinning return null; case OsuSkinComponents.Cursor: - if (source.GetTexture("cursor") != null) + if (Source.GetTexture("cursor") != null) return new LegacyCursor(); return null; case OsuSkinComponents.CursorTrail: - if (source.GetTexture("cursortrail") != null) + if (Source.GetTexture("cursortrail") != null) return new LegacyCursorTrail(); return null; @@ -102,7 +96,7 @@ namespace osu.Game.Rulesets.Osu.Skinning return !hasFont(font) ? null - : new LegacySpriteText(source, font) + : new LegacySpriteText(Source, font) { // stable applies a blanket 0.8x scale to hitcircle fonts Scale = new Vector2(0.8f), @@ -113,16 +107,12 @@ namespace osu.Game.Rulesets.Osu.Skinning return null; } - public Texture GetTexture(string componentName) => source.GetTexture(componentName); - - public SampleChannel GetSample(ISampleInfo sample) => source.GetSample(sample); - - public IBindable GetConfig(TLookup lookup) + public override IBindable GetConfig(TLookup lookup) { switch (lookup) { case OsuSkinColour colour: - return source.GetConfig(new SkinCustomColourLookup(colour)); + return Source.GetConfig(new SkinCustomColourLookup(colour)); case OsuSkinConfiguration osuLookup: switch (osuLookup) @@ -136,16 +126,16 @@ namespace osu.Game.Rulesets.Osu.Skinning case OsuSkinConfiguration.HitCircleOverlayAboveNumber: // See https://osu.ppy.sh/help/wiki/Skinning/skin.ini#%5Bgeneral%5D // HitCircleOverlayAboveNumer (with typo) should still be supported for now. - return source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? - source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); + return Source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumber) ?? + Source.GetConfig(OsuSkinConfiguration.HitCircleOverlayAboveNumer); } break; } - return source.GetConfig(lookup); + return Source.GetConfig(lookup); } - private bool hasFont(string fontName) => source.GetTexture($"{fontName}-0") != null; + private bool hasFont(string fontName) => Source.GetTexture($"{fontName}-0") != null; } } diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs index 6e9a37eb93..23d675cfb0 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacySkinTransformer.cs @@ -6,23 +6,20 @@ using System.Collections.Generic; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Textures; using osu.Game.Audio; using osu.Game.Rulesets.Taiko.UI; using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko.Skinning { - public class TaikoLegacySkinTransformer : ISkin + public class TaikoLegacySkinTransformer : LegacySkinTransformer { - private readonly ISkinSource source; - public TaikoLegacySkinTransformer(ISkinSource source) + : base(source) { - this.source = source; } - public Drawable GetDrawableComponent(ISkinComponent component) + public override Drawable GetDrawableComponent(ISkinComponent component) { if (!(component is TaikoSkinComponent taikoComponent)) return null; @@ -100,7 +97,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning return null; } - return source.GetDrawableComponent(component); + return Source.GetDrawableComponent(component); } private string getHitName(TaikoSkinComponents component) @@ -120,11 +117,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning throw new ArgumentOutOfRangeException(nameof(component), "Invalid result type"); } - public Texture GetTexture(string componentName) => source.GetTexture(componentName); + public override SampleChannel GetSample(ISampleInfo sampleInfo) => Source.GetSample(new LegacyTaikoSampleInfo(sampleInfo)); - public SampleChannel GetSample(ISampleInfo sampleInfo) => source.GetSample(new LegacyTaikoSampleInfo(sampleInfo)); - - public IBindable GetConfig(TLookup lookup) => source.GetConfig(lookup); + public override IBindable GetConfig(TLookup lookup) => Source.GetConfig(lookup); private class LegacyTaikoSampleInfo : ISampleInfo { diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs new file mode 100644 index 0000000000..1131c93288 --- /dev/null +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -0,0 +1,35 @@ +// 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.Audio.Sample; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Textures; +using osu.Game.Audio; + +namespace osu.Game.Skinning +{ + /// + /// Transformer used to handle support of legacy features for individual rulesets. + /// + public abstract class LegacySkinTransformer : ISkin + { + /// + /// Source of the which is being transformed. + /// + protected ISkinSource Source { get; } + + protected LegacySkinTransformer(ISkinSource source) + { + Source = source; + } + + public abstract Drawable GetDrawableComponent(ISkinComponent component); + + public Texture GetTexture(string componentName) => Source.GetTexture(componentName); + + public virtual SampleChannel GetSample(ISampleInfo sampleInfo) => Source.GetSample(sampleInfo); + + public abstract IBindable GetConfig(TLookup lookup); + } +} From 1bc5f3618417a15fe1fd0f44e705e4911d5adacd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Jun 2020 22:05:10 +0200 Subject: [PATCH 550/871] Adjust test usage --- osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs index 7deeec527f..b570f090ca 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchSkinColourDecodingTest.cs @@ -17,7 +17,8 @@ namespace osu.Game.Rulesets.Catch.Tests { var store = new NamespacedResourceStore(new DllResourceStore(GetType().Assembly), "Resources/special-skin"); var rawSkin = new TestLegacySkin(new SkinInfo { Name = "special-skin" }, store); - var skin = new CatchLegacySkinTransformer(rawSkin); + var skinSource = new SkinProvidingContainer(rawSkin); + var skin = new CatchLegacySkinTransformer(skinSource); Assert.AreEqual(new Color4(232, 185, 35, 255), skin.GetConfig(CatchSkinColour.HyperDash)?.Value); Assert.AreEqual(new Color4(232, 74, 35, 255), skin.GetConfig(CatchSkinColour.HyperDashAfterImage)?.Value); From 3ede095b9c88e3d2f99a483c8b6171eb7f889f03 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 15:42:55 +0900 Subject: [PATCH 551/871] Apply refactorings from review --- osu.Game/Screens/Ranking/ResultsScreen.cs | 20 ++++++++------------ osu.Game/Screens/Ranking/ScorePanelList.cs | 4 ++-- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 133efd6e7b..193d975e42 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -236,13 +236,11 @@ namespace osu.Game.Screens.Ranking scorePanelList.Detach(expandedPanel); detachedPanelContainer.Add(expandedPanel); - // Move into its original location in the local container. + // Move into its original location in the local container first, then to the final location. var origLocation = detachedPanelContainer.ToLocalSpace(screenSpacePos); - expandedPanel.MoveTo(origLocation); - expandedPanel.MoveToX(origLocation.X); - - // Move into the final location. - expandedPanel.MoveToX(StatisticsPanel.SIDE_PADDING, 150, Easing.OutQuint); + expandedPanel.MoveTo(origLocation) + .Then() + .MoveTo(new Vector2(StatisticsPanel.SIDE_PADDING, origLocation.Y), 150, Easing.OutQuint); // Hide contracted panels. foreach (var contracted in scorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) @@ -262,13 +260,11 @@ namespace osu.Game.Screens.Ranking detachedPanelContainer.Remove(detachedPanel); scorePanelList.Attach(detachedPanel); - // Move into its original location in the attached container. + // Move into its original location in the attached container first, then to the final location. var origLocation = detachedPanel.Parent.ToLocalSpace(screenSpacePos); - detachedPanel.MoveTo(origLocation); - detachedPanel.MoveToX(origLocation.X); - - // Move into the final location. - detachedPanel.MoveToX(0, 150, Easing.OutQuint); + detachedPanel.MoveTo(origLocation) + .Then() + .MoveTo(new Vector2(0, origLocation.Y), 150, Easing.OutQuint); // Show contracted panels. foreach (var contracted in scorePanelList.GetScorePanels().Where(p => p.State == PanelState.Contracted)) diff --git a/osu.Game/Screens/Ranking/ScorePanelList.cs b/osu.Game/Screens/Ranking/ScorePanelList.cs index 9ebd7822c0..0f8bc82ac0 100644 --- a/osu.Game/Screens/Ranking/ScorePanelList.cs +++ b/osu.Game/Screens/Ranking/ScorePanelList.cs @@ -202,7 +202,7 @@ namespace osu.Game.Screens.Ranking /// If is not a part of this . public void Detach(ScorePanel panel) { - var container = flow.FirstOrDefault(t => t.Panel == panel); + var container = flow.SingleOrDefault(t => t.Panel == panel); if (container == null) throw new InvalidOperationException("Panel is not contained by the score panel list."); @@ -216,7 +216,7 @@ namespace osu.Game.Screens.Ranking /// If is not a part of this . public void Attach(ScorePanel panel) { - var container = flow.FirstOrDefault(t => t.Panel == panel); + var container = flow.SingleOrDefault(t => t.Panel == panel); if (container == null) throw new InvalidOperationException("Panel is not contained by the score panel list."); From 21f776e51feafd8a06e397e90ef88bace4900d0c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 15:48:42 +0900 Subject: [PATCH 552/871] Simplify/optimise heatmap point additoin --- .../Statistics/AccuracyHeatmap.cs | 22 +++---------------- 1 file changed, 3 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 10ca3eb9be..f05bfce8d7 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -175,25 +174,10 @@ namespace osu.Game.Rulesets.Osu.Statistics Vector2 localPoint = localCentre + localRadius * new Vector2((float)Math.Cos(localAngle), (float)Math.Sin(localAngle)); // Find the most relevant hit point. - double minDist = double.PositiveInfinity; - HitPoint point = null; + int r = Math.Clamp((int)Math.Round(localPoint.Y), 0, points_per_dimension - 1); + int c = Math.Clamp((int)Math.Round(localPoint.X), 0, points_per_dimension - 1); - for (int r = 0; r < points_per_dimension; r++) - { - for (int c = 0; c < points_per_dimension; c++) - { - float dist = Vector2.Distance(new Vector2(c, r), localPoint); - - if (dist < minDist) - { - minDist = dist; - point = (HitPoint)pointGrid.Content[r][c]; - } - } - } - - Debug.Assert(point != null); - point.Increment(); + ((HitPoint)pointGrid.Content[r][c]).Increment(); } private class HitPoint : Circle From e91c2ee5e275bcdc398bcc117055a468ea2f8348 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jun 2020 16:19:38 +0900 Subject: [PATCH 553/871] Simplify logic by considering all buttons equally --- osu.Game/Graphics/Cursor/MenuCursor.cs | 36 +++++++++----------------- 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/osu.Game/Graphics/Cursor/MenuCursor.cs b/osu.Game/Graphics/Cursor/MenuCursor.cs index ff28dddd40..fd8f016860 100644 --- a/osu.Game/Graphics/Cursor/MenuCursor.cs +++ b/osu.Game/Graphics/Cursor/MenuCursor.cs @@ -13,7 +13,6 @@ using JetBrains.Annotations; using osu.Framework.Bindables; using osu.Framework.Graphics.Textures; using osu.Framework.Input.Events; -using osuTK.Input; using osu.Framework.Utils; namespace osu.Game.Graphics.Cursor @@ -74,23 +73,17 @@ namespace osu.Game.Graphics.Cursor protected override bool OnMouseDown(MouseDownEvent e) { // only trigger animation for main mouse buttons - if (e.Button <= MouseButton.Right) - { - activeCursor.Scale = new Vector2(1); - activeCursor.ScaleTo(0.90f, 800, Easing.OutQuint); + activeCursor.Scale = new Vector2(1); + activeCursor.ScaleTo(0.90f, 800, Easing.OutQuint); - activeCursor.AdditiveLayer.Alpha = 0; - activeCursor.AdditiveLayer.FadeInFromZero(800, Easing.OutQuint); - } + activeCursor.AdditiveLayer.Alpha = 0; + activeCursor.AdditiveLayer.FadeInFromZero(800, Easing.OutQuint); - if (shouldKeepRotating(e)) + if (cursorRotate.Value && dragRotationState != DragRotationState.Rotating) { // if cursor is already rotating don't reset its rotate origin - if (dragRotationState != DragRotationState.Rotating) - { - dragRotationState = DragRotationState.DragStarted; - positionMouseDown = e.MousePosition; - } + dragRotationState = DragRotationState.DragStarted; + positionMouseDown = e.MousePosition; } return base.OnMouseDown(e); @@ -98,17 +91,16 @@ namespace osu.Game.Graphics.Cursor protected override void OnMouseUp(MouseUpEvent e) { - if (!anyMainButtonPressed(e)) + if (!e.HasAnyButtonPressed) { activeCursor.AdditiveLayer.FadeOutFromOne(500, Easing.OutQuint); activeCursor.ScaleTo(1, 500, Easing.OutElastic); - } - if (!shouldKeepRotating(e)) - { - if (dragRotationState == DragRotationState.Rotating) + if (dragRotationState != DragRotationState.NotDragging) + { activeCursor.RotateTo(0, 600 * (1 + Math.Abs(activeCursor.Rotation / 720)), Easing.OutElasticHalf); - dragRotationState = DragRotationState.NotDragging; + dragRotationState = DragRotationState.NotDragging; + } } base.OnMouseUp(e); @@ -126,10 +118,6 @@ namespace osu.Game.Graphics.Cursor activeCursor.ScaleTo(0.6f, 250, Easing.In); } - private bool shouldKeepRotating(MouseEvent e) => cursorRotate.Value && (anyMainButtonPressed(e)); - - private static bool anyMainButtonPressed(MouseEvent e) => e.IsPressed(MouseButton.Left) || e.IsPressed(MouseButton.Middle) || e.IsPressed(MouseButton.Right); - public class Cursor : Container { private Container cursorContainer; From 2d121b4e3dd8f10bf70d2f6051167d848fdc4fef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jun 2020 16:32:27 +0900 Subject: [PATCH 554/871] Simplify lookup fallback code --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 32 +++++-------------------- 1 file changed, 6 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index a17491bf2d..42be6ea119 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -4,7 +4,6 @@ using System; using System.IO; using System.Linq; -using System.Collections.Generic; using JetBrains.Annotations; using Microsoft.Win32; using osu.Framework.Allocation; @@ -187,32 +186,13 @@ namespace osu.Game.Tournament.IPC [CanBeNull] private string findStablePath() { - string stableInstallPath = string.Empty; + var stableInstallPath = findFromEnvVar() ?? + findFromRegistry() ?? + findFromLocalAppData() ?? + findFromDotFolder(); - try - { - List> stableFindMethods = new List> - { - findFromEnvVar, - findFromRegistry, - findFromLocalAppData, - findFromDotFolder - }; - - foreach (var r in stableFindMethods) - { - stableInstallPath = r.Invoke(); - - if (stableInstallPath != null) - return stableInstallPath; - } - - return null; - } - finally - { - Logger.Log($"Stable path for tourney usage: {stableInstallPath}"); - } + Logger.Log($"Stable path for tourney usage: {stableInstallPath}"); + return stableInstallPath; } private string findFromEnvVar() From fc31d4962938dc29c22e43c63e403be0530c909d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jun 2020 16:34:04 +0900 Subject: [PATCH 555/871] try-catch registry lookup to avoid crashes on non-windows platforms --- osu.Game.Tournament/IPC/FileBasedIPC.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tournament/IPC/FileBasedIPC.cs b/osu.Game.Tournament/IPC/FileBasedIPC.cs index 42be6ea119..999ce61ac8 100644 --- a/osu.Game.Tournament/IPC/FileBasedIPC.cs +++ b/osu.Game.Tournament/IPC/FileBasedIPC.cs @@ -238,13 +238,19 @@ namespace osu.Game.Tournament.IPC { Logger.Log("Trying to find stable in registry"); - string stableInstallPath; + try + { + string stableInstallPath; - using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) - stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); + using (RegistryKey key = Registry.ClassesRoot.OpenSubKey("osu")) + stableInstallPath = key?.OpenSubKey(@"shell\open\command")?.GetValue(string.Empty).ToString().Split('"')[1].Replace("osu!.exe", ""); - if (ipcFileExistsInDirectory(stableInstallPath)) - return stableInstallPath; + if (ipcFileExistsInDirectory(stableInstallPath)) + return stableInstallPath; + } + catch + { + } return null; } From 628e05f655efc53043448f5cc6f33e0bac117347 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jun 2020 17:09:22 +0900 Subject: [PATCH 556/871] 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 119c309675..192be999eb 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 bec3bc9d39..911292c6ae 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -25,7 +25,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index de5130b66a..18249b40ca 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 8d3ed0584878edf8914df04f7265028492cbf740 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jun 2020 17:42:54 +0900 Subject: [PATCH 557/871] Update welcome text sprite location --- osu.Game/Screens/Menu/IntroWelcome.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 711c7b64e4..92c844db8b 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -124,7 +124,7 @@ namespace osu.Game.Screens.Menu Scale = new Vector2(0.1f), Height = 156, Alpha = 0, - Texture = textures.Get(@"Welcome/welcome_text") + Texture = textures.Get(@"Intro/Welcome/welcome_text") }, }; } From 533d6e72eb5dc403408b0917af2ca431e2890e86 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 18:05:21 +0900 Subject: [PATCH 558/871] Refactor + comment angle math --- .../Statistics/AccuracyHeatmap.cs | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index f05bfce8d7..f8ab03aad0 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -167,11 +167,28 @@ namespace osu.Game.Rulesets.Osu.Statistics double finalAngle = angle2 - angle1; // Angle between start, end, and hit points. float normalisedDistance = Vector2.Distance(hitPoint, end) / radius; - // Convert the above into the local search space. + // Consider two objects placed horizontally, with the start on the left and the end on the right. + // The above calculated the angle between {end, start}, and the angle between {end, hitPoint}, in the form: + // +pi | 0 + // O --------- O -----> Note: Math.Atan2 has a range (-pi <= theta <= +pi) + // -pi | 0 + // E.g. If the hit point was directly above end, it would have an angle pi/2. + // + // It also calculated the angle separating hitPoint from the line joining {start, end}, that is anti-clockwise in the form: + // 0 | pi + // O --------- O -----> + // 2pi | pi + // + // However keep in mind that cos(0)=1 and cos(2pi)=1, whereas we actually want these values to appear on the left, so the x-coordinate needs to be inverted. + // Likewise sin(pi/2)=1 and sin(3pi/2)=-1, whereas we actually want these values to appear on the bottom/top respectively, so the y-coordinate also needs to be inverted. + // + // We also need to apply the anti-clockwise rotation. + var rotatedAngle = finalAngle - MathUtils.DegreesToRadians(rotation); + var rotatedCoordinate = -1 * new Vector2((float)Math.Cos(rotatedAngle), (float)Math.Sin(rotatedAngle)); + Vector2 localCentre = new Vector2(points_per_dimension) / 2; float localRadius = localCentre.X * inner_portion * normalisedDistance; // The radius inside the inner portion which of the heatmap which the closest point lies. - double localAngle = finalAngle + Math.PI - MathUtils.DegreesToRadians(rotation); // The angle inside the heatmap on which the closest point lies. - Vector2 localPoint = localCentre + localRadius * new Vector2((float)Math.Cos(localAngle), (float)Math.Sin(localAngle)); + Vector2 localPoint = localCentre + localRadius * rotatedCoordinate; // Find the most relevant hit point. int r = Math.Clamp((int)Math.Round(localPoint.Y), 0, points_per_dimension - 1); From 9dbd230ad30f9f4e8564ae986c0f66f99415b131 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 18:06:52 +0900 Subject: [PATCH 559/871] Don't consider slider tails in timing distribution --- osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 8222eba339..a164265290 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Osu { Columns = new[] { - new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is HitCircle).ToList()) + new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList()) { RelativeSizeAxes = Axes.X, Height = 130 From 261adfc4e682973738960c6ace4c284f502811fd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 18:38:41 +0900 Subject: [PATCH 560/871] Create a local playable beatmap instead --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- .../TestSceneAccuracyHeatmap.cs | 6 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 4 +- .../Statistics/AccuracyHeatmap.cs | 7 +- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- osu.Game/Rulesets/Ruleset.cs | 3 +- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Screens/Play/ReplayPlayer.cs | 2 - .../Ranking/Statistics/StatisticsPanel.cs | 67 ++++++++++++------- 9 files changed, 57 insertions(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index f8fa5d4c40..411956e120 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -311,7 +311,7 @@ namespace osu.Game.Rulesets.Mania return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast().OrderByDescending(i => i).First(v => variant >= v); } - public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score) => new[] + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[] { new StatisticRow { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs index f2a36ea017..49b469ba24 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs @@ -39,12 +39,12 @@ namespace osu.Game.Rulesets.Osu.Tests RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333"), }, - object1 = new BorderCircle + object2 = new BorderCircle { Position = new Vector2(256, 192), Colour = Color4.Yellow, }, - object2 = new BorderCircle + object1 = new BorderCircle { Position = new Vector2(100, 300), }, @@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Tests private class TestAccuracyHeatmap : AccuracyHeatmap { public TestAccuracyHeatmap(ScoreInfo score) - : base(score) + : base(score, new TestBeatmap(new OsuRuleset().RulesetInfo)) { } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index a164265290..2ba2f4b097 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -192,7 +192,7 @@ namespace osu.Game.Rulesets.Osu public override IRulesetConfigManager CreateConfig(SettingsStore settings) => new OsuRulesetConfigManager(settings, RulesetInfo); - public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score) => new[] + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[] { new StatisticRow { @@ -203,7 +203,7 @@ namespace osu.Game.Rulesets.Osu RelativeSizeAxes = Axes.X, Height = 130 }), - new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score) + new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap) { RelativeSizeAxes = Axes.X, Height = 130 diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index f8ab03aad0..58089553a4 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Utils; +using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Scoring; using osuTK; @@ -35,10 +36,12 @@ namespace osu.Game.Rulesets.Osu.Statistics private GridContainer pointGrid; private readonly ScoreInfo score; + private readonly IBeatmap playableBeatmap; - public AccuracyHeatmap(ScoreInfo score) + public AccuracyHeatmap(ScoreInfo score, IBeatmap playableBeatmap) { this.score = score; + this.playableBeatmap = playableBeatmap; } [BackgroundDependencyLoader] @@ -146,7 +149,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 * (score.Beatmap.BaseDifficulty.CircleSize - 5) / 5) / 2; + float radius = OsuHitObject.OBJECT_RADIUS * (1.0f - 0.7f * (playableBeatmap.BeatmapInfo.BaseDifficulty.CircleSize - 5) / 5) / 2; foreach (var e in score.HitEvents.Where(e => e.HitObject is HitCircle)) { diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 92b04e8397..17d0800228 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -159,7 +159,7 @@ namespace osu.Game.Rulesets.Taiko public override IConvertibleReplayFrame CreateConvertibleReplayFrame() => new TaikoReplayFrame(); - public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score) => new[] + public override StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => new[] { new StatisticRow { diff --git a/osu.Game/Rulesets/Ruleset.cs b/osu.Game/Rulesets/Ruleset.cs index f9c2b09be9..3a7f433a37 100644 --- a/osu.Game/Rulesets/Ruleset.cs +++ b/osu.Game/Rulesets/Ruleset.cs @@ -214,8 +214,9 @@ namespace osu.Game.Rulesets /// Creates the statistics for a to be displayed in the results screen. /// /// The to create the statistics for. The score is guaranteed to have populated. + /// The , converted for this with all relevant s applied. /// The s to display. Each may contain 0 or more . [NotNull] - public virtual StatisticRow[] CreateStatisticsForScore(ScoreInfo score) => Array.Empty(); + public virtual StatisticRow[] CreateStatisticsForScore(ScoreInfo score, IBeatmap playableBeatmap) => Array.Empty(); } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c2bb75b8f3..d3b88e56ae 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -463,7 +463,7 @@ namespace osu.Game.Screens.Play { var score = new ScoreInfo { - Beatmap = gameplayBeatmap.BeatmapInfo, + Beatmap = Beatmap.Value.BeatmapInfo, Ruleset = rulesetInfo, Mods = Mods.Value.ToArray(), }; diff --git a/osu.Game/Screens/Play/ReplayPlayer.cs b/osu.Game/Screens/Play/ReplayPlayer.cs index 8a925958fd..7f5c17a265 100644 --- a/osu.Game/Screens/Play/ReplayPlayer.cs +++ b/osu.Game/Screens/Play/ReplayPlayer.cs @@ -31,8 +31,6 @@ namespace osu.Game.Screens.Play var baseScore = base.CreateScore(); // Since the replay score doesn't contain statistics, we'll pass them through here. - // We also have to pass in the beatmap to get the post-mod-application version. - score.ScoreInfo.Beatmap = baseScore.Beatmap; score.ScoreInfo.HitEvents = baseScore.HitEvents; return score.ScoreInfo; diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index cac2bf866b..8aceaa335c 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -1,14 +1,18 @@ // 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 System.Threading; +using System.Threading.Tasks; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Placeholders; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osuTK; @@ -22,6 +26,9 @@ namespace osu.Game.Screens.Ranking.Statistics protected override bool StartHidden => true; + [Resolved] + private BeatmapManager beatmapManager { get; set; } + private readonly Container content; private readonly LoadingSpinner spinner; @@ -71,37 +78,47 @@ namespace osu.Game.Screens.Ranking.Statistics { spinner.Show(); - var rows = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Spacing = new Vector2(30, 15), - }; + var localCancellationSource = loadCancellation = new CancellationTokenSource(); + IBeatmap playableBeatmap = null; - foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore)) + // 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(() => { - rows.Add(new GridContainer + playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.Beatmap).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods ?? Array.Empty()); + }, loadCancellation.Token).ContinueWith(t => + { + var rows = new FillFlowContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(30, 15), + }; + + foreach (var row in newScore.Ruleset.CreateInstance().CreateStatisticsForScore(newScore, playableBeatmap)) + { + rows.Add(new GridContainer { - row.Columns?.Select(c => new StatisticContainer(c)).Cast().ToArray() - }, - ColumnDimensions = Enumerable.Range(0, row.Columns?.Length ?? 0) - .Select(i => row.Columns[i].Dimension ?? new Dimension()).ToArray(), - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } - }); - } + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] + { + row.Columns?.Select(c => new StatisticContainer(c)).Cast().ToArray() + }, + ColumnDimensions = Enumerable.Range(0, row.Columns?.Length ?? 0) + .Select(i => row.Columns[i].Dimension ?? new Dimension()).ToArray(), + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }); + } - LoadComponentAsync(rows, d => - { - if (Score.Value != newScore) - return; + LoadComponentAsync(rows, d => + { + if (Score.Value != newScore) + return; - spinner.Hide(); - content.Add(d); - }, (loadCancellation = new CancellationTokenSource()).Token); + spinner.Hide(); + content.Add(d); + }, localCancellationSource.Token); + }, localCancellationSource.Token); } } From 2b7fb2b71d352a97031b7477315c50f0a9f8ba70 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 19:04:51 +0900 Subject: [PATCH 561/871] Rename to Position --- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 4 ++-- osu.Game/Rulesets/Scoring/HitEvent.cs | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 58089553a4..40bdeeaa88 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -153,10 +153,10 @@ namespace osu.Game.Rulesets.Osu.Statistics foreach (var e in score.HitEvents.Where(e => e.HitObject is HitCircle)) { - if (e.LastHitObject == null || e.PositionOffset == null) + if (e.LastHitObject == null || e.Position == null) continue; - AddPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.PositionOffset.Value, radius); + AddPoint(((OsuHitObject)e.LastHitObject).StackedEndPosition, ((OsuHitObject)e.HitObject).StackedEndPosition, e.Position.Value, radius); } } diff --git a/osu.Game/Rulesets/Scoring/HitEvent.cs b/osu.Game/Rulesets/Scoring/HitEvent.cs index ea2975a6c4..0ebbec62ba 100644 --- a/osu.Game/Rulesets/Scoring/HitEvent.cs +++ b/osu.Game/Rulesets/Scoring/HitEvent.cs @@ -34,10 +34,10 @@ namespace osu.Game.Rulesets.Scoring public readonly HitObject LastHitObject; /// - /// A position offset, if available, at the time of the event. + /// A position, if available, at the time of the event. /// [CanBeNull] - public readonly Vector2? PositionOffset; + public readonly Vector2? Position; /// /// Creates a new . @@ -46,14 +46,14 @@ namespace osu.Game.Rulesets.Scoring /// The . /// The that triggered the event. /// The previous . - /// A positional offset. - public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, [CanBeNull] Vector2? positionOffset) + /// A position corresponding to the event. + public HitEvent(double timeOffset, HitResult result, HitObject hitObject, [CanBeNull] HitObject lastHitObject, [CanBeNull] Vector2? position) { TimeOffset = timeOffset; Result = result; HitObject = hitObject; LastHitObject = lastHitObject; - PositionOffset = positionOffset; + Position = position; } /// From 30aa6ec2d3ff6dc63c0bf9b3d33cb5aef820c35c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 19:05:41 +0900 Subject: [PATCH 562/871] Don't consider slider tails in accuracy heatmap --- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 40bdeeaa88..cba753e003 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.Osu.Statistics // 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; - foreach (var e in score.HitEvents.Where(e => e.HitObject is HitCircle)) + foreach (var e in score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle))) { if (e.LastHitObject == null || e.Position == null) continue; From 988baad16f296bb4f3df9f2c5e3478651057ceff Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 19:20:43 +0900 Subject: [PATCH 563/871] Expand statistics to fill more of the screen --- osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 +- osu.Game.Rulesets.Osu/OsuRuleset.cs | 13 +++++++++---- osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 +- .../Screens/Ranking/Statistics/StatisticsPanel.cs | 8 +++++++- 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index 411956e120..a27485dd06 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -320,7 +320,7 @@ namespace osu.Game.Rulesets.Mania new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents) { RelativeSizeAxes = Axes.X, - Height = 130 + Height = 250 }), } } diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index 2ba2f4b097..e488ba65c8 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -30,7 +30,6 @@ using osu.Game.Scoring; using osu.Game.Skinning; using System; using System.Linq; -using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Screens.Ranking.Statistics; @@ -201,13 +200,19 @@ namespace osu.Game.Rulesets.Osu new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is HitCircle && !(e.HitObject is SliderTailCircle)).ToList()) { RelativeSizeAxes = Axes.X, - Height = 130 + Height = 250 }), + } + }, + new StatisticRow + { + Columns = new[] + { new StatisticItem("Accuracy Heatmap", new AccuracyHeatmap(score, playableBeatmap) { RelativeSizeAxes = Axes.X, - Height = 130 - }, new Dimension(GridSizeMode.Absolute, 130)), + Height = 250 + }), } } }; diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 17d0800228..156905fa9c 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -168,7 +168,7 @@ namespace osu.Game.Rulesets.Taiko new StatisticItem("Timing Distribution", new HitEventTimingDistributionGraph(score.HitEvents.Where(e => e.HitObject is Hit).ToList()) { RelativeSizeAxes = Axes.X, - Height = 130 + Height = 250 }), } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 8aceaa335c..d2d2adb2f4 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -98,11 +98,17 @@ namespace osu.Game.Screens.Ranking.Statistics { rows.Add(new GridContainer { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Content = new[] { - row.Columns?.Select(c => new StatisticContainer(c)).Cast().ToArray() + row.Columns?.Select(c => new StatisticContainer(c) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }).Cast().ToArray() }, ColumnDimensions = Enumerable.Range(0, row.Columns?.Length ?? 0) .Select(i => row.Columns[i].Dimension ?? new Dimension()).ToArray(), From 4d30761ce3131eccaab114285018f5ab0cc54a79 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 19:49:38 +0900 Subject: [PATCH 564/871] Fix 1M score being possible with only GREATs in mania --- osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs index c2f8fb8678..53db676a54 100644 --- a/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs +++ b/osu.Game.Rulesets.Mania/Judgements/ManiaJudgement.cs @@ -25,8 +25,10 @@ namespace osu.Game.Rulesets.Mania.Judgements return 200; case HitResult.Great: - case HitResult.Perfect: return 300; + + case HitResult.Perfect: + return 320; } } } From 5c4df2e32c0a1cd8f79f8d5e1e99e9fef6bfd228 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 20:20:42 +0900 Subject: [PATCH 565/871] Cancel load on dispose --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index d2d2adb2f4..651cdc4b0f 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -63,6 +63,7 @@ namespace osu.Game.Screens.Ranking.Statistics private void populateStatistics(ValueChangedEvent score) { loadCancellation?.Cancel(); + loadCancellation = null; foreach (var child in content) child.FadeOut(150).Expire(); @@ -131,5 +132,12 @@ namespace osu.Game.Screens.Ranking.Statistics protected override void PopIn() => this.FadeIn(150, Easing.OutQuint); protected override void PopOut() => this.FadeOut(150, Easing.OutQuint); + + protected override void Dispose(bool isDisposing) + { + loadCancellation?.Cancel(); + + base.Dispose(isDisposing); + } } } From ff2f3a8484022a209b09d5c72343561e26e4128a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 20:32:04 +0900 Subject: [PATCH 566/871] Fix div-by-zero errors with autoplay --- ...estSceneHitEventTimingDistributionGraph.cs | 28 ++++++++++++++++--- .../HitEventTimingDistributionGraph.cs | 4 +++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs index b34529cca7..7ca1fc842f 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneHitEventTimingDistributionGraph.cs @@ -3,6 +3,8 @@ using System; using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; @@ -15,7 +17,25 @@ namespace osu.Game.Tests.Visual.Ranking { public class TestSceneHitEventTimingDistributionGraph : OsuTestScene { - public TestSceneHitEventTimingDistributionGraph() + [Test] + public void TestManyDistributedEvents() + { + createTest(CreateDistributedHitEvents()); + } + + [Test] + public void TestZeroTimeOffset() + { + createTest(Enumerable.Range(0, 100).Select(_ => new HitEvent(0, HitResult.Perfect, new HitCircle(), new HitCircle(), null)).ToList()); + } + + [Test] + public void TestNoEvents() + { + createTest(new List()); + } + + private void createTest(List events) => AddStep("create test", () => { Children = new Drawable[] { @@ -24,14 +44,14 @@ namespace osu.Game.Tests.Visual.Ranking RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333") }, - new HitEventTimingDistributionGraph(CreateDistributedHitEvents()) + new HitEventTimingDistributionGraph(events) { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(400, 130) + Size = new Vector2(600, 130) } }; - } + }); public static List CreateDistributedHitEvents() { diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 9b46bea2cb..8ec7e863b1 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -58,8 +58,12 @@ namespace osu.Game.Screens.Ranking.Statistics return; int[] bins = new int[total_timing_distribution_bins]; + double binSize = Math.Ceiling(hitEvents.Max(e => Math.Abs(e.TimeOffset)) / timing_distribution_bins); + // Prevent div-by-0 by enforcing a minimum bin size + binSize = Math.Max(1, binSize); + foreach (var e in hitEvents) { int binOffset = (int)(e.TimeOffset / binSize); From 6afd6efdeba5fe08b9598c9de1c089a162bcc467 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 20:33:08 +0900 Subject: [PATCH 567/871] Return default beatmap if local beatmap can't be retrieved --- osu.Game/Beatmaps/BeatmapManager.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 2cf3a21975..637833fb5d 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -240,6 +240,9 @@ namespace osu.Game.Beatmaps beatmapInfo = QueryBeatmap(b => b.ID == info.ID); } + if (beatmapInfo == null) + return DefaultBeatmap; + lock (workingCache) { var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID); From 983f0ada2da68527f5892ffd6ff0202c05d1d439 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 20:44:39 +0900 Subject: [PATCH 568/871] Increase number of points to ensure there's a centre --- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index cba753e003..23539f3a12 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -27,9 +27,9 @@ namespace osu.Game.Rulesets.Osu.Statistics /// /// Number of rows/columns of points. - /// 4px per point @ 128x128 size (the contents of the are always square). 1024 total points. + /// ~4px per point @ 128x128 size (the contents of the are always square). 1089 total points. /// - private const int points_per_dimension = 32; + private const int points_per_dimension = 33; private const float rotation = 45; From 1aec1ea53fc9566e384f03fd96e5e5af72f3a2be Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 20:45:44 +0900 Subject: [PATCH 569/871] Fix off-by-one causing auto to not be centred --- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 23539f3a12..0d6d05292a 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -189,7 +189,7 @@ namespace osu.Game.Rulesets.Osu.Statistics var rotatedAngle = finalAngle - MathUtils.DegreesToRadians(rotation); var rotatedCoordinate = -1 * new Vector2((float)Math.Cos(rotatedAngle), (float)Math.Sin(rotatedAngle)); - Vector2 localCentre = new Vector2(points_per_dimension) / 2; + Vector2 localCentre = new Vector2(points_per_dimension - 1) / 2; float localRadius = localCentre.X * inner_portion * normalisedDistance; // The radius inside the inner portion which of the heatmap which the closest point lies. Vector2 localPoint = localCentre + localRadius * rotatedCoordinate; From beb6e6ea88af3dbf213808accea49d247e358ce8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 21:00:13 +0900 Subject: [PATCH 570/871] Buffer the accuracy heatmap for performance --- .../Statistics/AccuracyHeatmap.cs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 0d6d05292a..6e1b6ef9b5 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -33,6 +33,7 @@ namespace osu.Game.Rulesets.Osu.Statistics private const float rotation = 45; + private BufferedContainer bufferedGrid; private GridContainer pointGrid; private readonly ScoreInfo score; @@ -112,10 +113,16 @@ namespace osu.Game.Rulesets.Osu.Statistics } } }, - pointGrid = new GridContainer + bufferedGrid = new BufferedContainer { - RelativeSizeAxes = Axes.Both - } + RelativeSizeAxes = Axes.Both, + CacheDrawnFrameBuffer = true, + BackgroundColour = Color4Extensions.FromHex("#202624").Opacity(0), + Child = pointGrid = new GridContainer + { + RelativeSizeAxes = Axes.Both + } + }, } }; @@ -198,6 +205,8 @@ namespace osu.Game.Rulesets.Osu.Statistics int c = Math.Clamp((int)Math.Round(localPoint.X), 0, points_per_dimension - 1); ((HitPoint)pointGrid.Content[r][c]).Increment(); + + bufferedGrid.ForceRedraw(); } private class HitPoint : Circle From b3e200ee7face7f246ef82ee56faba604a216740 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 21:00:35 +0900 Subject: [PATCH 571/871] Re-invert test --- osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs index 49b469ba24..10d9d7ffde 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneAccuracyHeatmap.cs @@ -39,12 +39,12 @@ namespace osu.Game.Rulesets.Osu.Tests RelativeSizeAxes = Axes.Both, Colour = Color4Extensions.FromHex("#333"), }, - object2 = new BorderCircle + object1 = new BorderCircle { Position = new Vector2(256, 192), Colour = Color4.Yellow, }, - object1 = new BorderCircle + object2 = new BorderCircle { Position = new Vector2(100, 300), }, From cb03e6faa9cf975719a8d3753ca063bb986ed8bb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 21:09:47 +0900 Subject: [PATCH 572/871] Improve visual display of arrow --- .../Statistics/AccuracyHeatmap.cs | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 6e1b6ef9b5..94d47ecb32 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -74,41 +74,52 @@ namespace osu.Game.Rulesets.Osu.Statistics new Container { RelativeSizeAxes = Axes.Both, - Masking = true, Children = new Drawable[] { - new Box + new Container { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = 1f, - Rotation = -rotation, - Alpha = 0.3f, - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = 1f, - Rotation = rotation + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(1), + Child = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 2, // We're rotating along a diagonal - we don't really care how big this is. + Width = 1f, + Rotation = -rotation, + Alpha = 0.3f, + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Height = 2, // We're rotating along a diagonal - we don't really care how big this is. + Width = 1f, + Rotation = rotation + }, + } + }, }, new Box { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Width = 10, - Height = 2f, + Height = 2, }, new Box { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Y = -1, - Width = 2f, + Width = 2, Height = 10, } } From f60a80b2635f4beb8d45f5a8432abbb2bf36e278 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 22 Jun 2020 18:01:08 +0900 Subject: [PATCH 573/871] Fix animations and general code quality --- osu.Game/Screens/Menu/IntroWelcome.cs | 73 +++++++++++---------------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 92c844db8b..7714ec6ee1 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -69,63 +69,47 @@ namespace osu.Game.Screens.Menu private class WelcomeIntroSequence : Container { private Sprite welcomeText; + private Container scaleContainer; [BackgroundDependencyLoader] private void load(TextureStore textures) { Origin = Anchor.Centre; Anchor = Anchor.Centre; + Children = new Drawable[] { - new Container + scaleContainer = new Container { + AutoSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - AutoSizeAxes = Axes.Both, Children = new Drawable[] { - new Container + new LogoVisualisation { - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new LogoVisualisation - { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Alpha = 0.5f, - AccentColour = Color4.DarkBlue, - Size = new Vector2(0.96f) - }, - new Container - { - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new Circle - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(480), - Colour = Color4.Black - } - } - } - } - } + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0.5f, + AccentColour = Color4.DarkBlue, + Size = new Vector2(0.96f) + }, + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(480), + Colour = Color4.Black + }, + welcomeText = new Sprite + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Texture = textures.Get(@"Intro/Welcome/welcome_text") + }, } }, - welcomeText = new Sprite - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Scale = new Vector2(0.1f), - Height = 156, - Alpha = 0, - Texture = textures.Get(@"Intro/Welcome/welcome_text") - }, }; } @@ -135,9 +119,10 @@ namespace osu.Game.Screens.Menu using (BeginDelayedSequence(0, true)) { - welcomeText.ResizeHeightTo(welcomeText.Height * 2, 500, Easing.In); - welcomeText.FadeIn(delay_step_two); - welcomeText.ScaleTo(welcomeText.Scale + new Vector2(0.1f), delay_step_two, Easing.Out).OnComplete(_ => Expire()); + scaleContainer.ScaleTo(0.9f).ScaleTo(1, delay_step_two).OnComplete(_ => Expire()); + scaleContainer.FadeInFromZero(1800); + + welcomeText.ScaleTo(new Vector2(1, 0)).ScaleTo(Vector2.One, 400, Easing.Out); } } } From 1bf00e0c820c8c391a0b7574b8cfc28ea0374c43 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 22 Jun 2020 23:22:49 +0900 Subject: [PATCH 574/871] Schedule continuation --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 651cdc4b0f..77f3bd7b5c 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Ranking.Statistics Task.Run(() => { playableBeatmap = beatmapManager.GetWorkingBeatmap(newScore.Beatmap).GetPlayableBeatmap(newScore.Ruleset, newScore.Mods ?? Array.Empty()); - }, loadCancellation.Token).ContinueWith(t => + }, loadCancellation.Token).ContinueWith(t => Schedule(() => { var rows = new FillFlowContainer { @@ -125,7 +125,7 @@ namespace osu.Game.Screens.Ranking.Statistics spinner.Hide(); content.Add(d); }, localCancellationSource.Token); - }, localCancellationSource.Token); + }), localCancellationSource.Token); } } From 7a48ab1774cfaca8697f47806651ec6e3cd6c8ff Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 22 Jun 2020 17:19:46 +0000 Subject: [PATCH 575/871] Bump ppy.osu.Game.Resources from 2020.602.0 to 2020.622.1 Bumps [ppy.osu.Game.Resources](https://github.com/ppy/osu-resources) from 2020.602.0 to 2020.622.1. - [Release notes](https://github.com/ppy/osu-resources/releases) - [Commits](https://github.com/ppy/osu-resources/compare/2020.602.0...2020.622.1) Signed-off-by: dependabot-preview[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 119c309675..192be999eb 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 bec3bc9d39..911292c6ae 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -25,7 +25,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index de5130b66a..18249b40ca 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From e827b14abf5212aa0809256b4830456acda994e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Jun 2020 16:40:05 +0200 Subject: [PATCH 576/871] Add LayeredHitSamples skin config lookup --- osu.Game/Skinning/GlobalSkinConfiguration.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/GlobalSkinConfiguration.cs b/osu.Game/Skinning/GlobalSkinConfiguration.cs index 8774fe5a97..d405702ea5 100644 --- a/osu.Game/Skinning/GlobalSkinConfiguration.cs +++ b/osu.Game/Skinning/GlobalSkinConfiguration.cs @@ -5,6 +5,7 @@ namespace osu.Game.Skinning { public enum GlobalSkinConfiguration { - AnimationFramerate + AnimationFramerate, + LayeredHitSounds, } } From c5049b51c5835ab6950d1f9244d0d355157439a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Jun 2020 16:43:21 +0200 Subject: [PATCH 577/871] Mark normal-hitnormal sample as layered --- .../Objects/Legacy/ConvertHitObjectParser.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs index 9e936c7717..77075b2abe 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertHitObjectParser.cs @@ -12,6 +12,7 @@ using System.Linq; using JetBrains.Annotations; using osu.Framework.Utils; using osu.Game.Beatmaps.Legacy; +using osu.Game.Skinning; namespace osu.Game.Rulesets.Objects.Legacy { @@ -356,7 +357,10 @@ namespace osu.Game.Rulesets.Objects.Legacy Bank = bankInfo.Normal, Name = HitSampleInfo.HIT_NORMAL, Volume = bankInfo.Volume, - CustomSampleBank = bankInfo.CustomSampleBank + CustomSampleBank = bankInfo.CustomSampleBank, + // if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample. + // None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds + IsLayered = type != LegacyHitSoundType.None && !type.HasFlag(LegacyHitSoundType.Normal) } }; @@ -409,7 +413,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone(); } - internal class LegacyHitSampleInfo : HitSampleInfo + public class LegacyHitSampleInfo : HitSampleInfo { private int customSampleBank; @@ -424,6 +428,15 @@ namespace osu.Game.Rulesets.Objects.Legacy Suffix = value.ToString(); } } + + /// + /// Whether this hit sample is layered. + /// + /// + /// Layered hit samples are automatically added in all modes (except osu!mania), but can be disabled + /// using the skin config option. + /// + public bool IsLayered { get; set; } } private class FileHitSampleInfo : LegacyHitSampleInfo From c7d2ce12eb1cbf0cd6a8f5d1c72ac482d6ed62a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Jun 2020 18:52:15 +0200 Subject: [PATCH 578/871] Add failing test cases --- ...a-hitobject-beatmap-custom-sample-bank.osu | 10 ++++ ...a-hitobject-beatmap-normal-sample-bank.osu | 10 ++++ .../TestSceneManiaHitObjectSamples.cs | 49 +++++++++++++++ .../Gameplay/TestSceneHitObjectSamples.cs | 60 +++++++++++++++++++ .../hitobject-beatmap-custom-sample-bank.osu | 7 +++ .../Tests/Beatmaps/HitObjectSampleTest.cs | 12 +++- 6 files changed, 147 insertions(+), 1 deletion(-) create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-custom-sample-bank.osu create mode 100644 osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-normal-sample-bank.osu create mode 100644 osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectSamples.cs create mode 100644 osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample-bank.osu diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-custom-sample-bank.osu b/osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-custom-sample-bank.osu new file mode 100644 index 0000000000..4f8e1b68dd --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-custom-sample-bank.osu @@ -0,0 +1,10 @@ +osu file format v14 + +[General] +Mode: 3 + +[TimingPoints] +0,300,4,0,2,100,1,0 + +[HitObjects] +444,320,1000,5,2,0:0:0:0: diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-normal-sample-bank.osu b/osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-normal-sample-bank.osu new file mode 100644 index 0000000000..f22901e304 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/Resources/SampleLookups/mania-hitobject-beatmap-normal-sample-bank.osu @@ -0,0 +1,10 @@ +osu file format v14 + +[General] +Mode: 3 + +[TimingPoints] +0,300,4,0,2,100,1,0 + +[HitObjects] +444,320,1000,5,1,0:0:0:0: diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectSamples.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectSamples.cs new file mode 100644 index 0000000000..0d726e1a50 --- /dev/null +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneManiaHitObjectSamples.cs @@ -0,0 +1,49 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Reflection; +using NUnit.Framework; +using osu.Framework.IO.Stores; +using osu.Game.Tests.Beatmaps; + +namespace osu.Game.Rulesets.Mania.Tests +{ + public class TestSceneManiaHitObjectSamples : HitObjectSampleTest + { + protected override Ruleset CreatePlayerRuleset() => new ManiaRuleset(); + protected override IResourceStore Resources => new DllResourceStore(Assembly.GetAssembly(typeof(TestSceneManiaHitObjectSamples))); + + /// + /// Tests that when a normal sample bank is used, the normal hitsound will be looked up. + /// + [Test] + public void TestManiaHitObjectNormalSampleBank() + { + const string expected_sample = "normal-hitnormal2"; + + SetupSkins(expected_sample, expected_sample); + + CreateTestWithBeatmap("mania-hitobject-beatmap-normal-sample-bank.osu"); + + AssertBeatmapLookup(expected_sample); + } + + /// + /// Tests that when a custom sample bank is used, layered hitsounds are not played + /// (only the sample from the custom bank is looked up). + /// + [Test] + public void TestManiaHitObjectCustomSampleBank() + { + const string expected_sample = "normal-hitwhistle2"; + const string unwanted_sample = "normal-hitnormal2"; + + SetupSkins(expected_sample, unwanted_sample); + + CreateTestWithBeatmap("mania-hitobject-beatmap-custom-sample-bank.osu"); + + AssertBeatmapLookup(expected_sample); + AssertNoLookup(unwanted_sample); + } + } +} diff --git a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs index ef6efb7fec..737946e1e0 100644 --- a/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs +++ b/osu.Game.Tests/Gameplay/TestSceneHitObjectSamples.cs @@ -6,6 +6,7 @@ using osu.Framework.IO.Stores; using osu.Framework.Testing; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Skinning; using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; @@ -167,5 +168,64 @@ namespace osu.Game.Tests.Gameplay AssertBeatmapLookup(expected_sample); } + + /// + /// Tests that when a custom sample bank is used, both the normal and additional sounds will be looked up. + /// + [Test] + public void TestHitObjectCustomSampleBank() + { + string[] expectedSamples = + { + "normal-hitnormal2", + "normal-hitwhistle2" + }; + + SetupSkins(expectedSamples[0], expectedSamples[1]); + + CreateTestWithBeatmap("hitobject-beatmap-custom-sample-bank.osu"); + + AssertBeatmapLookup(expectedSamples[0]); + AssertUserLookup(expectedSamples[1]); + } + + /// + /// Tests that when a custom sample bank is used, but is disabled, + /// only the additional sound will be looked up. + /// + [Test] + public void TestHitObjectCustomSampleBankWithoutLayered() + { + const string expected_sample = "normal-hitwhistle2"; + const string unwanted_sample = "normal-hitnormal2"; + + SetupSkins(expected_sample, unwanted_sample); + disableLayeredHitSounds(); + + CreateTestWithBeatmap("hitobject-beatmap-custom-sample-bank.osu"); + + AssertBeatmapLookup(expected_sample); + AssertNoLookup(unwanted_sample); + } + + /// + /// Tests that when a normal sample bank is used and is disabled, + /// the normal sound will be looked up anyway. + /// + [Test] + public void TestHitObjectNormalSampleBankWithoutLayered() + { + const string expected_sample = "normal-hitnormal"; + + SetupSkins(expected_sample, expected_sample); + disableLayeredHitSounds(); + + CreateTestWithBeatmap("hitobject-beatmap-sample.osu"); + + AssertBeatmapLookup(expected_sample); + } + + private void disableLayeredHitSounds() + => AddStep("set LayeredHitSounds to false", () => Skin.Configuration.ConfigDictionary[GlobalSkinConfiguration.LayeredHitSounds.ToString()] = "0"); } } diff --git a/osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample-bank.osu b/osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample-bank.osu new file mode 100644 index 0000000000..c50c921839 --- /dev/null +++ b/osu.Game.Tests/Resources/SampleLookups/hitobject-beatmap-custom-sample-bank.osu @@ -0,0 +1,7 @@ +osu file format v14 + +[TimingPoints] +0,300,4,0,2,100,1,0 + +[HitObjects] +444,320,1000,5,2,0:0:0:0: diff --git a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs index b4ce322165..ab4fb38657 100644 --- a/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs +++ b/osu.Game/Tests/Beatmaps/HitObjectSampleTest.cs @@ -24,6 +24,10 @@ namespace osu.Game.Tests.Beatmaps public abstract class HitObjectSampleTest : PlayerTestScene { protected abstract IResourceStore Resources { get; } + protected LegacySkin Skin { get; private set; } + + [Resolved] + private RulesetStore rulesetStore { get; set; } private readonly SkinInfo userSkinInfo = new SkinInfo(); @@ -64,6 +68,9 @@ namespace osu.Game.Tests.Beatmaps { using (var reader = new LineBufferedReader(Resources.GetStream($"Resources/SampleLookups/{filename}"))) currentTestBeatmap = Decoder.GetDecoder(reader).Decode(reader); + + // populate ruleset for beatmap converters that require it to be present. + currentTestBeatmap.BeatmapInfo.Ruleset = rulesetStore.GetRuleset(currentTestBeatmap.BeatmapInfo.RulesetID); }); }); } @@ -91,7 +98,7 @@ namespace osu.Game.Tests.Beatmaps }; // Need to refresh the cached skin source to refresh the skin resource store. - dependencies.SkinSource = new SkinProvidingContainer(new LegacySkin(userSkinInfo, userSkinResourceStore, Audio)); + dependencies.SkinSource = new SkinProvidingContainer(Skin = new LegacySkin(userSkinInfo, userSkinResourceStore, Audio)); }); } @@ -101,6 +108,9 @@ namespace osu.Game.Tests.Beatmaps protected void AssertUserLookup(string name) => AddAssert($"\"{name}\" looked up from user skin", () => !beatmapSkinResourceStore.PerformedLookups.Contains(name) && userSkinResourceStore.PerformedLookups.Contains(name)); + protected void AssertNoLookup(string name) => AddAssert($"\"{name}\" not looked up", + () => !beatmapSkinResourceStore.PerformedLookups.Contains(name) && !userSkinResourceStore.PerformedLookups.Contains(name)); + private class SkinSourceDependencyContainer : IReadOnlyDependencyContainer { public ISkinSource SkinSource; From 8233f5fbc4e532cedc6a02b54d453ab106f5bf64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 21 Jun 2020 22:44:35 +0200 Subject: [PATCH 579/871] Check skin option in skin transformers --- .../Skinning/ManiaLegacySkinTransformer.cs | 12 ++++++++++++ osu.Game/Skinning/LegacySkinTransformer.cs | 13 ++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs index 84e88a10be..e167135556 100644 --- a/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs +++ b/osu.Game.Rulesets.Mania/Skinning/ManiaLegacySkinTransformer.cs @@ -9,6 +9,9 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Skinning; using System.Collections.Generic; +using osu.Framework.Audio.Sample; +using osu.Game.Audio; +using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Rulesets.Mania.Skinning { @@ -129,6 +132,15 @@ namespace osu.Game.Rulesets.Mania.Skinning return this.GetAnimation(filename, true, true); } + public override SampleChannel GetSample(ISampleInfo sampleInfo) + { + // layered hit sounds never play in mania + if (sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample && legacySample.IsLayered) + return new SampleChannelVirtual(); + + return Source.GetSample(sampleInfo); + } + public override IBindable GetConfig(TLookup lookup) { if (lookup is ManiaSkinConfigurationLookup maniaLookup) diff --git a/osu.Game/Skinning/LegacySkinTransformer.cs b/osu.Game/Skinning/LegacySkinTransformer.cs index 1131c93288..94a7a32f05 100644 --- a/osu.Game/Skinning/LegacySkinTransformer.cs +++ b/osu.Game/Skinning/LegacySkinTransformer.cs @@ -6,6 +6,7 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; using osu.Game.Audio; +using osu.Game.Rulesets.Objects.Legacy; namespace osu.Game.Skinning { @@ -28,7 +29,17 @@ namespace osu.Game.Skinning public Texture GetTexture(string componentName) => Source.GetTexture(componentName); - public virtual SampleChannel GetSample(ISampleInfo sampleInfo) => Source.GetSample(sampleInfo); + public virtual SampleChannel GetSample(ISampleInfo sampleInfo) + { + if (!(sampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacySample)) + return Source.GetSample(sampleInfo); + + var playLayeredHitSounds = GetConfig(GlobalSkinConfiguration.LayeredHitSounds); + if (legacySample.IsLayered && playLayeredHitSounds?.Value == false) + return new SampleChannelVirtual(); + + return Source.GetSample(sampleInfo); + } public abstract IBindable GetConfig(TLookup lookup); } From a2a2bf4f787fbecb798074e90aed86ae5a8197bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jun 2020 10:05:28 +0900 Subject: [PATCH 580/871] Don't activate run tool window on rider run --- .../.idea/runConfigurations/CatchRuleset__Tests_.xml | 6 +++--- .../.idea/runConfigurations/ManiaRuleset__Tests_.xml | 6 +++--- .../.idea/runConfigurations/OsuRuleset__Tests_.xml | 6 +++--- .../.idea/runConfigurations/TaikoRuleset__Tests_.xml | 6 +++--- .../.idea/runConfigurations/Tournament.xml | 6 +++--- .../.idea/runConfigurations/Tournament__Tests_.xml | 6 +++--- .idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml | 6 +++--- .../.idea.osu.Desktop/.idea/runConfigurations/osu_SDL.xml | 8 ++++---- .../.idea/runConfigurations/osu___Tests_.xml | 6 +++--- 9 files changed, 28 insertions(+), 28 deletions(-) diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml index a4154623b6..512ac4393a 100644 --- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml +++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/CatchRuleset__Tests_.xml @@ -1,8 +1,8 @@ - + \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml index 080dc04001..dec1ef717f 100644 --- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml +++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/ManiaRuleset__Tests_.xml @@ -1,8 +1,8 @@ - + \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml index 3de6a7e609..d9370d5440 100644 --- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml +++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/OsuRuleset__Tests_.xml @@ -1,8 +1,8 @@ - + \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml index da14c2a29e..def4940bb1 100644 --- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml +++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/TaikoRuleset__Tests_.xml @@ -1,8 +1,8 @@ - + \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml index 45d1ce25e9..1ffa73c257 100644 --- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml +++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament.xml @@ -1,8 +1,8 @@ - + \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml index ba80f7c100..e64da796b7 100644 --- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml +++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/Tournament__Tests_.xml @@ -1,8 +1,8 @@ - + \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml index 911c3ed9b7..22105e1de2 100644 --- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml +++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_.xml @@ -1,8 +1,8 @@ - + \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_SDL.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_SDL.xml index d85a0ae44c..31f1fda09d 100644 --- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_SDL.xml +++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu_SDL.xml @@ -1,8 +1,8 @@ - + \ No newline at end of file diff --git a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml index ec3c81f4cd..cc243f6901 100644 --- a/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml +++ b/.idea/.idea.osu.Desktop/.idea/runConfigurations/osu___Tests_.xml @@ -1,8 +1,8 @@ - + \ No newline at end of file From b289beca53e68647d1fd38f57d0a15e94edbaa41 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jun 2020 13:33:33 +0900 Subject: [PATCH 581/871] Fix samples being played too early --- osu.Game/Screens/Menu/IntroWelcome.cs | 33 +++++++++++++++------------ 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 7714ec6ee1..7ab74cbf22 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -39,24 +39,27 @@ namespace osu.Game.Screens.Menu if (!resuming) { - welcome?.Play(); - pianoReverb?.Play(); - - Scheduler.AddDelayed(() => - { - StartTrack(); - PrepareMenuLoad(); - - logo.ScaleTo(1); - logo.FadeIn(); - - Scheduler.Add(LoadMenu); - }, delay_step_two); - LoadComponentAsync(new WelcomeIntroSequence { RelativeSizeAxes = Axes.Both - }, AddInternal); + }, intro => + { + AddInternal(intro); + + welcome?.Play(); + pianoReverb?.Play(); + + Scheduler.AddDelayed(() => + { + StartTrack(); + PrepareMenuLoad(); + + logo.ScaleTo(1); + logo.FadeIn(); + + Scheduler.Add(LoadMenu); + }, delay_step_two); + }); } } From 4554a7db3362d3e477cbbaee424c7b490578efb1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jun 2020 13:49:18 +0900 Subject: [PATCH 582/871] Update naming --- .../Objects/Drawables/Pieces/ReverseArrowPiece.cs | 2 +- .../Objects/Drawables/Pieces/CirclePiece.cs | 4 ++-- .../Skinning/TaikoLegacyPlayfieldBackgroundRight.cs | 2 +- osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs | 2 +- osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs | 2 +- .../Visual/UserInterface/TestSceneBeatSyncedContainer.cs | 2 +- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 8 ++++---- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 2 +- osu.Game/Graphics/UserInterface/TwoLayerButton.cs | 2 +- osu.Game/Rulesets/Mods/ModNightcore.cs | 2 +- osu.Game/Screens/Menu/Button.cs | 4 ++-- osu.Game/Screens/Menu/MenuSideFlashes.cs | 6 +++--- osu.Game/Screens/Menu/OsuLogo.cs | 2 +- 13 files changed, 20 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs index 1a5195acf8..ae43006e76 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces }; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { if (!drawableRepeat.IsHit) Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs index b5471e6976..f515a35c18 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.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 osu.Framework.Audio.Track; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -8,7 +9,6 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Backgrounds; using osuTK.Graphics; using osu.Game.Beatmaps.ControlPoints; -using osu.Framework.Audio.Track; using osu.Framework.Graphics.Effects; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces }; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { if (!effectPoint.KiaiMode) return; diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyPlayfieldBackgroundRight.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyPlayfieldBackgroundRight.cs index 7508c75231..4bbb6be6b1 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyPlayfieldBackgroundRight.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyPlayfieldBackgroundRight.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning }; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs index 407ab30e12..b937beae3c 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Taiko.UI lastObjectHit = result.IsHit; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { kiaiMode = effectPoint.KiaiMode; } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs index cce2be7758..6f25a5f662 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Taiko.UI textureAnimation.Seek(0); } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { // assume that if the animation is playing on its own, it's independent from the beat and doesn't need to be touched. if (textureAnimation.FrameCount == 0 || textureAnimation.IsPlaying) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index 4c32e995e8..dd5ceec739 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -177,7 +177,7 @@ namespace osu.Game.Tests.Visual.UserInterface timeSinceLastBeat.Value = TimeSinceLastBeat; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 5a613d1a54..c37fcc043d 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -18,7 +18,7 @@ namespace osu.Game.Graphics.Containers private TimingControlPoint lastTimingPoint; /// - /// The amount of time before a beat we should fire . + /// The amount of time before a beat we should fire . /// This allows for adding easing to animations that may be synchronised to the beat. /// protected double EarlyActivationMilliseconds; @@ -50,7 +50,7 @@ namespace osu.Game.Graphics.Containers private TimingControlPoint defaultTiming; private EffectControlPoint defaultEffect; - private TrackAmplitudes defaultAmplitudes; + private ChannelAmplitudes defaultAmplitudes; protected bool IsBeatSyncedWithTrack { get; private set; } @@ -129,7 +129,7 @@ namespace osu.Game.Graphics.Containers OmitFirstBarLine = false }; - defaultAmplitudes = new TrackAmplitudes + defaultAmplitudes = new ChannelAmplitudes { FrequencyAmplitudes = new float[256], LeftChannel = 0, @@ -137,7 +137,7 @@ namespace osu.Game.Graphics.Containers }; } - protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { } } diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 6f440d8138..06c46fbb91 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -147,7 +147,7 @@ namespace osu.Game.Graphics.UserInterface }; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { if (!hasSelection) this.FadeTo(0.7f).FadeTo(0.4f, timingPoint.BeatLength, Easing.InOutSine); diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index aa96796cf1..120149d8c1 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -230,7 +230,7 @@ namespace osu.Game.Graphics.UserInterface }; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index 1df2aeb348..ed8eb2fb66 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Mods private const int bars_per_segment = 4; - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index 6708ce0ba0..be6ed9700c 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -6,6 +6,7 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -15,7 +16,6 @@ using osuTK.Graphics; using osuTK.Input; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; -using osu.Framework.Audio.Track; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; @@ -132,7 +132,7 @@ namespace osu.Game.Screens.Menu private bool rightward; - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index 321381ac8d..2ff8132d47 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -3,7 +3,6 @@ using osuTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Audio.Track; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -16,6 +15,7 @@ using osu.Game.Skinning; using osu.Game.Online.API; using osu.Game.Users; using System; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; namespace osu.Game.Screens.Menu @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Menu skin.BindValueChanged(_ => updateColour(), true); } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { if (beatIndex < 0) return; @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Menu flash(rightBox, timingPoint.BeatLength, effectPoint.KiaiMode, amplitudes); } - private void flash(Drawable d, double beatLength, bool kiai, TrackAmplitudes amplitudes) + private void flash(Drawable d, double beatLength, bool kiai, ChannelAmplitudes amplitudes) { d.FadeTo(Math.Max(0, ((ReferenceEquals(d, leftBox) ? amplitudes.LeftChannel : amplitudes.RightChannel) - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), box_fade_in_time) .Then() diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 9cadfd7df6..089906c342 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -264,7 +264,7 @@ namespace osu.Game.Screens.Menu private int lastBeatIndex; - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); From 49d3511063a14ad024ff8bb9da2932b08ca8fdd1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jun 2020 13:55:44 +0900 Subject: [PATCH 583/871] Read amplitudes from piano reverb source --- osu.Game/Screens/Menu/IntroWelcome.cs | 6 ++- osu.Game/Screens/Menu/LogoVisualisation.cs | 59 ++++++++++++++++------ 2 files changed, 49 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 7ab74cbf22..81e473dc04 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -44,6 +44,8 @@ namespace osu.Game.Screens.Menu RelativeSizeAxes = Axes.Both }, intro => { + intro.LogoVisualisation.AddAmplitudeSource(pianoReverb); + AddInternal(intro); welcome?.Play(); @@ -74,6 +76,8 @@ namespace osu.Game.Screens.Menu private Sprite welcomeText; private Container scaleContainer; + public LogoVisualisation LogoVisualisation { get; private set; } + [BackgroundDependencyLoader] private void load(TextureStore textures) { @@ -89,7 +93,7 @@ namespace osu.Game.Screens.Menu Origin = Anchor.Centre, Children = new Drawable[] { - new LogoVisualisation + LogoVisualisation = new LogoVisualisation { RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 6a28740d4e..dcbfe15210 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -13,7 +13,10 @@ using osu.Framework.Graphics.Textures; using osu.Game.Beatmaps; using osu.Game.Graphics; using System; +using System.Collections.Generic; +using JetBrains.Annotations; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Bindables; using osu.Framework.Utils; @@ -65,6 +68,11 @@ namespace osu.Game.Screens.Menu public Color4 AccentColour { get; set; } + /// + /// The relative movement of bars based on input amplification. Defaults to 1. + /// + public float Magnitude { get; set; } = 1; + private readonly float[] frequencyAmplitudes = new float[256]; private IShader shader; @@ -76,6 +84,13 @@ namespace osu.Game.Screens.Menu Blending = BlendingParameters.Additive; } + private readonly List amplitudeSources = new List(); + + public void AddAmplitudeSource(IHasAmplitudes amplitudeSource) + { + amplitudeSources.Add(amplitudeSource); + } + [BackgroundDependencyLoader] private void load(ShaderManager shaders, IBindable beatmap) { @@ -83,27 +98,28 @@ namespace osu.Game.Screens.Menu shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE_ROUNDED); } + private readonly float[] temporalAmplitudes = new float[256]; + private void updateAmplitudes() { - var track = beatmap.Value.TrackLoaded ? beatmap.Value.Track : null; - var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.Beatmap?.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null; + var effect = beatmap.Value.BeatmapLoaded && beatmap.Value.TrackLoaded + ? beatmap.Value.Beatmap?.ControlPointInfo.EffectPointAt(beatmap.Value.Track.CurrentTime) + : null; - float[] temporalAmplitudes = track?.CurrentAmplitudes.FrequencyAmplitudes; + for (int i = 0; i < temporalAmplitudes.Length; i++) + temporalAmplitudes[i] = 0; + + if (beatmap.Value.TrackLoaded) + addAmplitudesFromSource(beatmap.Value.Track); + + foreach (var source in amplitudeSources) + addAmplitudesFromSource(source); for (int i = 0; i < bars_per_visualiser; i++) { - if (track?.IsRunning ?? false) - { - float targetAmplitude = (temporalAmplitudes?[(i + indexOffset) % bars_per_visualiser] ?? 0) * (effect?.KiaiMode == true ? 1 : 0.5f); - if (targetAmplitude > frequencyAmplitudes[i]) - frequencyAmplitudes[i] = targetAmplitude; - } - else - { - int index = (i + index_change) % bars_per_visualiser; - if (frequencyAmplitudes[index] > frequencyAmplitudes[i]) - frequencyAmplitudes[i] = frequencyAmplitudes[index]; - } + float targetAmplitude = Magnitude * (temporalAmplitudes[(i + indexOffset) % bars_per_visualiser]) * (effect?.KiaiMode == true ? 1 : 0.5f); + if (targetAmplitude > frequencyAmplitudes[i]) + frequencyAmplitudes[i] = targetAmplitude; } indexOffset = (indexOffset + index_change) % bars_per_visualiser; @@ -136,6 +152,19 @@ namespace osu.Game.Screens.Menu protected override DrawNode CreateDrawNode() => new VisualisationDrawNode(this); + private void addAmplitudesFromSource([NotNull] IHasAmplitudes source) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + + var amplitudes = source.CurrentAmplitudes.FrequencyAmplitudes; + + for (int i = 0; i < amplitudes.Length; i++) + { + if (i < temporalAmplitudes.Length) + temporalAmplitudes[i] += amplitudes[i]; + } + } + private class VisualisationDrawNode : DrawNode { protected new LogoVisualisation Source => (LogoVisualisation)base.Source; From 6d19fd936ef5e05fd0e95fc4aa12417e29d3f36c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jun 2020 15:13:30 +0900 Subject: [PATCH 584/871] Change test scene to not inherit unused ScreenTestScene --- osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index ac364b5233..f5c5a4d75c 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -25,7 +25,7 @@ using osuTK.Input; namespace osu.Game.Tests.Visual.Ranking { [TestFixture] - public class TestSceneResultsScreen : ScreenTestScene + public class TestSceneResultsScreen : OsuManualInputManagerTestScene { private BeatmapManager beatmaps; From 6bcc693c2f8fc80649e5af944bf87c1e8c946145 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jun 2020 15:21:23 +0900 Subject: [PATCH 585/871] Add ability to close statistics by clicking anywhere --- .../Visual/Ranking/TestSceneResultsScreen.cs | 40 +++++++++++++++++++ .../Ranking/Statistics/StatisticsPanel.cs | 7 ++++ 2 files changed, 47 insertions(+) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs index f5c5a4d75c..74808bc2f5 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneResultsScreen.cs @@ -20,6 +20,7 @@ using osu.Game.Screens; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Statistics; +using osuTK; using osuTK.Input; namespace osu.Game.Tests.Visual.Ranking @@ -87,6 +88,45 @@ namespace osu.Game.Tests.Visual.Ranking AddAssert("retry overlay present", () => screen.RetryOverlay != null); } + [Test] + public void TestShowHideStatisticsViaOutsideClick() + { + TestResultsScreen screen = null; + + AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + + AddStep("click expanded panel", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + InputManager.MoveMouseTo(expandedPanel); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("statistics shown", () => this.ChildrenOfType().Single().State.Value == Visibility.Visible); + + AddUntilStep("expanded panel at the left of the screen", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + return expandedPanel.ScreenSpaceDrawQuad.TopLeft.X - screen.ScreenSpaceDrawQuad.TopLeft.X < 150; + }); + + AddStep("click to right of panel", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + InputManager.MoveMouseTo(expandedPanel.ScreenSpaceDrawQuad.TopRight + new Vector2(100, 0)); + InputManager.Click(MouseButton.Left); + }); + + AddAssert("statistics hidden", () => this.ChildrenOfType().Single().State.Value == Visibility.Hidden); + + AddUntilStep("expanded panel in centre of screen", () => + { + var expandedPanel = this.ChildrenOfType().Single(p => p.State == PanelState.Expanded); + return Precision.AlmostEquals(expandedPanel.ScreenSpaceDrawQuad.Centre.X, screen.ScreenSpaceDrawQuad.Centre.X, 1); + }); + } + [Test] public void TestShowHideStatistics() { diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index 77f3bd7b5c..7f406331cd 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -9,6 +9,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Placeholders; @@ -129,6 +130,12 @@ namespace osu.Game.Screens.Ranking.Statistics } } + protected override bool OnClick(ClickEvent e) + { + ToggleVisibility(); + return true; + } + protected override void PopIn() => this.FadeIn(150, Easing.OutQuint); protected override void PopOut() => this.FadeOut(150, Easing.OutQuint); From a6c6e391caaa93f2c024fb5832b4db90ff4c95e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jun 2020 17:38:30 +0900 Subject: [PATCH 586/871] Fix player not exiting immediately on Alt-F4 --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 4 +--- osu.Game/Screens/Play/Player.cs | 6 ------ 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 387ac42f67..1961a224c1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -174,9 +174,7 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestExitFromGameplay() { - AddStep("exit", () => Player.Exit()); - confirmPaused(); - + // an externally triggered exit should immediately exit, skipping all pause logic. AddStep("exit", () => Player.Exit()); confirmExited(); } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index d3b88e56ae..541275cf55 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -656,12 +656,6 @@ namespace osu.Game.Screens.Play return true; } - if (canPause) - { - Pause(); - return true; - } - // GameplayClockContainer performs seeks / start / stop operations on the beatmap's track. // as we are no longer the current screen, we cannot guarantee the track is still usable. GameplayClockContainer?.StopUsingBeatmapClock(); From 53d542546e1ae25edbf4afe3e777eee2b5038a92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jun 2020 18:04:50 +0900 Subject: [PATCH 587/871] Fix editor drag selection not continuing to select unless the mouse is moved --- .../Edit/Compose/Components/DragBox.cs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs index c5f1bd1575..0615ebfc20 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DragBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DragBox.cs @@ -53,6 +53,8 @@ namespace osu.Game.Screens.Edit.Compose.Components } }; + private RectangleF? dragRectangle; + /// /// Handle a forwarded mouse event. /// @@ -66,15 +68,14 @@ namespace osu.Game.Screens.Edit.Compose.Components var dragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y); // We use AABBFloat instead of RectangleF since it handles negative sizes for us - var dragRectangle = dragQuad.AABBFloat; + var rec = dragQuad.AABBFloat; + dragRectangle = rec; - var topLeft = ToLocalSpace(dragRectangle.TopLeft); - var bottomRight = ToLocalSpace(dragRectangle.BottomRight); + var topLeft = ToLocalSpace(rec.TopLeft); + var bottomRight = ToLocalSpace(rec.BottomRight); Box.Position = topLeft; Box.Size = bottomRight - topLeft; - - PerformSelection?.Invoke(dragRectangle); return true; } @@ -93,7 +94,19 @@ namespace osu.Game.Screens.Edit.Compose.Components } } - public override void Hide() => State = Visibility.Hidden; + protected override void Update() + { + base.Update(); + + if (dragRectangle != null) + PerformSelection?.Invoke(dragRectangle.Value); + } + + public override void Hide() + { + State = Visibility.Hidden; + dragRectangle = null; + } public override void Show() => State = Visibility.Visible; From a5eac716ec8bc3ae84da15aaca17457a78fcbf1b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jun 2020 18:42:56 +0900 Subject: [PATCH 588/871] Make work for all editors based on track running state --- .../Compose/Components/BlueprintContainer.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index cc417bbb10..4aa235ba50 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.Primitives; using osu.Framework.Input; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; @@ -320,10 +321,22 @@ namespace osu.Game.Screens.Edit.Compose.Components { foreach (var blueprint in SelectionBlueprints) { - if (blueprint.IsAlive && blueprint.IsPresent && rect.Contains(blueprint.ScreenSpaceSelectionPoint)) - blueprint.Select(); - else - blueprint.Deselect(); + // only run when utmost necessary to avoid unnecessary rect computations. + bool isValidForSelection() => blueprint.IsAlive && blueprint.IsPresent && rect.Contains(blueprint.ScreenSpaceSelectionPoint); + + switch (blueprint.State) + { + case SelectionState.NotSelected: + if (isValidForSelection()) + blueprint.Select(); + break; + + case SelectionState.Selected: + // if the editor is playing, we generally don't want to deselect objects even if outside the selection area. + if (!editorClock.IsRunning && !isValidForSelection()) + blueprint.Deselect(); + break; + } } } From e7238e25f96de3c9de8c622d3026a04d34039cc8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 23 Jun 2020 20:36:09 +0900 Subject: [PATCH 589/871] Fix exception when dragging after deleting object --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index cc417bbb10..767f60cf71 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -82,6 +82,7 @@ namespace osu.Game.Screens.Edit.Compose.Components case NotifyCollectionChangedAction.Remove: foreach (var o in args.OldItems) SelectionBlueprints.FirstOrDefault(b => b.HitObject == o)?.Deselect(); + break; } }; @@ -250,6 +251,9 @@ namespace osu.Game.Screens.Edit.Compose.Components blueprint.Deselected -= onBlueprintDeselected; SelectionBlueprints.Remove(blueprint); + + if (movementBlueprint == blueprint) + finishSelectionMovement(); } protected virtual void AddBlueprintFor(HitObject hitObject) From 61c4ed327c6c7f54be05430644ba8bc9ba479fb9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jun 2020 21:26:41 +0900 Subject: [PATCH 590/871] 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 192be999eb..493b1f5529 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 911292c6ae..26d81a1004 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 18249b40ca..72f09ee287 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 14ad3835ff01e51c0992f3f2d09072fe1bc5b8fe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jun 2020 13:49:18 +0900 Subject: [PATCH 591/871] Update naming --- .../Objects/Drawables/Pieces/ReverseArrowPiece.cs | 2 +- .../Objects/Drawables/Pieces/CirclePiece.cs | 4 ++-- .../Skinning/TaikoLegacyPlayfieldBackgroundRight.cs | 2 +- osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs | 2 +- osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs | 2 +- .../Visual/UserInterface/TestSceneBeatSyncedContainer.cs | 2 +- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 8 ++++---- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 2 +- osu.Game/Graphics/UserInterface/TwoLayerButton.cs | 2 +- osu.Game/Rulesets/Mods/ModNightcore.cs | 2 +- osu.Game/Screens/Menu/Button.cs | 4 ++-- osu.Game/Screens/Menu/MenuSideFlashes.cs | 6 +++--- osu.Game/Screens/Menu/OsuLogo.cs | 2 +- 13 files changed, 20 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs index 1a5195acf8..ae43006e76 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/ReverseArrowPiece.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces }; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { if (!drawableRepeat.IsHit) Child.ScaleTo(1.3f).ScaleTo(1f, timingPoint.BeatLength, Easing.Out); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs index b5471e6976..f515a35c18 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/Pieces/CirclePiece.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 osu.Framework.Audio.Track; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -8,7 +9,6 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Backgrounds; using osuTK.Graphics; using osu.Game.Beatmaps.ControlPoints; -using osu.Framework.Audio.Track; using osu.Framework.Graphics.Effects; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces }; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { if (!effectPoint.KiaiMode) return; diff --git a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyPlayfieldBackgroundRight.cs b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyPlayfieldBackgroundRight.cs index 7508c75231..4bbb6be6b1 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyPlayfieldBackgroundRight.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/TaikoLegacyPlayfieldBackgroundRight.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning }; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); diff --git a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs index 407ab30e12..b937beae3c 100644 --- a/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs +++ b/osu.Game.Rulesets.Taiko/UI/DrawableTaikoMascot.cs @@ -83,7 +83,7 @@ namespace osu.Game.Rulesets.Taiko.UI lastObjectHit = result.IsHit; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { kiaiMode = effectPoint.KiaiMode; } diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs index cce2be7758..6f25a5f662 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoMascotAnimation.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Taiko.UI textureAnimation.Seek(0); } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { // assume that if the animation is playing on its own, it's independent from the beat and doesn't need to be touched. if (textureAnimation.FrameCount == 0 || textureAnimation.IsPlaying) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index 4c32e995e8..dd5ceec739 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -177,7 +177,7 @@ namespace osu.Game.Tests.Visual.UserInterface timeSinceLastBeat.Value = TimeSinceLastBeat; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 5a613d1a54..c37fcc043d 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -18,7 +18,7 @@ namespace osu.Game.Graphics.Containers private TimingControlPoint lastTimingPoint; /// - /// The amount of time before a beat we should fire . + /// The amount of time before a beat we should fire . /// This allows for adding easing to animations that may be synchronised to the beat. /// protected double EarlyActivationMilliseconds; @@ -50,7 +50,7 @@ namespace osu.Game.Graphics.Containers private TimingControlPoint defaultTiming; private EffectControlPoint defaultEffect; - private TrackAmplitudes defaultAmplitudes; + private ChannelAmplitudes defaultAmplitudes; protected bool IsBeatSyncedWithTrack { get; private set; } @@ -129,7 +129,7 @@ namespace osu.Game.Graphics.Containers OmitFirstBarLine = false }; - defaultAmplitudes = new TrackAmplitudes + defaultAmplitudes = new ChannelAmplitudes { FrequencyAmplitudes = new float[256], LeftChannel = 0, @@ -137,7 +137,7 @@ namespace osu.Game.Graphics.Containers }; } - protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { } } diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 6f440d8138..06c46fbb91 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -147,7 +147,7 @@ namespace osu.Game.Graphics.UserInterface }; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { if (!hasSelection) this.FadeTo(0.7f).FadeTo(0.4f, timingPoint.BeatLength, Easing.InOutSine); diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index aa96796cf1..120149d8c1 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -230,7 +230,7 @@ namespace osu.Game.Graphics.UserInterface }; } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); diff --git a/osu.Game/Rulesets/Mods/ModNightcore.cs b/osu.Game/Rulesets/Mods/ModNightcore.cs index 1df2aeb348..ed8eb2fb66 100644 --- a/osu.Game/Rulesets/Mods/ModNightcore.cs +++ b/osu.Game/Rulesets/Mods/ModNightcore.cs @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Mods private const int bars_per_segment = 4; - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index 6708ce0ba0..be6ed9700c 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -6,6 +6,7 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Audio.Track; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -15,7 +16,6 @@ using osuTK.Graphics; using osuTK.Input; using osu.Framework.Extensions.Color4Extensions; using osu.Game.Graphics.Containers; -using osu.Framework.Audio.Track; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; @@ -132,7 +132,7 @@ namespace osu.Game.Screens.Menu private bool rightward; - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index 321381ac8d..2ff8132d47 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -3,7 +3,6 @@ using osuTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Audio.Track; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -16,6 +15,7 @@ using osu.Game.Skinning; using osu.Game.Online.API; using osu.Game.Users; using System; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; namespace osu.Game.Screens.Menu @@ -89,7 +89,7 @@ namespace osu.Game.Screens.Menu skin.BindValueChanged(_ => updateColour(), true); } - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { if (beatIndex < 0) return; @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Menu flash(rightBox, timingPoint.BeatLength, effectPoint.KiaiMode, amplitudes); } - private void flash(Drawable d, double beatLength, bool kiai, TrackAmplitudes amplitudes) + private void flash(Drawable d, double beatLength, bool kiai, ChannelAmplitudes amplitudes) { d.FadeTo(Math.Max(0, ((ReferenceEquals(d, leftBox) ? amplitudes.LeftChannel : amplitudes.RightChannel) - amplitude_dead_zone) / (kiai ? kiai_multiplier : alpha_multiplier)), box_fade_in_time) .Then() diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 9cadfd7df6..089906c342 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -264,7 +264,7 @@ namespace osu.Game.Screens.Menu private int lastBeatIndex; - protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, TrackAmplitudes amplitudes) + protected override void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) { base.OnNewBeat(beatIndex, timingPoint, effectPoint, amplitudes); From f2735a77975db231e525117a1092ce5756ba8353 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jun 2020 21:30:37 +0900 Subject: [PATCH 592/871] Use new empty ChannelAmplitudes spec --- osu.Game/Graphics/Containers/BeatSyncedContainer.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index c37fcc043d..dd5c41285a 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -50,7 +50,6 @@ namespace osu.Game.Graphics.Containers private TimingControlPoint defaultTiming; private EffectControlPoint defaultEffect; - private ChannelAmplitudes defaultAmplitudes; protected bool IsBeatSyncedWithTrack { get; private set; } @@ -107,7 +106,7 @@ namespace osu.Game.Graphics.Containers return; using (BeginDelayedSequence(-TimeSinceLastBeat, true)) - OnNewBeat(beatIndex, timingPoint, effectPoint, track?.CurrentAmplitudes ?? defaultAmplitudes); + OnNewBeat(beatIndex, timingPoint, effectPoint, track?.CurrentAmplitudes ?? ChannelAmplitudes.Empty); lastBeat = beatIndex; lastTimingPoint = timingPoint; @@ -128,13 +127,6 @@ namespace osu.Game.Graphics.Containers KiaiMode = false, OmitFirstBarLine = false }; - - defaultAmplitudes = new ChannelAmplitudes - { - FrequencyAmplitudes = new float[256], - LeftChannel = 0, - RightChannel = 0 - }; } protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes) From 5cdabbc8bb7f888ae3f8e0d9f270ea9e2b4dc365 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jun 2020 21:33:03 +0900 Subject: [PATCH 593/871] Update access to FrequencyAmplitudes via span --- osu.Game/Screens/Menu/LogoVisualisation.cs | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Menu/LogoVisualisation.cs b/osu.Game/Screens/Menu/LogoVisualisation.cs index 6a28740d4e..cbed1d2e0e 100644 --- a/osu.Game/Screens/Menu/LogoVisualisation.cs +++ b/osu.Game/Screens/Menu/LogoVisualisation.cs @@ -14,6 +14,7 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using System; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Utils; @@ -88,22 +89,13 @@ namespace osu.Game.Screens.Menu var track = beatmap.Value.TrackLoaded ? beatmap.Value.Track : null; var effect = beatmap.Value.BeatmapLoaded ? beatmap.Value.Beatmap?.ControlPointInfo.EffectPointAt(track?.CurrentTime ?? Time.Current) : null; - float[] temporalAmplitudes = track?.CurrentAmplitudes.FrequencyAmplitudes; + ReadOnlySpan temporalAmplitudes = (track?.CurrentAmplitudes ?? ChannelAmplitudes.Empty).FrequencyAmplitudes.Span; for (int i = 0; i < bars_per_visualiser; i++) { - if (track?.IsRunning ?? false) - { - float targetAmplitude = (temporalAmplitudes?[(i + indexOffset) % bars_per_visualiser] ?? 0) * (effect?.KiaiMode == true ? 1 : 0.5f); - if (targetAmplitude > frequencyAmplitudes[i]) - frequencyAmplitudes[i] = targetAmplitude; - } - else - { - int index = (i + index_change) % bars_per_visualiser; - if (frequencyAmplitudes[index] > frequencyAmplitudes[i]) - frequencyAmplitudes[i] = frequencyAmplitudes[index]; - } + float targetAmplitude = (temporalAmplitudes[(i + indexOffset) % bars_per_visualiser]) * (effect?.KiaiMode == true ? 1 : 0.5f); + if (targetAmplitude > frequencyAmplitudes[i]) + frequencyAmplitudes[i] = targetAmplitude; } indexOffset = (indexOffset + index_change) % bars_per_visualiser; From 9d753a4fc2966e048ebdbeb3eaa2127e1569694e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 23 Jun 2020 21:34:57 +0900 Subject: [PATCH 594/871] Update intro resource locations --- osu.Game/Screens/Menu/IntroCircles.cs | 2 +- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroCircles.cs b/osu.Game/Screens/Menu/IntroCircles.cs index aa9cee969c..d4cd073b7a 100644 --- a/osu.Game/Screens/Menu/IntroCircles.cs +++ b/osu.Game/Screens/Menu/IntroCircles.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Menu private void load(AudioManager audio) { if (MenuVoice.Value) - welcome = audio.Samples.Get(@"welcome"); + welcome = audio.Samples.Get(@"Intro/welcome"); } protected override void LogoArriving(OsuLogo logo, bool resuming) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index b99d8ae9d1..20964549f5 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -72,7 +72,7 @@ namespace osu.Game.Screens.Menu MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); - seeya = audio.Samples.Get(@"seeya"); + seeya = audio.Samples.Get(@"Intro/seeya"); BeatmapSetInfo setInfo = null; From ccb27082d52c2bda1bfd01e4a5ca3583e49c3035 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Jun 2020 11:08:32 +0900 Subject: [PATCH 595/871] Fix background appearing too late --- osu.Game/Screens/Menu/IntroWelcome.cs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index 81e473dc04..abd4a68d4f 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; +using osu.Game.Screens.Backgrounds; using osuTK.Graphics; namespace osu.Game.Screens.Menu @@ -24,6 +25,13 @@ namespace osu.Game.Screens.Menu private SampleChannel pianoReverb; protected override string SeeyaSampleName => "Intro/Welcome/seeya"; + protected override BackgroundScreen CreateBackground() => background = new BackgroundScreenDefault(false) + { + Alpha = 0, + }; + + private BackgroundScreenDefault background; + [BackgroundDependencyLoader] private void load(AudioManager audio) { @@ -44,6 +52,8 @@ namespace osu.Game.Screens.Menu RelativeSizeAxes = Axes.Both }, intro => { + PrepareMenuLoad(); + intro.LogoVisualisation.AddAmplitudeSource(pianoReverb); AddInternal(intro); @@ -54,21 +64,24 @@ namespace osu.Game.Screens.Menu Scheduler.AddDelayed(() => { StartTrack(); - PrepareMenuLoad(); + + const float fade_in_time = 200; logo.ScaleTo(1); - logo.FadeIn(); + logo.FadeIn(fade_in_time); - Scheduler.Add(LoadMenu); + background.FadeIn(fade_in_time); + + LoadMenu(); }, delay_step_two); }); } } - public override void OnSuspending(IScreen next) + public override void OnResuming(IScreen last) { - this.FadeOut(300); - base.OnSuspending(next); + base.OnResuming(last); + background.FadeOut(100); } private class WelcomeIntroSequence : Container From 1387a9e2c63a0d46200a4ea7ef71e28cdb68c893 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Jun 2020 16:57:17 +0900 Subject: [PATCH 596/871] Move all tournament tests to using placeholder data rather than reading from bracket --- .../Components/TestSceneMatchScoreDisplay.cs | 2 +- osu.Game.Tournament.Tests/LadderTestScene.cs | 146 ------------------ .../Screens/TestSceneLadderEditorScreen.cs | 2 +- .../Screens/TestSceneLadderScreen.cs | 2 +- .../Screens/TestSceneMapPoolScreen.cs | 2 +- .../Screens/TestSceneRoundEditorScreen.cs | 2 +- .../Screens/TestSceneSeedingEditorScreen.cs | 2 +- .../Screens/TestSceneSeedingScreen.cs | 2 +- .../Screens/TestSceneTeamEditorScreen.cs | 2 +- .../Screens/TestSceneTeamIntroScreen.cs | 2 +- .../Screens/TestSceneTeamWinScreen.cs | 2 +- .../TournamentTestScene.cs | 141 +++++++++++++++++ 12 files changed, 151 insertions(+), 156 deletions(-) delete mode 100644 osu.Game.Tournament.Tests/LadderTestScene.cs diff --git a/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs b/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs index 77119f7a60..acd5d53310 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneMatchScoreDisplay.cs @@ -9,7 +9,7 @@ using osu.Game.Tournament.Screens.Gameplay.Components; namespace osu.Game.Tournament.Tests.Components { - public class TestSceneMatchScoreDisplay : LadderTestScene + public class TestSceneMatchScoreDisplay : TournamentTestScene { [Cached(Type = typeof(MatchIPCInfo))] private MatchIPCInfo matchInfo = new MatchIPCInfo(); diff --git a/osu.Game.Tournament.Tests/LadderTestScene.cs b/osu.Game.Tournament.Tests/LadderTestScene.cs deleted file mode 100644 index 2f4373679c..0000000000 --- a/osu.Game.Tournament.Tests/LadderTestScene.cs +++ /dev/null @@ -1,146 +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.Linq; -using NUnit.Framework; -using osu.Framework.Allocation; -using osu.Framework.Utils; -using osu.Game.Beatmaps; -using osu.Game.Rulesets; -using osu.Game.Tournament.Models; -using osu.Game.Users; - -namespace osu.Game.Tournament.Tests -{ - [TestFixture] - public abstract class LadderTestScene : TournamentTestScene - { - [Cached] - protected LadderInfo Ladder { get; private set; } = new LadderInfo(); - - [Resolved] - private RulesetStore rulesetStore { get; set; } - - [BackgroundDependencyLoader] - private void load() - { - Ladder.Ruleset.Value ??= rulesetStore.AvailableRulesets.First(); - - Ruleset.BindTo(Ladder.Ruleset); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - TournamentMatch match = CreateSampleMatch(); - - Ladder.Rounds.Add(match.Round.Value); - Ladder.Matches.Add(match); - Ladder.Teams.Add(match.Team1.Value); - Ladder.Teams.Add(match.Team2.Value); - - Ladder.CurrentMatch.Value = match; - } - - public static TournamentMatch CreateSampleMatch() => new TournamentMatch - { - Team1 = - { - Value = new TournamentTeam - { - FlagName = { Value = "JP" }, - FullName = { Value = "Japan" }, - LastYearPlacing = { Value = 10 }, - Seed = { Value = "Low" }, - SeedingResults = - { - new SeedingResult - { - Mod = { Value = "NM" }, - Seed = { Value = 10 }, - Beatmaps = - { - new SeedingBeatmap - { - BeatmapInfo = CreateSampleBeatmapInfo(), - Score = 12345672, - Seed = { Value = 24 }, - }, - new SeedingBeatmap - { - BeatmapInfo = CreateSampleBeatmapInfo(), - Score = 1234567, - Seed = { Value = 12 }, - }, - new SeedingBeatmap - { - BeatmapInfo = CreateSampleBeatmapInfo(), - Score = 1234567, - Seed = { Value = 16 }, - } - } - }, - new SeedingResult - { - Mod = { Value = "DT" }, - Seed = { Value = 5 }, - Beatmaps = - { - new SeedingBeatmap - { - BeatmapInfo = CreateSampleBeatmapInfo(), - Score = 234567, - Seed = { Value = 3 }, - }, - new SeedingBeatmap - { - BeatmapInfo = CreateSampleBeatmapInfo(), - Score = 234567, - Seed = { Value = 6 }, - }, - new SeedingBeatmap - { - BeatmapInfo = CreateSampleBeatmapInfo(), - Score = 234567, - Seed = { Value = 12 }, - } - } - } - }, - Players = - { - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } }, - new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } }, - } - } - }, - Team2 = - { - Value = new TournamentTeam - { - FlagName = { Value = "US" }, - FullName = { Value = "United States" }, - Players = - { - new User { Username = "Hello" }, - new User { Username = "Hello" }, - new User { Username = "Hello" }, - new User { Username = "Hello" }, - new User { Username = "Hello" }, - } - } - }, - Round = - { - Value = new TournamentRound { Name = { Value = "Quarterfinals" } } - } - }; - - public static BeatmapInfo CreateSampleBeatmapInfo() => - new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist", ID = RNG.Next(0, 1000000) } }; - } -} diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs index a45c5de2bd..bceb3e6b74 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneLadderEditorScreen.cs @@ -8,7 +8,7 @@ using osu.Game.Tournament.Screens.Editors; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneLadderEditorScreen : LadderTestScene + public class TestSceneLadderEditorScreen : TournamentTestScene { [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneLadderScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneLadderScreen.cs index 2be0564c82..c4c100d506 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneLadderScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneLadderScreen.cs @@ -8,7 +8,7 @@ using osu.Game.Tournament.Screens.Ladder; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneLadderScreen : LadderTestScene + public class TestSceneLadderScreen : TournamentTestScene { [BackgroundDependencyLoader] private void load() diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs index a4538be384..f4032fdd54 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneMapPoolScreen.cs @@ -12,7 +12,7 @@ using osu.Game.Tournament.Screens.MapPool; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneMapPoolScreen : LadderTestScene + public class TestSceneMapPoolScreen : TournamentTestScene { private MapPoolScreen screen; diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneRoundEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneRoundEditorScreen.cs index e15ac416b0..5c2b59df3a 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneRoundEditorScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneRoundEditorScreen.cs @@ -5,7 +5,7 @@ using osu.Game.Tournament.Screens.Editors; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneRoundEditorScreen : LadderTestScene + public class TestSceneRoundEditorScreen : TournamentTestScene { public TestSceneRoundEditorScreen() { diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs index 8d12d5393d..2722021216 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingEditorScreen.cs @@ -7,7 +7,7 @@ using osu.Game.Tournament.Screens.Editors; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneSeedingEditorScreen : LadderTestScene + public class TestSceneSeedingEditorScreen : TournamentTestScene { [Cached] private readonly LadderInfo ladder = new LadderInfo(); diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs index 4269f8f56a..d414d8e36e 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneSeedingScreen.cs @@ -8,7 +8,7 @@ using osu.Game.Tournament.Screens.TeamIntro; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneSeedingScreen : LadderTestScene + public class TestSceneSeedingScreen : TournamentTestScene { [Cached] private readonly LadderInfo ladder = new LadderInfo(); diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamEditorScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamEditorScreen.cs index 097bad4a02..fc6574ec8a 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamEditorScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamEditorScreen.cs @@ -5,7 +5,7 @@ using osu.Game.Tournament.Screens.Editors; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneTeamEditorScreen : LadderTestScene + public class TestSceneTeamEditorScreen : TournamentTestScene { public TestSceneTeamEditorScreen() { diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs index e36b594ff2..b3f78c92d9 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamIntroScreen.cs @@ -9,7 +9,7 @@ using osu.Game.Tournament.Screens.TeamIntro; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneTeamIntroScreen : LadderTestScene + public class TestSceneTeamIntroScreen : TournamentTestScene { [Cached] private readonly LadderInfo ladder = new LadderInfo(); diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs index 1a2faa76c1..6873fb0f4b 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs @@ -9,7 +9,7 @@ using osu.Game.Tournament.Screens.TeamWin; namespace osu.Game.Tournament.Tests.Screens { - public class TestSceneTeamWinScreen : LadderTestScene + public class TestSceneTeamWinScreen : TournamentTestScene { [Cached] private readonly LadderInfo ladder = new LadderInfo(); diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs index 18ac3230da..a7b141cf43 100644 --- a/osu.Game.Tournament.Tests/TournamentTestScene.cs +++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs @@ -1,13 +1,154 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Platform; using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; using osu.Game.Tests.Visual; +using osu.Game.Tournament.IPC; +using osu.Game.Tournament.Models; +using osu.Game.Users; namespace osu.Game.Tournament.Tests { public abstract class TournamentTestScene : OsuTestScene { + [Cached] + protected LadderInfo Ladder { get; private set; } = new LadderInfo(); + + [Resolved] + private RulesetStore rulesetStore { get; set; } + + [Cached] + protected MatchIPCInfo IPCInfo { get; private set; } = new MatchIPCInfo(); + + [BackgroundDependencyLoader] + private void load(Storage storage) + { + Ladder.Ruleset.Value ??= rulesetStore.AvailableRulesets.First(); + + Ruleset.BindTo(Ladder.Ruleset); + Dependencies.CacheAs(new StableInfo(storage)); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + TournamentMatch match = CreateSampleMatch(); + + Ladder.Rounds.Add(match.Round.Value); + Ladder.Matches.Add(match); + Ladder.Teams.Add(match.Team1.Value); + Ladder.Teams.Add(match.Team2.Value); + + Ladder.CurrentMatch.Value = match; + } + + public static TournamentMatch CreateSampleMatch() => new TournamentMatch + { + Team1 = + { + Value = new TournamentTeam + { + FlagName = { Value = "JP" }, + FullName = { Value = "Japan" }, + LastYearPlacing = { Value = 10 }, + Seed = { Value = "Low" }, + SeedingResults = + { + new SeedingResult + { + Mod = { Value = "NM" }, + Seed = { Value = 10 }, + Beatmaps = + { + new SeedingBeatmap + { + BeatmapInfo = CreateSampleBeatmapInfo(), + Score = 12345672, + Seed = { Value = 24 }, + }, + new SeedingBeatmap + { + BeatmapInfo = CreateSampleBeatmapInfo(), + Score = 1234567, + Seed = { Value = 12 }, + }, + new SeedingBeatmap + { + BeatmapInfo = CreateSampleBeatmapInfo(), + Score = 1234567, + Seed = { Value = 16 }, + } + } + }, + new SeedingResult + { + Mod = { Value = "DT" }, + Seed = { Value = 5 }, + Beatmaps = + { + new SeedingBeatmap + { + BeatmapInfo = CreateSampleBeatmapInfo(), + Score = 234567, + Seed = { Value = 3 }, + }, + new SeedingBeatmap + { + BeatmapInfo = CreateSampleBeatmapInfo(), + Score = 234567, + Seed = { Value = 6 }, + }, + new SeedingBeatmap + { + BeatmapInfo = CreateSampleBeatmapInfo(), + Score = 234567, + Seed = { Value = 12 }, + } + } + } + }, + Players = + { + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 12 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 16 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 20 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24 } } }, + new User { Username = "Hello", Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 30 } } }, + } + } + }, + Team2 = + { + Value = new TournamentTeam + { + FlagName = { Value = "US" }, + FullName = { Value = "United States" }, + Players = + { + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + new User { Username = "Hello" }, + } + } + }, + Round = + { + Value = new TournamentRound { Name = { Value = "Quarterfinals" } } + } + }; + + public static BeatmapInfo CreateSampleBeatmapInfo() => + new BeatmapInfo { Metadata = new BeatmapMetadata { Title = "Test Title", Artist = "Test Artist", ID = RNG.Next(0, 1000000) } }; + protected override ITestSceneTestRunner CreateRunner() => new TournamentTestSceneTestRunner(); public class TournamentTestSceneTestRunner : TournamentGameBase, ITestSceneTestRunner From 92e272ebb6f301a5b896ea2a3166d2cbf761be99 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Jun 2020 16:57:40 +0900 Subject: [PATCH 597/871] Remove unnecessary prefixes --- osu.Game.Tournament/TournamentSceneManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 23fcb01db7..2c539cdd43 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -37,7 +37,7 @@ namespace osu.Game.Tournament public const float STREAM_AREA_WIDTH = 1366; - public const double REQUIRED_WIDTH = TournamentSceneManager.CONTROL_AREA_WIDTH * 2 + TournamentSceneManager.STREAM_AREA_WIDTH; + public const double REQUIRED_WIDTH = CONTROL_AREA_WIDTH * 2 + STREAM_AREA_WIDTH; [Cached] private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay(); From eb3e1b2b2698ab5bc843bd5a90c945ca01cd7d5f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Jun 2020 17:03:22 +0900 Subject: [PATCH 598/871] Fix incorrect inheritance on remaining test scene --- .../Components/TestSceneTournamentBeatmapPanel.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs index 77fa411058..bc32a12ab7 100644 --- a/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs +++ b/osu.Game.Tournament.Tests/Components/TestSceneTournamentBeatmapPanel.cs @@ -8,12 +8,11 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets; -using osu.Game.Tests.Visual; using osu.Game.Tournament.Components; namespace osu.Game.Tournament.Tests.Components { - public class TestSceneTournamentBeatmapPanel : OsuTestScene + public class TestSceneTournamentBeatmapPanel : TournamentTestScene { [Resolved] private IAPIProvider api { get; set; } From 68f078c9e67d4239eaf02575e58c81b120e44197 Mon Sep 17 00:00:00 2001 From: Sebastian Krajewski Date: Sun, 5 Jan 2020 22:11:37 +0100 Subject: [PATCH 599/871] Replace logo-triangles.mp4 with shadered logo-triangles.png --- osu.Game/Screens/Menu/IntroTriangles.cs | 9 +- osu.Game/Screens/Menu/LazerLogo.cs | 109 ++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Screens/Menu/LazerLogo.cs diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 9be74a0fd9..36f00a13ef 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; @@ -12,7 +11,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Textures; -using osu.Framework.Graphics.Video; using osu.Framework.Utils; using osu.Framework.Timing; using osu.Game.Graphics; @@ -88,7 +86,7 @@ namespace osu.Game.Screens.Menu private RulesetFlow rulesets; private Container rulesetsScale; private Container logoContainerSecondary; - private Drawable lazerLogo; + private LazerLogo lazerLogo; private GlitchingTriangles triangles; @@ -139,10 +137,10 @@ namespace osu.Game.Screens.Menu RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Child = lazerLogo = new LazerLogo(textures.GetStream("Menu/logo-triangles.mp4")) + Child = lazerLogo = new LazerLogo() { Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Origin = Anchor.Centre } }, }; @@ -218,6 +216,7 @@ namespace osu.Game.Screens.Menu // matching flyte curve y = 0.25x^2 + (max(0, x - 0.7) / 0.3) ^ 5 lazerLogo.FadeIn().ScaleTo(scale_start).Then().Delay(logo_scale_duration * 0.7f).ScaleTo(scale_start - scale_adjust, logo_scale_duration * 0.3f, Easing.InQuint); + lazerLogo.Start(logo_1, logo_scale_duration); logoContainerSecondary.ScaleTo(scale_start).Then().ScaleTo(scale_start - scale_adjust * 0.25f, logo_scale_duration, Easing.InQuad); } diff --git a/osu.Game/Screens/Menu/LazerLogo.cs b/osu.Game/Screens/Menu/LazerLogo.cs new file mode 100644 index 0000000000..ab9c82bc58 --- /dev/null +++ b/osu.Game/Screens/Menu/LazerLogo.cs @@ -0,0 +1,109 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.OpenGL.Vertices; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Textures; +using osu.Framework.MathUtils; +using osuTK; + +namespace osu.Game.Screens.Menu +{ + public class LazerLogo : Drawable + { + private IShader shader; + private Texture texture; + + private double startTime = -1000; + private double animationTime = -1000; + + private float animation; + private float highlight; + + public LazerLogo() + { + Size = new Vector2(960); + } + + [BackgroundDependencyLoader] + private void load(ShaderManager shaders, TextureStore textures) + { + shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"LazerLogo"); + texture = textures.Get("Menu/logo-triangles.png"); + } + + public void Start(double delay, double duration) + { + startTime = Clock.CurrentTime + delay; + animationTime = duration; + } + + public override bool IsPresent => true; + + protected override void Update() + { + base.Update(); + + if (animationTime < 0) return; + + highlight = Clock.CurrentTime < startTime + 0.4 * animationTime + ? Interpolation.ValueAt(Clock.CurrentTime, 0f, 1f, startTime, startTime + animationTime * 1.07, Easing.OutCirc) + : Interpolation.ValueAt(Clock.CurrentTime, 0.6f, 1f, startTime, startTime + animationTime * 0.9); + + animation = Clock.CurrentTime < startTime + 0.5 * animationTime + ? Interpolation.ValueAt(Clock.CurrentTime, 0f, 0.8f, startTime, startTime + animationTime * 1.23, Easing.OutQuart) + : Interpolation.ValueAt(Clock.CurrentTime, 0.4f, 1f, startTime, startTime + animationTime); + } + + protected override DrawNode CreateDrawNode() => new LazerLogoDrawNode(this); + + private class LazerLogoDrawNode : DrawNode + { + protected new LazerLogo Source => (LazerLogo)base.Source; + + private IShader shader; + private Texture texture; + private Quad screenSpaceDrawQuad; + private float animation; + private float highlight; + + public LazerLogoDrawNode(LazerLogo source) + : base(source) + { + } + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + texture = Source.texture; + screenSpaceDrawQuad = Source.ScreenSpaceDrawQuad; + animation = Source.animation; + highlight = Source.highlight; + } + + protected virtual void Blit(Action vertexAction) + { + DrawQuad(texture, screenSpaceDrawQuad, DrawColourInfo.Colour, null, vertexAction); + } + + public override void Draw(Action vertexAction) + { + base.Draw(vertexAction); + + shader.Bind(); + shader.GetUniform("highlight").Value = highlight; + shader.GetUniform("animation").Value = animation; + + Blit(vertexAction); + + shader.Unbind(); + } + } + } +} From 5fd6246d1b5e3b0d650cf4117d10df84b6d9f5de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Jun 2020 17:57:07 +0900 Subject: [PATCH 600/871] Fix remaining test scenes --- .../Screens/TestSceneTeamWinScreen.cs | 10 ++-------- osu.Game.Tournament.Tests/TournamentTestScene.cs | 13 +++++-------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs index 6873fb0f4b..3ca58dcaf4 100644 --- a/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs +++ b/osu.Game.Tournament.Tests/Screens/TestSceneTeamWinScreen.cs @@ -4,25 +4,19 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.TeamWin; namespace osu.Game.Tournament.Tests.Screens { public class TestSceneTeamWinScreen : TournamentTestScene { - [Cached] - private readonly LadderInfo ladder = new LadderInfo(); - [BackgroundDependencyLoader] private void load() { - var match = new TournamentMatch(); - match.Team1.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "USA"); - match.Team2.Value = Ladder.Teams.FirstOrDefault(t => t.Acronym.Value == "JPN"); + var match = Ladder.CurrentMatch.Value; + match.Round.Value = Ladder.Rounds.FirstOrDefault(g => g.Name.Value == "Finals"); match.Completed.Value = true; - ladder.CurrentMatch.Value = match; Add(new TeamWinScreen { diff --git a/osu.Game.Tournament.Tests/TournamentTestScene.cs b/osu.Game.Tournament.Tests/TournamentTestScene.cs index a7b141cf43..d22da25f9d 100644 --- a/osu.Game.Tournament.Tests/TournamentTestScene.cs +++ b/osu.Game.Tournament.Tests/TournamentTestScene.cs @@ -31,14 +31,6 @@ namespace osu.Game.Tournament.Tests { Ladder.Ruleset.Value ??= rulesetStore.AvailableRulesets.First(); - Ruleset.BindTo(Ladder.Ruleset); - Dependencies.CacheAs(new StableInfo(storage)); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - TournamentMatch match = CreateSampleMatch(); Ladder.Rounds.Add(match.Round.Value); @@ -47,6 +39,9 @@ namespace osu.Game.Tournament.Tests Ladder.Teams.Add(match.Team2.Value); Ladder.CurrentMatch.Value = match; + + Ruleset.BindTo(Ladder.Ruleset); + Dependencies.CacheAs(new StableInfo(storage)); } public static TournamentMatch CreateSampleMatch() => new TournamentMatch @@ -55,6 +50,7 @@ namespace osu.Game.Tournament.Tests { Value = new TournamentTeam { + Acronym = { Value = "JPN" }, FlagName = { Value = "JP" }, FullName = { Value = "Japan" }, LastYearPlacing = { Value = 10 }, @@ -128,6 +124,7 @@ namespace osu.Game.Tournament.Tests { Value = new TournamentTeam { + Acronym = { Value = "USA" }, FlagName = { Value = "US" }, FullName = { Value = "United States" }, Players = From 9e1bf71233b66a88b2419339db6cf181a7705534 Mon Sep 17 00:00:00 2001 From: Viktor Rosvall Date: Wed, 24 Jun 2020 11:29:38 +0200 Subject: [PATCH 601/871] Added text explaining a second copy will be made --- osu.Game/Screens/Select/ImportFromStablePopup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/ImportFromStablePopup.cs b/osu.Game/Screens/Select/ImportFromStablePopup.cs index 20494829ae..272f9566d5 100644 --- a/osu.Game/Screens/Select/ImportFromStablePopup.cs +++ b/osu.Game/Screens/Select/ImportFromStablePopup.cs @@ -12,7 +12,7 @@ namespace osu.Game.Screens.Select public ImportFromStablePopup(Action importFromStable) { HeaderText = @"You have no beatmaps!"; - BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps, skins and scores?"; + BodyText = "An existing copy of osu! was found, though.\nWould you like to import your beatmaps, skins and scores?\nThis will create a second copy of all files on disk."; Icon = FontAwesome.Solid.Plane; From 6bc507d49ed2bc76cbbaffc5dc9ddac087a5bd8f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 24 Jun 2020 18:53:52 +0900 Subject: [PATCH 602/871] Increase coordinate parsing limits --- osu.Game/Beatmaps/Formats/Parsing.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/Parsing.cs b/osu.Game/Beatmaps/Formats/Parsing.cs index c3efb8c760..c4795a6931 100644 --- a/osu.Game/Beatmaps/Formats/Parsing.cs +++ b/osu.Game/Beatmaps/Formats/Parsing.cs @@ -11,7 +11,7 @@ namespace osu.Game.Beatmaps.Formats /// public static class Parsing { - public const int MAX_COORDINATE_VALUE = 65536; + public const int MAX_COORDINATE_VALUE = 131072; public const double MAX_PARSE_VALUE = int.MaxValue; From 768e28faba318a3c2ef46fbce030ffb52d98b060 Mon Sep 17 00:00:00 2001 From: jorolf Date: Wed, 24 Jun 2020 14:11:38 +0200 Subject: [PATCH 603/871] generalize and simplify animation --- .../UserInterface/TestSceneHueAnimation.cs | 29 +++++ osu.Game/Graphics/Sprites/HueAnimation.cs | 75 ++++++++++++ osu.Game/Screens/Menu/IntroTriangles.cs | 45 ++++++-- osu.Game/Screens/Menu/LazerLogo.cs | 109 ------------------ 4 files changed, 141 insertions(+), 117 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs create mode 100644 osu.Game/Graphics/Sprites/HueAnimation.cs delete mode 100644 osu.Game/Screens/Menu/LazerLogo.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs new file mode 100644 index 0000000000..582849a053 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs @@ -0,0 +1,29 @@ +// 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.Graphics; +using osu.Framework.Graphics.Textures; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Tests.Visual.UserInterface +{ + [TestFixture] + public class TestSceneHueAnimation : OsuTestScene + { + [BackgroundDependencyLoader] + private void load(TextureStore textures) + { + HueAnimation anim; + + Add(anim = new HueAnimation + { + Texture = textures.Get("Intro/Triangles/logo-triangles.png"), + Colour = Colour4.White, + }); + + AddSliderStep("Progress", 0f, 1f, 0f, newValue => anim.AnimationProgress = newValue); + } + } +} diff --git a/osu.Game/Graphics/Sprites/HueAnimation.cs b/osu.Game/Graphics/Sprites/HueAnimation.cs new file mode 100644 index 0000000000..55a167cf59 --- /dev/null +++ b/osu.Game/Graphics/Sprites/HueAnimation.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 System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.OpenGL.Vertices; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osuTK; + +namespace osu.Game.Graphics.Sprites +{ + public class HueAnimation : Sprite + { + public HueAnimation() + { + Size = new Vector2(960); + } + + [BackgroundDependencyLoader] + private void load(ShaderManager shaders, TextureStore textures) + { + TextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"HueAnimation"); + RoundedTextureShader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"HueAnimation"); // Masking isn't supported for now + } + + private float animationProgress; + + public float AnimationProgress + { + get => animationProgress; + set + { + if (animationProgress == value) return; + + animationProgress = value; + Invalidate(Invalidation.DrawInfo); + } + } + + public override bool IsPresent => true; + + protected override DrawNode CreateDrawNode() => new HueAnimationDrawNode(this); + + private class HueAnimationDrawNode : SpriteDrawNode + { + protected new HueAnimation Source => (HueAnimation)base.Source; + + private float progress; + + public HueAnimationDrawNode(HueAnimation source) + : base(source) + { + } + + public override void ApplyState() + { + base.ApplyState(); + + progress = Source.animationProgress; + } + + protected override void Blit(Action vertexAction) + { + Shader.GetUniform("progress").UpdateValue(ref progress); + + base.Blit(vertexAction); + } + + protected override bool CanDrawOpaqueInterior => false; + } + } +} diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 36f00a13ef..53eb0eb270 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -137,7 +138,7 @@ namespace osu.Game.Screens.Menu RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Child = lazerLogo = new LazerLogo() + Child = lazerLogo = new LazerLogo { Anchor = Anchor.Centre, Origin = Anchor.Centre @@ -216,7 +217,13 @@ namespace osu.Game.Screens.Menu // matching flyte curve y = 0.25x^2 + (max(0, x - 0.7) / 0.3) ^ 5 lazerLogo.FadeIn().ScaleTo(scale_start).Then().Delay(logo_scale_duration * 0.7f).ScaleTo(scale_start - scale_adjust, logo_scale_duration * 0.3f, Easing.InQuint); - lazerLogo.Start(logo_1, logo_scale_duration); + + lazerLogo.TransformTo(nameof(LazerLogo.Highlight), 0.6f, logo_scale_duration * 0.4f, Easing.OutCirc).Then() + .TransformTo(nameof(LazerLogo.Highlight), 1f, logo_scale_duration * 0.4f); + + lazerLogo.TransformTo(nameof(LazerLogo.Animation), 0.4f, logo_scale_duration * 0.5f, Easing.OutQuart).Then() + .TransformTo(nameof(LazerLogo.Animation), 1f, logo_scale_duration * 0.4f); + logoContainerSecondary.ScaleTo(scale_start).Then().ScaleTo(scale_start - scale_adjust * 0.25f, logo_scale_duration, Easing.InQuad); } @@ -258,20 +265,42 @@ namespace osu.Game.Screens.Menu private class LazerLogo : CompositeDrawable { - private readonly Stream videoStream; + private HueAnimation highlight, animation; - public LazerLogo(Stream videoStream) + public float Highlight + { + get => highlight.AnimationProgress; + set => highlight.AnimationProgress = value; + } + + public float Animation + { + get => animation.AnimationProgress; + set => animation.AnimationProgress = value; + } + + public LazerLogo() { - this.videoStream = videoStream; Size = new Vector2(960); } [BackgroundDependencyLoader] - private void load() + private void load(TextureStore textures) { - InternalChild = new Video(videoStream) + const string lazer_logo_texture = @"Intro/Triangles/logo-triangles"; + + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, + highlight = new HueAnimation + { + Texture = textures.Get(lazer_logo_texture), + Colour = OsuColour.Gray(0.6f).Opacity(0.8f), + }, + animation = new HueAnimation + { + Texture = textures.Get(lazer_logo_texture), + Colour = Color4.White.Opacity(0.8f), + }, }; } } diff --git a/osu.Game/Screens/Menu/LazerLogo.cs b/osu.Game/Screens/Menu/LazerLogo.cs deleted file mode 100644 index ab9c82bc58..0000000000 --- a/osu.Game/Screens/Menu/LazerLogo.cs +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using System; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.OpenGL.Vertices; -using osu.Framework.Graphics.Primitives; -using osu.Framework.Graphics.Shaders; -using osu.Framework.Graphics.Textures; -using osu.Framework.MathUtils; -using osuTK; - -namespace osu.Game.Screens.Menu -{ - public class LazerLogo : Drawable - { - private IShader shader; - private Texture texture; - - private double startTime = -1000; - private double animationTime = -1000; - - private float animation; - private float highlight; - - public LazerLogo() - { - Size = new Vector2(960); - } - - [BackgroundDependencyLoader] - private void load(ShaderManager shaders, TextureStore textures) - { - shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, @"LazerLogo"); - texture = textures.Get("Menu/logo-triangles.png"); - } - - public void Start(double delay, double duration) - { - startTime = Clock.CurrentTime + delay; - animationTime = duration; - } - - public override bool IsPresent => true; - - protected override void Update() - { - base.Update(); - - if (animationTime < 0) return; - - highlight = Clock.CurrentTime < startTime + 0.4 * animationTime - ? Interpolation.ValueAt(Clock.CurrentTime, 0f, 1f, startTime, startTime + animationTime * 1.07, Easing.OutCirc) - : Interpolation.ValueAt(Clock.CurrentTime, 0.6f, 1f, startTime, startTime + animationTime * 0.9); - - animation = Clock.CurrentTime < startTime + 0.5 * animationTime - ? Interpolation.ValueAt(Clock.CurrentTime, 0f, 0.8f, startTime, startTime + animationTime * 1.23, Easing.OutQuart) - : Interpolation.ValueAt(Clock.CurrentTime, 0.4f, 1f, startTime, startTime + animationTime); - } - - protected override DrawNode CreateDrawNode() => new LazerLogoDrawNode(this); - - private class LazerLogoDrawNode : DrawNode - { - protected new LazerLogo Source => (LazerLogo)base.Source; - - private IShader shader; - private Texture texture; - private Quad screenSpaceDrawQuad; - private float animation; - private float highlight; - - public LazerLogoDrawNode(LazerLogo source) - : base(source) - { - } - - public override void ApplyState() - { - base.ApplyState(); - - shader = Source.shader; - texture = Source.texture; - screenSpaceDrawQuad = Source.ScreenSpaceDrawQuad; - animation = Source.animation; - highlight = Source.highlight; - } - - protected virtual void Blit(Action vertexAction) - { - DrawQuad(texture, screenSpaceDrawQuad, DrawColourInfo.Colour, null, vertexAction); - } - - public override void Draw(Action vertexAction) - { - base.Draw(vertexAction); - - shader.Bind(); - shader.GetUniform("highlight").Value = highlight; - shader.GetUniform("animation").Value = animation; - - Blit(vertexAction); - - shader.Unbind(); - } - } - } -} From 0d3bc1ac29685628e57cceddd94d525b6ef48ea2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 24 Jun 2020 22:29:30 +0900 Subject: [PATCH 604/871] Add basic heatmap colour scaling based on peak value --- .../Statistics/AccuracyHeatmap.cs | 59 ++++++++++++++++--- 1 file changed, 50 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 94d47ecb32..eeb8b519ca 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -39,6 +40,11 @@ namespace osu.Game.Rulesets.Osu.Statistics private readonly ScoreInfo score; private readonly IBeatmap playableBeatmap; + /// + /// The highest count of any point currently being displayed. + /// + protected float PeakValue { get; private set; } + public AccuracyHeatmap(ScoreInfo score, IBeatmap playableBeatmap) { this.score = score; @@ -152,7 +158,7 @@ namespace osu.Game.Rulesets.Osu.Statistics ? HitPointType.Hit : HitPointType.Miss; - var point = new HitPoint(pointType) + var point = new HitPoint(pointType, this) { Colour = pointType == HitPointType.Hit ? new Color4(102, 255, 204, 255) : new Color4(255, 102, 102, 255) }; @@ -215,7 +221,7 @@ namespace osu.Game.Rulesets.Osu.Statistics int r = Math.Clamp((int)Math.Round(localPoint.Y), 0, points_per_dimension - 1); int c = Math.Clamp((int)Math.Round(localPoint.X), 0, points_per_dimension - 1); - ((HitPoint)pointGrid.Content[r][c]).Increment(); + PeakValue = Math.Max(PeakValue, ((HitPoint)pointGrid.Content[r][c]).Increment()); bufferedGrid.ForceRedraw(); } @@ -223,21 +229,56 @@ namespace osu.Game.Rulesets.Osu.Statistics private class HitPoint : Circle { private readonly HitPointType pointType; + private readonly AccuracyHeatmap heatmap; - public HitPoint(HitPointType pointType) + public override bool IsPresent => count > 0; + + public HitPoint(HitPointType pointType, AccuracyHeatmap heatmap) { this.pointType = pointType; + this.heatmap = heatmap; RelativeSizeAxes = Axes.Both; - Alpha = 0; + Alpha = 1; } - public void Increment() + private int count; + + /// + /// Increment the value of this point by one. + /// + /// The value after incrementing. + public int Increment() { - if (Alpha < 1) - Alpha += 0.1f; - else if (pointType == HitPointType.Hit) - Colour = ((Color4)Colour).Lighten(0.1f); + return ++count; + } + + protected override void Update() + { + base.Update(); + + // the point at which alpha is saturated and we begin to adjust colour lightness. + const float lighten_cutoff = 0.95f; + + // the amount of lightness to attribute regardless of relative value to peak point. + const float non_relative_portion = 0.2f; + + float amount = 0; + + // give some amount of alpha regardless of relative count + amount += non_relative_portion * Math.Min(1, count / 10f); + + // add relative portion + amount += (1 - non_relative_portion) * (count / heatmap.PeakValue); + + // apply easing + amount = (float)Interpolation.ApplyEasing(Easing.OutQuint, Math.Min(1, amount)); + + Debug.Assert(amount <= 1); + + Alpha = Math.Min(amount / lighten_cutoff, 1); + if (pointType == HitPointType.Hit) + Colour = ((Color4)Colour).Lighten(Math.Max(0, amount - lighten_cutoff)); } } From 6f0ec36407510e54c49b4cef6710bf620c35a564 Mon Sep 17 00:00:00 2001 From: jorolf Date: Wed, 24 Jun 2020 16:27:00 +0200 Subject: [PATCH 605/871] remove size from hue animation --- .../Visual/UserInterface/TestSceneHueAnimation.cs | 6 ++++-- osu.Game/Graphics/Sprites/HueAnimation.cs | 6 ------ osu.Game/Screens/Menu/IntroTriangles.cs | 2 ++ 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs index 582849a053..85ddfb08f9 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs @@ -13,13 +13,15 @@ namespace osu.Game.Tests.Visual.UserInterface public class TestSceneHueAnimation : OsuTestScene { [BackgroundDependencyLoader] - private void load(TextureStore textures) + private void load(LargeTextureStore textures) { HueAnimation anim; Add(anim = new HueAnimation { - Texture = textures.Get("Intro/Triangles/logo-triangles.png"), + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Texture = textures.Get("Intro/Triangles/logo-triangles"), Colour = Colour4.White, }); diff --git a/osu.Game/Graphics/Sprites/HueAnimation.cs b/osu.Game/Graphics/Sprites/HueAnimation.cs index 55a167cf59..4f2bafe27f 100644 --- a/osu.Game/Graphics/Sprites/HueAnimation.cs +++ b/osu.Game/Graphics/Sprites/HueAnimation.cs @@ -8,17 +8,11 @@ using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Shaders; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using osuTK; namespace osu.Game.Graphics.Sprites { public class HueAnimation : Sprite { - public HueAnimation() - { - Size = new Vector2(960); - } - [BackgroundDependencyLoader] private void load(ShaderManager shaders, TextureStore textures) { diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 53eb0eb270..95fc101094 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -293,11 +293,13 @@ namespace osu.Game.Screens.Menu { highlight = new HueAnimation { + RelativeSizeAxes = Axes.Both, Texture = textures.Get(lazer_logo_texture), Colour = OsuColour.Gray(0.6f).Opacity(0.8f), }, animation = new HueAnimation { + RelativeSizeAxes = Axes.Both, Texture = textures.Get(lazer_logo_texture), Colour = Color4.White.Opacity(0.8f), }, From 4c283476866d7cf9d276a8e3219fa3a069f7604f Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Wed, 24 Jun 2020 15:34:20 +0100 Subject: [PATCH 606/871] Adjust sample rate by UserPlaybackRate --- osu.Game/Screens/Play/Player.cs | 2 +- osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 83991ad027..71a97da5c2 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Play public bool LoadedBeatmapSuccessfully => DrawableRuleset?.Objects.Any() == true; - protected GameplayClockContainer GameplayClockContainer { get; private set; } + public GameplayClockContainer GameplayClockContainer { get; private set; } public DimmableStoryboard DimmableStoryboard { get; private set; } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 8eaf9ac652..3dc7eab968 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -4,11 +4,13 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; +using osu.Game.Screens.Play; namespace osu.Game.Storyboards.Drawables { @@ -32,7 +34,7 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap, IBindable> mods) + private void load(IBindable beatmap, IBindable> mods, Player player) { Channel = beatmap.Value.Skin.GetSample(sampleInfo); if (Channel == null) @@ -42,6 +44,8 @@ namespace osu.Game.Storyboards.Drawables foreach (var mod in mods.Value.OfType()) mod.ApplyToSample(Channel); + + Channel.AddAdjustment(AdjustableProperty.Frequency, player.GameplayClockContainer.UserPlaybackRate); } protected override void Update() From 992ada46700d6f6420a6f22b0904376bb7ec7c58 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Wed, 24 Jun 2020 16:17:18 +0100 Subject: [PATCH 607/871] Revert UserPlaybackRate changes --- osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 3dc7eab968..5aeadb2e1f 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -34,7 +34,7 @@ namespace osu.Game.Storyboards.Drawables } [BackgroundDependencyLoader] - private void load(IBindable beatmap, IBindable> mods, Player player) + private void load(IBindable beatmap, IBindable> mods) { Channel = beatmap.Value.Skin.GetSample(sampleInfo); if (Channel == null) @@ -44,8 +44,6 @@ namespace osu.Game.Storyboards.Drawables foreach (var mod in mods.Value.OfType()) mod.ApplyToSample(Channel); - - Channel.AddAdjustment(AdjustableProperty.Frequency, player.GameplayClockContainer.UserPlaybackRate); } protected override void Update() From f2a48a339ea7e643ab5156764f99450d53f30bf2 Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Wed, 24 Jun 2020 16:33:19 +0100 Subject: [PATCH 608/871] Remove unused usings --- osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs index 5aeadb2e1f..8eaf9ac652 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -4,13 +4,11 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Mods; -using osu.Game.Screens.Play; namespace osu.Game.Storyboards.Drawables { From 1409ace282035eb5c5b01e5d2e099049c0dad829 Mon Sep 17 00:00:00 2001 From: jorolf Date: Thu, 25 Jun 2020 00:59:12 +0200 Subject: [PATCH 609/871] apply suggestions --- osu.Game/Graphics/Sprites/HueAnimation.cs | 4 ++-- osu.Game/Screens/Menu/IntroTriangles.cs | 26 +++++++++++------------ 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game/Graphics/Sprites/HueAnimation.cs b/osu.Game/Graphics/Sprites/HueAnimation.cs index 4f2bafe27f..82ac1aad36 100644 --- a/osu.Game/Graphics/Sprites/HueAnimation.cs +++ b/osu.Game/Graphics/Sprites/HueAnimation.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.Sprites private class HueAnimationDrawNode : SpriteDrawNode { - protected new HueAnimation Source => (HueAnimation)base.Source; + private HueAnimation source => (HueAnimation)base.Source; private float progress; @@ -53,7 +53,7 @@ namespace osu.Game.Graphics.Sprites { base.ApplyState(); - progress = Source.animationProgress; + progress = source.animationProgress; } protected override void Blit(Action vertexAction) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 95fc101094..2074fc7081 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -218,11 +218,11 @@ namespace osu.Game.Screens.Menu // matching flyte curve y = 0.25x^2 + (max(0, x - 0.7) / 0.3) ^ 5 lazerLogo.FadeIn().ScaleTo(scale_start).Then().Delay(logo_scale_duration * 0.7f).ScaleTo(scale_start - scale_adjust, logo_scale_duration * 0.3f, Easing.InQuint); - lazerLogo.TransformTo(nameof(LazerLogo.Highlight), 0.6f, logo_scale_duration * 0.4f, Easing.OutCirc).Then() - .TransformTo(nameof(LazerLogo.Highlight), 1f, logo_scale_duration * 0.4f); + lazerLogo.TransformTo(nameof(LazerLogo.OutlineHighlight), 0.6f, logo_scale_duration * 0.4f, Easing.OutCirc).Then() + .TransformTo(nameof(LazerLogo.OutlineHighlight), 1f, logo_scale_duration * 0.4f); - lazerLogo.TransformTo(nameof(LazerLogo.Animation), 0.4f, logo_scale_duration * 0.5f, Easing.OutQuart).Then() - .TransformTo(nameof(LazerLogo.Animation), 1f, logo_scale_duration * 0.4f); + lazerLogo.TransformTo(nameof(LazerLogo.Outline), 0.4f, logo_scale_duration * 0.5f, Easing.OutQuart).Then() + .TransformTo(nameof(LazerLogo.Outline), 1f, logo_scale_duration * 0.4f); logoContainerSecondary.ScaleTo(scale_start).Then().ScaleTo(scale_start - scale_adjust * 0.25f, logo_scale_duration, Easing.InQuad); } @@ -265,18 +265,18 @@ namespace osu.Game.Screens.Menu private class LazerLogo : CompositeDrawable { - private HueAnimation highlight, animation; + private HueAnimation outlineHighlight, outline; - public float Highlight + public float OutlineHighlight { - get => highlight.AnimationProgress; - set => highlight.AnimationProgress = value; + get => outlineHighlight.AnimationProgress; + set => outlineHighlight.AnimationProgress = value; } - public float Animation + public float Outline { - get => animation.AnimationProgress; - set => animation.AnimationProgress = value; + get => outline.AnimationProgress; + set => outline.AnimationProgress = value; } public LazerLogo() @@ -291,13 +291,13 @@ namespace osu.Game.Screens.Menu InternalChildren = new Drawable[] { - highlight = new HueAnimation + outlineHighlight = new HueAnimation { RelativeSizeAxes = Axes.Both, Texture = textures.Get(lazer_logo_texture), Colour = OsuColour.Gray(0.6f).Opacity(0.8f), }, - animation = new HueAnimation + outline = new HueAnimation { RelativeSizeAxes = Axes.Both, Texture = textures.Get(lazer_logo_texture), From ac5cd8f25a3a1f08c3adc3fe562e0fbdc5b0585c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 13:40:26 +0900 Subject: [PATCH 610/871] Fix colours with 0 alpha being invisible in legacy skins --- osu.Game/Skinning/LegacySkin.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 0b2b723440..bbc64a24e7 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -271,7 +271,15 @@ namespace osu.Game.Skinning } private IBindable getCustomColour(IHasCustomColours source, string lookup) - => source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable(col) : null; + { + if (!source.CustomColours.TryGetValue(lookup, out var col)) + return null; + + if (col.A == 0) + col.A = 1; + + return new Bindable(col); + } private IBindable getManiaImage(LegacyManiaSkinConfiguration source, string lookup) => source.ImageLookups.TryGetValue(lookup, out var image) ? new Bindable(image) : null; From 4c601af207c3374eb37fd588e989e2ee9f129acb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 13:43:14 +0900 Subject: [PATCH 611/871] Match condition --- osu.Game/Skinning/LegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index bbc64a24e7..be6d694efe 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -275,7 +275,7 @@ namespace osu.Game.Skinning if (!source.CustomColours.TryGetValue(lookup, out var col)) return null; - if (col.A == 0) + if (col.A <= 0 || col.A >= 255) col.A = 1; return new Bindable(col); From 8b84aa454d61cd61390fcce8f8f79be4e8ab1ebb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 13:43:56 +0900 Subject: [PATCH 612/871] Fix incorrect upper bound --- osu.Game/Skinning/LegacySkin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index be6d694efe..ea630b9b8d 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -275,7 +275,7 @@ namespace osu.Game.Skinning if (!source.CustomColours.TryGetValue(lookup, out var col)) return null; - if (col.A <= 0 || col.A >= 255) + if (col.A <= 0 || col.A >= 1) col.A = 1; return new Bindable(col); From 4ff9a910121d85957cdb94011a8ed5dba653c3ad Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 14:15:26 +0900 Subject: [PATCH 613/871] Adjust at parse time instead --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 7 ++++++- osu.Game/Skinning/LegacySkin.cs | 10 +--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 6406bd88a5..a0e83554a3 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -103,7 +103,12 @@ namespace osu.Game.Beatmaps.Formats try { - colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), split.Length == 4 ? byte.Parse(split[3]) : (byte)255); + byte alpha = split.Length == 4 ? byte.Parse(split[3]) : (byte)255; + + if (alpha == 0) + alpha = 255; + + colour = new Color4(byte.Parse(split[0]), byte.Parse(split[1]), byte.Parse(split[2]), alpha); } catch { diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index ea630b9b8d..0b2b723440 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -271,15 +271,7 @@ namespace osu.Game.Skinning } private IBindable getCustomColour(IHasCustomColours source, string lookup) - { - if (!source.CustomColours.TryGetValue(lookup, out var col)) - return null; - - if (col.A <= 0 || col.A >= 1) - col.A = 1; - - return new Bindable(col); - } + => source.CustomColours.TryGetValue(lookup, out var col) ? new Bindable(col) : null; private IBindable getManiaImage(LegacyManiaSkinConfiguration source, string lookup) => source.ImageLookups.TryGetValue(lookup, out var image) ? new Bindable(image) : null; From 531a69650f390ef3825f449daf46f0a32149895a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 14:22:40 +0900 Subject: [PATCH 614/871] Add test --- osu.Game.Tests/Resources/skin-zero-alpha-colour.ini | 5 +++++ osu.Game.Tests/Skins/LegacySkinDecoderTest.cs | 10 ++++++++++ 2 files changed, 15 insertions(+) create mode 100644 osu.Game.Tests/Resources/skin-zero-alpha-colour.ini diff --git a/osu.Game.Tests/Resources/skin-zero-alpha-colour.ini b/osu.Game.Tests/Resources/skin-zero-alpha-colour.ini new file mode 100644 index 0000000000..3c0dae6b13 --- /dev/null +++ b/osu.Game.Tests/Resources/skin-zero-alpha-colour.ini @@ -0,0 +1,5 @@ +[General] +Version: latest + +[Colours] +Combo1: 255,255,255,0 \ No newline at end of file diff --git a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs index aedf26ee75..c408d2f182 100644 --- a/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs +++ b/osu.Game.Tests/Skins/LegacySkinDecoderTest.cs @@ -108,5 +108,15 @@ namespace osu.Game.Tests.Skins using (var stream = new LineBufferedReader(resStream)) Assert.That(decoder.Decode(stream).LegacyVersion, Is.EqualTo(1.0m)); } + + [Test] + public void TestDecodeColourWithZeroAlpha() + { + var decoder = new LegacySkinDecoder(); + + using (var resStream = TestResources.OpenResource("skin-zero-alpha-colour.ini")) + using (var stream = new LineBufferedReader(resStream)) + Assert.That(decoder.Decode(stream).ComboColours[0].A, Is.EqualTo(1.0f)); + } } } From fd13c0a6ddb5c8ea3260e64f956af86ab63bee5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jun 2020 18:44:04 +0900 Subject: [PATCH 615/871] Standardise line thickness --- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index eeb8b519ca..89707b3ebb 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -40,6 +40,8 @@ namespace osu.Game.Rulesets.Osu.Statistics private readonly ScoreInfo score; private readonly IBeatmap playableBeatmap; + private const float line_thickness = 2; + /// /// The highest count of any point currently being displayed. /// @@ -69,7 +71,7 @@ namespace osu.Game.Rulesets.Osu.Statistics RelativeSizeAxes = Axes.Both, Size = new Vector2(inner_portion), Masking = true, - BorderThickness = 2f, + BorderThickness = line_thickness, BorderColour = Color4.White, Child = new Box { @@ -98,7 +100,7 @@ namespace osu.Game.Rulesets.Osu.Statistics Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = 1f, + Width = line_thickness, Rotation = -rotation, Alpha = 0.3f, }, @@ -108,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Statistics Origin = Anchor.Centre, RelativeSizeAxes = Axes.Y, Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = 1f, + Width = line_thickness, Rotation = rotation }, } From c095753f2444ce052b8fa7588c9cfb1eb0956cef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jun 2020 19:02:04 +0900 Subject: [PATCH 616/871] Add better line smoothing --- osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs index 89707b3ebb..20adbc1c02 100644 --- a/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs +++ b/osu.Game.Rulesets.Osu/Statistics/AccuracyHeatmap.cs @@ -98,9 +98,10 @@ namespace osu.Game.Rulesets.Osu.Statistics { Anchor = Anchor.Centre, Origin = Anchor.Centre, + EdgeSmoothness = new Vector2(1), RelativeSizeAxes = Axes.Y, Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = line_thickness, + Width = line_thickness / 2, Rotation = -rotation, Alpha = 0.3f, }, @@ -108,9 +109,10 @@ namespace osu.Game.Rulesets.Osu.Statistics { Anchor = Anchor.Centre, Origin = Anchor.Centre, + EdgeSmoothness = new Vector2(1), RelativeSizeAxes = Axes.Y, Height = 2, // We're rotating along a diagonal - we don't really care how big this is. - Width = line_thickness, + Width = line_thickness / 2, // adjust for edgesmoothness Rotation = rotation }, } @@ -121,13 +123,15 @@ namespace osu.Game.Rulesets.Osu.Statistics Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Width = 10, - Height = 2, + EdgeSmoothness = new Vector2(1), + Height = line_thickness / 2, // adjust for edgesmoothness }, new Box { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Width = 2, + EdgeSmoothness = new Vector2(1), + Width = line_thickness / 2, // adjust for edgesmoothness Height = 10, } } From d7742766d054ca1d036985b6ca6c62ab946851c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jun 2020 19:47:23 +0900 Subject: [PATCH 617/871] Add key/press repeat support to carousel --- osu.Game/Screens/Select/BeatmapCarousel.cs | 63 ++++++++++++++++++++-- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index e174c46610..6611955cce 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -452,32 +452,49 @@ namespace osu.Game.Screens.Select /// public void ScrollToSelected() => scrollPositionCache.Invalidate(); + #region Key / button selection logic + protected override bool OnKeyDown(KeyDownEvent e) { switch (e.Key) { case Key.Left: - SelectNext(-1, true); + if (!e.Repeat) + beginRepeatSelection(() => SelectNext(-1, true), e.Key); return true; case Key.Right: - SelectNext(1, true); + if (!e.Repeat) + beginRepeatSelection(() => SelectNext(1, true), e.Key); return true; } return false; } + protected override void OnKeyUp(KeyUpEvent e) + { + switch (e.Key) + { + case Key.Left: + case Key.Right: + endRepeatSelection(e.Key); + break; + } + + base.OnKeyUp(e); + } + public bool OnPressed(GlobalAction action) { switch (action) { case GlobalAction.SelectNext: - SelectNext(1, false); + beginRepeatSelection(() => SelectNext(1, false), action); return true; case GlobalAction.SelectPrevious: - SelectNext(-1, false); + beginRepeatSelection(() => SelectNext(-1, false), action); return true; } @@ -486,8 +503,46 @@ namespace osu.Game.Screens.Select public void OnReleased(GlobalAction action) { + switch (action) + { + case GlobalAction.SelectNext: + case GlobalAction.SelectPrevious: + endRepeatSelection(action); + break; + } } + private const double repeat_interval = 120; + + private ScheduledDelegate repeatDelegate; + private object lastRepeatSource; + + /// + /// Begin repeating the specified selection action. + /// + /// The action to perform. + /// The source of the action. Used in conjunction with to only cancel the correct action (most recently pressed key). + private void beginRepeatSelection(Action action, object source) + { + endRepeatSelection(); + + lastRepeatSource = source; + Scheduler.Add(repeatDelegate = new ScheduledDelegate(action, Time.Current, repeat_interval)); + } + + private void endRepeatSelection(object source = null) + { + // only the most recent source should be able to cancel the current action. + if (source != null && !EqualityComparer.Default.Equals(lastRepeatSource, source)) + return; + + repeatDelegate?.Cancel(); + repeatDelegate = null; + lastRepeatSource = null; + } + + #endregion + protected override void Update() { base.Update(); From c36d9d4fc3fa87aef05600dae76ada50bf5c076d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jun 2020 20:01:29 +0900 Subject: [PATCH 618/871] Add test coverage --- .../SongSelect/TestSceneBeatmapCarousel.cs | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 2f12194ede..073d75692e 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -17,11 +17,12 @@ using osu.Game.Rulesets; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Carousel; using osu.Game.Screens.Select.Filter; +using osuTK.Input; namespace osu.Game.Tests.Visual.SongSelect { [TestFixture] - public class TestSceneBeatmapCarousel : OsuTestScene + public class TestSceneBeatmapCarousel : OsuManualInputManagerTestScene { private TestBeatmapCarousel carousel; private RulesetStore rulesets; @@ -39,6 +40,43 @@ namespace osu.Game.Tests.Visual.SongSelect this.rulesets = rulesets; } + [Test] + public void TestKeyRepeat() + { + loadBeatmaps(); + advanceSelection(false); + + AddStep("press down arrow", () => InputManager.PressKey(Key.Down)); + + BeatmapInfo selection = null; + + checkSelectionIterating(true); + + AddStep("press up arrow", () => InputManager.PressKey(Key.Up)); + + checkSelectionIterating(true); + + AddStep("release down arrow", () => InputManager.ReleaseKey(Key.Down)); + + checkSelectionIterating(true); + + AddStep("release up arrow", () => InputManager.ReleaseKey(Key.Up)); + + checkSelectionIterating(false); + + void checkSelectionIterating(bool isIterating) + { + for (int i = 0; i < 3; i++) + { + AddStep("store selection", () => selection = carousel.SelectedBeatmap); + if (isIterating) + AddUntilStep("selection changed", () => carousel.SelectedBeatmap != selection); + else + AddUntilStep("selection not changed", () => carousel.SelectedBeatmap == selection); + } + } + } + [Test] public void TestRecommendedSelection() { From 54f087b933ef1c6df225508271c3d5c634454d69 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 18:59:14 +0900 Subject: [PATCH 619/871] Re-layout match subscreen columns --- .../TestSceneMatchLeaderboardChatDisplay.cs | 32 ------ .../Multiplayer/TestSceneMatchSubScreen.cs | 16 +++ .../Components/LeaderboardChatDisplay.cs | 100 ------------------ .../Match/Components/OverlinedChatDisplay.cs | 20 ++++ .../Match/Components/OverlinedLeaderboard.cs | 24 +++++ .../Screens/Multi/Match/MatchSubScreen.cs | 60 +++-------- osu.Game/Tests/Visual/ModTestScene.cs | 13 --- osu.Game/Tests/Visual/ScreenTestScene.cs | 4 +- 8 files changed, 77 insertions(+), 192 deletions(-) delete mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboardChatDisplay.cs delete mode 100644 osu.Game/Screens/Multi/Match/Components/LeaderboardChatDisplay.cs create mode 100644 osu.Game/Screens/Multi/Match/Components/OverlinedChatDisplay.cs create mode 100644 osu.Game/Screens/Multi/Match/Components/OverlinedLeaderboard.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboardChatDisplay.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboardChatDisplay.cs deleted file mode 100644 index 72bbc11cd0..0000000000 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboardChatDisplay.cs +++ /dev/null @@ -1,32 +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 osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Game.Screens.Multi.Match.Components; -using osuTK; - -namespace osu.Game.Tests.Visual.Multiplayer -{ - public class TestSceneMatchLeaderboardChatDisplay : MultiplayerTestScene - { - protected override bool UseOnlineAPI => true; - - public TestSceneMatchLeaderboardChatDisplay() - { - Room.RoomID.Value = 7; - - Add(new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Size = new Vector2(500), - Child = new LeaderboardChatDisplay - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } - }); - } - } -} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs index b687724105..8c54f49b8f 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs @@ -58,6 +58,22 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for load", () => match.IsCurrentScreen()); } + [Test] + public void TestLoadSimpleMatch() + { + AddStep("set room properties", () => + { + Room.RoomID.Value = 1; + Room.Name.Value = "my awesome room"; + Room.Host.Value = new User { Id = 2, Username = "peppy" }; + Room.Playlist.Add(new PlaylistItem + { + Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, + Ruleset = { Value = new OsuRuleset().RulesetInfo } + }); + }); + } + [Test] public void TestPlaylistItemSelectedOnCreate() { diff --git a/osu.Game/Screens/Multi/Match/Components/LeaderboardChatDisplay.cs b/osu.Game/Screens/Multi/Match/Components/LeaderboardChatDisplay.cs deleted file mode 100644 index de02b7f605..0000000000 --- a/osu.Game/Screens/Multi/Match/Components/LeaderboardChatDisplay.cs +++ /dev/null @@ -1,100 +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 osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; - -namespace osu.Game.Screens.Multi.Match.Components -{ - public class LeaderboardChatDisplay : MultiplayerComposite - { - private const double fade_duration = 100; - - private readonly OsuTabControl tabControl; - private readonly MatchLeaderboard leaderboard; - private readonly MatchChatDisplay chat; - - public LeaderboardChatDisplay() - { - RelativeSizeAxes = Axes.Both; - - InternalChild = new GridContainer - { - RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - tabControl = new DisplayModeTabControl - { - RelativeSizeAxes = Axes.X, - Height = 24, - } - }, - new Drawable[] - { - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 10 }, - Children = new Drawable[] - { - leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, - chat = new MatchChatDisplay - { - RelativeSizeAxes = Axes.Both, - Alpha = 0 - } - } - } - }, - }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - tabControl.AccentColour = colours.Yellow; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - tabControl.Current.BindValueChanged(changeTab); - } - - public void RefreshScores() => leaderboard.RefreshScores(); - - private void changeTab(ValueChangedEvent mode) - { - chat.FadeTo(mode.NewValue == DisplayMode.Chat ? 1 : 0, fade_duration); - leaderboard.FadeTo(mode.NewValue == DisplayMode.Leaderboard ? 1 : 0, fade_duration); - } - - private class DisplayModeTabControl : OsuTabControl - { - protected override TabItem CreateTabItem(DisplayMode value) => base.CreateTabItem(value).With(d => - { - d.Anchor = Anchor.Centre; - d.Origin = Anchor.Centre; - }); - } - - private enum DisplayMode - { - Leaderboard, - Chat, - } - } -} diff --git a/osu.Game/Screens/Multi/Match/Components/OverlinedChatDisplay.cs b/osu.Game/Screens/Multi/Match/Components/OverlinedChatDisplay.cs new file mode 100644 index 0000000000..a8d898385a --- /dev/null +++ b/osu.Game/Screens/Multi/Match/Components/OverlinedChatDisplay.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.Framework.Graphics; +using osu.Game.Screens.Multi.Components; + +namespace osu.Game.Screens.Multi.Match.Components +{ + public class OverlinedChatDisplay : OverlinedDisplay + { + public OverlinedChatDisplay() + : base("Chat") + { + Content.Add(new MatchChatDisplay + { + RelativeSizeAxes = Axes.Both + }); + } + } +} diff --git a/osu.Game/Screens/Multi/Match/Components/OverlinedLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/OverlinedLeaderboard.cs new file mode 100644 index 0000000000..bda2cd70d7 --- /dev/null +++ b/osu.Game/Screens/Multi/Match/Components/OverlinedLeaderboard.cs @@ -0,0 +1,24 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Game.Screens.Multi.Components; + +namespace osu.Game.Screens.Multi.Match.Components +{ + public class OverlinedLeaderboard : OverlinedDisplay + { + private readonly MatchLeaderboard leaderboard; + + public OverlinedLeaderboard() + : base("Leaderboard") + { + Content.Add(leaderboard = new MatchLeaderboard + { + RelativeSizeAxes = Axes.Both + }); + } + + public void RefreshScores() => leaderboard.RefreshScores(); + } +} diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index f837a407a5..a2a8816b13 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.GameTypes; @@ -52,8 +51,8 @@ namespace osu.Game.Screens.Multi.Match protected readonly Bindable SelectedItem = new Bindable(); - private LeaderboardChatDisplay leaderboardChatDisplay; private MatchSettingsOverlay settingsOverlay; + private OverlinedLeaderboard leaderboard; private IBindable> managerUpdated; @@ -87,7 +86,10 @@ namespace osu.Game.Screens.Multi.Match RelativeSizeAxes = Axes.Both, Content = new[] { - new Drawable[] { new Components.Header() }, + new Drawable[] + { + new Components.Header() + }, new Drawable[] { new Container @@ -96,12 +98,6 @@ namespace osu.Game.Screens.Multi.Match Padding = new MarginPadding { Top = 65 }, Child = new GridContainer { - ColumnDimensions = new[] - { - new Dimension(minSize: 160), - new Dimension(minSize: 360), - new Dimension(minSize: 400), - }, RelativeSizeAxes = Axes.Both, Content = new[] { @@ -111,49 +107,23 @@ namespace osu.Game.Screens.Multi.Match { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Right = 5 }, - Child = new OverlinedParticipants(Direction.Vertical) { RelativeSizeAxes = Axes.Both } - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 5 }, - Child = new GridContainer + Child = new OverlinedPlaylist(true) // Temporarily always allow selection { RelativeSizeAxes = Axes.Both, - Content = new[] - { - new Drawable[] - { - new OverlinedPlaylist(true) // Temporarily always allow selection - { - RelativeSizeAxes = Axes.Both, - SelectedItem = { BindTarget = SelectedItem } - } - }, - null, - new Drawable[] - { - new TriangleButton - { - RelativeSizeAxes = Axes.X, - Text = "Show beatmap results", - Action = showBeatmapResults - } - } - }, - RowDimensions = new[] - { - new Dimension(), - new Dimension(GridSizeMode.Absolute, 5), - new Dimension(GridSizeMode.AutoSize) - } + SelectedItem = { BindTarget = SelectedItem } } }, new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = 5 }, + Child = leaderboard = new OverlinedLeaderboard { RelativeSizeAxes = Axes.Both }, + }, + new Container { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Left = 5 }, - Child = leaderboardChatDisplay = new LeaderboardChatDisplay() + Child = new OverlinedChatDisplay { RelativeSizeAxes = Axes.Both } } }, } @@ -261,7 +231,7 @@ namespace osu.Game.Screens.Multi.Match case GameTypeTimeshift _: multiplayer?.Push(new PlayerLoader(() => new TimeshiftPlayer(SelectedItem.Value) { - Exited = () => leaderboardChatDisplay.RefreshScores() + Exited = () => leaderboard.RefreshScores() })); break; } diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index 23b5ad0bd8..add851ebf3 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -21,19 +21,6 @@ namespace osu.Game.Tests.Visual AddStep("set test data", () => currentTestData = testData); }); - public override void TearDownSteps() - { - AddUntilStep("test passed", () => - { - if (currentTestData == null) - return true; - - return currentTestData.PassCondition?.Invoke() ?? false; - }); - - base.TearDownSteps(); - } - protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestData?.Beatmap ?? base.CreateBeatmap(ruleset); protected sealed override TestPlayer CreatePlayer(Ruleset ruleset) diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs index 33cc00e748..067d8faf54 100644 --- a/osu.Game/Tests/Visual/ScreenTestScene.cs +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -33,8 +33,8 @@ namespace osu.Game.Tests.Visual [SetUpSteps] public virtual void SetUpSteps() => addExitAllScreensStep(); - [TearDownSteps] - public virtual void TearDownSteps() => addExitAllScreensStep(); + // [TearDownSteps] + // public virtual void TearDownSteps() => addExitAllScreensStep(); private void addExitAllScreensStep() { From 01fa664b7dc57587357a29a7ac6c812da294de67 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 20:53:48 +0900 Subject: [PATCH 620/871] Add recent participants --- .../Multiplayer/TestSceneMatchSubScreen.cs | 1 + .../Multi/Components/OverlinedDisplay.cs | 12 ++++ .../Screens/Multi/Match/MatchSubScreen.cs | 63 +++++++++++-------- 3 files changed, 50 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs index 8c54f49b8f..66091f5679 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs @@ -66,6 +66,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Room.RoomID.Value = 1; Room.Name.Value = "my awesome room"; Room.Host.Value = new User { Id = 2, Username = "peppy" }; + Room.RecentParticipants.Add(Room.Host.Value); Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo }, diff --git a/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs b/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs index 8d8d4cc404..6aeb6c94df 100644 --- a/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs +++ b/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs @@ -35,6 +35,18 @@ namespace osu.Game.Screens.Multi.Components } } + private bool showLine = true; + + public bool ShowLine + { + get => showLine; + set + { + showLine = value; + line.Alpha = value ? 1 : 0; + } + } + protected string Details { set => details.Text = value; diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index a2a8816b13..8216f64872 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -94,45 +94,56 @@ namespace osu.Game.Screens.Multi.Match { new Container { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 65 }, - Child = new GridContainer + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 10 }, + Child = new OverlinedParticipants(Direction.Horizontal) { - RelativeSizeAxes = Axes.Both, - Content = new[] + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ShowLine = false + } + } + }, + new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.Both, + Content = new[] + { + new Drawable[] { - new Drawable[] + new Container { - new Container + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 5 }, + Child = new OverlinedPlaylist(true) // Temporarily always allow selection { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 5 }, - Child = new OverlinedPlaylist(true) // Temporarily always allow selection - { - RelativeSizeAxes = Axes.Both, - SelectedItem = { BindTarget = SelectedItem } - } - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 5 }, - Child = leaderboard = new OverlinedLeaderboard { RelativeSizeAxes = Axes.Both }, - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 5 }, - Child = new OverlinedChatDisplay { RelativeSizeAxes = Axes.Both } + SelectedItem = { BindTarget = SelectedItem } } }, - } + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Horizontal = 5 }, + Child = leaderboard = new OverlinedLeaderboard { RelativeSizeAxes = Axes.Both }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 5 }, + Child = new OverlinedChatDisplay { RelativeSizeAxes = Axes.Both } + } + }, } } } }, RowDimensions = new[] { + new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.AutoSize), new Dimension(), } From d704a4597dc60d882d4b8f54d6245cbca81ab67f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 25 Jun 2020 21:33:02 +0900 Subject: [PATCH 621/871] Use existing helper function for key repeat --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 6611955cce..ad19c9661f 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -19,6 +19,7 @@ using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Beatmaps; +using osu.Game.Extensions; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Input.Bindings; @@ -527,7 +528,7 @@ namespace osu.Game.Screens.Select endRepeatSelection(); lastRepeatSource = source; - Scheduler.Add(repeatDelegate = new ScheduledDelegate(action, Time.Current, repeat_interval)); + Scheduler.Add(repeatDelegate = this.BeginKeyRepeat(Scheduler, action)); } private void endRepeatSelection(object source = null) From 7c1dd43899d1369106890723eda0c8c671991274 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 21:58:40 +0900 Subject: [PATCH 622/871] Re-style multiplayer header --- osu.Game/Screens/Multi/Header.cs | 101 +++++++++++++++---------------- 1 file changed, 50 insertions(+), 51 deletions(-) diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 5b8e8a7fd9..2cdd082068 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -1,12 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; +using Humanizer; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; @@ -19,41 +21,41 @@ namespace osu.Game.Screens.Multi { public class Header : Container { - public const float HEIGHT = 121; - - private readonly HeaderBreadcrumbControl breadcrumbs; + public const float HEIGHT = 100; public Header(ScreenStack stack) { - MultiHeaderTitle title; RelativeSizeAxes = Axes.X; Height = HEIGHT; + HeaderBreadcrumbControl breadcrumbs; + MultiHeaderTitle title; + Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex(@"2f2043"), + Colour = Color4Extensions.FromHex(@"#1f1921"), }, new Container { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, + Padding = new MarginPadding { Left = SearchableListOverlay.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING }, Children = new Drawable[] { title = new MultiHeaderTitle { Anchor = Anchor.CentreLeft, - Origin = Anchor.BottomLeft, - X = -MultiHeaderTitle.ICON_WIDTH, + Origin = Anchor.CentreLeft, }, breadcrumbs = new HeaderBreadcrumbControl(stack) { Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - }, + Origin = Anchor.BottomLeft + } }, }, }; @@ -62,37 +64,26 @@ namespace osu.Game.Screens.Multi { if (screen.NewValue is IMultiplayerSubScreen multiScreen) title.Screen = multiScreen; + + if (breadcrumbs.Items.Any() && screen.NewValue == breadcrumbs.Items.First()) + breadcrumbs.FadeOut(500, Easing.OutQuint); + else + breadcrumbs.FadeIn(500, Easing.OutQuint); }; breadcrumbs.Current.TriggerChange(); } - [BackgroundDependencyLoader] - private void load(OsuColour colours) + private class MultiHeaderTitle : CompositeDrawable { - breadcrumbs.StripColour = colours.Green; - } - - private class MultiHeaderTitle : CompositeDrawable, IHasAccentColour - { - public const float ICON_WIDTH = icon_size + spacing; - - private const float icon_size = 25; private const float spacing = 6; - private const int text_offset = 2; - private readonly SpriteIcon iconSprite; - private readonly OsuSpriteText title, pageText; + private readonly OsuSpriteText dot; + private readonly OsuSpriteText pageTitle; public IMultiplayerSubScreen Screen { - set => pageText.Text = value.ShortTitle.ToLowerInvariant(); - } - - public Color4 AccentColour - { - get => pageText.Colour; - set => pageText.Colour = value; + set => pageTitle.Text = value.ShortTitle.Titleize(); } public MultiHeaderTitle() @@ -108,32 +99,26 @@ namespace osu.Game.Screens.Multi Direction = FillDirection.Horizontal, Children = new Drawable[] { - iconSprite = new SpriteIcon - { - Size = new Vector2(icon_size), - Anchor = Anchor.Centre, - Origin = Anchor.Centre - }, - title = new OsuSpriteText + new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold), - Margin = new MarginPadding { Bottom = text_offset } + Font = OsuFont.GetFont(size: 24), + Text = "Multiplayer" }, - new Circle + dot = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(4), - Colour = Color4.Gray, + Font = OsuFont.GetFont(size: 24), + Text = "·" }, - pageText = new OsuSpriteText + pageTitle = new OsuSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 20), - Margin = new MarginPadding { Bottom = text_offset } + Font = OsuFont.GetFont(size: 24), + Text = "Lounge" } } }, @@ -143,9 +128,7 @@ namespace osu.Game.Screens.Multi [BackgroundDependencyLoader] private void load(OsuColour colours) { - title.Text = "multi"; - iconSprite.Icon = OsuIcon.Multi; - AccentColour = colours.Yellow; + pageTitle.Colour = dot.Colour = colours.Yellow; } } @@ -154,12 +137,28 @@ namespace osu.Game.Screens.Multi public HeaderBreadcrumbControl(ScreenStack stack) : base(stack) { + RelativeSizeAxes = Axes.X; + StripColour = Color4.Transparent; } protected override void LoadComplete() { base.LoadComplete(); - AccentColour = Color4.White; + AccentColour = Color4Extensions.FromHex("#e35c99"); + } + + protected override TabItem CreateTabItem(IScreen value) => new HeaderBreadcrumbTabItem(value) + { + AccentColour = AccentColour + }; + + private class HeaderBreadcrumbTabItem : BreadcrumbTabItem + { + public HeaderBreadcrumbTabItem(IScreen value) + : base(value) + { + Bar.Colour = Color4.Transparent; + } } } } From 20092c58ff6cac5194016236ff6a72cd8574fa92 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 21:59:36 +0900 Subject: [PATCH 623/871] Reduce spacing between recent participants tiles --- osu.Game/Screens/Multi/Components/ParticipantsList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Components/ParticipantsList.cs b/osu.Game/Screens/Multi/Components/ParticipantsList.cs index 79d130adf5..7978b4eaab 100644 --- a/osu.Game/Screens/Multi/Components/ParticipantsList.cs +++ b/osu.Game/Screens/Multi/Components/ParticipantsList.cs @@ -79,7 +79,7 @@ namespace osu.Game.Screens.Multi.Components Direction = Direction, AutoSizeAxes = AutoSizeAxes, RelativeSizeAxes = RelativeSizeAxes, - Spacing = new Vector2(10) + Spacing = Vector2.One }; for (int i = 0; i < RecentParticipants.Count; i++) From 668105dd6ee9857824066dd236106bd2b88cea02 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 22:06:28 +0900 Subject: [PATCH 624/871] Adjust boldening --- osu.Game/Screens/Multi/Components/OverlinedDisplay.cs | 4 ++-- osu.Game/Screens/Multi/Match/Components/Header.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs b/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs index 6aeb6c94df..d2bb3c4876 100644 --- a/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs +++ b/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs @@ -84,9 +84,9 @@ namespace osu.Game.Screens.Multi.Components new OsuSpriteText { Text = title, - Font = OsuFont.GetFont(size: 14) + Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold) }, - details = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) }, + details = new OsuSpriteText { Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold) }, } }, }, diff --git a/osu.Game/Screens/Multi/Match/Components/Header.cs b/osu.Game/Screens/Multi/Match/Components/Header.cs index ddbaab1706..134a0b3f2e 100644 --- a/osu.Game/Screens/Multi/Match/Components/Header.cs +++ b/osu.Game/Screens/Multi/Match/Components/Header.cs @@ -52,7 +52,7 @@ namespace osu.Game.Screens.Multi.Match.Components Font = OsuFont.GetFont(size: 30), Current = { BindTarget = RoomName } }, - hostText = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold)) + hostText = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 20)) { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, @@ -71,7 +71,7 @@ namespace osu.Game.Screens.Multi.Match.Components if (host.NewValue != null) { hostText.AddText("hosted by "); - hostText.AddUserLink(host.NewValue); + hostText.AddUserLink(host.NewValue, s => s.Font = s.Font.With(weight: FontWeight.SemiBold)); } }, true); } From b7f5a89f82b9216231ae079112922bbe41e78984 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 22:13:39 +0900 Subject: [PATCH 625/871] Reduce background fade opacity --- osu.Game/Screens/Multi/Multiplayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index e724152e08..3178e35581 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -117,7 +117,7 @@ namespace osu.Game.Screens.Multi Child = new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(backgroundColour.Opacity(0.7f), backgroundColour) + Colour = ColourInfo.GradientVertical(backgroundColour.Opacity(0.5f), backgroundColour) }, } } From 23f569351a11aaf945d53202438fb6dbb02009ed Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 22:22:19 +0900 Subject: [PATCH 626/871] Add back missing beatmap results button --- .../Screens/Multi/Match/MatchSubScreen.cs | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 8216f64872..1b2fdffa5e 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.GameTypes; @@ -118,10 +119,36 @@ namespace osu.Game.Screens.Multi.Match { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Right = 5 }, - Child = new OverlinedPlaylist(true) // Temporarily always allow selection + Child = new GridContainer { RelativeSizeAxes = Axes.Both, - SelectedItem = { BindTarget = SelectedItem } + Content = new[] + { + new Drawable[] + { + new OverlinedPlaylist(true) // Temporarily always allow selection + { + RelativeSizeAxes = Axes.Both, + SelectedItem = { BindTarget = SelectedItem } + } + }, + null, + new Drawable[] + { + new TriangleButton + { + RelativeSizeAxes = Axes.X, + Text = "Show beatmap results", + Action = showBeatmapResults + } + } + }, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 5), + new Dimension(GridSizeMode.AutoSize) + } } }, new Container From 44a8039e924b9a614bea16a2dbcaf8a52dfe03b1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 22:22:24 +0900 Subject: [PATCH 627/871] Reduce header further --- osu.Game/Screens/Multi/Header.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index 2cdd082068..f5f429a37d 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Multi { public class Header : Container { - public const float HEIGHT = 100; + public const float HEIGHT = 80; public Header(ScreenStack stack) { @@ -49,7 +49,7 @@ namespace osu.Game.Screens.Multi title = new MultiHeaderTitle { Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + Origin = Anchor.BottomLeft, }, breadcrumbs = new HeaderBreadcrumbControl(stack) { From 8d47c908ad2909211383b2e0e5d39110c13a24b4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jun 2020 22:28:31 +0900 Subject: [PATCH 628/871] Remove breadcrumb fade --- osu.Game/Screens/Multi/Header.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index f5f429a37d..e27fa154af 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using Humanizer; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -64,11 +63,6 @@ namespace osu.Game.Screens.Multi { if (screen.NewValue is IMultiplayerSubScreen multiScreen) title.Screen = multiScreen; - - if (breadcrumbs.Items.Any() && screen.NewValue == breadcrumbs.Items.First()) - breadcrumbs.FadeOut(500, Easing.OutQuint); - else - breadcrumbs.FadeIn(500, Easing.OutQuint); }; breadcrumbs.Current.TriggerChange(); From 65a2fc3bfc052a150d67d0b14a19fece40cf47cb Mon Sep 17 00:00:00 2001 From: Ronnie Moir <7267697+H2n9@users.noreply.github.com> Date: Thu, 25 Jun 2020 17:53:14 +0100 Subject: [PATCH 629/871] Revert access modifier --- 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 71a97da5c2..83991ad027 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -95,7 +95,7 @@ namespace osu.Game.Screens.Play public bool LoadedBeatmapSuccessfully => DrawableRuleset?.Objects.Any() == true; - public GameplayClockContainer GameplayClockContainer { get; private set; } + protected GameplayClockContainer GameplayClockContainer { get; private set; } public DimmableStoryboard DimmableStoryboard { get; private set; } From e3d654d33f2f5d1daeed1f72e263e1e943104ed3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Jun 2020 20:14:02 +0900 Subject: [PATCH 630/871] Cleanup --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index ad19c9661f..c58b34f9f2 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -513,8 +513,6 @@ namespace osu.Game.Screens.Select } } - private const double repeat_interval = 120; - private ScheduledDelegate repeatDelegate; private object lastRepeatSource; From 1b4c31a84f3e2e4654ea065f0dee3095bcae47c0 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Jun 2020 20:14:08 +0900 Subject: [PATCH 631/871] Remove double schedule --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index c58b34f9f2..5fbe917943 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -526,7 +526,7 @@ namespace osu.Game.Screens.Select endRepeatSelection(); lastRepeatSource = source; - Scheduler.Add(repeatDelegate = this.BeginKeyRepeat(Scheduler, action)); + repeatDelegate = this.BeginKeyRepeat(Scheduler, action); } private void endRepeatSelection(object source = null) From 8f6d52550f6ddfb46a7f9f8384fa1bd7fbc5c34b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Jun 2020 20:32:13 +0900 Subject: [PATCH 632/871] Fix potential exception if button is pressed before selection --- osu.Game/Screens/Select/BeatmapCarousel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5fbe917943..71ccd6fada 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -279,6 +279,9 @@ namespace osu.Game.Screens.Select /// Whether to skip individual difficulties and only increment over full groups. public void SelectNext(int direction = 1, bool skipDifficulties = true) { + if (selectedBeatmap == null) + return; + if (beatmapSets.All(s => s.Filtered.Value)) return; From 099416b4c3d981d7e844ca4b57833597f8e7715a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Jun 2020 21:03:34 +0900 Subject: [PATCH 633/871] Move check inside next difficulty selection --- osu.Game/Screens/Select/BeatmapCarousel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 71ccd6fada..6f913a3177 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -279,9 +279,6 @@ namespace osu.Game.Screens.Select /// Whether to skip individual difficulties and only increment over full groups. public void SelectNext(int direction = 1, bool skipDifficulties = true) { - if (selectedBeatmap == null) - return; - if (beatmapSets.All(s => s.Filtered.Value)) return; @@ -305,6 +302,9 @@ namespace osu.Game.Screens.Select private void selectNextDifficulty(int direction) { + if (selectedBeatmap == null) + return; + var unfilteredDifficulties = selectedBeatmapSet.Children.Where(s => !s.Filtered.Value).ToList(); int index = unfilteredDifficulties.IndexOf(selectedBeatmap); From 97a212a7f6a35a17a098e9ae11ff2a9b27833666 Mon Sep 17 00:00:00 2001 From: Power Maker Date: Fri, 26 Jun 2020 14:32:01 +0200 Subject: [PATCH 634/871] Hide red tint based on "Show health display even when you can't fail" setting --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 25 ++++++++++++++++++++++- osu.Game/Screens/Play/HUDOverlay.cs | 5 +++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index a49aa89a7c..6fda5a1214 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -31,6 +31,7 @@ namespace osu.Game.Screens.Play.HUD /// public double LowHealthThreshold = 0.20f; + public readonly Bindable HUDEnabled = new Bindable(); private readonly Bindable enabled = new Bindable(); private readonly Container boxes; @@ -74,7 +75,7 @@ namespace osu.Game.Screens.Play.HUD boxes.Colour = color.Red; configEnabled = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); - enabled.BindValueChanged(e => this.FadeTo(e.NewValue ? 1 : 0, fade_time, Easing.OutQuint), true); + enabled.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue ? true : false), true); } protected override void LoadComplete() @@ -105,6 +106,28 @@ namespace osu.Game.Screens.Play.HUD enabled.Value = false; } + /// + /// Tries to fade based on "Fade playfield when health is low" setting + /// + /// Duration of the fade + /// Type of easing + /// True when you want to fade in, false when you want to fade out + public void TryToFade(float fadeDuration, Easing easing, bool fadeIn) + { + if (HUDEnabled.Value) + { + if (fadeIn) + { + if (enabled.Value) + this.FadeIn(fadeDuration, easing); + } + else + this.FadeOut(fadeDuration, easing); + } + else + this.FadeOut(fadeDuration, easing); + } + protected override void Update() { double target = Math.Clamp(max_alpha * (1 - Current.Value / LowHealthThreshold), 0, max_alpha); diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 5114efd9a9..73b93582ef 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.Diagnostics.Runtime.Interop; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; @@ -153,6 +154,8 @@ namespace osu.Game.Screens.Play // start all elements hidden hideTargets.ForEach(d => d.Hide()); + + FailingLayer.HUDEnabled.BindTo(ShowHealthbar); } public override void Hide() => throw new InvalidOperationException($"{nameof(HUDOverlay)} should not be hidden as it will remove the ability of a user to quit. Use {nameof(ShowHud)} instead."); @@ -168,11 +171,13 @@ namespace osu.Game.Screens.Play if (healthBar.NewValue) { HealthDisplay.FadeIn(fade_duration, fade_easing); + FailingLayer.TryToFade(fade_duration, fade_easing, true); topScoreContainer.MoveToY(30, fade_duration, fade_easing); } else { HealthDisplay.FadeOut(fade_duration, fade_easing); + FailingLayer.TryToFade(fade_duration, fade_easing, false); topScoreContainer.MoveToY(0, fade_duration, fade_easing); } }, true); From efeaa1cc10ddd6d0b80ebd16651908a4be7c818a Mon Sep 17 00:00:00 2001 From: Power Maker Date: Fri, 26 Jun 2020 14:58:42 +0200 Subject: [PATCH 635/871] Make some changes, fix and add tests --- .../Visual/Gameplay/TestSceneFailingLayer.cs | 27 +++++++++++++++++++ osu.Game/Screens/Play/HUD/FailingLayer.cs | 3 ++- osu.Game/Screens/Play/HUDOverlay.cs | 2 -- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index a95e806862..83d9e888f1 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Testing; using osu.Game.Configuration; using osu.Game.Rulesets.Scoring; @@ -14,6 +15,8 @@ namespace osu.Game.Tests.Visual.Gameplay { private FailingLayer layer; + private Bindable enabledHUD = new Bindable(); + [Resolved] private OsuConfigManager config { get; set; } @@ -24,8 +27,10 @@ namespace osu.Game.Tests.Visual.Gameplay { Child = layer = new FailingLayer(); layer.BindHealthProcessor(new DrainingHealthProcessor(1)); + layer.HUDEnabled.BindTo(enabledHUD); }); + AddStep("enable HUDOverlay", () => enabledHUD.Value = true); AddStep("enable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); AddUntilStep("layer is visible", () => layer.IsPresent); } @@ -69,5 +74,27 @@ namespace osu.Game.Tests.Visual.Gameplay AddWaitStep("wait for potential fade", 10); AddAssert("layer is still visible", () => layer.IsPresent); } + + [Test] + public void TestLayerVisibilityWithDifferentOptions() + { + AddStep("set health to 0.10", () => layer.Current.Value = 0.1); + + AddStep("disable HUDOverlay", () => enabledHUD.Value = false); + AddStep("disable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false)); + AddUntilStep("layer fade is invisible", () => !layer.IsPresent); + + AddStep("disable HUDOverlay", () => enabledHUD.Value = false); + AddStep("enable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); + AddUntilStep("layer fade is invisible", () => !layer.IsPresent); + + AddStep("enable HUDOverlay", () => enabledHUD.Value = true); + AddStep("disable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false)); + AddUntilStep("layer fade is invisible", () => !layer.IsPresent); + + AddStep("enable HUDOverlay", () => enabledHUD.Value = true); + AddStep("enable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); + AddUntilStep("layer fade is visible", () => layer.IsPresent); + } } } diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index 6fda5a1214..d982764c30 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -75,7 +75,8 @@ namespace osu.Game.Screens.Play.HUD boxes.Colour = color.Red; configEnabled = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); - enabled.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue ? true : false), true); + enabled.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue), true); + HUDEnabled.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue), true); } protected override void LoadComplete() diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 73b93582ef..d4c548dce7 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -171,13 +171,11 @@ namespace osu.Game.Screens.Play if (healthBar.NewValue) { HealthDisplay.FadeIn(fade_duration, fade_easing); - FailingLayer.TryToFade(fade_duration, fade_easing, true); topScoreContainer.MoveToY(30, fade_duration, fade_easing); } else { HealthDisplay.FadeOut(fade_duration, fade_easing); - FailingLayer.TryToFade(fade_duration, fade_easing, false); topScoreContainer.MoveToY(0, fade_duration, fade_easing); } }, true); From 798e8e7a8deea5d1ac665bc9491604c0f082e5ed Mon Sep 17 00:00:00 2001 From: Power Maker Date: Fri, 26 Jun 2020 15:12:01 +0200 Subject: [PATCH 636/871] Fix CI fail --- osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs | 2 +- osu.Game/Screens/Play/HUDOverlay.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index 83d9e888f1..3eda47627b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual.Gameplay { private FailingLayer layer; - private Bindable enabledHUD = new Bindable(); + private readonly Bindable enabledHUD = new Bindable(); [Resolved] private OsuConfigManager config { get; set; } diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index d4c548dce7..b55a93db1f 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using Microsoft.Diagnostics.Runtime.Interop; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; From bd1f38cc3ef41c0ca8bbd586c81b1cbf8b6a6d9f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Jun 2020 23:21:44 +0900 Subject: [PATCH 637/871] Fix crash due to unsafe mod deserialisation --- .../Online/TestAPIModSerialization.cs | 59 ++++++++++++++++++- osu.Game/Rulesets/Mods/ModTimeRamp.cs | 4 +- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Online/TestAPIModSerialization.cs b/osu.Game.Tests/Online/TestAPIModSerialization.cs index d9318aa822..5948582d77 100644 --- a/osu.Game.Tests/Online/TestAPIModSerialization.cs +++ b/osu.Game.Tests/Online/TestAPIModSerialization.cs @@ -49,9 +49,32 @@ namespace osu.Game.Tests.Online Assert.That(converted.TestSetting.Value, Is.EqualTo(2)); } + [Test] + public void TestDeserialiseTimeRampMod() + { + // Create the mod with values different from default. + var apiMod = new APIMod(new TestModTimeRamp + { + AdjustPitch = { Value = false }, + InitialRate = { Value = 1.25 }, + FinalRate = { Value = 0.25 } + }); + + var deserialised = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(apiMod)); + var converted = (TestModTimeRamp)deserialised.ToMod(new TestRuleset()); + + Assert.That(converted.AdjustPitch.Value, Is.EqualTo(false)); + Assert.That(converted.InitialRate.Value, Is.EqualTo(1.25)); + Assert.That(converted.FinalRate.Value, Is.EqualTo(0.25)); + } + private class TestRuleset : Ruleset { - public override IEnumerable GetModsFor(ModType type) => new[] { new TestMod() }; + public override IEnumerable GetModsFor(ModType type) => new Mod[] + { + new TestMod(), + new TestModTimeRamp(), + }; public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => throw new System.NotImplementedException(); @@ -78,5 +101,39 @@ namespace osu.Game.Tests.Online Precision = 0.01, }; } + + private class TestModTimeRamp : ModTimeRamp + { + public override string Name => "Test Mod"; + public override string Acronym => "TMTR"; + public override double ScoreMultiplier => 1; + + [SettingSource("Initial rate", "The starting speed of the track")] + public override BindableNumber InitialRate { get; } = new BindableDouble + { + MinValue = 1, + MaxValue = 2, + Default = 1.5, + Value = 1.5, + Precision = 0.01, + }; + + [SettingSource("Final rate", "The speed increase to ramp towards")] + public override BindableNumber FinalRate { get; } = new BindableDouble + { + MinValue = 0, + MaxValue = 1, + Default = 0.5, + Value = 0.5, + Precision = 0.01, + }; + + [SettingSource("Adjust pitch", "Should pitch be adjusted with speed")] + public override BindableBool AdjustPitch { get; } = new BindableBool + { + Default = true, + Value = true + }; + } } } diff --git a/osu.Game/Rulesets/Mods/ModTimeRamp.cs b/osu.Game/Rulesets/Mods/ModTimeRamp.cs index cbd07efa97..839d97f04e 100644 --- a/osu.Game/Rulesets/Mods/ModTimeRamp.cs +++ b/osu.Game/Rulesets/Mods/ModTimeRamp.cs @@ -89,9 +89,9 @@ namespace osu.Game.Rulesets.Mods private void applyPitchAdjustment(ValueChangedEvent adjustPitchSetting) { // remove existing old adjustment - track.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange); + track?.RemoveAdjustment(adjustmentForPitchSetting(adjustPitchSetting.OldValue), SpeedChange); - track.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange); + track?.AddAdjustment(adjustmentForPitchSetting(adjustPitchSetting.NewValue), SpeedChange); } private AdjustableProperty adjustmentForPitchSetting(bool adjustPitchSettingValue) From c233dc476800e7df39ee242a7515cc6bc8beb5e9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 27 Jun 2020 00:16:16 +0900 Subject: [PATCH 638/871] Add some global error handling --- osu.Game/Screens/Multi/RoomManager.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index 4d6ac46c84..b8c969a845 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -166,8 +166,16 @@ namespace osu.Game.Screens.Multi var r = listing[i]; r.Position.Value = i; - update(r, r); - addRoom(r); + try + { + update(r, r); + addRoom(r); + } + catch (Exception ex) + { + Logger.Error(ex, $"Failed to update room: {r.Name.Value}."); + rooms.Remove(r); + } } RoomsUpdated?.Invoke(); From e8d36bc3cbb5c6aa2c0f0fd6dde7d18d34799590 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 27 Jun 2020 00:19:22 +0900 Subject: [PATCH 639/871] Don't trigger the same exception multiple times --- osu.Game/Screens/Multi/RoomManager.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index b8c969a845..5083fb2ee3 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using osu.Framework.Allocation; @@ -142,6 +143,8 @@ namespace osu.Game.Screens.Multi joinedRoom = null; } + private readonly List roomsFailedUpdate = new List(); + /// /// Invoked when the listing of all s is received from the server. /// @@ -173,7 +176,14 @@ namespace osu.Game.Screens.Multi } catch (Exception ex) { - Logger.Error(ex, $"Failed to update room: {r.Name.Value}."); + Debug.Assert(r.RoomID.Value != null); + + if (!roomsFailedUpdate.Contains(r.RoomID.Value.Value)) + { + Logger.Error(ex, $"Failed to update room: {r.Name.Value}."); + roomsFailedUpdate.Add(r.RoomID.Value.Value); + } + rooms.Remove(r); } } From 3783fe8d6a2797925e4ad77525c676226fcf9bcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jun 2020 19:03:41 +0200 Subject: [PATCH 640/871] Rename fields for clarity --- .../Visual/Gameplay/TestSceneFailingLayer.cs | 14 +++++------ osu.Game/Screens/Play/HUD/FailingLayer.cs | 23 ++++++++++--------- osu.Game/Screens/Play/HUDOverlay.cs | 2 +- 3 files changed, 20 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs index 3eda47627b..1c55595c97 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneFailingLayer.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual.Gameplay { private FailingLayer layer; - private readonly Bindable enabledHUD = new Bindable(); + private readonly Bindable showHealth = new Bindable(); [Resolved] private OsuConfigManager config { get; set; } @@ -27,10 +27,10 @@ namespace osu.Game.Tests.Visual.Gameplay { Child = layer = new FailingLayer(); layer.BindHealthProcessor(new DrainingHealthProcessor(1)); - layer.HUDEnabled.BindTo(enabledHUD); + layer.ShowHealth.BindTo(showHealth); }); - AddStep("enable HUDOverlay", () => enabledHUD.Value = true); + AddStep("show health", () => showHealth.Value = true); AddStep("enable layer", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); AddUntilStep("layer is visible", () => layer.IsPresent); } @@ -80,19 +80,19 @@ namespace osu.Game.Tests.Visual.Gameplay { AddStep("set health to 0.10", () => layer.Current.Value = 0.1); - AddStep("disable HUDOverlay", () => enabledHUD.Value = false); + AddStep("don't show health", () => showHealth.Value = false); AddStep("disable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false)); AddUntilStep("layer fade is invisible", () => !layer.IsPresent); - AddStep("disable HUDOverlay", () => enabledHUD.Value = false); + AddStep("don't show health", () => showHealth.Value = false); AddStep("enable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); AddUntilStep("layer fade is invisible", () => !layer.IsPresent); - AddStep("enable HUDOverlay", () => enabledHUD.Value = true); + AddStep("show health", () => showHealth.Value = true); AddStep("disable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, false)); AddUntilStep("layer fade is invisible", () => !layer.IsPresent); - AddStep("enable HUDOverlay", () => enabledHUD.Value = true); + AddStep("show health", () => showHealth.Value = true); AddStep("enable FadePlayfieldWhenHealthLow", () => config.Set(OsuSetting.FadePlayfieldWhenHealthLow, true)); AddUntilStep("layer fade is visible", () => layer.IsPresent); } diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index d982764c30..e8c99c2ed8 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -31,11 +31,12 @@ namespace osu.Game.Screens.Play.HUD /// public double LowHealthThreshold = 0.20f; - public readonly Bindable HUDEnabled = new Bindable(); - private readonly Bindable enabled = new Bindable(); + public readonly Bindable ShowHealth = new Bindable(); + + private readonly Bindable fadePlayfieldWhenHealthLow = new Bindable(); private readonly Container boxes; - private Bindable configEnabled; + private Bindable fadePlayfieldWhenHealthLowSetting; private HealthProcessor healthProcessor; public FailingLayer() @@ -74,9 +75,9 @@ namespace osu.Game.Screens.Play.HUD { boxes.Colour = color.Red; - configEnabled = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); - enabled.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue), true); - HUDEnabled.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue), true); + fadePlayfieldWhenHealthLowSetting = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); + fadePlayfieldWhenHealthLow.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue), true); + ShowHealth.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue), true); } protected override void LoadComplete() @@ -98,13 +99,13 @@ namespace osu.Game.Screens.Play.HUD if (LoadState < LoadState.Ready) return; - enabled.UnbindBindings(); + fadePlayfieldWhenHealthLow.UnbindBindings(); // Don't display ever if the ruleset is not using a draining health display. if (healthProcessor is DrainingHealthProcessor) - enabled.BindTo(configEnabled); + fadePlayfieldWhenHealthLow.BindTo(fadePlayfieldWhenHealthLowSetting); else - enabled.Value = false; + fadePlayfieldWhenHealthLow.Value = false; } /// @@ -115,11 +116,11 @@ namespace osu.Game.Screens.Play.HUD /// True when you want to fade in, false when you want to fade out public void TryToFade(float fadeDuration, Easing easing, bool fadeIn) { - if (HUDEnabled.Value) + if (ShowHealth.Value) { if (fadeIn) { - if (enabled.Value) + if (fadePlayfieldWhenHealthLow.Value) this.FadeIn(fadeDuration, easing); } else diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index b55a93db1f..96e9625f76 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -154,7 +154,7 @@ namespace osu.Game.Screens.Play // start all elements hidden hideTargets.ForEach(d => d.Hide()); - FailingLayer.HUDEnabled.BindTo(ShowHealthbar); + FailingLayer.ShowHealth.BindTo(ShowHealthbar); } public override void Hide() => throw new InvalidOperationException($"{nameof(HUDOverlay)} should not be hidden as it will remove the ability of a user to quit. Use {nameof(ShowHud)} instead."); From 415e1c05ff7c83f9a2ef0f7981a80ecf24f60d9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jun 2020 19:06:41 +0200 Subject: [PATCH 641/871] Simplify implementation --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 26 +++++------------------ 1 file changed, 5 insertions(+), 21 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index e8c99c2ed8..22b7950d31 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -76,8 +76,8 @@ namespace osu.Game.Screens.Play.HUD boxes.Colour = color.Red; fadePlayfieldWhenHealthLowSetting = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); - fadePlayfieldWhenHealthLow.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue), true); - ShowHealth.BindValueChanged(e => TryToFade(fade_time, Easing.OutQuint, e.NewValue), true); + fadePlayfieldWhenHealthLow.BindValueChanged(_ => updateState(), true); + ShowHealth.BindValueChanged(_ => updateState(), true); } protected override void LoadComplete() @@ -108,26 +108,10 @@ namespace osu.Game.Screens.Play.HUD fadePlayfieldWhenHealthLow.Value = false; } - /// - /// Tries to fade based on "Fade playfield when health is low" setting - /// - /// Duration of the fade - /// Type of easing - /// True when you want to fade in, false when you want to fade out - public void TryToFade(float fadeDuration, Easing easing, bool fadeIn) + private void updateState() { - if (ShowHealth.Value) - { - if (fadeIn) - { - if (fadePlayfieldWhenHealthLow.Value) - this.FadeIn(fadeDuration, easing); - } - else - this.FadeOut(fadeDuration, easing); - } - else - this.FadeOut(fadeDuration, easing); + var showLayer = fadePlayfieldWhenHealthLow.Value && ShowHealth.Value; + this.FadeTo(showLayer ? 1 : 0, fade_time, Easing.OutQuint); } protected override void Update() From a63b6a3ddf571bb941b858347fe903a4b82a1c5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jun 2020 19:22:30 +0200 Subject: [PATCH 642/871] Simplify binding --- osu.Game/Screens/Play/HUDOverlay.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUDOverlay.cs b/osu.Game/Screens/Play/HUDOverlay.cs index 96e9625f76..f09745cf71 100644 --- a/osu.Game/Screens/Play/HUDOverlay.cs +++ b/osu.Game/Screens/Play/HUDOverlay.cs @@ -153,8 +153,6 @@ namespace osu.Game.Screens.Play // start all elements hidden hideTargets.ForEach(d => d.Hide()); - - FailingLayer.ShowHealth.BindTo(ShowHealthbar); } public override void Hide() => throw new InvalidOperationException($"{nameof(HUDOverlay)} should not be hidden as it will remove the ability of a user to quit. Use {nameof(ShowHud)} instead."); @@ -264,7 +262,10 @@ namespace osu.Game.Screens.Play Margin = new MarginPadding { Top = 20 } }; - protected virtual FailingLayer CreateFailingLayer() => new FailingLayer(); + protected virtual FailingLayer CreateFailingLayer() => new FailingLayer + { + ShowHealth = { BindTarget = ShowHealthbar } + }; protected virtual KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay { From 02f590309d9b67c40e582a1c4f4302ee216204f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jun 2020 19:22:45 +0200 Subject: [PATCH 643/871] Add xmldoc for public property --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index 22b7950d31..d4faa4bbb7 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -31,6 +31,9 @@ namespace osu.Game.Screens.Play.HUD /// public double LowHealthThreshold = 0.20f; + /// + /// Whether the current player health should be shown on screen. + /// public readonly Bindable ShowHealth = new Bindable(); private readonly Bindable fadePlayfieldWhenHealthLow = new Bindable(); From 3637bf2f9bc4929cd58cffb4aeb8830e1ceee690 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 26 Jun 2020 19:23:42 +0200 Subject: [PATCH 644/871] Clean up member order & access modifiers --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index d4faa4bbb7..b96cfd170e 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -18,10 +18,15 @@ using osuTK.Graphics; namespace osu.Game.Screens.Play.HUD { /// - /// An overlay layer on top of the playfield which fades to red when the current player health falls below a certain threshold defined by . + /// An overlay layer on top of the playfield which fades to red when the current player health falls below a certain threshold defined by . /// public class FailingLayer : HealthDisplay { + /// + /// Whether the current player health should be shown on screen. + /// + public readonly Bindable ShowHealth = new Bindable(); + private const float max_alpha = 0.4f; private const int fade_time = 400; private const float gradient_size = 0.3f; @@ -29,12 +34,7 @@ namespace osu.Game.Screens.Play.HUD /// /// The threshold under which the current player life should be considered low and the layer should start fading in. /// - public double LowHealthThreshold = 0.20f; - - /// - /// Whether the current player health should be shown on screen. - /// - public readonly Bindable ShowHealth = new Bindable(); + private const double low_health_threshold = 0.20f; private readonly Bindable fadePlayfieldWhenHealthLow = new Bindable(); private readonly Container boxes; @@ -119,7 +119,7 @@ namespace osu.Game.Screens.Play.HUD protected override void Update() { - double target = Math.Clamp(max_alpha * (1 - Current.Value / LowHealthThreshold), 0, max_alpha); + double target = Math.Clamp(max_alpha * (1 - Current.Value / low_health_threshold), 0, max_alpha); boxes.Alpha = (float)Interpolation.Lerp(boxes.Alpha, target, Clock.ElapsedFrameTime * 0.01f); From c47f762f24c007fe504144694d93945565fbeafc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Jun 2020 15:59:26 +0200 Subject: [PATCH 645/871] Update test scene to allow checking samples --- .../ManiaBeatmapSampleConversionTest.cs | 20 +++++++++++++------ .../convert-samples-expected-conversion.json | 9 ++++++--- .../Testing/Beatmaps/convert-samples.osu | 2 +- .../mania-samples-expected-conversion.json | 6 ++++-- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs index d8f87195d1..dd1b2e1745 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs @@ -29,13 +29,16 @@ namespace osu.Game.Rulesets.Mania.Tests StartTime = hitObject.StartTime, EndTime = hitObject.GetEndTime(), Column = ((ManiaHitObject)hitObject).Column, - NodeSamples = getSampleNames((hitObject as HoldNote)?.NodeSamples) + Samples = getSampleNames(hitObject.Samples), + NodeSamples = getNodeSampleNames((hitObject as HoldNote)?.NodeSamples) }; } - private IList> getSampleNames(List> hitSampleInfo) - => hitSampleInfo?.Select(samples => - (IList)samples.Select(sample => sample.LookupNames.First()).ToList()) + private IList getSampleNames(IList hitSampleInfo) + => hitSampleInfo.Select(sample => sample.LookupNames.First()).ToList(); + + private IList> getNodeSampleNames(List> hitSampleInfo) + => hitSampleInfo?.Select(getSampleNames) .ToList(); protected override Ruleset CreateRuleset() => new ManiaRuleset(); @@ -51,14 +54,19 @@ namespace osu.Game.Rulesets.Mania.Tests public double StartTime; public double EndTime; public int Column; + public IList Samples; public IList> NodeSamples; public bool Equals(SampleConvertValue other) => Precision.AlmostEquals(StartTime, other.StartTime, conversion_lenience) && Precision.AlmostEquals(EndTime, other.EndTime, conversion_lenience) - && samplesEqual(NodeSamples, other.NodeSamples); + && samplesEqual(Samples, other.Samples) + && nodeSamplesEqual(NodeSamples, other.NodeSamples); - private static bool samplesEqual(ICollection> firstSampleList, ICollection> secondSampleList) + private static bool samplesEqual(ICollection firstSampleList, ICollection secondSampleList) + => firstSampleList.SequenceEqual(secondSampleList); + + private static bool nodeSamplesEqual(ICollection> firstSampleList, ICollection> secondSampleList) { if (firstSampleList == null && secondSampleList == null) return true; diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json index b8ce85eef5..fec1360b26 100644 --- a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples-expected-conversion.json @@ -9,7 +9,8 @@ ["normal-hitnormal"], ["soft-hitnormal"], ["drum-hitnormal"] - ] + ], + "Samples": ["drum-hitnormal"] }, { "StartTime": 1875.0, "EndTime": 2750.0, @@ -17,14 +18,16 @@ "NodeSamples": [ ["soft-hitnormal"], ["drum-hitnormal"] - ] + ], + "Samples": ["drum-hitnormal"] }] }, { "StartTime": 3750.0, "Objects": [{ "StartTime": 3750.0, "EndTime": 3750.0, - "Column": 3 + "Column": 3, + "Samples": ["normal-hitnormal"] }] }] } \ No newline at end of file diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu index 16b73992d2..fea1de6614 100644 --- a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/convert-samples.osu @@ -13,4 +13,4 @@ SliderTickRate:1 [HitObjects] 88,99,1000,6,0,L|306:259,2,245,0|0|0,1:0|2:0|3:0,0:0:0:0: -259,118,3750,1,0,0:0:0:0: +259,118,3750,1,0,1:0:0:0: diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json index e22540614d..1aca75a796 100644 --- a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/mania-samples-expected-conversion.json @@ -8,7 +8,8 @@ "NodeSamples": [ ["normal-hitnormal"], [] - ] + ], + "Samples": ["normal-hitnormal"] }] }, { "StartTime": 2000.0, @@ -19,7 +20,8 @@ "NodeSamples": [ ["drum-hitnormal"], [] - ] + ], + "Samples": ["drum-hitnormal"] }] }] } \ No newline at end of file From 5e92809401122afcd4504ebf99ad17e234c6dbd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Jun 2020 16:46:43 +0200 Subject: [PATCH 646/871] Add failing test case --- .../ManiaBeatmapSampleConversionTest.cs | 1 + ...r-convert-samples-expected-conversion.json | 21 +++++++++++++++++++ .../Beatmaps/slider-convert-samples.osu | 15 +++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json create mode 100644 osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples.osu diff --git a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs index dd1b2e1745..c8feb4ae24 100644 --- a/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs +++ b/osu.Game.Rulesets.Mania.Tests/ManiaBeatmapSampleConversionTest.cs @@ -20,6 +20,7 @@ namespace osu.Game.Rulesets.Mania.Tests [TestCase("convert-samples")] [TestCase("mania-samples")] + [TestCase("slider-convert-samples")] public void Test(string name) => base.Test(name); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json new file mode 100644 index 0000000000..e3768a90d7 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples-expected-conversion.json @@ -0,0 +1,21 @@ +{ + "Mappings": [{ + "StartTime": 8470.0, + "Objects": [{ + "StartTime": 8470.0, + "EndTime": 8470.0, + "Column": 0, + "Samples": ["normal-hitnormal", "normal-hitclap"] + }, { + "StartTime": 8626.470587768974, + "EndTime": 8626.470587768974, + "Column": 1, + "Samples": ["normal-hitnormal"] + }, { + "StartTime": 8782.941175537948, + "EndTime": 8782.941175537948, + "Column": 2, + "Samples": ["normal-hitnormal", "normal-hitclap"] + }] + }] +} diff --git a/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples.osu b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples.osu new file mode 100644 index 0000000000..08e90ce807 --- /dev/null +++ b/osu.Game.Rulesets.Mania/Resources/Testing/Beatmaps/slider-convert-samples.osu @@ -0,0 +1,15 @@ +osu file format v14 + +[Difficulty] +HPDrainRate:6 +CircleSize:4 +OverallDifficulty:8 +ApproachRate:9.5 +SliderMultiplier:2.00000000596047 +SliderTickRate:1 + +[TimingPoints] +0,312.941176470588,4,1,0,100,1,0 + +[HitObjects] +82,216,8470,6,0,P|52:161|99:113,2,100,8|0|8,1:0|1:0|1:0,0:0:0:0: From 1551c42c122119172a67c9a0900ef8d8376284fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Jun 2020 16:49:14 +0200 Subject: [PATCH 647/871] Avoid division when slicing node sample list --- .../Patterns/Legacy/DistanceObjectPatternGenerator.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index 9fbdf58e21..a09ef6d5b6 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -483,9 +483,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (!(HitObject is IHasPathWithRepeats curveData)) return null; - double segmentTime = (EndTime - HitObject.StartTime) / spanCount; - - int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime); + // mathematically speaking this could be done by calculating (time - HitObject.StartTime) / SegmentDuration + // however, floating-point operations can introduce inaccuracies - therefore resort to iterated addition + // (all callers use this method to calculate repeat point times, so this way is consistent and deterministic) + int index = 0; + for (double nodeTime = HitObject.StartTime; nodeTime < time; nodeTime += SegmentDuration) + index += 1; // avoid slicing the list & creating copies, if at all possible. return index == 0 ? curveData.NodeSamples : curveData.NodeSamples.Skip(index).ToList(); From 082c94f98dfd7b00515846a06045e7b3949205b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 28 Jun 2020 13:14:46 +0200 Subject: [PATCH 648/871] Temporarily disable masking of tournament song bar --- osu.Game.Tournament/Components/SongBar.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tournament/Components/SongBar.cs b/osu.Game.Tournament/Components/SongBar.cs index fc7fcef892..cafec0a88b 100644 --- a/osu.Game.Tournament/Components/SongBar.cs +++ b/osu.Game.Tournament/Components/SongBar.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Game.Beatmaps; @@ -66,6 +67,9 @@ namespace osu.Game.Tournament.Components } } + // Todo: This is a hack for https://github.com/ppy/osu-framework/issues/3617 since this container is at the very edge of the screen and potentially initially masked away. + protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false; + [BackgroundDependencyLoader] private void load() { @@ -77,8 +81,6 @@ namespace osu.Game.Tournament.Components flow = new FillFlowContainer { RelativeSizeAxes = Axes.X, - // Todo: This is a hack for https://github.com/ppy/osu-framework/issues/3617 since this container is at the very edge of the screen and potentially initially masked away. - Height = 1, AutoSizeAxes = Axes.Y, LayoutDuration = 500, LayoutEasing = Easing.OutQuint, From 006adf0fb50a903c67c7317b3c721ff653615506 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 28 Jun 2020 22:45:13 +0900 Subject: [PATCH 649/871] Change logic to ignore rooms completely after first error --- osu.Game/Screens/Multi/RoomManager.cs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index 5083fb2ee3..642378d8d5 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -143,7 +143,7 @@ namespace osu.Game.Screens.Multi joinedRoom = null; } - private readonly List roomsFailedUpdate = new List(); + private readonly HashSet ignoredRooms = new HashSet(); /// /// Invoked when the listing of all s is received from the server. @@ -166,25 +166,26 @@ namespace osu.Game.Screens.Multi continue; } - var r = listing[i]; - r.Position.Value = i; + var room = listing[i]; + + Debug.Assert(room.RoomID.Value != null); + + if (ignoredRooms.Contains(room.RoomID.Value.Value)) + continue; + + room.Position.Value = i; try { - update(r, r); - addRoom(r); + update(room, room); + addRoom(room); } catch (Exception ex) { - Debug.Assert(r.RoomID.Value != null); + Logger.Error(ex, $"Failed to update room: {room.Name.Value}."); - if (!roomsFailedUpdate.Contains(r.RoomID.Value.Value)) - { - Logger.Error(ex, $"Failed to update room: {r.Name.Value}."); - roomsFailedUpdate.Add(r.RoomID.Value.Value); - } - - rooms.Remove(r); + ignoredRooms.Add(room.RoomID.Value.Value); + rooms.Remove(room); } } From 820056cc4e2e1497a53a3d9af7db3594940e5fcd Mon Sep 17 00:00:00 2001 From: jorolf Date: Sun, 28 Jun 2020 17:53:53 +0200 Subject: [PATCH 650/871] update colours/transformations --- osu.Game/Screens/Menu/IntroTriangles.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 2074fc7081..8118491c36 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -218,11 +217,14 @@ namespace osu.Game.Screens.Menu // matching flyte curve y = 0.25x^2 + (max(0, x - 0.7) / 0.3) ^ 5 lazerLogo.FadeIn().ScaleTo(scale_start).Then().Delay(logo_scale_duration * 0.7f).ScaleTo(scale_start - scale_adjust, logo_scale_duration * 0.3f, Easing.InQuint); - lazerLogo.TransformTo(nameof(LazerLogo.OutlineHighlight), 0.6f, logo_scale_duration * 0.4f, Easing.OutCirc).Then() - .TransformTo(nameof(LazerLogo.OutlineHighlight), 1f, logo_scale_duration * 0.4f); + const double highlight_duration = logo_scale_duration / 1.4; - lazerLogo.TransformTo(nameof(LazerLogo.Outline), 0.4f, logo_scale_duration * 0.5f, Easing.OutQuart).Then() - .TransformTo(nameof(LazerLogo.Outline), 1f, logo_scale_duration * 0.4f); + //Since we only have one texture, roughly align it by changing the timing + lazerLogo.Outline = -0.4f; + lazerLogo.TransformTo(nameof(LazerLogo.Outline), 1f, highlight_duration * 1.4); + + lazerLogo.OutlineHighlight = 0f; + lazerLogo.TransformTo(nameof(LazerLogo.OutlineHighlight), 1f, highlight_duration); logoContainerSecondary.ScaleTo(scale_start).Then().ScaleTo(scale_start - scale_adjust * 0.25f, logo_scale_duration, Easing.InQuad); } @@ -295,13 +297,13 @@ namespace osu.Game.Screens.Menu { RelativeSizeAxes = Axes.Both, Texture = textures.Get(lazer_logo_texture), - Colour = OsuColour.Gray(0.6f).Opacity(0.8f), + Colour = OsuColour.Gray(0.8f), }, outline = new HueAnimation { RelativeSizeAxes = Axes.Both, Texture = textures.Get(lazer_logo_texture), - Colour = Color4.White.Opacity(0.8f), + Colour = OsuColour.Gray(0.6f * 0.8f), }, }; } From 79eca8e1bffed2138d9b89cb1216d013205e5a74 Mon Sep 17 00:00:00 2001 From: jorolf Date: Sun, 28 Jun 2020 17:55:01 +0200 Subject: [PATCH 651/871] remove unneeded "base." --- osu.Game/Graphics/Sprites/HueAnimation.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Sprites/HueAnimation.cs b/osu.Game/Graphics/Sprites/HueAnimation.cs index 82ac1aad36..8ad68ace05 100644 --- a/osu.Game/Graphics/Sprites/HueAnimation.cs +++ b/osu.Game/Graphics/Sprites/HueAnimation.cs @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.Sprites private class HueAnimationDrawNode : SpriteDrawNode { - private HueAnimation source => (HueAnimation)base.Source; + private HueAnimation source => (HueAnimation)Source; private float progress; From 678767918e29dee961730f5f8f18a2a0e6e98c5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 28 Jun 2020 23:32:04 +0200 Subject: [PATCH 652/871] Centralise logic further --- osu.Game/Screens/Play/HUD/FailingLayer.cs | 30 ++++++----------------- 1 file changed, 8 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/FailingLayer.cs b/osu.Game/Screens/Play/HUD/FailingLayer.cs index b96cfd170e..84dbb35f68 100644 --- a/osu.Game/Screens/Play/HUD/FailingLayer.cs +++ b/osu.Game/Screens/Play/HUD/FailingLayer.cs @@ -36,10 +36,9 @@ namespace osu.Game.Screens.Play.HUD /// private const double low_health_threshold = 0.20f; - private readonly Bindable fadePlayfieldWhenHealthLow = new Bindable(); private readonly Container boxes; - private Bindable fadePlayfieldWhenHealthLowSetting; + private Bindable fadePlayfieldWhenHealthLow; private HealthProcessor healthProcessor; public FailingLayer() @@ -78,15 +77,15 @@ namespace osu.Game.Screens.Play.HUD { boxes.Colour = color.Red; - fadePlayfieldWhenHealthLowSetting = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); - fadePlayfieldWhenHealthLow.BindValueChanged(_ => updateState(), true); - ShowHealth.BindValueChanged(_ => updateState(), true); + fadePlayfieldWhenHealthLow = config.GetBindable(OsuSetting.FadePlayfieldWhenHealthLow); + fadePlayfieldWhenHealthLow.BindValueChanged(_ => updateState()); + ShowHealth.BindValueChanged(_ => updateState()); } protected override void LoadComplete() { base.LoadComplete(); - updateBindings(); + updateState(); } public override void BindHealthProcessor(HealthProcessor processor) @@ -94,26 +93,13 @@ namespace osu.Game.Screens.Play.HUD base.BindHealthProcessor(processor); healthProcessor = processor; - updateBindings(); - } - - private void updateBindings() - { - if (LoadState < LoadState.Ready) - return; - - fadePlayfieldWhenHealthLow.UnbindBindings(); - - // Don't display ever if the ruleset is not using a draining health display. - if (healthProcessor is DrainingHealthProcessor) - fadePlayfieldWhenHealthLow.BindTo(fadePlayfieldWhenHealthLowSetting); - else - fadePlayfieldWhenHealthLow.Value = false; + updateState(); } private void updateState() { - var showLayer = fadePlayfieldWhenHealthLow.Value && ShowHealth.Value; + // Don't display ever if the ruleset is not using a draining health display. + var showLayer = healthProcessor is DrainingHealthProcessor && fadePlayfieldWhenHealthLow.Value && ShowHealth.Value; this.FadeTo(showLayer ? 1 : 0, fade_time, Easing.OutQuint); } From ffbce61ca884320098351a331bcd658041fe79b2 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 29 Jun 2020 00:39:49 +0200 Subject: [PATCH 653/871] Add the option to loop the intro in the main menu --- osu.Game/Configuration/OsuConfigManager.cs | 2 ++ .../Overlays/Settings/Sections/Audio/MainMenuSettings.cs | 5 +++++ osu.Game/Screens/Menu/IntroScreen.cs | 4 ++++ 3 files changed, 11 insertions(+) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 9d31bc9bba..aa9b5340f6 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -55,6 +55,7 @@ namespace osu.Game.Configuration Set(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01); Set(OsuSetting.MenuVoice, true); + Set(OsuSetting.MenuMusicLoop, true); Set(OsuSetting.MenuMusic, true); Set(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1); @@ -191,6 +192,7 @@ namespace osu.Game.Configuration AudioOffset, VolumeInactive, MenuMusic, + MenuMusicLoop, MenuVoice, CursorRotation, MenuParallax, diff --git a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs index a303f93b34..7ec123c04c 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs @@ -28,6 +28,11 @@ namespace osu.Game.Overlays.Settings.Sections.Audio LabelText = "osu! music theme", Bindable = config.GetBindable(OsuSetting.MenuMusic) }, + new SettingsCheckbox + { + LabelText = "loop the music theme", + Bindable = config.GetBindable(OsuSetting.MenuMusicLoop) + }, new SettingsDropdown { LabelText = "Intro sequence", diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 88d18d0073..57f93690a8 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -40,6 +40,7 @@ namespace osu.Game.Screens.Menu protected IBindable MenuVoice { get; private set; } protected IBindable MenuMusic { get; private set; } + private IBindable menuMusicLoop { get; set; } private WorkingBeatmap initialBeatmap; @@ -73,6 +74,7 @@ namespace osu.Game.Screens.Menu MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); + menuMusicLoop = config.GetBindable(OsuSetting.MenuMusicLoop); seeya = audio.Samples.Get(SeeyaSampleName); @@ -152,6 +154,8 @@ namespace osu.Game.Screens.Menu // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. if (UsingThemedIntro) Track.Restart(); + if (menuMusicLoop.Value) + Track.Looping = true; } protected override void LogoArriving(OsuLogo logo, bool resuming) From 5689f279871de69937a37342e10efcb98e5232e1 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 29 Jun 2020 00:54:06 +0200 Subject: [PATCH 654/871] Make sure it only loops for themed intros if true --- osu.Game/Screens/Menu/IntroScreen.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 57f93690a8..fa8a641203 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -152,8 +152,10 @@ namespace osu.Game.Screens.Menu protected void StartTrack() { // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. - if (UsingThemedIntro) - Track.Restart(); + if (!UsingThemedIntro) + return; + + Track.Restart(); if (menuMusicLoop.Value) Track.Looping = true; } From 270384e71e1fe41226eaf4864b6955fe5abcc4b1 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 29 Jun 2020 00:59:44 +0200 Subject: [PATCH 655/871] Remove redundant get set --- osu.Game/Screens/Menu/IntroScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index fa8a641203..8ef7ebe5e6 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Menu protected IBindable MenuVoice { get; private set; } protected IBindable MenuMusic { get; private set; } - private IBindable menuMusicLoop { get; set; } + private IBindable menuMusicLoop; private WorkingBeatmap initialBeatmap; From 24dceb9f84e49bf778ad575076f917065e6d5f67 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 29 Jun 2020 01:41:47 +0200 Subject: [PATCH 656/871] Make only "Welcome" loop --- osu.Game/Configuration/OsuConfigManager.cs | 2 -- .../Settings/Sections/Audio/MainMenuSettings.cs | 5 ----- osu.Game/Screens/Menu/IntroScreen.cs | 11 ++--------- osu.Game/Screens/Menu/IntroWelcome.cs | 2 ++ 4 files changed, 4 insertions(+), 16 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index aa9b5340f6..9d31bc9bba 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -55,7 +55,6 @@ namespace osu.Game.Configuration Set(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01); Set(OsuSetting.MenuVoice, true); - Set(OsuSetting.MenuMusicLoop, true); Set(OsuSetting.MenuMusic, true); Set(OsuSetting.AudioOffset, 0, -500.0, 500.0, 1); @@ -192,7 +191,6 @@ namespace osu.Game.Configuration AudioOffset, VolumeInactive, MenuMusic, - MenuMusicLoop, MenuVoice, CursorRotation, MenuParallax, diff --git a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs index 7ec123c04c..a303f93b34 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs @@ -28,11 +28,6 @@ namespace osu.Game.Overlays.Settings.Sections.Audio LabelText = "osu! music theme", Bindable = config.GetBindable(OsuSetting.MenuMusic) }, - new SettingsCheckbox - { - LabelText = "loop the music theme", - Bindable = config.GetBindable(OsuSetting.MenuMusicLoop) - }, new SettingsDropdown { LabelText = "Intro sequence", diff --git a/osu.Game/Screens/Menu/IntroScreen.cs b/osu.Game/Screens/Menu/IntroScreen.cs index 8ef7ebe5e6..5f91aaad15 100644 --- a/osu.Game/Screens/Menu/IntroScreen.cs +++ b/osu.Game/Screens/Menu/IntroScreen.cs @@ -40,7 +40,6 @@ namespace osu.Game.Screens.Menu protected IBindable MenuVoice { get; private set; } protected IBindable MenuMusic { get; private set; } - private IBindable menuMusicLoop; private WorkingBeatmap initialBeatmap; @@ -74,8 +73,6 @@ namespace osu.Game.Screens.Menu MenuVoice = config.GetBindable(OsuSetting.MenuVoice); MenuMusic = config.GetBindable(OsuSetting.MenuMusic); - menuMusicLoop = config.GetBindable(OsuSetting.MenuMusicLoop); - seeya = audio.Samples.Get(SeeyaSampleName); BeatmapSetInfo setInfo = null; @@ -152,12 +149,8 @@ namespace osu.Game.Screens.Menu protected void StartTrack() { // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu. - if (!UsingThemedIntro) - return; - - Track.Restart(); - if (menuMusicLoop.Value) - Track.Looping = true; + if (UsingThemedIntro) + Track.Restart(); } protected override void LogoArriving(OsuLogo logo, bool resuming) diff --git a/osu.Game/Screens/Menu/IntroWelcome.cs b/osu.Game/Screens/Menu/IntroWelcome.cs index abd4a68d4f..bf42e36e8c 100644 --- a/osu.Game/Screens/Menu/IntroWelcome.cs +++ b/osu.Game/Screens/Menu/IntroWelcome.cs @@ -39,6 +39,8 @@ namespace osu.Game.Screens.Menu welcome = audio.Samples.Get(@"Intro/Welcome/welcome"); pianoReverb = audio.Samples.Get(@"Intro/Welcome/welcome_piano"); + + Track.Looping = true; } protected override void LogoArriving(OsuLogo logo, bool resuming) From 444504f2b9c7765d6219b643e1b10464b8810240 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 29 Jun 2020 02:10:40 +0200 Subject: [PATCH 657/871] Expose MainMenu Track as internal get private set --- osu.Game/Screens/Menu/MainMenu.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index f0da2482d6..9245df2a7d 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -6,6 +6,7 @@ using System.Linq; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; @@ -63,6 +64,8 @@ namespace osu.Game.Screens.Menu protected override BackgroundScreen CreateBackground() => background; + internal Track Track { get; private set; } + private Bindable holdDelay; private Bindable loginDisplayed; @@ -173,15 +176,15 @@ namespace osu.Game.Screens.Menu base.OnEntering(last); buttons.FadeInFromZero(500); - var track = Beatmap.Value.Track; + Track = Beatmap.Value.Track; var metadata = Beatmap.Value.Metadata; - if (last is IntroScreen && track != null) + if (last is IntroScreen && Track != null) { - if (!track.IsRunning) + if (!Track.IsRunning) { - track.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * track.Length); - track.Start(); + Track.Seek(metadata.PreviewTime != -1 ? metadata.PreviewTime : 0.4f * Track.Length); + Track.Start(); } } } From 0c4b06b48562fc15ef0ccdec53932ffaefb9d109 Mon Sep 17 00:00:00 2001 From: Shivam Date: Mon, 29 Jun 2020 02:16:19 +0200 Subject: [PATCH 658/871] Add visualtest to check if Track loops in Welcome --- osu.Game.Tests/Visual/Menus/IntroTestScene.cs | 12 ++++++------ .../Visual/Menus/TestSceneIntroWelcome.cs | 13 +++++++++++++ 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs index 2d2f1a1618..f71d13ed35 100644 --- a/osu.Game.Tests/Visual/Menus/IntroTestScene.cs +++ b/osu.Game.Tests/Visual/Menus/IntroTestScene.cs @@ -19,10 +19,10 @@ namespace osu.Game.Tests.Visual.Menus [Cached] private OsuLogo logo; + protected OsuScreenStack IntroStack; + protected IntroTestScene() { - OsuScreenStack introStack = null; - Children = new Drawable[] { new Box @@ -45,17 +45,17 @@ namespace osu.Game.Tests.Visual.Menus logo.FinishTransforms(); logo.IsTracking = false; - introStack?.Expire(); + IntroStack?.Expire(); - Add(introStack = new OsuScreenStack + Add(IntroStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both, }); - introStack.Push(CreateScreen()); + IntroStack.Push(CreateScreen()); }); - AddUntilStep("wait for menu", () => introStack.CurrentScreen is MainMenu); + AddUntilStep("wait for menu", () => IntroStack.CurrentScreen is MainMenu); } protected abstract IScreen CreateScreen(); diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs index 905f17ef0b..1347bae2ad 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs @@ -11,5 +11,18 @@ namespace osu.Game.Tests.Visual.Menus public class TestSceneIntroWelcome : IntroTestScene { protected override IScreen CreateScreen() => new IntroWelcome(); + + public TestSceneIntroWelcome() + { + AddAssert("check if menu music loops", () => + { + var menu = IntroStack?.CurrentScreen as MainMenu; + + if (menu == null) + return false; + + return menu.Track.Looping; + }); + } } } From af7494b2325e31a4a3fef36fc263b19a9b3e9dfb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 29 Jun 2020 13:58:35 +0900 Subject: [PATCH 659/871] Improve quality of song select beatmap wedge --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 7a8a1593b9..27ce9e82dd 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -155,7 +155,6 @@ namespace osu.Game.Screens.Select var metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); CacheDrawnFrameBuffer = true; - RedrawOnScale = false; RelativeSizeAxes = Axes.Both; From 5db103dc613d238413b56ea3b2d31312b68e67cc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 29 Jun 2020 14:38:50 +0900 Subject: [PATCH 660/871] Improve quality of taiko hit target --- osu.Game.Rulesets.Taiko/UI/TaikoHitTarget.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoHitTarget.cs b/osu.Game.Rulesets.Taiko/UI/TaikoHitTarget.cs index 7de1593ab6..caddc8b122 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoHitTarget.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoHitTarget.cs @@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Scale = new Vector2(TaikoHitObject.DEFAULT_STRONG_SIZE), + Size = new Vector2(TaikoHitObject.DEFAULT_STRONG_SIZE), Masking = true, BorderColour = Color4.White, BorderThickness = border_thickness, @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Taiko.UI Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Scale = new Vector2(TaikoHitObject.DEFAULT_SIZE), + Size = new Vector2(TaikoHitObject.DEFAULT_SIZE), Masking = true, BorderColour = Color4.White, BorderThickness = border_thickness, From bb81f908fb163f0d77d2d2fb74c54be563cbb859 Mon Sep 17 00:00:00 2001 From: Derrick Timmermans Date: Mon, 29 Jun 2020 15:44:10 +0800 Subject: [PATCH 661/871] Exclude EmptyHitWindow from being considered in TimingDistributionGraph --- .../Ranking/Statistics/HitEventTimingDistributionGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs index 8ec7e863b1..527da429ed 100644 --- a/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs +++ b/osu.Game/Screens/Ranking/Statistics/HitEventTimingDistributionGraph.cs @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Ranking.Statistics /// The s to display the timing distribution of. public HitEventTimingDistributionGraph(IReadOnlyList hitEvents) { - this.hitEvents = hitEvents; + this.hitEvents = hitEvents.Where(e => !(e.HitObject.HitWindows is HitWindows.EmptyHitWindows)).ToList(); } [BackgroundDependencyLoader] From 51f5083c2d71a87eb8fcda3f6aa1b1a748f86b47 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 29 Jun 2020 17:17:52 +0000 Subject: [PATCH 662/871] Bump Sentry from 2.1.3 to 2.1.4 Bumps [Sentry](https://github.com/getsentry/sentry-dotnet) from 2.1.3 to 2.1.4. - [Release notes](https://github.com/getsentry/sentry-dotnet/releases) - [Commits](https://github.com/getsentry/sentry-dotnet/compare/2.1.3...2.1.4) Signed-off-by: dependabot-preview[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 26d81a1004..5f326a361d 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + From 1701c844a6a34f035e09c20b3c7a62950290be9e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 30 Jun 2020 16:36:53 +0900 Subject: [PATCH 663/871] Fix scroll container height on smaller ui scales --- osu.Game/Screens/Ranking/ResultsScreen.cs | 68 +++++++++++++---------- 1 file changed, 39 insertions(+), 29 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 193d975e42..968b446df9 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -70,41 +70,33 @@ namespace osu.Game.Screens.Ranking { new Drawable[] { - new Container + new VerticalScrollContainer { RelativeSizeAxes = Axes.Both, - Children = new Drawable[] + ScrollbarVisible = false, + Child = new Container { - new OsuScrollContainer + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - ScrollbarVisible = false, - Child = new Container + scorePanelList = new ScorePanelList { - RelativeSizeAxes = Axes.X, - Height = screen_height, - Children = new Drawable[] - { - scorePanelList = new ScorePanelList - { - RelativeSizeAxes = Axes.Both, - SelectedScore = { BindTarget = SelectedScore }, - PostExpandAction = () => statisticsPanel.ToggleVisibility() - }, - detachedPanelContainer = new Container - { - RelativeSizeAxes = Axes.Both - }, - statisticsPanel = new StatisticsPanel - { - RelativeSizeAxes = Axes.Both, - Score = { BindTarget = SelectedScore } - }, - } - } - }, + RelativeSizeAxes = Axes.Both, + SelectedScore = { BindTarget = SelectedScore }, + PostExpandAction = () => statisticsPanel.ToggleVisibility() + }, + detachedPanelContainer = new Container + { + RelativeSizeAxes = Axes.Both + }, + statisticsPanel = new StatisticsPanel + { + RelativeSizeAxes = Axes.Both, + Score = { BindTarget = SelectedScore } + }, + } } - } + }, }, new[] { @@ -277,5 +269,23 @@ namespace osu.Game.Screens.Ranking detachedPanel = null; } } + + private class VerticalScrollContainer : OsuScrollContainer + { + protected override Container Content => content; + + private readonly Container content; + + public VerticalScrollContainer() + { + base.Content.Add(content = new Container { RelativeSizeAxes = Axes.X }); + } + + protected override void Update() + { + base.Update(); + content.Height = Math.Max(screen_height, DrawHeight); + } + } } } From 85c42456f25c9a27cd4871fb2cc71e1e3caf1b17 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 30 Jun 2020 21:38:51 +0900 Subject: [PATCH 664/871] Improve performance of sequential scrolling algorithm --- .../Algorithms/SequentialScrollAlgorithm.cs | 164 +++++++++++------- 1 file changed, 104 insertions(+), 60 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs index 0052c877f6..a1f68d7201 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs @@ -3,21 +3,26 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using JetBrains.Annotations; using osu.Game.Rulesets.Timing; namespace osu.Game.Rulesets.UI.Scrolling.Algorithms { public class SequentialScrollAlgorithm : IScrollAlgorithm { - private readonly Dictionary positionCache; + private static readonly IComparer by_position_comparer = Comparer.Create((c1, c2) => c1.Position.CompareTo(c2.Position)); private readonly IReadOnlyList controlPoints; + /// + /// Stores a mapping of time -> position for each control point. + /// + private readonly List positionMappings = new List(); + public SequentialScrollAlgorithm(IReadOnlyList controlPoints) { this.controlPoints = controlPoints; - - positionCache = new Dictionary(); } public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength) @@ -27,55 +32,31 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms public float GetLength(double startTime, double endTime, double timeRange, float scrollLength) { - var objectLength = relativePositionAtCached(endTime, timeRange) - relativePositionAtCached(startTime, timeRange); + var objectLength = relativePositionAt(endTime, timeRange) - relativePositionAt(startTime, timeRange); return (float)(objectLength * scrollLength); } public float PositionAt(double time, double currentTime, double timeRange, float scrollLength) { - // Caching is not used here as currentTime is unlikely to have been previously cached - double timelinePosition = relativePositionAt(currentTime, timeRange); - return (float)((relativePositionAtCached(time, timeRange) - timelinePosition) * scrollLength); + double timelineLength = relativePositionAt(time, timeRange) - relativePositionAt(currentTime, timeRange); + return (float)(timelineLength * scrollLength); } public double TimeAt(float position, double currentTime, double timeRange, float scrollLength) { - // Convert the position to a length relative to time = 0 - double length = position / scrollLength + relativePositionAt(currentTime, timeRange); + if (controlPoints.Count == 0) + return position * timeRange; - // We need to consider all timing points until the specified time and not just the currently-active one, - // since each timing point individually affects the positions of _all_ hitobjects after its start time - for (int i = 0; i < controlPoints.Count; i++) - { - var current = controlPoints[i]; - var next = i < controlPoints.Count - 1 ? controlPoints[i + 1] : null; + // Find the position at the current time, and the given length. + double relativePosition = relativePositionAt(currentTime, timeRange) + position / scrollLength; - // Duration of the current control point - var currentDuration = (next?.StartTime ?? double.PositiveInfinity) - current.StartTime; + var positionMapping = findControlPointMapping(timeRange, new PositionMapping(0, null, relativePosition), by_position_comparer); - // Figure out the length of control point - var currentLength = currentDuration / timeRange * current.Multiplier; - - if (currentLength > length) - { - // The point is within this control point - return current.StartTime + length * timeRange / current.Multiplier; - } - - length -= currentLength; - } - - return 0; // Should never occur + // Begin at the control point's time and add the remaining time to reach the given position. + return positionMapping.Time + (relativePosition - positionMapping.Position) * timeRange / positionMapping.ControlPoint.Multiplier; } - private double relativePositionAtCached(double time, double timeRange) - { - if (!positionCache.TryGetValue(time, out double existing)) - positionCache[time] = existing = relativePositionAt(time, timeRange); - return existing; - } - - public void Reset() => positionCache.Clear(); + public void Reset() => positionMappings.Clear(); /// /// Finds the position which corresponds to a point in time. @@ -84,37 +65,100 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms /// The time to find the position at. /// The amount of time visualised by the scrolling area. /// A positive value indicating the position at . - private double relativePositionAt(double time, double timeRange) + private double relativePositionAt(in double time, in double timeRange) { if (controlPoints.Count == 0) return time / timeRange; - double length = 0; + var mapping = findControlPointMapping(timeRange, new PositionMapping(time)); - // We need to consider all timing points until the specified time and not just the currently-active one, - // since each timing point individually affects the positions of _all_ hitobjects after its start time - for (int i = 0; i < controlPoints.Count; i++) + // Begin at the control point's position and add the remaining distance to reach the given time. + return mapping.Position + (time - mapping.Time) / timeRange * mapping.ControlPoint.Multiplier; + } + + /// + /// Finds a 's that is relevant to a given . + /// + /// + /// This is used to find the last occuring prior to a time value, or prior to a position value (if is used). + /// + /// The time range. + /// The to find the closest to. + /// The comparison. If null, the default comparer is used (by time). + /// The 's that is relevant for . + private PositionMapping findControlPointMapping(in double timeRange, in PositionMapping search, IComparer comparer = null) + { + generatePositionMappings(timeRange); + + var mappingIndex = positionMappings.BinarySearch(search, comparer ?? Comparer.Default); + + if (mappingIndex < 0) { - var current = controlPoints[i]; - var next = i < controlPoints.Count - 1 ? controlPoints[i + 1] : null; + // If the search value isn't found, the _next_ control point is returned, but we actually want the _previous_ control point. + // In doing so, we must make sure to not underflow the position mapping list (i.e. always use the 0th control point for time < first_control_point_time). + mappingIndex = Math.Max(0, ~mappingIndex - 1); - // We don't need to consider any control points beyond the current time, since it will not yet - // affect any hitobjects - if (i > 0 && current.StartTime > time) - continue; - - // Duration of the current control point - var currentDuration = (next?.StartTime ?? double.PositiveInfinity) - current.StartTime; - - // We want to consider the minimal amount of time that this control point has affected, - // which may be either its duration, or the amount of time that has passed within it - var durationInCurrent = Math.Min(currentDuration, time - current.StartTime); - - // Figure out how much of the time range the duration represents, and adjust it by the speed multiplier - length += durationInCurrent / timeRange * current.Multiplier; + Debug.Assert(mappingIndex < positionMappings.Count); } - return length; + var mapping = positionMappings[mappingIndex]; + Debug.Assert(mapping.ControlPoint != null); + + return mapping; + } + + /// + /// Generates the mapping of (and their respective start times) to their relative position from 0. + /// + /// The time range. + private void generatePositionMappings(in double timeRange) + { + if (positionMappings.Count > 0) + return; + + if (controlPoints.Count == 0) + return; + + positionMappings.Add(new PositionMapping(controlPoints[0].StartTime, controlPoints[0])); + + for (int i = 0; i < controlPoints.Count - 1; i++) + { + var current = controlPoints[i]; + var next = controlPoints[i + 1]; + + // Figure out how much of the time range the duration represents, and adjust it by the speed multiplier + float length = (float)((next.StartTime - current.StartTime) / timeRange * current.Multiplier); + + positionMappings.Add(new PositionMapping(next.StartTime, next, positionMappings[^1].Position + length)); + } + } + + private readonly struct PositionMapping : IComparable + { + /// + /// The time corresponding to this position. + /// + public readonly double Time; + + /// + /// The at . + /// + [CanBeNull] + public readonly MultiplierControlPoint ControlPoint; + + /// + /// The relative position from 0 of . + /// + public readonly double Position; + + public PositionMapping(double time, MultiplierControlPoint controlPoint = null, double position = default) + { + Time = time; + ControlPoint = controlPoint; + Position = position; + } + + public int CompareTo(PositionMapping other) => Time.CompareTo(other.Time); } } } From 508d34fd3ac6d48dae4e5aa578e0c112f609cb3a Mon Sep 17 00:00:00 2001 From: Lucas A Date: Tue, 30 Jun 2020 19:51:10 +0200 Subject: [PATCH 665/871] Fix notification redirecting to the old log folder when game installation has been migrated to another location. --- osu.Game/OsuGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b0d7b14d34..92233f143d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -767,7 +767,7 @@ namespace osu.Game Text = "Subsequent messages have been logged. Click to view log files.", Activated = () => { - Host.Storage.GetStorageForDirectory("logs").OpenInNativeExplorer(); + Storage.GetStorageForDirectory("logs").OpenInNativeExplorer(); return true; } })); From 39cfbb67ad7962f1b75beb72fd793e445de66512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 30 Jun 2020 20:16:19 +0200 Subject: [PATCH 666/871] Replace iterated addition with rounding --- .../Patterns/Legacy/DistanceObjectPatternGenerator.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs index a09ef6d5b6..d03eb0b3c9 100644 --- a/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs +++ b/osu.Game.Rulesets.Mania/Beatmaps/Patterns/Legacy/DistanceObjectPatternGenerator.cs @@ -483,12 +483,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy if (!(HitObject is IHasPathWithRepeats curveData)) return null; - // mathematically speaking this could be done by calculating (time - HitObject.StartTime) / SegmentDuration - // however, floating-point operations can introduce inaccuracies - therefore resort to iterated addition - // (all callers use this method to calculate repeat point times, so this way is consistent and deterministic) - int index = 0; - for (double nodeTime = HitObject.StartTime; nodeTime < time; nodeTime += SegmentDuration) - index += 1; + // mathematically speaking this should be a whole number always, but floating-point arithmetic is not so kind + var index = (int)Math.Round(SegmentDuration == 0 ? 0 : (time - HitObject.StartTime) / SegmentDuration, MidpointRounding.AwayFromZero); // avoid slicing the list & creating copies, if at all possible. return index == 0 ? curveData.NodeSamples : curveData.NodeSamples.Skip(index).ToList(); From ab15b6031d662fb660149f0c9085be99f5c33b59 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Jul 2020 17:12:07 +0900 Subject: [PATCH 667/871] Update with framework-side storage changes --- osu.Game/IO/OsuStorage.cs | 6 +++--- osu.Game/OsuGameBase.cs | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 499bcb4063..f5ce1c0105 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -24,12 +24,12 @@ namespace osu.Game.IO "storage.ini" }; - public OsuStorage(GameHost host) - : base(host.Storage, string.Empty) + public OsuStorage(GameHost host, Storage defaultStorage) + : base(defaultStorage, string.Empty) { this.host = host; - storageConfig = new StorageConfigManager(host.Storage); + storageConfig = new StorageConfigManager(defaultStorage); var customStoragePath = storageConfig.Get(StorageConfig.FullPath); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 3e7311092e..c79f710151 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -312,11 +312,13 @@ namespace osu.Game base.SetHost(host); // may be non-null for certain tests - Storage ??= new OsuStorage(host); + Storage ??= host.Storage; LocalConfig ??= new OsuConfigManager(Storage); } + protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorage(host, defaultStorage); + private readonly List fileImporters = new List(); public async Task Import(params string[] paths) From cdcad94e9f0a8ce75c6be8572408795aaa6bde16 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Jul 2020 17:47:29 +0900 Subject: [PATCH 668/871] Handle exception thrown due to custom stoage on startup --- osu.Game/IO/OsuStorage.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index f5ce1c0105..8bcc0941c1 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -34,7 +34,17 @@ namespace osu.Game.IO var customStoragePath = storageConfig.Get(StorageConfig.FullPath); if (!string.IsNullOrEmpty(customStoragePath)) - ChangeTargetStorage(host.GetStorage(customStoragePath)); + { + try + { + ChangeTargetStorage(host.GetStorage(customStoragePath)); + } + catch (Exception ex) + { + Logger.Log($"Couldn't use custom storage path ({customStoragePath}): {ex}. Using default path.", LoggingTarget.Runtime, LogLevel.Error); + ChangeTargetStorage(defaultStorage); + } + } } protected override void ChangeTargetStorage(Storage newStorage) From 5f577797a7dd491d8ccecd49b28967ca826eb038 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jul 2020 18:41:00 +0900 Subject: [PATCH 669/871] Expose transform helpers in SkinnableSound --- osu.Game/Skinning/SkinnableSound.cs | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game/Skinning/SkinnableSound.cs b/osu.Game/Skinning/SkinnableSound.cs index 30320c89a6..24d6648273 100644 --- a/osu.Game/Skinning/SkinnableSound.cs +++ b/osu.Game/Skinning/SkinnableSound.cs @@ -7,8 +7,10 @@ using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Framework.Graphics; using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Transforms; using osu.Game.Audio; namespace osu.Game.Skinning @@ -43,6 +45,34 @@ namespace osu.Game.Skinning public BindableNumber Tempo => samplesContainer.Tempo; + /// + /// Smoothly adjusts over time. + /// + /// A to which further transforms can be added. + public TransformSequence VolumeTo(double newVolume, double duration = 0, Easing easing = Easing.None) => + samplesContainer.VolumeTo(newVolume, duration, easing); + + /// + /// Smoothly adjusts over time. + /// + /// A to which further transforms can be added. + public TransformSequence BalanceTo(double newBalance, double duration = 0, Easing easing = Easing.None) => + samplesContainer.BalanceTo(newBalance, duration, easing); + + /// + /// Smoothly adjusts over time. + /// + /// A to which further transforms can be added. + public TransformSequence FrequencyTo(double newFrequency, double duration = 0, Easing easing = Easing.None) => + samplesContainer.FrequencyTo(newFrequency, duration, easing); + + /// + /// Smoothly adjusts over time. + /// + /// A to which further transforms can be added. + public TransformSequence TempoTo(double newTempo, double duration = 0, Easing easing = Easing.None) => + samplesContainer.TempoTo(newTempo, duration, easing); + public bool Looping { get => looping; From 6f6376d53c56e5f592a6ae253349b4cc1923f5e8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jul 2020 18:52:05 +0900 Subject: [PATCH 670/871] Update framework --- .idea/.idea.osu.Desktop/.idea/modules.xml | 1 - osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.idea/.idea.osu.Desktop/.idea/modules.xml b/.idea/.idea.osu.Desktop/.idea/modules.xml index 366f172c30..fe63f5faf3 100644 --- a/.idea/.idea.osu.Desktop/.idea/modules.xml +++ b/.idea/.idea.osu.Desktop/.idea/modules.xml @@ -2,7 +2,6 @@ - diff --git a/osu.Android.props b/osu.Android.props index 493b1f5529..a2c97ead2f 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5f326a361d..3ef53a2a53 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 72f09ee287..492bf89fab 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 49aa839872b5291e2df9011c410f8d72edf3823b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jul 2020 18:54:11 +0900 Subject: [PATCH 671/871] Update RulesetInputManager to use new method --- osu.Game/Rulesets/UI/RulesetInputManager.cs | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index ba30fe28d5..f2ac61eaf4 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -18,9 +18,6 @@ using osu.Game.Input.Handlers; using osu.Game.Screens.Play; using osuTK.Input; using static osu.Game.Input.Handlers.ReplayInputHandler; -using JoystickState = osu.Framework.Input.States.JoystickState; -using KeyboardState = osu.Framework.Input.States.KeyboardState; -using MouseState = osu.Framework.Input.States.MouseState; namespace osu.Game.Rulesets.UI { @@ -42,11 +39,7 @@ namespace osu.Game.Rulesets.UI } } - protected override InputState CreateInitialState() - { - var state = base.CreateInitialState(); - return new RulesetInputManagerInputState(state.Mouse, state.Keyboard, state.Joystick); - } + protected override InputState CreateInitialState() => new RulesetInputManagerInputState(base.CreateInitialState()); protected readonly KeyBindingContainer KeyBindingContainer; @@ -203,8 +196,8 @@ namespace osu.Game.Rulesets.UI { public ReplayState LastReplayState; - public RulesetInputManagerInputState(MouseState mouse = null, KeyboardState keyboard = null, JoystickState joystick = null) - : base(mouse, keyboard, joystick) + public RulesetInputManagerInputState(InputState state = null) + : base(state) { } } From 4e839e4f1fb595740caa29f901f7072fc2858f23 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 1 Jul 2020 19:02:05 +0900 Subject: [PATCH 672/871] Fix "welcome" intro test failure due to no wait logic --- .../Visual/Menus/TestSceneIntroWelcome.cs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs index 1347bae2ad..8f20e38494 100644 --- a/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs +++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroWelcome.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using NUnit.Framework; +using osu.Framework.Audio.Track; using osu.Framework.Screens; using osu.Game.Screens.Menu; @@ -14,15 +15,11 @@ namespace osu.Game.Tests.Visual.Menus public TestSceneIntroWelcome() { - AddAssert("check if menu music loops", () => - { - var menu = IntroStack?.CurrentScreen as MainMenu; + AddUntilStep("wait for load", () => getTrack() != null); - if (menu == null) - return false; - - return menu.Track.Looping; - }); + AddAssert("check if menu music loops", () => getTrack().Looping); } + + private Track getTrack() => (IntroStack?.CurrentScreen as MainMenu)?.Track; } } From 1edfac4923623a1d78b1379c4f2c7e8e4177a01b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 1 Jul 2020 23:21:08 +0900 Subject: [PATCH 673/871] Fix test failing --- osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index f3d54d876a..8ea0e34214 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -127,6 +127,9 @@ namespace osu.Game.Tests.NonVisual var osu = loadOsu(host); var storage = osu.Dependencies.Get(); + // Store the current storage's path. We'll need to refer to this for assertions in the original directory after the migration completes. + string originalDirectory = storage.GetFullPath("."); + // ensure we perform a save host.Dependencies.Get().Save(); @@ -145,25 +148,25 @@ namespace osu.Game.Tests.NonVisual Assert.That(storage.GetFullPath("."), Is.EqualTo(customPath)); // ensure cache was not moved - Assert.That(host.Storage.ExistsDirectory("cache")); + Assert.That(Directory.Exists(Path.Combine(originalDirectory, "cache"))); // ensure nested cache was moved - Assert.That(!host.Storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); + Assert.That(!Directory.Exists(Path.Combine(originalDirectory, "test-nested", "cache"))); Assert.That(storage.ExistsDirectory(Path.Combine("test-nested", "cache"))); foreach (var file in OsuStorage.IGNORE_FILES) { - Assert.That(host.Storage.Exists(file), Is.True); + Assert.That(File.Exists(Path.Combine(originalDirectory, file))); Assert.That(storage.Exists(file), Is.False); } foreach (var dir in OsuStorage.IGNORE_DIRECTORIES) { - Assert.That(host.Storage.ExistsDirectory(dir), Is.True); + Assert.That(Directory.Exists(Path.Combine(originalDirectory, dir))); Assert.That(storage.ExistsDirectory(dir), Is.False); } - Assert.That(new StreamReader(host.Storage.GetStream("storage.ini")).ReadToEnd().Contains($"FullPath = {customPath}")); + Assert.That(new StreamReader(Path.Combine(originalDirectory, "storage.ini")).ReadToEnd().Contains($"FullPath = {customPath}")); } finally { From 3278a1d7d821e4fd5cfe9d4bde6125ef9a77a09c Mon Sep 17 00:00:00 2001 From: ekrctb Date: Thu, 2 Jul 2020 00:21:45 +0900 Subject: [PATCH 674/871] Standardize osu!catch coordinate system There were two coordinate systems used: - 0..512 (used in osu!stable) - 0..1 (relative coordinate) This commit replaces the usage of the relative coordinate system to the coordinate system of 0..512. --- .../CatchBeatmapConversionTest.cs | 3 +-- .../TestSceneAutoJuiceStream.cs | 6 +++--- .../TestSceneCatchStacker.cs | 10 +++++++++- .../TestSceneCatcherArea.cs | 4 ++-- .../TestSceneDrawableHitObjects.cs | 4 ++-- .../TestSceneHyperDash.cs | 8 ++++---- .../TestSceneJuiceStream.cs | 5 +++-- .../Beatmaps/CatchBeatmapConverter.cs | 5 ++--- .../Beatmaps/CatchBeatmapProcessor.cs | 14 +++++++------- .../Preprocessing/CatchDifficultyHitObject.cs | 5 ++--- .../Difficulty/Skills/Movement.cs | 5 ++--- osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs | 4 ++++ .../Objects/Drawables/DrawableCatchHitObject.cs | 4 ++-- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 9 ++++----- .../Replays/CatchAutoGenerator.cs | 4 ++-- .../Replays/CatchReplayFrame.cs | 5 ++--- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 11 ++++++++++- .../UI/CatchPlayfieldAdjustmentContainer.cs | 2 +- osu.Game.Rulesets.Catch/UI/Catcher.cs | 13 +++++-------- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 10 ++-------- osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs | 2 +- 21 files changed, 70 insertions(+), 63 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs index f4749be370..df54df7b01 100644 --- a/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Catch.Tests/CatchBeatmapConversionTest.cs @@ -8,7 +8,6 @@ using NUnit.Framework; using osu.Framework.Utils; using osu.Game.Rulesets.Catch.Mods; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; using osu.Game.Tests.Beatmaps; @@ -83,7 +82,7 @@ namespace osu.Game.Rulesets.Catch.Tests public float Position { - get => HitObject?.X * CatchPlayfield.BASE_WIDTH ?? position; + get => HitObject?.X ?? position; set => position = value; } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs index 7c2304694f..d6bba3d55e 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneAutoJuiceStream.cs @@ -27,15 +27,15 @@ namespace osu.Game.Rulesets.Catch.Tests for (int i = 0; i < 100; i++) { - float width = (i % 10 + 1) / 20f; + float width = (i % 10 + 1) / 20f * CatchPlayfield.WIDTH; beatmap.HitObjects.Add(new JuiceStream { - X = 0.5f - width / 2, + X = CatchPlayfield.CENTER_X - width / 2, Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, - new Vector2(width * CatchPlayfield.BASE_WIDTH, 0) + new Vector2(width, 0) }), StartTime = i * 2000, NewCombo = i % 8 == 0 diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs index 44672b6526..1ff31697b8 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchStacker.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; namespace osu.Game.Rulesets.Catch.Tests { @@ -22,7 +23,14 @@ namespace osu.Game.Rulesets.Catch.Tests }; for (int i = 0; i < 512; i++) - beatmap.HitObjects.Add(new Fruit { X = 0.5f + i / 2048f * (i % 10 - 5), StartTime = i * 100, NewCombo = i % 8 == 0 }); + { + beatmap.HitObjects.Add(new Fruit + { + X = (0.5f + i / 2048f * (i % 10 - 5)) * CatchPlayfield.WIDTH, + StartTime = i * 100, + NewCombo = i % 8 == 0 + }); + } return beatmap; } diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs index 2b30edb70b..fbb22a8498 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatcherArea.cs @@ -76,8 +76,8 @@ namespace osu.Game.Rulesets.Catch.Tests RelativeSizeAxes = Axes.Both, Child = new TestCatcherArea(new BeatmapDifficulty { CircleSize = size }) { - Anchor = Anchor.CentreLeft, - Origin = Anchor.TopLeft, + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, CreateDrawableRepresentation = ((DrawableRuleset)catchRuleset.CreateInstance().CreateDrawableRulesetWith(new CatchBeatmap())).CreateDrawableRepresentation }, }); diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs index a7094c00be..d35f828e28 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs @@ -158,8 +158,8 @@ namespace osu.Game.Rulesets.Catch.Tests private float getXCoords(bool hit) { - const float x_offset = 0.2f; - float xCoords = drawableRuleset.Playfield.Width / 2; + const float x_offset = 0.2f * CatchPlayfield.WIDTH; + float xCoords = CatchPlayfield.CENTER_X; if (drawableRuleset.Playfield is CatchPlayfield catchPlayfield) catchPlayfield.CatcherArea.MovableCatcher.X = xCoords - x_offset; diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs index a0dcb86d57..ad24adf352 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneHyperDash.cs @@ -47,13 +47,13 @@ namespace osu.Game.Rulesets.Catch.Tests }; // Should produce a hyper-dash (edge case test) - beatmap.HitObjects.Add(new Fruit { StartTime = 1816, X = 56 / 512f, NewCombo = true }); - beatmap.HitObjects.Add(new Fruit { StartTime = 2008, X = 308 / 512f, NewCombo = true }); + beatmap.HitObjects.Add(new Fruit { StartTime = 1816, X = 56, NewCombo = true }); + beatmap.HitObjects.Add(new Fruit { StartTime = 2008, X = 308, NewCombo = true }); double startTime = 3000; - const float left_x = 0.02f; - const float right_x = 0.98f; + const float left_x = 0.02f * CatchPlayfield.WIDTH; + const float right_x = 0.98f * CatchPlayfield.WIDTH; createObjects(() => new Fruit { X = left_x }); createObjects(() => new TestJuiceStream(right_x), 1); diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs index ffcf61a4bf..269e783899 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneJuiceStream.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using NUnit.Framework; using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osuTK; @@ -30,7 +31,7 @@ namespace osu.Game.Rulesets.Catch.Tests { new JuiceStream { - X = 0.5f, + X = CatchPlayfield.CENTER_X, Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, @@ -40,7 +41,7 @@ namespace osu.Game.Rulesets.Catch.Tests }, new Banana { - X = 0.5f, + X = CatchPlayfield.CENTER_X, StartTime = 1000, NewCombo = true } diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs index 0de2060e2d..145a40f5f5 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapConverter.cs @@ -5,7 +5,6 @@ using osu.Game.Beatmaps; using osu.Game.Rulesets.Catch.Objects; using System.Collections.Generic; using System.Linq; -using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects; using osu.Framework.Extensions.IEnumerableExtensions; @@ -36,7 +35,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps Path = curveData.Path, NodeSamples = curveData.NodeSamples, RepeatCount = curveData.RepeatCount, - X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH, + X = positionData?.X ?? 0, NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, LegacyLastTickOffset = (obj as IHasLegacyLastTickOffset)?.LegacyLastTickOffset ?? 0 @@ -59,7 +58,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps Samples = obj.Samples, NewCombo = comboData?.NewCombo ?? false, ComboOffset = comboData?.ComboOffset ?? 0, - X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH + X = positionData?.X ?? 0 }.Yield(); } } diff --git a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs index 7c81bcdf0c..bb14988414 100644 --- a/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Catch/Beatmaps/CatchBeatmapProcessor.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps case BananaShower bananaShower: foreach (var banana in bananaShower.NestedHitObjects.OfType()) { - banana.XOffset = (float)rng.NextDouble(); + banana.XOffset = (float)(rng.NextDouble() * CatchPlayfield.WIDTH); rng.Next(); // osu!stable retrieved a random banana type rng.Next(); // osu!stable retrieved a random banana rotation rng.Next(); // osu!stable retrieved a random banana colour @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps case JuiceStream juiceStream: // Todo: BUG!! Stable used the last control point as the final position of the path, but it should use the computed path instead. - lastPosition = juiceStream.X + juiceStream.Path.ControlPoints[^1].Position.Value.X / CatchPlayfield.BASE_WIDTH; + lastPosition = juiceStream.X + juiceStream.Path.ControlPoints[^1].Position.Value.X; // Todo: BUG!! Stable attempted to use the end time of the stream, but referenced it too early in execution and used the start time instead. lastStartTime = juiceStream.StartTime; @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps catchObject.XOffset = 0; if (catchObject is TinyDroplet) - catchObject.XOffset = Math.Clamp(rng.Next(-20, 20) / CatchPlayfield.BASE_WIDTH, -catchObject.X, 1 - catchObject.X); + catchObject.XOffset = Math.Clamp(rng.Next(-20, 20), -catchObject.X, CatchPlayfield.WIDTH - catchObject.X); else if (catchObject is Droplet) rng.Next(); // osu!stable retrieved a random droplet rotation } @@ -131,7 +131,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps } // ReSharper disable once PossibleLossOfFraction - if (Math.Abs(positionDiff * CatchPlayfield.BASE_WIDTH) < timeDiff / 3) + if (Math.Abs(positionDiff) < timeDiff / 3) applyOffset(ref offsetPosition, positionDiff); hitObject.XOffset = offsetPosition - hitObject.X; @@ -149,12 +149,12 @@ namespace osu.Game.Rulesets.Catch.Beatmaps private static void applyRandomOffset(ref float position, double maxOffset, FastRandom rng) { bool right = rng.NextBool(); - float rand = Math.Min(20, (float)rng.Next(0, Math.Max(0, maxOffset))) / CatchPlayfield.BASE_WIDTH; + float rand = Math.Min(20, (float)rng.Next(0, Math.Max(0, maxOffset))); if (right) { // Clamp to the right bound - if (position + rand <= 1) + if (position + rand <= CatchPlayfield.WIDTH) position += rand; else position -= rand; @@ -211,7 +211,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps objectWithDroplets.Sort((h1, h2) => h1.StartTime.CompareTo(h2.StartTime)); - double halfCatcherWidth = CatcherArea.GetCatcherSize(beatmap.BeatmapInfo.BaseDifficulty) / 2; + double halfCatcherWidth = Catcher.CalculateCatchWidth(beatmap.BeatmapInfo.BaseDifficulty) / 2; int lastDirection = 0; double lastExcess = halfCatcherWidth; diff --git a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs index 360af1a8c9..3e21b8fbaf 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Preprocessing/CatchDifficultyHitObject.cs @@ -3,7 +3,6 @@ using System; using osu.Game.Rulesets.Catch.Objects; -using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Objects; @@ -33,8 +32,8 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Preprocessing // We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps. var scalingFactor = normalized_hitobject_radius / halfCatcherWidth; - NormalizedPosition = BaseObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; - LastNormalizedPosition = LastObject.X * CatchPlayfield.BASE_WIDTH * scalingFactor; + NormalizedPosition = BaseObject.X * scalingFactor; + LastNormalizedPosition = LastObject.X * scalingFactor; // Every strain interval is hard capped at the equivalent of 375 BPM streaming speed as a safety measure StrainTime = Math.Max(40, DeltaTime); diff --git a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs index 918ed77683..e679231638 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/Skills/Movement.cs @@ -3,7 +3,6 @@ using System; using osu.Game.Rulesets.Catch.Difficulty.Preprocessing; -using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; @@ -68,7 +67,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills } // Bonus for edge dashes. - if (catchCurrent.LastObject.DistanceToHyperDash <= 20.0f / CatchPlayfield.BASE_WIDTH) + if (catchCurrent.LastObject.DistanceToHyperDash <= 20.0f) { if (!catchCurrent.LastObject.HyperDash) edgeDashBonus += 5.7; @@ -78,7 +77,7 @@ namespace osu.Game.Rulesets.Catch.Difficulty.Skills playerPosition = catchCurrent.NormalizedPosition; } - distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash * CatchPlayfield.BASE_WIDTH) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values + distanceAddition *= 1.0 + edgeDashBonus * ((20 - catchCurrent.LastObject.DistanceToHyperDash) / 20) * Math.Pow((Math.Min(catchCurrent.StrainTime * catchCurrent.ClockRate, 265) / 265), 1.5); // Edge Dashes are easier at lower ms values } lastPlayerPosition = playerPosition; diff --git a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs index f3b566f340..04932ecdbb 100644 --- a/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/CatchHitObject.cs @@ -5,6 +5,7 @@ using osu.Framework.Bindables; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Catch.Beatmaps; +using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; @@ -17,6 +18,9 @@ namespace osu.Game.Rulesets.Catch.Objects private float x; + /// + /// The horizontal position of the fruit between 0 and . + /// public float X { get => x + XOffset; diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index b12cdd4ccb..c6345a9df7 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.Catch.UI; using osuTK; using osuTK.Graphics; @@ -70,12 +71,11 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale; - protected override float SamplePlaybackPosition => HitObject.X; + protected override float SamplePlaybackPosition => HitObject.X / CatchPlayfield.WIDTH; protected DrawableCatchHitObject(CatchHitObject hitObject) : base(hitObject) { - RelativePositionAxes = Axes.X; X = hitObject.X; } diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 2c96ee2b19..6b8b70ed54 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -7,7 +7,6 @@ using System.Threading; using osu.Game.Audio; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -80,7 +79,7 @@ namespace osu.Game.Rulesets.Catch.Objects { StartTime = t + lastEvent.Value.Time, X = X + Path.PositionAt( - lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X / CatchPlayfield.BASE_WIDTH, + lastEvent.Value.PathProgress + (t / sinceLastTick) * (e.PathProgress - lastEvent.Value.PathProgress)).X, }); } } @@ -97,7 +96,7 @@ namespace osu.Game.Rulesets.Catch.Objects { Samples = dropletSamples, StartTime = e.Time, - X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH, + X = X + Path.PositionAt(e.PathProgress).X, }); break; @@ -108,14 +107,14 @@ namespace osu.Game.Rulesets.Catch.Objects { Samples = Samples, StartTime = e.Time, - X = X + Path.PositionAt(e.PathProgress).X / CatchPlayfield.BASE_WIDTH, + X = X + Path.PositionAt(e.PathProgress).X, }); break; } } } - public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; + public float EndX => X + this.CurvePositionAt(1).X; public double Duration { diff --git a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs index 7a33cb0577..5d11c574b1 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchAutoGenerator.cs @@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Catch.Replays // todo: add support for HT DT const double dash_speed = Catcher.BASE_SPEED; const double movement_speed = dash_speed / 2; - float lastPosition = 0.5f; + float lastPosition = CatchPlayfield.CENTER_X; double lastTime = 0; void moveToNext(CatchHitObject h) @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.Replays bool impossibleJump = speedRequired > movement_speed * 2; // todo: get correct catcher size, based on difficulty CS. - const float catcher_width_half = CatcherArea.CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * 0.3f * 0.5f; + const float catcher_width_half = CatcherArea.CATCHER_SIZE * 0.3f * 0.5f; if (lastPosition - catcher_width_half < h.X && lastPosition + catcher_width_half > h.X) { diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs index 9dab3ed630..7efd832f62 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using osu.Game.Beatmaps; using osu.Game.Replays.Legacy; -using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; @@ -41,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.Replays public void FromLegacy(LegacyReplayFrame currentFrame, IBeatmap beatmap, ReplayFrame lastFrame = null) { - Position = currentFrame.Position.X / CatchPlayfield.BASE_WIDTH; + Position = currentFrame.Position.X; Dashing = currentFrame.ButtonState == ReplayButtonState.Left1; if (Dashing) @@ -63,7 +62,7 @@ namespace osu.Game.Rulesets.Catch.Replays if (Actions.Contains(CatchAction.Dash)) state |= ReplayButtonState.Left1; - return new LegacyReplayFrame(Time, Position * CatchPlayfield.BASE_WIDTH, null, state); + return new LegacyReplayFrame(Time, Position, null, state); } } } diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 2319c5ac1f..d034f3c7d4 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -16,7 +16,16 @@ namespace osu.Game.Rulesets.Catch.UI { public class CatchPlayfield : ScrollingPlayfield { - public const float BASE_WIDTH = 512; + /// + /// The width of the playfield. + /// The horizontal movement of the catcher is confined in the area of this width. + /// + public const float WIDTH = 512; + + /// + /// The center position of the playfield. + /// + public const float CENTER_X = WIDTH / 2; internal readonly CatcherArea CatcherArea; diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs index b8d3dc9017..8ee23461ba 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfieldAdjustmentContainer.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Catch.UI { base.Update(); - Scale = new Vector2(Parent.ChildSize.X / CatchPlayfield.BASE_WIDTH); + Scale = new Vector2(Parent.ChildSize.X / CatchPlayfield.WIDTH); Size = Vector2.Divide(Vector2.One, Scale); } } diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 9cce46d730..82cbbefcca 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Catch.UI /// /// The relative space to cover in 1 millisecond. based on 1 game pixel per millisecond as in osu-stable. /// - public const double BASE_SPEED = 1.0 / 512; + public const double BASE_SPEED = 1.0; public Container ExplodingFruitTarget; @@ -104,9 +104,6 @@ namespace osu.Game.Rulesets.Catch.UI { this.trailsTarget = trailsTarget; - RelativePositionAxes = Axes.X; - X = 0.5f; - Origin = Anchor.TopCentre; Size = new Vector2(CatcherArea.CATCHER_SIZE); @@ -209,8 +206,8 @@ namespace osu.Game.Rulesets.Catch.UI var halfCatchWidth = catchWidth * 0.5f; // this stuff wil disappear once we move fruit to non-relative coordinate space in the future. - var catchObjectPosition = fruit.X * CatchPlayfield.BASE_WIDTH; - var catcherPosition = Position.X * CatchPlayfield.BASE_WIDTH; + var catchObjectPosition = fruit.X; + var catcherPosition = Position.X; var validCatch = catchObjectPosition >= catcherPosition - halfCatchWidth && @@ -224,7 +221,7 @@ namespace osu.Game.Rulesets.Catch.UI { var target = fruit.HyperDashTarget; var timeDifference = target.StartTime - fruit.StartTime; - double positionDifference = target.X * CatchPlayfield.BASE_WIDTH - catcherPosition; + double positionDifference = target.X - catcherPosition; var velocity = positionDifference / Math.Max(1.0, timeDifference - 1000.0 / 60.0); SetHyperDashState(Math.Abs(velocity), target.X); @@ -331,7 +328,7 @@ namespace osu.Game.Rulesets.Catch.UI public void UpdatePosition(float position) { - position = Math.Clamp(position, 0, 1); + position = Math.Clamp(position, 0, CatchPlayfield.WIDTH); if (position == X) return; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index 37d177b936..bf1ac5bc0e 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -31,14 +31,8 @@ namespace osu.Game.Rulesets.Catch.UI public CatcherArea(BeatmapDifficulty difficulty = null) { - RelativeSizeAxes = Axes.X; - Height = CATCHER_SIZE; - Child = MovableCatcher = new Catcher(this, difficulty); - } - - public static float GetCatcherSize(BeatmapDifficulty difficulty) - { - return CATCHER_SIZE / CatchPlayfield.BASE_WIDTH * (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5); + Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE); + Child = MovableCatcher = new Catcher(this, difficulty) { X = CatchPlayfield.CENTER_X }; } public void OnResult(DrawableCatchHitObject fruit, JudgementResult result) diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs index cefb47893c..57555cce90 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapEncoder.cs @@ -218,7 +218,7 @@ namespace osu.Game.Beatmaps.Formats break; case 2: - position.X = ((IHasXPosition)hitObject).X * 512; + position.X = ((IHasXPosition)hitObject).X; break; case 3: From 5c1f1ab622c8a4e862a7652564b557c76b9514ab Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 1 Jul 2020 14:31:06 -0700 Subject: [PATCH 675/871] Fix avatar in score panel being unclickable when statistics panel is visible --- osu.Game/Screens/Ranking/ResultsScreen.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 968b446df9..49ce07b708 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -79,6 +79,11 @@ namespace osu.Game.Screens.Ranking RelativeSizeAxes = Axes.Both, Children = new Drawable[] { + statisticsPanel = new StatisticsPanel + { + RelativeSizeAxes = Axes.Both, + Score = { BindTarget = SelectedScore } + }, scorePanelList = new ScorePanelList { RelativeSizeAxes = Axes.Both, @@ -89,11 +94,6 @@ namespace osu.Game.Screens.Ranking { RelativeSizeAxes = Axes.Both }, - statisticsPanel = new StatisticsPanel - { - RelativeSizeAxes = Axes.Both, - Score = { BindTarget = SelectedScore } - }, } } }, From fa252d5e950d685699632b9cfb78ea7d25a95c58 Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 1 Jul 2020 17:37:38 -0700 Subject: [PATCH 676/871] Fix score panel not showing silver s/ss badges on hd/fl plays --- .../Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index ee53ee9879..213c1692ee 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -9,6 +10,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Utils; using osu.Game.Graphics; +using osu.Game.Rulesets.Mods; using osu.Game.Scoring; using osuTK; @@ -191,8 +193,8 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Padding = new MarginPadding { Vertical = -15, Horizontal = -20 }, Children = new[] { - new RankBadge(1f, ScoreRank.X), - new RankBadge(0.95f, ScoreRank.S), + new RankBadge(1f, score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X), + new RankBadge(0.95f, score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S), new RankBadge(0.9f, ScoreRank.A), new RankBadge(0.8f, ScoreRank.B), new RankBadge(0.7f, ScoreRank.C), From 18e30a7fc4123a297f271bfb8ddc4fbe06fa9f23 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 2 Jul 2020 19:12:45 +0200 Subject: [PATCH 677/871] Implement background switching based on the intro Only the Welcome intro has its own unique background right now --- .../Backgrounds/BackgroundScreenDefault.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 980a127cf4..ae3ad63ac8 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -22,11 +22,12 @@ namespace osu.Game.Screens.Backgrounds private int currentDisplay; private const int background_count = 7; - private string backgroundName => $@"Menu/menu-background-{currentDisplay % background_count + 1}"; + private string backgroundName; private Bindable user; private Bindable skin; private Bindable mode; + private Bindable introSequence; [Resolved] private IBindable beatmap { get; set; } @@ -42,11 +43,13 @@ namespace osu.Game.Screens.Backgrounds user = api.LocalUser.GetBoundCopy(); skin = skinManager.CurrentSkin.GetBoundCopy(); mode = config.GetBindable(OsuSetting.MenuBackgroundSource); + introSequence = config.GetBindable(OsuSetting.IntroSequence); user.ValueChanged += _ => Next(); skin.ValueChanged += _ => Next(); mode.ValueChanged += _ => Next(); beatmap.ValueChanged += _ => Next(); + introSequence.ValueChanged += _ => Next(); currentDisplay = RNG.Next(0, background_count); @@ -74,6 +77,17 @@ namespace osu.Game.Screens.Backgrounds { Background newBackground; + switch (introSequence.Value) + { + case IntroSequence.Welcome: + backgroundName = "Menu/menu-background-welcome"; + break; + + default: + backgroundName = $@"Menu/menu-background-{currentDisplay % background_count + 1}"; + break; + } + if (user.Value?.IsSupporter ?? false) { switch (mode.Value) From e80a5a085afe07c55c9c112450df8f173af153e2 Mon Sep 17 00:00:00 2001 From: Shivam Date: Thu, 2 Jul 2020 19:45:18 +0200 Subject: [PATCH 678/871] Make backgroundName local --- osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index ae3ad63ac8..2c22e60195 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -21,9 +21,6 @@ namespace osu.Game.Screens.Backgrounds private int currentDisplay; private const int background_count = 7; - - private string backgroundName; - private Bindable user; private Bindable skin; private Bindable mode; @@ -76,6 +73,7 @@ namespace osu.Game.Screens.Backgrounds private Background createBackground() { Background newBackground; + string backgroundName; switch (introSequence.Value) { From 718f06c69075b9199ac07de2db66e6b8124182e5 Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 2 Jul 2020 12:35:32 -0700 Subject: [PATCH 679/871] Use Mod.AdjustRank() instead --- .../Expanded/Accuracy/AccuracyCircle.cs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs index 213c1692ee..45da23f1f9 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/AccuracyCircle.cs @@ -193,18 +193,26 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Padding = new MarginPadding { Vertical = -15, Horizontal = -20 }, Children = new[] { - new RankBadge(1f, score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.XH : ScoreRank.X), - new RankBadge(0.95f, score.Mods.Any(m => m is ModHidden || m is ModFlashlight) ? ScoreRank.SH : ScoreRank.S), - new RankBadge(0.9f, ScoreRank.A), - new RankBadge(0.8f, ScoreRank.B), - new RankBadge(0.7f, ScoreRank.C), - new RankBadge(0.35f, ScoreRank.D), + new RankBadge(1f, getRank(ScoreRank.X)), + new RankBadge(0.95f, getRank(ScoreRank.S)), + new RankBadge(0.9f, getRank(ScoreRank.A)), + new RankBadge(0.8f, getRank(ScoreRank.B)), + new RankBadge(0.7f, getRank(ScoreRank.C)), + new RankBadge(0.35f, getRank(ScoreRank.D)), } }, rankText = new RankText(score.Rank) }; } + private ScoreRank getRank(ScoreRank rank) + { + foreach (var mod in score.Mods.OfType()) + rank = mod.AdjustRank(rank, score.Accuracy); + + return rank; + } + protected override void LoadComplete() { base.LoadComplete(); From d66b97868c4db2e057bf7340200d49eace3dd16f Mon Sep 17 00:00:00 2001 From: Joehu Date: Thu, 2 Jul 2020 12:39:37 -0700 Subject: [PATCH 680/871] Adjust rank when flashlight is enabled --- osu.Game/Rulesets/Mods/ModFlashlight.cs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Mods/ModFlashlight.cs b/osu.Game/Rulesets/Mods/ModFlashlight.cs index 35a8334237..6e94a84e7d 100644 --- a/osu.Game/Rulesets/Mods/ModFlashlight.cs +++ b/osu.Game/Rulesets/Mods/ModFlashlight.cs @@ -47,9 +47,25 @@ namespace osu.Game.Rulesets.Mods public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor) { Combo.BindTo(scoreProcessor.Combo); + + // Default value of ScoreProcessor's Rank in Flashlight Mod should be SS+ + scoreProcessor.Rank.Value = ScoreRank.XH; } - public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank; + public ScoreRank AdjustRank(ScoreRank rank, double accuracy) + { + switch (rank) + { + case ScoreRank.X: + return ScoreRank.XH; + + case ScoreRank.S: + return ScoreRank.SH; + + default: + return rank; + } + } public virtual void ApplyToDrawableRuleset(DrawableRuleset drawableRuleset) { From cb69d1a86537fe5904229e8c0b7291952dc7aece Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 3 Jul 2020 16:47:14 +0900 Subject: [PATCH 681/871] Fix crash when changing tabs in changelog --- osu.Game/Graphics/UserInterface/BreadcrumbControl.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs index 84429bf5bd..fb5ff4aad3 100644 --- a/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs +++ b/osu.Game/Graphics/UserInterface/BreadcrumbControl.cs @@ -27,6 +27,8 @@ namespace osu.Game.Graphics.UserInterface { Height = 32; TabContainer.Spacing = new Vector2(padding, 0f); + SwitchTabOnRemove = false; + Current.ValueChanged += index => { foreach (var t in TabContainer.Children.OfType()) From 4ded6d1913c6db7811cb3f4ac4a5037e3f843cb0 Mon Sep 17 00:00:00 2001 From: Shivam Date: Fri, 3 Jul 2020 11:36:03 +0200 Subject: [PATCH 682/871] Change background path with resource change --- osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs index 2c22e60195..ef41c5be3d 100644 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs @@ -78,7 +78,7 @@ namespace osu.Game.Screens.Backgrounds switch (introSequence.Value) { case IntroSequence.Welcome: - backgroundName = "Menu/menu-background-welcome"; + backgroundName = "Intro/Welcome/menu-background"; break; default: From ffec4298a7b9c535db5b5dca6c8c2c4074cbf1e2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Jul 2020 16:45:46 +0900 Subject: [PATCH 683/871] Use DrawablePool for DrawableJudgements --- .../Objects/Drawables/DrawableOsuJudgement.cs | 35 +++++--- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 20 +++-- .../Rulesets/Judgements/DrawableJudgement.cs | 83 ++++++++++++++----- 3 files changed, 99 insertions(+), 39 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 022e9ea12b..9d0c406295 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -24,10 +24,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { } + public DrawableOsuJudgement() + { + } + [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - if (config.Get(OsuSetting.HitLighting) && Result.Type != HitResult.Miss) + if (config.Get(OsuSetting.HitLighting)) { AddInternal(lighting = new SkinnableSprite("lighting") { @@ -36,16 +40,23 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Blending = BlendingParameters.Additive, Depth = float.MaxValue }); + } + } - if (JudgedObject != null) - { - lightingColour = JudgedObject.AccentColour.GetBoundCopy(); - lightingColour.BindValueChanged(colour => lighting.Colour = colour.NewValue, true); - } - else - { - lighting.Colour = Color4.White; - } + protected override void PrepareForUse() + { + base.PrepareForUse(); + + lightingColour?.UnbindAll(); + + if (JudgedObject != null) + { + lightingColour = JudgedObject.AccentColour.GetBoundCopy(); + lightingColour.BindValueChanged(colour => lighting.Colour = Result.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true); + } + else + { + lighting.Colour = Color4.White; } } @@ -55,13 +66,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { if (lighting != null) { - JudgementBody.Delay(FadeInDuration).FadeOut(400); + JudgementBody.FadeIn().Delay(FadeInDuration).FadeOut(400); lighting.ScaleTo(0.8f).ScaleTo(1.2f, 600, Easing.Out); lighting.FadeIn(200).Then().Delay(200).FadeOut(1000); } - JudgementText?.TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); + JudgementText?.TransformSpacingTo(Vector2.Zero).Then().TransformSpacingTo(new Vector2(14, 0), 1800, Easing.OutQuint); base.ApplyHitAnimations(); } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 4b1a2ce43c..f9002a29ca 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -4,6 +4,8 @@ using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; +using osu.Framework.Logging; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -26,10 +28,13 @@ namespace osu.Game.Rulesets.Osu.UI protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); + private readonly DrawablePool judgementPool; + public OsuPlayfield() { InternalChildren = new Drawable[] { + judgementPool = new DrawablePool(20), followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both, @@ -91,12 +96,17 @@ namespace osu.Game.Rulesets.Osu.UI if (!judgedObject.DisplayResult || !DisplayJudgements.Value) return; - DrawableOsuJudgement explosion = new DrawableOsuJudgement(result, judgedObject) + DrawableOsuJudgement explosion = judgementPool.Get(doj => { - Origin = Anchor.Centre, - Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition, - Scale = new Vector2(((OsuHitObject)judgedObject.HitObject).Scale) - }; + if (doj.Result != null) + Logger.Log("reused!"); + doj.Result = result; + doj.JudgedObject = judgedObject; + + doj.Origin = Anchor.Centre; + doj.Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition; + doj.Scale = new Vector2(((OsuHitObject)judgedObject.HitObject).Scale); + }); judgementLayer.Add(explosion); } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 7113acbbfb..86dd02f2bb 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -1,11 +1,14 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osuTK; using osu.Framework.Allocation; +using osu.Framework.Caching; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -18,16 +21,31 @@ namespace osu.Game.Rulesets.Judgements /// /// A drawable object which visualises the hit result of a . /// - public class DrawableJudgement : CompositeDrawable + public class DrawableJudgement : PoolableDrawable { private const float judgement_size = 128; [Resolved] private OsuColour colours { get; set; } - protected readonly JudgementResult Result; + private readonly Cached drawableCache = new Cached(); - public readonly DrawableHitObject JudgedObject; + private JudgementResult result; + + public JudgementResult Result + { + get => result; + set + { + if (result?.Type == value.Type) + return; + + result = value; + drawableCache.Invalidate(); + } + } + + public DrawableHitObject JudgedObject; protected Container JudgementBody; protected SpriteText JudgementText; @@ -48,29 +66,15 @@ namespace osu.Game.Rulesets.Judgements /// The judgement to visualise. /// The object which was judged. public DrawableJudgement(JudgementResult result, DrawableHitObject judgedObject) + : this() { Result = result; JudgedObject = judgedObject; - - Size = new Vector2(judgement_size); } - [BackgroundDependencyLoader] - private void load() + public DrawableJudgement() { - InternalChild = JudgementBody = new Container - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Child = new SkinnableDrawable(new GameplaySkinComponent(Result.Type), _ => JudgementText = new OsuSpriteText - { - Text = Result.Type.GetDescription().ToUpperInvariant(), - Font = OsuFont.Numeric.With(size: 20), - Colour = colours.ForHitResult(Result.Type), - Scale = new Vector2(0.85f, 1), - }, confineMode: ConfineMode.NoScaling) - }; + Size = new Vector2(judgement_size); } protected virtual void ApplyHitAnimations() @@ -81,11 +85,25 @@ namespace osu.Game.Rulesets.Judgements this.Delay(FadeOutDelay).FadeOut(400); } - protected override void LoadComplete() + [BackgroundDependencyLoader] + private void load() { - base.LoadComplete(); + prepareDrawables(); + } + + protected override void PrepareForUse() + { + base.PrepareForUse(); + + Debug.Assert(Result != null); + + if (!drawableCache.IsValid) + prepareDrawables(); this.FadeInFromZero(FadeInDuration, Easing.OutQuint); + JudgementBody.ScaleTo(1); + JudgementBody.RotateTo(0); + JudgementBody.MoveTo(Vector2.Zero); switch (Result.Type) { @@ -109,5 +127,26 @@ namespace osu.Game.Rulesets.Judgements Expire(true); } + + private void prepareDrawables() + { + var type = Result?.Type ?? HitResult.Perfect; //TODO: better default type from ruleset + + InternalChild = JudgementBody = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Child = new SkinnableDrawable(new GameplaySkinComponent(type), _ => JudgementText = new OsuSpriteText + { + Text = type.GetDescription().ToUpperInvariant(), + Font = OsuFont.Numeric.With(size: 20), + Colour = colours.ForHitResult(type), + Scale = new Vector2(0.85f, 1), + }, confineMode: ConfineMode.NoScaling) + }; + + drawableCache.Validate(); + } } } From 02871c960ba37153075a2781f94de943e49b5ac0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 4 Jul 2020 23:25:06 +0900 Subject: [PATCH 684/871] 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 a2c97ead2f..ff86ac6574 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3ef53a2a53..afe2348c6e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 492bf89fab..80c37ab8f9 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 52b313f2909eca32da32c66ab9bf0aad9ff09abd Mon Sep 17 00:00:00 2001 From: jorolf Date: Sat, 4 Jul 2020 19:06:26 +0200 Subject: [PATCH 685/871] change textures --- .../UserInterface/TestSceneHueAnimation.cs | 2 +- osu.Game/Screens/Menu/IntroTriangles.cs | 39 ++++++++----------- 2 files changed, 18 insertions(+), 23 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs index 85ddfb08f9..b341291c58 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.UserInterface { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, - Texture = textures.Get("Intro/Triangles/logo-triangles"), + Texture = textures.Get("Intro/Triangles/logo-background"), Colour = Colour4.White, }); diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 8118491c36..38da98220d 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -217,14 +217,8 @@ namespace osu.Game.Screens.Menu // matching flyte curve y = 0.25x^2 + (max(0, x - 0.7) / 0.3) ^ 5 lazerLogo.FadeIn().ScaleTo(scale_start).Then().Delay(logo_scale_duration * 0.7f).ScaleTo(scale_start - scale_adjust, logo_scale_duration * 0.3f, Easing.InQuint); - const double highlight_duration = logo_scale_duration / 1.4; - - //Since we only have one texture, roughly align it by changing the timing - lazerLogo.Outline = -0.4f; - lazerLogo.TransformTo(nameof(LazerLogo.Outline), 1f, highlight_duration * 1.4); - - lazerLogo.OutlineHighlight = 0f; - lazerLogo.TransformTo(nameof(LazerLogo.OutlineHighlight), 1f, highlight_duration); + lazerLogo.TransformTo(nameof(LazerLogo.Background), 1f, logo_scale_duration); + lazerLogo.TransformTo(nameof(LazerLogo.Highlight), 1f, logo_scale_duration); logoContainerSecondary.ScaleTo(scale_start).Then().ScaleTo(scale_start - scale_adjust * 0.25f, logo_scale_duration, Easing.InQuad); } @@ -267,18 +261,18 @@ namespace osu.Game.Screens.Menu private class LazerLogo : CompositeDrawable { - private HueAnimation outlineHighlight, outline; + private HueAnimation highlight, background; - public float OutlineHighlight + public float Highlight { - get => outlineHighlight.AnimationProgress; - set => outlineHighlight.AnimationProgress = value; + get => highlight.AnimationProgress; + set => highlight.AnimationProgress = value; } - public float Outline + public float Background { - get => outline.AnimationProgress; - set => outline.AnimationProgress = value; + get => background.AnimationProgress; + set => background.AnimationProgress = value; } public LazerLogo() @@ -289,21 +283,22 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(TextureStore textures) { - const string lazer_logo_texture = @"Intro/Triangles/logo-triangles"; + const string lazer_logo_background = @"Intro/Triangles/logo-background"; + const string lazer_logo_highlight = @"Intro/Triangles/logo-highlight"; InternalChildren = new Drawable[] { - outlineHighlight = new HueAnimation + highlight = new HueAnimation { RelativeSizeAxes = Axes.Both, - Texture = textures.Get(lazer_logo_texture), - Colour = OsuColour.Gray(0.8f), + Texture = textures.Get(lazer_logo_highlight), + Colour = OsuColour.Gray(1f), }, - outline = new HueAnimation + background = new HueAnimation { RelativeSizeAxes = Axes.Both, - Texture = textures.Get(lazer_logo_texture), - Colour = OsuColour.Gray(0.6f * 0.8f), + Texture = textures.Get(lazer_logo_background), + Colour = OsuColour.Gray(0.6f), }, }; } From cd6bdcdb88035e5d0715d90520f5e083c47ea6ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Jul 2020 00:25:01 +0200 Subject: [PATCH 686/871] Replace further spinner transforms with manual lerp --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 3c8ab0f5ab..bb1b6fdd26 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -14,6 +14,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Sprites; +using osu.Framework.Utils; using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Ranking; @@ -193,9 +194,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables SpmCounter.SetRotation(Disc.RotationAbsolute); float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; - Disc.ScaleTo(relativeCircleScale + (1 - relativeCircleScale) * Progress, 200, Easing.OutQuint); + float targetScale = relativeCircleScale + (1 - relativeCircleScale) * Progress; + Disc.Scale = new Vector2((float)Interpolation.Lerp(Disc.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1))); - symbol.RotateTo(Disc.Rotation / 2, 500, Easing.OutQuint); + symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, Disc.Rotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); } protected override void UpdateInitialTransforms() From d229993e5c727ff9c10328a1360ad776606053b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 5 Jul 2020 02:12:26 +0200 Subject: [PATCH 687/871] Use RotationAbsolute instead --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index bb1b6fdd26..4d37622be5 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables float targetScale = relativeCircleScale + (1 - relativeCircleScale) * Progress; Disc.Scale = new Vector2((float)Interpolation.Lerp(Disc.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1))); - symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, Disc.Rotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); + symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, Disc.RotationAbsolute / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); } protected override void UpdateInitialTransforms() From ec689ce824c0ff9930b6d8f4a38322f40e5a61af Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 5 Jul 2020 12:31:16 +0800 Subject: [PATCH 688/871] add support for custom mania skin paths for stage decorations --- osu.Game/Skinning/LegacyManiaSkinDecoder.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs index 0806676fde..aebc229f7c 100644 --- a/osu.Game/Skinning/LegacyManiaSkinDecoder.cs +++ b/osu.Game/Skinning/LegacyManiaSkinDecoder.cs @@ -115,6 +115,7 @@ namespace osu.Game.Skinning case string _ when pair.Key.StartsWith("NoteImage"): case string _ when pair.Key.StartsWith("KeyImage"): case string _ when pair.Key.StartsWith("Hit"): + case string _ when pair.Key.StartsWith("Stage"): currentConfig.ImageLookups[pair.Key] = pair.Value; break; } From 5c2959eeb6844552a82ef8f2085448a1dc9a6b1d Mon Sep 17 00:00:00 2001 From: mcendu Date: Sun, 5 Jul 2020 13:02:50 +0800 Subject: [PATCH 689/871] allow lookup of stage decoration paths and add test images --- .../{mania-stage-left.png => mania/stage-left.png} | Bin .../stage-right.png} | Bin .../Resources/special-skin/skin.ini | 4 +++- osu.Game/Skinning/LegacySkin.cs | 9 +++++++++ 4 files changed, 12 insertions(+), 1 deletion(-) rename osu.Game.Rulesets.Mania.Tests/Resources/special-skin/{mania-stage-left.png => mania/stage-left.png} (100%) rename osu.Game.Rulesets.Mania.Tests/Resources/special-skin/{mania-stage-right.png => mania/stage-right.png} (100%) diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-left.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-left.png similarity index 100% rename from osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-left.png rename to osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-left.png diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-right.png b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-right.png similarity index 100% rename from osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania-stage-right.png rename to osu.Game.Rulesets.Mania.Tests/Resources/special-skin/mania/stage-right.png diff --git a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini index 941abac1da..36765d61bf 100644 --- a/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini +++ b/osu.Game.Rulesets.Mania.Tests/Resources/special-skin/skin.ini @@ -9,4 +9,6 @@ Hit50: mania/hit50 Hit100: mania/hit100 Hit200: mania/hit200 Hit300: mania/hit300 -Hit300g: mania/hit300g \ No newline at end of file +Hit300g: mania/hit300g +StageLeft: mania/stage-left +StageRight: mania/stage-right \ No newline at end of file diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 0b2b723440..4b70ccc6ad 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -250,6 +250,15 @@ namespace osu.Game.Skinning case LegacyManiaSkinConfigurationLookups.RightStageImage: return SkinUtils.As(getManiaImage(existing, "StageRight")); + case LegacyManiaSkinConfigurationLookups.BottomStageImage: + return SkinUtils.As(getManiaImage(existing, "StageBottom")); + + case LegacyManiaSkinConfigurationLookups.LightImage: + return SkinUtils.As(getManiaImage(existing, "StageLight")); + + case LegacyManiaSkinConfigurationLookups.HitTargetImage: + return SkinUtils.As(getManiaImage(existing, "StageHint")); + case LegacyManiaSkinConfigurationLookups.LeftLineWidth: Debug.Assert(maniaLookup.TargetColumn != null); return SkinUtils.As(new Bindable(existing.ColumnLineWidth[maniaLookup.TargetColumn.Value])); From c18ca19c9d60c1e5715d7b2ea05e00566f827fbd Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 6 Jul 2020 05:31:34 +0300 Subject: [PATCH 690/871] Add NewsPost api response --- .../Online/API/Requests/Responses/NewsPost.cs | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 osu.Game/Online/API/Requests/Responses/NewsPost.cs diff --git a/osu.Game/Online/API/Requests/Responses/NewsPost.cs b/osu.Game/Online/API/Requests/Responses/NewsPost.cs new file mode 100644 index 0000000000..f3ee0f9c35 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/NewsPost.cs @@ -0,0 +1,38 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using System; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class NewsPost + { + [JsonProperty(@"id")] + public long Id { get; set; } + + [JsonProperty(@"author")] + public string Author { get; set; } + + [JsonProperty(@"edit_url")] + public string EditUrl { get; set; } + + [JsonProperty(@"first_image")] + public string FirstImage { get; set; } + + [JsonProperty(@"published_at")] + public DateTimeOffset PublishedAt { get; set; } + + [JsonProperty(@"updated_at")] + public DateTimeOffset UpdatedAt { get; set; } + + [JsonProperty(@"slug")] + public string Slug { get; set; } + + [JsonProperty(@"title")] + public string Title { get; set; } + + [JsonProperty(@"preview")] + public string Preview { get; set; } + } +} From 51050ec4eff4a6d5dc81c0f95757f2194cbb9ee4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Jul 2020 12:54:39 +0900 Subject: [PATCH 691/871] Add per-result type pooling --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 51 +++++++++++++++---- .../Rulesets/Judgements/DrawableJudgement.cs | 1 + 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index f9002a29ca..2eff99bd3e 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -1,18 +1,23 @@ // 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 osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; -using osu.Framework.Logging; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Rulesets.Scoring; using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.UI @@ -28,13 +33,12 @@ namespace osu.Game.Rulesets.Osu.UI protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer(); - private readonly DrawablePool judgementPool; + private readonly IDictionary> poolDictionary = new Dictionary>(); public OsuPlayfield() { InternalChildren = new Drawable[] { - judgementPool = new DrawablePool(20), followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both, @@ -59,6 +63,13 @@ namespace osu.Game.Rulesets.Osu.UI }; hitPolicy = new OrderedHitPolicy(HitObjectContainer); + + var hitWindows = new OsuHitWindows(); + + foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) + poolDictionary.Add(result, new DrawableJudgementPool(result)); + + AddRangeInternal(poolDictionary.Values); } public override void Add(DrawableHitObject h) @@ -96,16 +107,15 @@ namespace osu.Game.Rulesets.Osu.UI if (!judgedObject.DisplayResult || !DisplayJudgements.Value) return; - DrawableOsuJudgement explosion = judgementPool.Get(doj => + var osuObject = (OsuHitObject)judgedObject.HitObject; + + DrawableOsuJudgement explosion = poolDictionary[result.Type].Get(doj => { - if (doj.Result != null) - Logger.Log("reused!"); - doj.Result = result; doj.JudgedObject = judgedObject; - doj.Origin = Anchor.Centre; - doj.Position = ((OsuHitObject)judgedObject.HitObject).StackedEndPosition; - doj.Scale = new Vector2(((OsuHitObject)judgedObject.HitObject).Scale); + // todo: move to JudgedObject property? + doj.Position = osuObject.StackedEndPosition; + doj.Scale = new Vector2(osuObject.Scale); }); judgementLayer.Add(explosion); @@ -117,5 +127,26 @@ namespace osu.Game.Rulesets.Osu.UI { public void Add(Drawable approachCircleProxy) => AddInternal(approachCircleProxy); } + + private class DrawableJudgementPool : DrawablePool + { + private readonly HitResult result; + + public DrawableJudgementPool(HitResult result) + : base(10) + { + this.result = result; + } + + protected override DrawableOsuJudgement CreateNewDrawable() + { + var judgement = base.CreateNewDrawable(); + + // just a placeholder to initialise the correct drawable hierarchy for this pool. + judgement.Result = new JudgementResult(new HitObject(), new Judgement()) { Type = result }; + + return judgement; + } + } } } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 86dd02f2bb..3ec5326299 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -75,6 +75,7 @@ namespace osu.Game.Rulesets.Judgements public DrawableJudgement() { Size = new Vector2(judgement_size); + Origin = Anchor.Centre; } protected virtual void ApplyHitAnimations() From 7550097eb66f149c71338d2e12da3216300a9b9e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 6 Jul 2020 07:27:53 +0300 Subject: [PATCH 692/871] Implement NewsCard --- .../Visual/Online/TestSceneNewsCard.cs | 52 +++++ .../Online/API/Requests/Responses/NewsPost.cs | 4 +- osu.Game/Overlays/News/NewsCard.cs | 196 ++++++++++++++++++ 3 files changed, 250 insertions(+), 2 deletions(-) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs create mode 100644 osu.Game/Overlays/News/NewsCard.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs new file mode 100644 index 0000000000..17e3d3eb7f --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics; +using osu.Game.Overlays.News; +using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Allocation; +using osu.Game.Overlays; +using osuTK; +using System; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneNewsCard : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Purple); + + public TestSceneNewsCard() + { + Add(new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + Width = 500, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 20), + Children = new[] + { + new NewsCard(new NewsPost + { + Title = "This post has an image which starts with \"/\" and has many authors!", + Preview = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + Author = "someone, someone1, someone2, someone3, someone4", + FirstImage = "/help/wiki/shared/news/banners/monthly-beatmapping-contest.png", + PublishedAt = DateTime.Now + }), + new NewsCard(new NewsPost + { + Title = "This post has a full-url image!", + Preview = "boom", + Author = "user", + FirstImage = "https://assets.ppy.sh/artists/88/header.jpg", + PublishedAt = DateTime.Now + }) + } + }); + } + } +} diff --git a/osu.Game/Online/API/Requests/Responses/NewsPost.cs b/osu.Game/Online/API/Requests/Responses/NewsPost.cs index f3ee0f9c35..fa10d7aa5c 100644 --- a/osu.Game/Online/API/Requests/Responses/NewsPost.cs +++ b/osu.Game/Online/API/Requests/Responses/NewsPost.cs @@ -21,10 +21,10 @@ namespace osu.Game.Online.API.Requests.Responses public string FirstImage { get; set; } [JsonProperty(@"published_at")] - public DateTimeOffset PublishedAt { get; set; } + public DateTime PublishedAt { get; set; } [JsonProperty(@"updated_at")] - public DateTimeOffset UpdatedAt { get; set; } + public DateTime UpdatedAt { get; set; } [JsonProperty(@"slug")] public string Slug { get; set; } diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs new file mode 100644 index 0000000000..052f8edf52 --- /dev/null +++ b/osu.Game/Overlays/News/NewsCard.cs @@ -0,0 +1,196 @@ +// 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.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Overlays.News +{ + public class NewsCard : CompositeDrawable + { + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + private readonly NewsPost post; + + private Box background; + private TextFlowContainer main; + + public NewsCard(NewsPost post) + { + this.post = post; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Masking = true; + CornerRadius = 6; + + NewsBackground bg; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4 + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = 160, + Masking = true, + CornerRadius = 6, + Children = new Drawable[] + { + new DelayedLoadWrapper(bg = new NewsBackground(post.FirstImage) + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0 + }) + { + RelativeSizeAxes = Axes.Both + }, + new DateContainer(post.PublishedAt) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Margin = new MarginPadding + { + Top = 10, + Right = 15 + } + } + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Horizontal = 15, + Vertical = 10 + }, + Child = main = new TextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + } + } + }, + new HoverClickSounds() + }; + + bg.OnLoadComplete += d => d.FadeIn(250, Easing.In); + + main.AddParagraph(post.Title, t => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold)); + main.AddParagraph(post.Preview, t => t.Font = OsuFont.GetFont(size: 12)); // Should use sans-serif font + main.AddParagraph("by ", t => t.Font = OsuFont.GetFont(size: 12)); + main.AddText(post.Author, t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)); + } + + protected override bool OnHover(HoverEvent e) + { + background.FadeColour(colourProvider.Background3, 200, Easing.OutQuint); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + background.FadeColour(colourProvider.Background4, 200, Easing.OutQuint); + base.OnHoverLost(e); + } + + [LongRunningLoad] + private class NewsBackground : Sprite + { + private readonly string sourceUrl; + + public NewsBackground(string sourceUrl) + { + this.sourceUrl = sourceUrl; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore store) + { + Texture = store.Get(createUrl(sourceUrl)); + } + + private string createUrl(string source) + { + if (string.IsNullOrEmpty(source)) + return "Headers/news"; + + if (source.StartsWith('/')) + return "https://osu.ppy.sh" + source; + + return source; + } + } + + private class DateContainer : CircularContainer, IHasTooltip + { + public string TooltipText => date.ToString("d MMMM yyyy hh:mm:ss UTCz"); + + private readonly DateTime date; + + public DateContainer(DateTime date) + { + this.date = date; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + AutoSizeAxes = Axes.Both; + Masking = true; + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6.Opacity(0.5f) + }, + new OsuSpriteText + { + Text = date.ToString("d MMM yyyy").ToUpper(), + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Margin = new MarginPadding + { + Horizontal = 20, + Vertical = 5 + } + } + }; + } + } + } +} From fdb7727e956a1de4f94b261b78abd5b6974d67bc Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 6 Jul 2020 07:28:44 +0300 Subject: [PATCH 693/871] Rename NewsPost to APINewsPost --- osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs | 4 ++-- .../API/Requests/Responses/{NewsPost.cs => APINewsPost.cs} | 2 +- osu.Game/Overlays/News/NewsCard.cs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename osu.Game/Online/API/Requests/Responses/{NewsPost.cs => APINewsPost.cs} (97%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs index 17e3d3eb7f..73218794a9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs @@ -29,7 +29,7 @@ namespace osu.Game.Tests.Visual.Online Spacing = new Vector2(0, 20), Children = new[] { - new NewsCard(new NewsPost + new NewsCard(new APINewsPost { Title = "This post has an image which starts with \"/\" and has many authors!", Preview = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", @@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Online FirstImage = "/help/wiki/shared/news/banners/monthly-beatmapping-contest.png", PublishedAt = DateTime.Now }), - new NewsCard(new NewsPost + new NewsCard(new APINewsPost { Title = "This post has a full-url image!", Preview = "boom", diff --git a/osu.Game/Online/API/Requests/Responses/NewsPost.cs b/osu.Game/Online/API/Requests/Responses/APINewsPost.cs similarity index 97% rename from osu.Game/Online/API/Requests/Responses/NewsPost.cs rename to osu.Game/Online/API/Requests/Responses/APINewsPost.cs index fa10d7aa5c..e25ad32594 100644 --- a/osu.Game/Online/API/Requests/Responses/NewsPost.cs +++ b/osu.Game/Online/API/Requests/Responses/APINewsPost.cs @@ -6,7 +6,7 @@ using System; namespace osu.Game.Online.API.Requests.Responses { - public class NewsPost + public class APINewsPost { [JsonProperty(@"id")] public long Id { get; set; } diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs index 052f8edf52..994b3c8fd1 100644 --- a/osu.Game/Overlays/News/NewsCard.cs +++ b/osu.Game/Overlays/News/NewsCard.cs @@ -23,12 +23,12 @@ namespace osu.Game.Overlays.News [Resolved] private OverlayColourProvider colourProvider { get; set; } - private readonly NewsPost post; + private readonly APINewsPost post; private Box background; private TextFlowContainer main; - public NewsCard(NewsPost post) + public NewsCard(APINewsPost post) { this.post = post; } From dbbee481f60b21911b5c67fa1d575272ac7a3a25 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Jul 2020 22:01:45 +0900 Subject: [PATCH 694/871] Expose dialog body text getter --- osu.Game/Overlays/Dialog/PopupDialog.cs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 02ef900dc5..1bcbe4dd2f 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -42,25 +42,34 @@ namespace osu.Game.Overlays.Dialog set => icon.Icon = value; } - private string text; + private string headerText; public string HeaderText { - get => text; + get => headerText; set { - if (text == value) + if (headerText == value) return; - text = value; - + headerText = value; header.Text = value; } } + private string bodyText; + public string BodyText { - set => body.Text = value; + get => bodyText; + set + { + if (bodyText == value) + return; + + bodyText = value; + body.Text = value; + } } public IEnumerable Buttons From 1effe71ec2279ea53aafe07e230160312612b5e4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Jul 2020 22:03:09 +0900 Subject: [PATCH 695/871] Add dialog for storage options --- osu.Game/IO/OsuStorage.cs | 95 ++++++++++++++++++++++++++----- osu.Game/Screens/Menu/MainMenu.cs | 79 +++++++++++++++++++++++++ 2 files changed, 161 insertions(+), 13 deletions(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 8bcc0941c1..3d6903c56f 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -2,9 +2,11 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Diagnostics; using System.IO; using System.Linq; using System.Threading; +using JetBrains.Annotations; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Configuration; @@ -13,12 +15,30 @@ namespace osu.Game.IO { public class OsuStorage : WrappedStorage { + /// + /// Indicates the error (if any) that occurred when initialising the custom storage during initial startup. + /// + public readonly OsuStorageError Error; + + /// + /// The custom storage path as selected by the user. + /// + [CanBeNull] + public string CustomStoragePath => storageConfig.Get(StorageConfig.FullPath); + + /// + /// The default storage path to be used if a custom storage path hasn't been selected or is not accessible. + /// + [NotNull] + public string DefaultStoragePath => defaultStorage.GetFullPath("."); + private readonly GameHost host; private readonly StorageConfigManager storageConfig; + private readonly Storage defaultStorage; - internal static readonly string[] IGNORE_DIRECTORIES = { "cache" }; + public static readonly string[] IGNORE_DIRECTORIES = { "cache" }; - internal static readonly string[] IGNORE_FILES = + public static readonly string[] IGNORE_FILES = { "framework.ini", "storage.ini" @@ -28,23 +48,53 @@ namespace osu.Game.IO : base(defaultStorage, string.Empty) { this.host = host; + this.defaultStorage = defaultStorage; storageConfig = new StorageConfigManager(defaultStorage); - var customStoragePath = storageConfig.Get(StorageConfig.FullPath); + if (!string.IsNullOrEmpty(CustomStoragePath)) + TryChangeToCustomStorage(out Error); + } - if (!string.IsNullOrEmpty(customStoragePath)) + /// + /// Resets the custom storage path, changing the target storage to the default location. + /// + public void ResetCustomStoragePath() + { + storageConfig.Set(StorageConfig.FullPath, string.Empty); + storageConfig.Save(); + + ChangeTargetStorage(defaultStorage); + } + + /// + /// Attempts to change to the user's custom storage path. + /// + /// The error that occurred. + /// Whether the custom storage path was used successfully. If not, will be populated with the reason. + public bool TryChangeToCustomStorage(out OsuStorageError error) + { + Debug.Assert(!string.IsNullOrEmpty(CustomStoragePath)); + + error = OsuStorageError.None; + Storage lastStorage = UnderlyingStorage; + + try { - try - { - ChangeTargetStorage(host.GetStorage(customStoragePath)); - } - catch (Exception ex) - { - Logger.Log($"Couldn't use custom storage path ({customStoragePath}): {ex}. Using default path.", LoggingTarget.Runtime, LogLevel.Error); - ChangeTargetStorage(defaultStorage); - } + Storage userStorage = host.GetStorage(CustomStoragePath); + + if (!userStorage.GetFiles(".").Any()) + error = OsuStorageError.AccessibleButEmpty; + + ChangeTargetStorage(userStorage); } + catch + { + error = OsuStorageError.NotAccessible; + ChangeTargetStorage(lastStorage); + } + + return error == OsuStorageError.None; } protected override void ChangeTargetStorage(Storage newStorage) @@ -155,4 +205,23 @@ namespace osu.Game.IO } } } + + public enum OsuStorageError + { + /// + /// No error. + /// + None, + + /// + /// Occurs when the target storage directory is accessible but does not already contain game files. + /// Only happens when the user changes the storage directory and then moves the files manually or mounts a different device to the same path. + /// + AccessibleButEmpty, + + /// + /// Occurs when the target storage directory cannot be accessed at all. + /// + NotAccessible, + } } diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 9245df2a7d..c391742c45 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osuTK; using osuTK.Graphics; @@ -15,6 +16,7 @@ using osu.Framework.Screens; using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.IO; using osu.Game.Online.API; using osu.Game.Overlays; using osu.Game.Overlays.Dialog; @@ -171,6 +173,9 @@ namespace osu.Game.Screens.Menu return s; } + [Resolved] + private Storage storage { get; set; } + public override void OnEntering(IScreen last) { base.OnEntering(last); @@ -187,6 +192,9 @@ namespace osu.Game.Screens.Menu Track.Start(); } } + + if (storage is OsuStorage osuStorage && osuStorage.Error != OsuStorageError.None) + dialogOverlay?.Push(new StorageErrorDialog(osuStorage, osuStorage.Error)); } private bool exitConfirmed; @@ -308,5 +316,76 @@ namespace osu.Game.Screens.Menu }; } } + + private class StorageErrorDialog : PopupDialog + { + [Resolved] + private DialogOverlay dialogOverlay { get; set; } + + [Resolved] + private OsuGameBase osuGame { get; set; } + + public StorageErrorDialog(OsuStorage storage, OsuStorageError error) + { + HeaderText = "osu! storage error"; + Icon = FontAwesome.Solid.ExclamationTriangle; + + var buttons = new List(); + + BodyText = $"osu! encountered an error when trying to use the custom storage path ('{storage.CustomStoragePath}').\n\n"; + + switch (error) + { + case OsuStorageError.NotAccessible: + BodyText += $"The default storage path ('{storage.DefaultStoragePath}') is currently being used because the custom storage path is not accessible.\n\n" + + "Is it on a removable device that is not currently connected?"; + + buttons.AddRange(new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = "Try again", + Action = () => + { + if (!storage.TryChangeToCustomStorage(out var nextError)) + dialogOverlay.Push(new StorageErrorDialog(storage, nextError)); + } + }, + new PopupDialogOkButton + { + Text = "Use the default path from now on", + Action = storage.ResetCustomStoragePath + }, + new PopupDialogCancelButton + { + Text = "Only use the default path for this session", + }, + }); + break; + + case OsuStorageError.AccessibleButEmpty: + BodyText += "The custom storage path is currently being used but is empty.\n\n" + + "Have you moved the files elsewhere?"; + + // Todo: Provide the option to search for the files similar to migration. + buttons.AddRange(new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = "Reset to default", + Action = storage.ResetCustomStoragePath + }, + new PopupDialogCancelButton + { + Text = "Keep using the custom path" + } + }); + + break; + } + + Buttons = buttons; + } + } } } From 8f792603ee6b03d19bba1ffc7d74203904205a35 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 6 Jul 2020 22:40:45 +0900 Subject: [PATCH 696/871] Apply suggestions from code review Co-authored-by: Dean Herbert --- osu.Game/Screens/Menu/MainMenu.cs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index c391742c45..d64d9b69fe 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -332,13 +332,11 @@ namespace osu.Game.Screens.Menu var buttons = new List(); - BodyText = $"osu! encountered an error when trying to use the custom storage path ('{storage.CustomStoragePath}').\n\n"; switch (error) { case OsuStorageError.NotAccessible: - BodyText += $"The default storage path ('{storage.DefaultStoragePath}') is currently being used because the custom storage path is not accessible.\n\n" - + "Is it on a removable device that is not currently connected?"; + BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is not accessible. If it is on external storage, please reconnect the device and try again."; buttons.AddRange(new PopupDialogButton[] { @@ -353,31 +351,30 @@ namespace osu.Game.Screens.Menu }, new PopupDialogOkButton { - Text = "Use the default path from now on", + Text = "Reset to default location", Action = storage.ResetCustomStoragePath }, new PopupDialogCancelButton { - Text = "Only use the default path for this session", + Text = "Use default location for this session", }, }); break; case OsuStorageError.AccessibleButEmpty: - BodyText += "The custom storage path is currently being used but is empty.\n\n" - + "Have you moved the files elsewhere?"; + BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is empty. If you have moved the files, please close osu! and move them back."; // Todo: Provide the option to search for the files similar to migration. buttons.AddRange(new PopupDialogButton[] { new PopupDialogOkButton { - Text = "Reset to default", + Text = "Reset to default location", Action = storage.ResetCustomStoragePath }, new PopupDialogCancelButton { - Text = "Keep using the custom path" + Text = "Start fresh at specified location" } }); From ddac511c8c5c3b4ef641c1a80e4e9dbbe6359ce4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Jul 2020 22:41:51 +0900 Subject: [PATCH 697/871] Move start fresh button above --- osu.Game/Screens/Menu/MainMenu.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index d64d9b69fe..dcb141cce5 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -332,7 +332,6 @@ namespace osu.Game.Screens.Menu var buttons = new List(); - switch (error) { case OsuStorageError.NotAccessible: @@ -367,15 +366,15 @@ namespace osu.Game.Screens.Menu // Todo: Provide the option to search for the files similar to migration. buttons.AddRange(new PopupDialogButton[] { + new PopupDialogCancelButton + { + Text = "Start fresh at specified location" + }, new PopupDialogOkButton { Text = "Reset to default location", Action = storage.ResetCustomStoragePath }, - new PopupDialogCancelButton - { - Text = "Start fresh at specified location" - } }); break; From 00a2fbce06ac67d5bc077f10e43c302c98af629e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 6 Jul 2020 22:41:58 +0900 Subject: [PATCH 698/871] Fix test failures --- osu.Game/IO/OsuStorage.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/IO/OsuStorage.cs b/osu.Game/IO/OsuStorage.cs index 3d6903c56f..1d15294666 100644 --- a/osu.Game/IO/OsuStorage.cs +++ b/osu.Game/IO/OsuStorage.cs @@ -83,7 +83,7 @@ namespace osu.Game.IO { Storage userStorage = host.GetStorage(CustomStoragePath); - if (!userStorage.GetFiles(".").Any()) + if (!userStorage.ExistsDirectory(".") || !userStorage.GetFiles(".").Any()) error = OsuStorageError.AccessibleButEmpty; ChangeTargetStorage(userStorage); From a650a5ec83ca93d17709c9c34fb5922857e23d90 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Jul 2020 23:44:26 +0900 Subject: [PATCH 699/871] Move dialog classes to own file --- osu.Game/Screens/Menu/ConfirmExitDialog.cs | 34 ++++++++ osu.Game/Screens/Menu/MainMenu.cs | 96 --------------------- osu.Game/Screens/Menu/StorageErrorDialog.cs | 79 +++++++++++++++++ 3 files changed, 113 insertions(+), 96 deletions(-) create mode 100644 osu.Game/Screens/Menu/ConfirmExitDialog.cs create mode 100644 osu.Game/Screens/Menu/StorageErrorDialog.cs diff --git a/osu.Game/Screens/Menu/ConfirmExitDialog.cs b/osu.Game/Screens/Menu/ConfirmExitDialog.cs new file mode 100644 index 0000000000..d120eb21a8 --- /dev/null +++ b/osu.Game/Screens/Menu/ConfirmExitDialog.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; +using osu.Framework.Graphics.Sprites; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.Menu +{ + public class ConfirmExitDialog : PopupDialog + { + public ConfirmExitDialog(Action confirm, Action cancel) + { + HeaderText = "Are you sure you want to exit?"; + BodyText = "Last chance to back out."; + + Icon = FontAwesome.Solid.ExclamationTriangle; + + Buttons = new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = @"Goodbye", + Action = confirm + }, + new PopupDialogCancelButton + { + Text = @"Just a little more", + Action = cancel + }, + }; + } + } +} diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index dcb141cce5..76950982e6 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.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; -using System.Collections.Generic; using System.Linq; using osuTK; using osuTK.Graphics; @@ -10,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Sprites; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Game.Configuration; @@ -19,7 +16,6 @@ using osu.Game.Graphics.Containers; using osu.Game.IO; using osu.Game.Online.API; using osu.Game.Overlays; -using osu.Game.Overlays.Dialog; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Edit; using osu.Game.Screens.Multi; @@ -291,97 +287,5 @@ namespace osu.Game.Screens.Menu this.FadeOut(3000); return base.OnExiting(next); } - - private class ConfirmExitDialog : PopupDialog - { - public ConfirmExitDialog(Action confirm, Action cancel) - { - HeaderText = "Are you sure you want to exit?"; - BodyText = "Last chance to back out."; - - Icon = FontAwesome.Solid.ExclamationTriangle; - - Buttons = new PopupDialogButton[] - { - new PopupDialogOkButton - { - Text = @"Goodbye", - Action = confirm - }, - new PopupDialogCancelButton - { - Text = @"Just a little more", - Action = cancel - }, - }; - } - } - - private class StorageErrorDialog : PopupDialog - { - [Resolved] - private DialogOverlay dialogOverlay { get; set; } - - [Resolved] - private OsuGameBase osuGame { get; set; } - - public StorageErrorDialog(OsuStorage storage, OsuStorageError error) - { - HeaderText = "osu! storage error"; - Icon = FontAwesome.Solid.ExclamationTriangle; - - var buttons = new List(); - - switch (error) - { - case OsuStorageError.NotAccessible: - BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is not accessible. If it is on external storage, please reconnect the device and try again."; - - buttons.AddRange(new PopupDialogButton[] - { - new PopupDialogOkButton - { - Text = "Try again", - Action = () => - { - if (!storage.TryChangeToCustomStorage(out var nextError)) - dialogOverlay.Push(new StorageErrorDialog(storage, nextError)); - } - }, - new PopupDialogOkButton - { - Text = "Reset to default location", - Action = storage.ResetCustomStoragePath - }, - new PopupDialogCancelButton - { - Text = "Use default location for this session", - }, - }); - break; - - case OsuStorageError.AccessibleButEmpty: - BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is empty. If you have moved the files, please close osu! and move them back."; - - // Todo: Provide the option to search for the files similar to migration. - buttons.AddRange(new PopupDialogButton[] - { - new PopupDialogCancelButton - { - Text = "Start fresh at specified location" - }, - new PopupDialogOkButton - { - Text = "Reset to default location", - Action = storage.ResetCustomStoragePath - }, - }); - - break; - } - - Buttons = buttons; - } - } } } diff --git a/osu.Game/Screens/Menu/StorageErrorDialog.cs b/osu.Game/Screens/Menu/StorageErrorDialog.cs new file mode 100644 index 0000000000..38a6c07ce7 --- /dev/null +++ b/osu.Game/Screens/Menu/StorageErrorDialog.cs @@ -0,0 +1,79 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Game.IO; +using osu.Game.Overlays; +using osu.Game.Overlays.Dialog; + +namespace osu.Game.Screens.Menu +{ + public class StorageErrorDialog : PopupDialog + { + [Resolved] + private DialogOverlay dialogOverlay { get; set; } + + [Resolved] + private OsuGameBase osuGame { get; set; } + + public StorageErrorDialog(OsuStorage storage, OsuStorageError error) + { + HeaderText = "osu! storage error"; + Icon = FontAwesome.Solid.ExclamationTriangle; + + var buttons = new List(); + + switch (error) + { + case OsuStorageError.NotAccessible: + BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is not accessible. If it is on external storage, please reconnect the device and try again."; + + buttons.AddRange(new PopupDialogButton[] + { + new PopupDialogOkButton + { + Text = "Try again", + Action = () => + { + if (!storage.TryChangeToCustomStorage(out var nextError)) + dialogOverlay.Push(new StorageErrorDialog(storage, nextError)); + } + }, + new PopupDialogOkButton + { + Text = "Reset to default location", + Action = storage.ResetCustomStoragePath + }, + new PopupDialogCancelButton + { + Text = "Use default location for this session", + }, + }); + break; + + case OsuStorageError.AccessibleButEmpty: + BodyText = $"The specified osu! data location (\"{storage.CustomStoragePath}\") is empty. If you have moved the files, please close osu! and move them back."; + + // Todo: Provide the option to search for the files similar to migration. + buttons.AddRange(new PopupDialogButton[] + { + new PopupDialogCancelButton + { + Text = "Start fresh at specified location" + }, + new PopupDialogOkButton + { + Text = "Reset to default location", + Action = storage.ResetCustomStoragePath + }, + }); + + break; + } + + Buttons = buttons; + } + } +} From 3f3bfb1ffbe001ed4016b6750ff830c5f983279b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 6 Jul 2020 23:51:16 +0900 Subject: [PATCH 700/871] Minor reshuffling / recolouring --- osu.Game/Screens/Menu/StorageErrorDialog.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Menu/StorageErrorDialog.cs b/osu.Game/Screens/Menu/StorageErrorDialog.cs index 38a6c07ce7..dcaad4013a 100644 --- a/osu.Game/Screens/Menu/StorageErrorDialog.cs +++ b/osu.Game/Screens/Menu/StorageErrorDialog.cs @@ -32,7 +32,7 @@ namespace osu.Game.Screens.Menu buttons.AddRange(new PopupDialogButton[] { - new PopupDialogOkButton + new PopupDialogCancelButton { Text = "Try again", Action = () => @@ -41,15 +41,15 @@ namespace osu.Game.Screens.Menu dialogOverlay.Push(new StorageErrorDialog(storage, nextError)); } }, + new PopupDialogCancelButton + { + Text = "Use default location until restart", + }, new PopupDialogOkButton { Text = "Reset to default location", Action = storage.ResetCustomStoragePath }, - new PopupDialogCancelButton - { - Text = "Use default location for this session", - }, }); break; From ebbc8298917db15105130ea2b12e1dab67173a88 Mon Sep 17 00:00:00 2001 From: Rsplwe Date: Tue, 7 Jul 2020 00:15:27 +0800 Subject: [PATCH 701/871] disable HardwareAccelerated --- osu.Android/OsuGameActivity.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android/OsuGameActivity.cs b/osu.Android/OsuGameActivity.cs index 2e5fa59d20..9839d16030 100644 --- a/osu.Android/OsuGameActivity.cs +++ b/osu.Android/OsuGameActivity.cs @@ -9,7 +9,7 @@ using osu.Framework.Android; namespace osu.Android { - [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullSensor, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = true)] + [Activity(Theme = "@android:style/Theme.NoTitleBar", MainLauncher = true, ScreenOrientation = ScreenOrientation.FullSensor, SupportsPictureInPicture = false, ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize, HardwareAccelerated = false)] public class OsuGameActivity : AndroidGameActivity { protected override Framework.Game CreateGame() => new OsuGameAndroid(); From 9dde101f12201e66b92005a31773125e44629bd1 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 6 Jul 2020 23:53:27 +0300 Subject: [PATCH 702/871] Remove string prefixes --- .../API/Requests/Responses/APINewsPost.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/API/Requests/Responses/APINewsPost.cs b/osu.Game/Online/API/Requests/Responses/APINewsPost.cs index e25ad32594..5cd94efdd2 100644 --- a/osu.Game/Online/API/Requests/Responses/APINewsPost.cs +++ b/osu.Game/Online/API/Requests/Responses/APINewsPost.cs @@ -8,31 +8,31 @@ namespace osu.Game.Online.API.Requests.Responses { public class APINewsPost { - [JsonProperty(@"id")] + [JsonProperty("id")] public long Id { get; set; } - [JsonProperty(@"author")] + [JsonProperty("author")] public string Author { get; set; } - [JsonProperty(@"edit_url")] + [JsonProperty("edit_url")] public string EditUrl { get; set; } - [JsonProperty(@"first_image")] + [JsonProperty("first_image")] public string FirstImage { get; set; } - [JsonProperty(@"published_at")] + [JsonProperty("published_at")] public DateTime PublishedAt { get; set; } - [JsonProperty(@"updated_at")] + [JsonProperty("updated_at")] public DateTime UpdatedAt { get; set; } - [JsonProperty(@"slug")] + [JsonProperty("slug")] public string Slug { get; set; } - [JsonProperty(@"title")] + [JsonProperty("title")] public string Title { get; set; } - [JsonProperty(@"preview")] + [JsonProperty("preview")] public string Preview { get; set; } } } From 68d9f9de4629da8b41bc4389b878cf826bb76bb8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 6 Jul 2020 23:55:20 +0300 Subject: [PATCH 703/871] Use DateTimeOffset --- osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs | 4 ++-- osu.Game/Online/API/Requests/Responses/APINewsPost.cs | 4 ++-- osu.Game/Overlays/News/NewsCard.cs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs index 73218794a9..82f603df6a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs @@ -35,7 +35,7 @@ namespace osu.Game.Tests.Visual.Online Preview = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", Author = "someone, someone1, someone2, someone3, someone4", FirstImage = "/help/wiki/shared/news/banners/monthly-beatmapping-contest.png", - PublishedAt = DateTime.Now + PublishedAt = DateTimeOffset.Now }), new NewsCard(new APINewsPost { @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Online Preview = "boom", Author = "user", FirstImage = "https://assets.ppy.sh/artists/88/header.jpg", - PublishedAt = DateTime.Now + PublishedAt = DateTimeOffset.Now }) } }); diff --git a/osu.Game/Online/API/Requests/Responses/APINewsPost.cs b/osu.Game/Online/API/Requests/Responses/APINewsPost.cs index 5cd94efdd2..7cc6907949 100644 --- a/osu.Game/Online/API/Requests/Responses/APINewsPost.cs +++ b/osu.Game/Online/API/Requests/Responses/APINewsPost.cs @@ -21,10 +21,10 @@ namespace osu.Game.Online.API.Requests.Responses public string FirstImage { get; set; } [JsonProperty("published_at")] - public DateTime PublishedAt { get; set; } + public DateTimeOffset PublishedAt { get; set; } [JsonProperty("updated_at")] - public DateTime UpdatedAt { get; set; } + public DateTimeOffset UpdatedAt { get; set; } [JsonProperty("slug")] public string Slug { get; set; } diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs index 994b3c8fd1..08a9fccc4e 100644 --- a/osu.Game/Overlays/News/NewsCard.cs +++ b/osu.Game/Overlays/News/NewsCard.cs @@ -160,9 +160,9 @@ namespace osu.Game.Overlays.News { public string TooltipText => date.ToString("d MMMM yyyy hh:mm:ss UTCz"); - private readonly DateTime date; + private readonly DateTimeOffset date; - public DateContainer(DateTime date) + public DateContainer(DateTimeOffset date) { this.date = date; } From c86bb2e755d9c43bfb7130d22883b19f40c8e3d2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 7 Jul 2020 00:01:06 +0300 Subject: [PATCH 704/871] Use DrawableDate tooltip for DateContainer --- osu.Game/Graphics/DrawableDate.cs | 2 +- osu.Game/Overlays/News/NewsCard.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index 8b6df4a834..953b7541e1 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -82,7 +82,7 @@ namespace osu.Game.Graphics public object TooltipContent => Date; - private class DateTooltip : VisibilityContainer, ITooltip + public class DateTooltip : VisibilityContainer, ITooltip { private readonly OsuSpriteText dateText, timeText; private readonly Box background; diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs index 08a9fccc4e..c22a3268bf 100644 --- a/osu.Game/Overlays/News/NewsCard.cs +++ b/osu.Game/Overlays/News/NewsCard.cs @@ -156,9 +156,11 @@ namespace osu.Game.Overlays.News } } - private class DateContainer : CircularContainer, IHasTooltip + private class DateContainer : CircularContainer, IHasCustomTooltip { - public string TooltipText => date.ToString("d MMMM yyyy hh:mm:ss UTCz"); + public ITooltip GetCustomTooltip() => new DrawableDate.DateTooltip(); + + public object TooltipContent => date; private readonly DateTimeOffset date; From 857a027a7366952209e7548c0fe40ea371bf75f8 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 7 Jul 2020 00:11:35 +0300 Subject: [PATCH 705/871] Parse HTML entities during APINewsPost deserialisation --- .../Visual/Online/TestSceneNewsCard.cs | 6 ++--- .../API/Requests/Responses/APINewsPost.cs | 25 ++++++++++++++++--- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs index 82f603df6a..0446cadac9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs @@ -39,9 +39,9 @@ namespace osu.Game.Tests.Visual.Online }), new NewsCard(new APINewsPost { - Title = "This post has a full-url image!", - Preview = "boom", - Author = "user", + Title = "This post has a full-url image! (HTML entity: &)", + Preview = "boom (HTML entity: &)", + Author = "user (HTML entity: &)", FirstImage = "https://assets.ppy.sh/artists/88/header.jpg", PublishedAt = DateTimeOffset.Now }) diff --git a/osu.Game/Online/API/Requests/Responses/APINewsPost.cs b/osu.Game/Online/API/Requests/Responses/APINewsPost.cs index 7cc6907949..ced08f0bf2 100644 --- a/osu.Game/Online/API/Requests/Responses/APINewsPost.cs +++ b/osu.Game/Online/API/Requests/Responses/APINewsPost.cs @@ -3,6 +3,7 @@ using Newtonsoft.Json; using System; +using System.Net; namespace osu.Game.Online.API.Requests.Responses { @@ -11,8 +12,14 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("id")] public long Id { get; set; } + private string author; + [JsonProperty("author")] - public string Author { get; set; } + public string Author + { + get => author; + set => author = WebUtility.HtmlDecode(value); + } [JsonProperty("edit_url")] public string EditUrl { get; set; } @@ -29,10 +36,22 @@ namespace osu.Game.Online.API.Requests.Responses [JsonProperty("slug")] public string Slug { get; set; } + private string title; + [JsonProperty("title")] - public string Title { get; set; } + public string Title + { + get => title; + set => title = WebUtility.HtmlDecode(value); + } + + private string preview; [JsonProperty("preview")] - public string Preview { get; set; } + public string Preview + { + get => preview; + set => preview = WebUtility.HtmlDecode(value); + } } } From 88b2a12c0942e6296f453456a42e8a7958a92488 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Jul 2020 17:38:42 +0900 Subject: [PATCH 706/871] Reduce footer height to match back button --- osu.Game/Screens/Multi/Match/Components/Footer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Match/Components/Footer.cs b/osu.Game/Screens/Multi/Match/Components/Footer.cs index 94d7df6194..be4ee873fa 100644 --- a/osu.Game/Screens/Multi/Match/Components/Footer.cs +++ b/osu.Game/Screens/Multi/Match/Components/Footer.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Multi.Match.Components { public class Footer : CompositeDrawable { - public const float HEIGHT = 100; + public const float HEIGHT = 50; public Action OnStart; public readonly Bindable SelectedItem = new Bindable(); From c74bfd5c88e2a55c35a996b9902e127f1da35df7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Jul 2020 17:42:20 +0900 Subject: [PATCH 707/871] Revert unintentional changes --- osu.Game/Tests/Visual/ModTestScene.cs | 13 +++++++++++++ osu.Game/Tests/Visual/ScreenTestScene.cs | 4 ++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game/Tests/Visual/ModTestScene.cs b/osu.Game/Tests/Visual/ModTestScene.cs index add851ebf3..23b5ad0bd8 100644 --- a/osu.Game/Tests/Visual/ModTestScene.cs +++ b/osu.Game/Tests/Visual/ModTestScene.cs @@ -21,6 +21,19 @@ namespace osu.Game.Tests.Visual AddStep("set test data", () => currentTestData = testData); }); + public override void TearDownSteps() + { + AddUntilStep("test passed", () => + { + if (currentTestData == null) + return true; + + return currentTestData.PassCondition?.Invoke() ?? false; + }); + + base.TearDownSteps(); + } + protected sealed override IBeatmap CreateBeatmap(RulesetInfo ruleset) => currentTestData?.Beatmap ?? base.CreateBeatmap(ruleset); protected sealed override TestPlayer CreatePlayer(Ruleset ruleset) diff --git a/osu.Game/Tests/Visual/ScreenTestScene.cs b/osu.Game/Tests/Visual/ScreenTestScene.cs index 067d8faf54..33cc00e748 100644 --- a/osu.Game/Tests/Visual/ScreenTestScene.cs +++ b/osu.Game/Tests/Visual/ScreenTestScene.cs @@ -33,8 +33,8 @@ namespace osu.Game.Tests.Visual [SetUpSteps] public virtual void SetUpSteps() => addExitAllScreensStep(); - // [TearDownSteps] - // public virtual void TearDownSteps() => addExitAllScreensStep(); + [TearDownSteps] + public virtual void TearDownSteps() => addExitAllScreensStep(); private void addExitAllScreensStep() { From 4a1bea48745e925ef46c300213e9da762afd2992 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Jul 2020 18:28:43 +0900 Subject: [PATCH 708/871] Adjust layout to be two columns (and more friendly to vertical screens) --- .../Multi/Components/OverlinedDisplay.cs | 5 ++- .../Screens/Multi/Match/MatchSubScreen.cs | 37 ++++++++++++++----- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs b/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs index d2bb3c4876..2b589256fa 100644 --- a/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs +++ b/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs @@ -86,7 +86,10 @@ namespace osu.Game.Screens.Multi.Components Text = title, Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold) }, - details = new OsuSpriteText { Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold) }, + details = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold) + }, } }, }, diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 1b2fdffa5e..a93caed09c 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -115,6 +115,7 @@ namespace osu.Game.Screens.Multi.Match { new Drawable[] { + null, new Container { RelativeSizeAxes = Axes.Both, @@ -151,19 +152,37 @@ namespace osu.Game.Screens.Multi.Match } } }, - new Container + null, + new GridContainer { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = 5 }, - Child = leaderboard = new OverlinedLeaderboard { RelativeSizeAxes = Axes.Both }, + Content = new[] + { + new Drawable[] + { + leaderboard = new OverlinedLeaderboard { RelativeSizeAxes = Axes.Both }, + }, + new Drawable[] + { + new OverlinedChatDisplay { RelativeSizeAxes = Axes.Both } + } + }, + RowDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Relative, size: 0.4f, minSize: 300), + } }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Left = 5 }, - Child = new OverlinedChatDisplay { RelativeSizeAxes = Axes.Both } - } + null }, + }, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400), + new Dimension(), + new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600), + new Dimension(), } } } From 4b4fcd39e396a95b82d4d1676ade91a693fbf8f2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Jul 2020 18:40:21 +0900 Subject: [PATCH 709/871] Further layout adjustments based on fedback --- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index a93caed09c..694315a3b3 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -115,7 +115,6 @@ namespace osu.Game.Screens.Multi.Match { new Drawable[] { - null, new Container { RelativeSizeAxes = Axes.Both, @@ -170,7 +169,7 @@ namespace osu.Game.Screens.Multi.Match RowDimensions = new[] { new Dimension(), - new Dimension(GridSizeMode.Relative, size: 0.4f, minSize: 300), + new Dimension(GridSizeMode.Relative, size: 0.4f, minSize: 240), } }, null @@ -178,7 +177,6 @@ namespace osu.Game.Screens.Multi.Match }, ColumnDimensions = new[] { - new Dimension(), new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 400), new Dimension(), new Dimension(GridSizeMode.Relative, size: 0.5f, maxSize: 600), From 8909bf628ca6d19843be0506b484d4ca7b609d55 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Jul 2020 21:08:13 +0900 Subject: [PATCH 710/871] 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 a2c97ead2f..0563e5319d 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3ef53a2a53..4e6de77e86 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 492bf89fab..c31e28638f 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 8152e0791dee15945908a565668e783d789a9514 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Jul 2020 21:47:44 +0900 Subject: [PATCH 711/871] Fix potential nullref --- osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs index 64b3afcae1..45ef793deb 100644 --- a/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs +++ b/osu.Game/Overlays/BeatmapListing/BeatmapSearchFilterRow.cs @@ -91,7 +91,8 @@ namespace osu.Game.Overlays.BeatmapListing [BackgroundDependencyLoader] private void load(OverlayColourProvider colourProvider) { - ((FilterDropdown)Dropdown).AccentColour = colourProvider.Light2; + if (Dropdown is FilterDropdown fd) + fd.AccentColour = colourProvider.Light2; } protected override Dropdown CreateDropdown() => new FilterDropdown(); From bdec13d4a48001b2b21c43b0e7c750ee4494bb88 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 7 Jul 2020 16:46:17 +0300 Subject: [PATCH 712/871] Move DateTooltip to it's on file --- osu.Game/Graphics/DateTooltip.cs | 78 ++++++++++++++++++++++++++++++ osu.Game/Graphics/DrawableDate.cs | 67 ------------------------- osu.Game/Overlays/News/NewsCard.cs | 2 +- 3 files changed, 79 insertions(+), 68 deletions(-) create mode 100644 osu.Game/Graphics/DateTooltip.cs diff --git a/osu.Game/Graphics/DateTooltip.cs b/osu.Game/Graphics/DateTooltip.cs new file mode 100644 index 0000000000..67fcab43f7 --- /dev/null +++ b/osu.Game/Graphics/DateTooltip.cs @@ -0,0 +1,78 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Graphics +{ + public class DateTooltip : VisibilityContainer, ITooltip + { + private readonly OsuSpriteText dateText, timeText; + private readonly Box background; + + public DateTooltip() + { + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 5; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + dateText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }, + timeText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + } + } + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.GreySeafoamDarker; + timeText.Colour = colours.BlueLighter; + } + + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); + + public bool SetContent(object content) + { + if (!(content is DateTimeOffset date)) + return false; + + dateText.Text = $"{date:d MMMM yyyy} "; + timeText.Text = $"{date:HH:mm:ss \"UTC\"z}"; + return true; + } + + public void Move(Vector2 pos) => Position = pos; + } +} diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index 953b7541e1..259d9c8d6e 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -4,12 +4,9 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; using osu.Game.Utils; -using osuTK; namespace osu.Game.Graphics { @@ -81,69 +78,5 @@ namespace osu.Game.Graphics public ITooltip GetCustomTooltip() => new DateTooltip(); public object TooltipContent => Date; - - public class DateTooltip : VisibilityContainer, ITooltip - { - private readonly OsuSpriteText dateText, timeText; - private readonly Box background; - - public DateTooltip() - { - AutoSizeAxes = Axes.Both; - Masking = true; - CornerRadius = 5; - - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Padding = new MarginPadding(10), - Children = new Drawable[] - { - dateText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - }, - timeText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - } - } - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.GreySeafoamDarker; - timeText.Colour = colours.BlueLighter; - } - - protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); - protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); - - public bool SetContent(object content) - { - if (!(content is DateTimeOffset date)) - return false; - - dateText.Text = $"{date:d MMMM yyyy} "; - timeText.Text = $"{date:HH:mm:ss \"UTC\"z}"; - return true; - } - - public void Move(Vector2 pos) => Position = pos; - } } } diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs index c22a3268bf..f9d7378279 100644 --- a/osu.Game/Overlays/News/NewsCard.cs +++ b/osu.Game/Overlays/News/NewsCard.cs @@ -158,7 +158,7 @@ namespace osu.Game.Overlays.News private class DateContainer : CircularContainer, IHasCustomTooltip { - public ITooltip GetCustomTooltip() => new DrawableDate.DateTooltip(); + public ITooltip GetCustomTooltip() => new DateTooltip(); public object TooltipContent => date; From c88a802b05b1ad2f13ad2c559e79af377444f50b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 7 Jul 2020 23:04:39 +0200 Subject: [PATCH 713/871] Adjust font size to match web design --- osu.Game/Overlays/News/NewsCard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs index f9d7378279..9c478a7c1d 100644 --- a/osu.Game/Overlays/News/NewsCard.cs +++ b/osu.Game/Overlays/News/NewsCard.cs @@ -184,7 +184,7 @@ namespace osu.Game.Overlays.News new OsuSpriteText { Text = date.ToString("d MMM yyyy").ToUpper(), - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), Margin = new MarginPadding { Horizontal = 20, From d98a64dfbc67b0689a9ca8a044b3d9954d232dcb Mon Sep 17 00:00:00 2001 From: Shivam Date: Wed, 8 Jul 2020 03:26:36 +0200 Subject: [PATCH 714/871] Make seeding # bg black and white text color Makes it consistent with TournamentSpriteTextWithBackground --- osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs index d48e396b89..eed3cac9f0 100644 --- a/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs +++ b/osu.Game.Tournament/Screens/TeamIntro/SeedingScreen.cs @@ -203,13 +203,14 @@ namespace osu.Game.Tournament.Screens.TeamIntro new Box { RelativeSizeAxes = Axes.Both, - Colour = TournamentGame.TEXT_COLOUR, + Colour = TournamentGame.ELEMENT_BACKGROUND_COLOUR, }, new TournamentSpriteText { Anchor = Anchor.Centre, Origin = Anchor.Centre, Text = seeding.ToString("#,0"), + Colour = TournamentGame.ELEMENT_FOREGROUND_COLOUR }, } }, From 0684ac90c6180f5debbf5b5aeb6dc9383aaf0166 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Jul 2020 13:36:32 +0900 Subject: [PATCH 715/871] Make toolbar opaque This is the general direction we're going with future designs. Just applying this now because it makes a lot of screens feel much better (multiplayer lobby / match, song select etc. where there are elements adjacent to the bar which cause the transparency to feel a bit awkward). --- osu.Game/Overlays/Toolbar/Toolbar.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 1b748cb672..ba6e52ec1d 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -28,9 +28,6 @@ namespace osu.Game.Overlays.Toolbar private const double transition_time = 500; - private const float alpha_hovering = 0.8f; - private const float alpha_normal = 0.6f; - private readonly Bindable overlayActivationMode = new Bindable(OverlayActivation.All); // Toolbar components like RulesetSelector should receive keyboard input events even when the toolbar is hidden. @@ -103,7 +100,6 @@ namespace osu.Game.Overlays.Toolbar public class ToolbarBackground : Container { - private readonly Box solidBackground; private readonly Box gradientBackground; public ToolbarBackground() @@ -111,11 +107,10 @@ namespace osu.Game.Overlays.Toolbar RelativeSizeAxes = Axes.Both; Children = new Drawable[] { - solidBackground = new Box + new Box { RelativeSizeAxes = Axes.Both, Colour = OsuColour.Gray(0.1f), - Alpha = alpha_normal, }, gradientBackground = new Box { @@ -131,14 +126,12 @@ namespace osu.Game.Overlays.Toolbar protected override bool OnHover(HoverEvent e) { - solidBackground.FadeTo(alpha_hovering, transition_time, Easing.OutQuint); gradientBackground.FadeIn(transition_time, Easing.OutQuint); return true; } protected override void OnHoverLost(HoverLostEvent e) { - solidBackground.FadeTo(alpha_normal, transition_time, Easing.OutQuint); gradientBackground.FadeOut(transition_time, Easing.OutQuint); } } From 35d329220028ed8e1c80760b29e4dfd325dbab4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 7 Jul 2020 19:23:11 +0900 Subject: [PATCH 716/871] Remove nesting of components inside overlined component I think this makes things a bit more readable. The only weird case is the transfer of details from the component to the `OverlinedHeader`, but bindables make it not too bad. --- .../TestSceneOverlinedParticipants.cs | 5 +- .../Multiplayer/TestSceneOverlinedPlaylist.cs | 4 +- .../Multi/Components/OverlinedDisplay.cs | 131 ------------------ .../Multi/Components/OverlinedHeader.cs | 89 ++++++++++++ .../Multi/Components/OverlinedPlaylist.cs | 33 ----- ...Participants.cs => ParticipantsDisplay.cs} | 31 +++-- .../Screens/Multi/DrawableRoomPlaylist.cs | 2 - .../Multi/Lounge/Components/RoomInspector.cs | 19 ++- .../Match/Components/OverlinedChatDisplay.cs | 20 --- .../Match/Components/OverlinedLeaderboard.cs | 24 ---- .../Screens/Multi/Match/MatchSubScreen.cs | 48 ++++--- 11 files changed, 151 insertions(+), 255 deletions(-) delete mode 100644 osu.Game/Screens/Multi/Components/OverlinedDisplay.cs create mode 100644 osu.Game/Screens/Multi/Components/OverlinedHeader.cs delete mode 100644 osu.Game/Screens/Multi/Components/OverlinedPlaylist.cs rename osu.Game/Screens/Multi/Components/{OverlinedParticipants.cs => ParticipantsDisplay.cs} (63%) delete mode 100644 osu.Game/Screens/Multi/Match/Components/OverlinedChatDisplay.cs delete mode 100644 osu.Game/Screens/Multi/Match/Components/OverlinedLeaderboard.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedParticipants.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedParticipants.cs index 7ea3bba23f..a13fcdaef8 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedParticipants.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedParticipants.cs @@ -22,12 +22,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create component", () => { - Child = new OverlinedParticipants(Direction.Horizontal) + Child = new ParticipantsDisplay(Direction.Horizontal) { Anchor = Anchor.Centre, Origin = Anchor.Centre, Width = 500, - AutoSizeAxes = Axes.Y, }; }); } @@ -37,7 +36,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { AddStep("create component", () => { - Child = new OverlinedParticipants(Direction.Vertical) + Child = new ParticipantsDisplay(Direction.Vertical) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs index 14b7934dc7..d3ffb9649e 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs @@ -4,7 +4,7 @@ using osu.Framework.Graphics; using osu.Game.Online.Multiplayer; using osu.Game.Rulesets.Osu; -using osu.Game.Screens.Multi.Components; +using osu.Game.Screens.Multi; using osu.Game.Tests.Beatmaps; using osuTK; @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } - Add(new OverlinedPlaylist(false) + Add(new DrawableRoomPlaylist(false, false) { Anchor = Anchor.Centre, Origin = Anchor.Centre, diff --git a/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs b/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs deleted file mode 100644 index 2b589256fa..0000000000 --- a/osu.Game/Screens/Multi/Components/OverlinedDisplay.cs +++ /dev/null @@ -1,131 +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 osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osuTK; - -namespace osu.Game.Screens.Multi.Components -{ - public abstract class OverlinedDisplay : MultiplayerComposite - { - protected readonly Container Content; - - public override Axes RelativeSizeAxes - { - get => base.RelativeSizeAxes; - set - { - base.RelativeSizeAxes = value; - updateDimensions(); - } - } - - public override Axes AutoSizeAxes - { - get => base.AutoSizeAxes; - protected set - { - base.AutoSizeAxes = value; - updateDimensions(); - } - } - - private bool showLine = true; - - public bool ShowLine - { - get => showLine; - set - { - showLine = value; - line.Alpha = value ? 1 : 0; - } - } - - protected string Details - { - set => details.Text = value; - } - - private readonly Circle line; - private readonly OsuSpriteText details; - private readonly GridContainer grid; - - protected OverlinedDisplay(string title) - { - InternalChild = grid = new GridContainer - { - Content = new[] - { - new Drawable[] - { - line = new Circle - { - RelativeSizeAxes = Axes.X, - Height = 2, - Margin = new MarginPadding { Bottom = 2 } - }, - }, - new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Margin = new MarginPadding { Top = 5 }, - Spacing = new Vector2(10, 0), - Children = new Drawable[] - { - new OsuSpriteText - { - Text = title, - Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold) - }, - details = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold) - }, - } - }, - }, - new Drawable[] - { - Content = new Container { Padding = new MarginPadding { Top = 5 } } - } - } - }; - - updateDimensions(); - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - line.Colour = colours.Yellow; - details.Colour = colours.Yellow; - } - - private void updateDimensions() - { - grid.RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(AutoSizeAxes.HasFlag(Axes.Y) ? GridSizeMode.AutoSize : GridSizeMode.Distributed), - }; - - // Assigning to none is done so that setting auto and relative size modes doesn't cause exceptions to be thrown - grid.AutoSizeAxes = Content.AutoSizeAxes = Axes.None; - grid.RelativeSizeAxes = Content.RelativeSizeAxes = Axes.None; - - // Auto-size when required, otherwise eagerly relative-size - grid.AutoSizeAxes = Content.AutoSizeAxes = AutoSizeAxes; - grid.RelativeSizeAxes = Content.RelativeSizeAxes = ~AutoSizeAxes; - } - } -} diff --git a/osu.Game/Screens/Multi/Components/OverlinedHeader.cs b/osu.Game/Screens/Multi/Components/OverlinedHeader.cs new file mode 100644 index 0000000000..7ec20c8cae --- /dev/null +++ b/osu.Game/Screens/Multi/Components/OverlinedHeader.cs @@ -0,0 +1,89 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Screens.Multi.Components +{ + /// + /// A header used in the multiplayer interface which shows text / details beneath a line. + /// + public class OverlinedHeader : MultiplayerComposite + { + private bool showLine = true; + + public bool ShowLine + { + get => showLine; + set + { + showLine = value; + line.Alpha = value ? 1 : 0; + } + } + + public Bindable Details = new Bindable(); + + private readonly Circle line; + private readonly OsuSpriteText details; + + public OverlinedHeader(string title) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Margin = new MarginPadding { Bottom = 5 }; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + line = new Circle + { + RelativeSizeAxes = Axes.X, + Height = 2, + Margin = new MarginPadding { Bottom = 2 } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Top = 5 }, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new OsuSpriteText + { + Text = title, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold) + }, + details = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold) + }, + } + }, + } + }; + + Details.BindValueChanged(val => details.Text = val.NewValue); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + line.Colour = colours.Yellow; + details.Colour = colours.Yellow; + } + } +} diff --git a/osu.Game/Screens/Multi/Components/OverlinedPlaylist.cs b/osu.Game/Screens/Multi/Components/OverlinedPlaylist.cs deleted file mode 100644 index 4fe79b40a0..0000000000 --- a/osu.Game/Screens/Multi/Components/OverlinedPlaylist.cs +++ /dev/null @@ -1,33 +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 osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Graphics; -using osu.Game.Online.Multiplayer; - -namespace osu.Game.Screens.Multi.Components -{ - public class OverlinedPlaylist : OverlinedDisplay - { - public readonly Bindable SelectedItem = new Bindable(); - - private readonly DrawableRoomPlaylist playlist; - - public OverlinedPlaylist(bool allowSelection) - : base("Playlist") - { - Content.Add(playlist = new DrawableRoomPlaylist(false, allowSelection) - { - RelativeSizeAxes = Axes.Both, - SelectedItem = { BindTarget = SelectedItem } - }); - } - - [BackgroundDependencyLoader] - private void load() - { - playlist.Items.BindTo(Playlist); - } - } -} diff --git a/osu.Game/Screens/Multi/Components/OverlinedParticipants.cs b/osu.Game/Screens/Multi/Components/ParticipantsDisplay.cs similarity index 63% rename from osu.Game/Screens/Multi/Components/OverlinedParticipants.cs rename to osu.Game/Screens/Multi/Components/ParticipantsDisplay.cs index eb1782d147..6ea4283379 100644 --- a/osu.Game/Screens/Multi/Components/OverlinedParticipants.cs +++ b/osu.Game/Screens/Multi/Components/ParticipantsDisplay.cs @@ -2,26 +2,22 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Graphics.Containers; namespace osu.Game.Screens.Multi.Components { - public class OverlinedParticipants : OverlinedDisplay + public class ParticipantsDisplay : MultiplayerComposite { - public new Axes AutoSizeAxes - { - get => base.AutoSizeAxes; - set => base.AutoSizeAxes = value; - } + public Bindable Details = new Bindable(); - public OverlinedParticipants(Direction direction) - : base("Recent participants") + public ParticipantsDisplay(Direction direction) { OsuScrollContainer scroll; ParticipantsList list; - Content.Add(scroll = new OsuScrollContainer(direction) + AddInternal(scroll = new OsuScrollContainer(direction) { Child = list = new ParticipantsList() }); @@ -29,13 +25,21 @@ namespace osu.Game.Screens.Multi.Components switch (direction) { case Direction.Horizontal: + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + scroll.RelativeSizeAxes = Axes.X; scroll.Height = ParticipantsList.TILE_SIZE + OsuScrollContainer.SCROLL_BAR_HEIGHT + OsuScrollContainer.SCROLL_BAR_PADDING * 2; - list.AutoSizeAxes = Axes.Both; + + list.RelativeSizeAxes = Axes.Y; + list.AutoSizeAxes = Axes.X; break; case Direction.Vertical: + RelativeSizeAxes = Axes.Both; + scroll.RelativeSizeAxes = Axes.Both; + list.RelativeSizeAxes = Axes.X; list.AutoSizeAxes = Axes.Y; break; @@ -46,11 +50,10 @@ namespace osu.Game.Screens.Multi.Components private void load() { ParticipantCount.BindValueChanged(_ => setParticipantCount()); - MaxParticipants.BindValueChanged(_ => setParticipantCount()); - - setParticipantCount(); + MaxParticipants.BindValueChanged(_ => setParticipantCount(), true); } - private void setParticipantCount() => Details = MaxParticipants.Value != null ? $"{ParticipantCount.Value}/{MaxParticipants.Value}" : ParticipantCount.Value.ToString(); + private void setParticipantCount() => + Details.Value = MaxParticipants.Value != null ? $"{ParticipantCount.Value}/{MaxParticipants.Value}" : ParticipantCount.Value.ToString(); } } diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylist.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylist.cs index 9a3fcb1cdc..89c335183b 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylist.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylist.cs @@ -60,8 +60,6 @@ namespace osu.Game.Screens.Multi RequestDeletion = requestDeletion }; - private void requestSelection(PlaylistItem item) => SelectedItem.Value = item; - private void requestDeletion(PlaylistItem item) { if (SelectedItem.Value == item) diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs index 891853dee5..77fbd606f4 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs @@ -24,6 +24,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components [BackgroundDependencyLoader] private void load(OsuColour colours) { + OverlinedHeader participantsHeader; + InternalChildren = new Drawable[] { new Box @@ -55,22 +57,31 @@ namespace osu.Game.Screens.Multi.Lounge.Components RelativeSizeAxes = Axes.X, Margin = new MarginPadding { Vertical = 60 }, }, - new OverlinedParticipants(Direction.Horizontal) + participantsHeader = new OverlinedHeader("Recent Participants"), + new ParticipantsDisplay(Direction.Vertical) { RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y - }, + Height = ParticipantsList.TILE_SIZE * 3, + Details = { BindTarget = participantsHeader.Details } + } } } }, + new Drawable[] { new OverlinedHeader("Playlist"), }, new Drawable[] { - new OverlinedPlaylist(false) { RelativeSizeAxes = Axes.Both }, + new DrawableRoomPlaylist(false, false) + { + RelativeSizeAxes = Axes.Both, + Items = { BindTarget = Playlist } + }, }, }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), } } } diff --git a/osu.Game/Screens/Multi/Match/Components/OverlinedChatDisplay.cs b/osu.Game/Screens/Multi/Match/Components/OverlinedChatDisplay.cs deleted file mode 100644 index a8d898385a..0000000000 --- a/osu.Game/Screens/Multi/Match/Components/OverlinedChatDisplay.cs +++ /dev/null @@ -1,20 +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 osu.Framework.Graphics; -using osu.Game.Screens.Multi.Components; - -namespace osu.Game.Screens.Multi.Match.Components -{ - public class OverlinedChatDisplay : OverlinedDisplay - { - public OverlinedChatDisplay() - : base("Chat") - { - Content.Add(new MatchChatDisplay - { - RelativeSizeAxes = Axes.Both - }); - } - } -} diff --git a/osu.Game/Screens/Multi/Match/Components/OverlinedLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/OverlinedLeaderboard.cs deleted file mode 100644 index bda2cd70d7..0000000000 --- a/osu.Game/Screens/Multi/Match/Components/OverlinedLeaderboard.cs +++ /dev/null @@ -1,24 +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 osu.Framework.Graphics; -using osu.Game.Screens.Multi.Components; - -namespace osu.Game.Screens.Multi.Match.Components -{ - public class OverlinedLeaderboard : OverlinedDisplay - { - private readonly MatchLeaderboard leaderboard; - - public OverlinedLeaderboard() - : base("Leaderboard") - { - Content.Add(leaderboard = new MatchLeaderboard - { - RelativeSizeAxes = Axes.Both - }); - } - - public void RefreshScores() => leaderboard.RefreshScores(); - } -} diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 694315a3b3..dffd6a0331 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -53,9 +53,10 @@ namespace osu.Game.Screens.Multi.Match protected readonly Bindable SelectedItem = new Bindable(); private MatchSettingsOverlay settingsOverlay; - private OverlinedLeaderboard leaderboard; + private MatchLeaderboard leaderboard; private IBindable> managerUpdated; + private OverlinedHeader participantsHeader; public MatchSubScreen(Room room) { @@ -85,11 +86,22 @@ namespace osu.Game.Screens.Multi.Match Child = new GridContainer { RelativeSizeAxes = Axes.Both, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + }, Content = new[] { + new Drawable[] { new Components.Header() }, new Drawable[] { - new Components.Header() + participantsHeader = new OverlinedHeader("Participants") + { + ShowLine = false + } }, new Drawable[] { @@ -97,12 +109,10 @@ namespace osu.Game.Screens.Multi.Match { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Margin = new MarginPadding { Top = 10 }, - Child = new OverlinedParticipants(Direction.Horizontal) + Margin = new MarginPadding { Top = 5 }, + Child = new ParticipantsDisplay(Direction.Horizontal) { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ShowLine = false + Details = { BindTarget = participantsHeader.Details } } } }, @@ -126,9 +136,10 @@ namespace osu.Game.Screens.Multi.Match { new Drawable[] { - new OverlinedPlaylist(true) // Temporarily always allow selection + new DrawableRoomPlaylist(false, true) // Temporarily always allow selection { RelativeSizeAxes = Axes.Both, + Items = { BindTarget = playlist }, SelectedItem = { BindTarget = SelectedItem } } }, @@ -157,18 +168,16 @@ namespace osu.Game.Screens.Multi.Match RelativeSizeAxes = Axes.Both, Content = new[] { - new Drawable[] - { - leaderboard = new OverlinedLeaderboard { RelativeSizeAxes = Axes.Both }, - }, - new Drawable[] - { - new OverlinedChatDisplay { RelativeSizeAxes = Axes.Both } - } + new Drawable[] { new OverlinedHeader("Leaderboard"), }, + new Drawable[] { leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both }, }, + new Drawable[] { new OverlinedHeader("Chat"), }, + new Drawable[] { new MatchChatDisplay { RelativeSizeAxes = Axes.Both } } }, RowDimensions = new[] { + new Dimension(GridSizeMode.AutoSize), new Dimension(), + new Dimension(GridSizeMode.AutoSize), new Dimension(GridSizeMode.Relative, size: 0.4f, minSize: 240), } }, @@ -185,12 +194,6 @@ namespace osu.Game.Screens.Multi.Match } } }, - RowDimensions = new[] - { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.AutoSize), - new Dimension(), - } } } }, @@ -219,6 +222,7 @@ namespace osu.Game.Screens.Multi.Match } [Resolved] + private IAPIProvider api { get; set; } protected override void LoadComplete() From 12e3a3c38a70095ab0a4ee50bf669374f9941186 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Jul 2020 15:06:40 +0900 Subject: [PATCH 717/871] Adjust toolbar fade in/out on toggle --- osu.Game/Overlays/Toolbar/Toolbar.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index ba6e52ec1d..de08b79f57 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -139,7 +139,7 @@ namespace osu.Game.Overlays.Toolbar protected override void PopIn() { this.MoveToY(0, transition_time, Easing.OutQuint); - this.FadeIn(transition_time / 2, Easing.OutQuint); + this.FadeIn(transition_time / 4, Easing.OutQuint); } protected override void PopOut() @@ -147,7 +147,7 @@ namespace osu.Game.Overlays.Toolbar userButton.StateContainer?.Hide(); this.MoveToY(-DrawSize.Y, transition_time, Easing.OutQuint); - this.FadeOut(transition_time); + this.FadeOut(transition_time, Easing.InQuint); } } } From 6c8b6f05f838ffd7a6139b2eeb93d91aabaa2ad8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Jul 2020 15:24:26 +0900 Subject: [PATCH 718/871] Fix key bindings switching order at random on consecutive "reset to defaults" --- osu.Game/Input/KeyBindingStore.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Input/KeyBindingStore.cs b/osu.Game/Input/KeyBindingStore.cs index 74b3134964..198ab6883d 100644 --- a/osu.Game/Input/KeyBindingStore.cs +++ b/osu.Game/Input/KeyBindingStore.cs @@ -55,6 +55,9 @@ namespace osu.Game.Input RulesetID = rulesetId, Variant = variant }); + + // required to ensure stable insert order (https://github.com/dotnet/efcore/issues/11686) + usage.Context.SaveChanges(); } } } From e6ec883084899f368847d3367f7000f409844b68 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 8 Jul 2020 20:20:50 +0900 Subject: [PATCH 719/871] Remove slider tail circle judgement requirements --- osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs index c11e20c9e7..1e54b576f1 100644 --- a/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/SliderTailCircle.cs @@ -4,6 +4,7 @@ using osu.Framework.Bindables; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects @@ -24,6 +25,13 @@ namespace osu.Game.Rulesets.Osu.Objects protected override HitWindows CreateHitWindows() => HitWindows.Empty; - public override Judgement CreateJudgement() => new SliderRepeat.SliderRepeatJudgement(); + public override Judgement CreateJudgement() => new SliderTailJudgement(); + + public class SliderTailJudgement : OsuJudgement + { + protected override int NumericResultFor(HitResult result) => 0; + + public override bool AffectsCombo => false; + } } } From 37ecab3f2f3cbea1a818941bf4153c58ec087158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 8 Jul 2020 20:44:27 +0200 Subject: [PATCH 720/871] Add assertions to make spinner tests fail --- .../TestSceneSpinnerRotation.cs | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index ea006ec607..579c47f585 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -14,6 +14,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables; using osuTK; using System.Collections.Generic; using System.Linq; +using osu.Framework.Graphics.Sprites; using osu.Game.Storyboards; using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap; @@ -36,6 +37,7 @@ namespace osu.Game.Rulesets.Osu.Tests } private DrawableSpinner drawableSpinner; + private SpriteIcon spinnerSymbol => drawableSpinner.ChildrenOfType().Single(); [SetUpSteps] public override void SetUpSteps() @@ -50,23 +52,38 @@ namespace osu.Game.Rulesets.Osu.Tests public void TestSpinnerRewindingRotation() { addSeekStep(5000); - AddAssert("is rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, 0, 100)); + AddAssert("is disc rotation not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Disc.Rotation, 0, 100)); + AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, 0, 100)); addSeekStep(0); - AddAssert("is rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, 0, 100)); + AddAssert("is disc rotation almost 0", () => Precision.AlmostEquals(drawableSpinner.Disc.Rotation, 0, 100)); + AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, 0, 100)); } [Test] public void TestSpinnerMiddleRewindingRotation() { - double estimatedRotation = 0; + double finalAbsoluteDiscRotation = 0, finalRelativeDiscRotation = 0, finalSpinnerSymbolRotation = 0; addSeekStep(5000); - AddStep("retrieve rotation", () => estimatedRotation = drawableSpinner.Disc.RotationAbsolute); + AddStep("retrieve disc relative rotation", () => finalRelativeDiscRotation = drawableSpinner.Disc.Rotation); + AddStep("retrieve disc absolute rotation", () => finalAbsoluteDiscRotation = drawableSpinner.Disc.RotationAbsolute); + AddStep("retrieve spinner symbol rotation", () => finalSpinnerSymbolRotation = spinnerSymbol.Rotation); addSeekStep(2500); + AddUntilStep("disc rotation rewound", + // we want to make sure that the rotation at time 2500 is in the same direction as at time 5000, but about half-way in. + () => Precision.AlmostEquals(drawableSpinner.Disc.Rotation, finalRelativeDiscRotation / 2, 100)); + AddUntilStep("symbol rotation rewound", + () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation / 2, 100)); + addSeekStep(5000); - AddAssert("is rotation absolute almost same", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, estimatedRotation, 100)); + AddAssert("is disc rotation almost same", + () => Precision.AlmostEquals(drawableSpinner.Disc.Rotation, finalRelativeDiscRotation, 100)); + AddAssert("is symbol rotation almost same", + () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation, 100)); + AddAssert("is disc rotation absolute almost same", + () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, finalAbsoluteDiscRotation, 100)); } [Test] From 31a1f8b9a75b944c6e52e8089f26feb671c061cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 8 Jul 2020 22:37:45 +0200 Subject: [PATCH 721/871] Add coverage for spinning in both directions --- .../TestSceneSpinnerRotation.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index 579c47f585..de06570d3c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -15,6 +15,11 @@ using osuTK; using System.Collections.Generic; using System.Linq; using osu.Framework.Graphics.Sprites; +using osu.Game.Replays; +using osu.Game.Rulesets.Osu.Replays; +using osu.Game.Rulesets.Osu.UI; +using osu.Game.Rulesets.Replays; +using osu.Game.Scoring; using osu.Game.Storyboards; using static osu.Game.Tests.Visual.OsuTestScene.ClockBackedTestWorkingBeatmap; @@ -86,6 +91,44 @@ namespace osu.Game.Rulesets.Osu.Tests () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, finalAbsoluteDiscRotation, 100)); } + [Test] + public void TestRotationDirection([Values(true, false)] bool clockwise) + { + if (clockwise) + { + AddStep("flip replay", () => + { + var drawableRuleset = this.ChildrenOfType().Single(); + var score = drawableRuleset.ReplayScore; + var scoreWithFlippedReplay = new Score + { + ScoreInfo = score.ScoreInfo, + Replay = flipReplay(score.Replay) + }; + drawableRuleset.SetReplayScore(scoreWithFlippedReplay); + }); + } + + addSeekStep(5000); + + AddAssert("disc spin direction correct", () => clockwise ? drawableSpinner.Disc.Rotation > 0 : drawableSpinner.Disc.Rotation < 0); + AddAssert("spinner symbol direction correct", () => clockwise ? spinnerSymbol.Rotation > 0 : spinnerSymbol.Rotation < 0); + } + + private Replay flipReplay(Replay scoreReplay) => new Replay + { + Frames = scoreReplay + .Frames + .Cast() + .Select(replayFrame => + { + var flippedPosition = new Vector2(OsuPlayfield.BASE_SIZE.X - replayFrame.Position.X, replayFrame.Position.Y); + return new OsuReplayFrame(replayFrame.Time, flippedPosition, replayFrame.Actions.ToArray()); + }) + .Cast() + .ToList() + }; + [Test] public void TestSpinPerMinuteOnRewind() { From 213dfac344f67f776874217bca80f7eaa2479bf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 8 Jul 2020 20:56:47 +0200 Subject: [PATCH 722/871] Fix broken spinner rotation logic --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 4d37622be5..12034ad333 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -197,7 +197,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables float targetScale = relativeCircleScale + (1 - relativeCircleScale) * Progress; Disc.Scale = new Vector2((float)Interpolation.Lerp(Disc.Scale.X, targetScale, Math.Clamp(Math.Abs(Time.Elapsed) / 100, 0, 1))); - symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, Disc.RotationAbsolute / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); + symbol.Rotation = (float)Interpolation.Lerp(symbol.Rotation, Disc.Rotation / 2, Math.Clamp(Math.Abs(Time.Elapsed) / 40, 0, 1)); } protected override void UpdateInitialTransforms() @@ -207,9 +207,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables circleContainer.ScaleTo(Spinner.Scale * 0.3f); circleContainer.ScaleTo(Spinner.Scale, HitObject.TimePreempt / 1.4f, Easing.OutQuint); - Disc.RotateTo(-720); - symbol.RotateTo(-720); - mainContainer .ScaleTo(0) .ScaleTo(Spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, HitObject.TimePreempt - 150, Easing.OutQuint) From 4cd874280cd853722d5cae76c5a2af16c99b58f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 8 Jul 2020 21:05:41 +0200 Subject: [PATCH 723/871] Add clarifying xmldoc for RotationAbsolute --- .../Objects/Drawables/Pieces/SpinnerDisc.cs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index d4ef039b79..408aba54d7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -73,6 +73,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } } + /// + /// The total rotation performed on the spinner disc, disregarding the spin direction. + /// + /// + /// This value is always non-negative and is monotonically increasing with time + /// (i.e. will only increase if time is passing forward, but can decrease during rewind). + /// + /// + /// If the spinner is spun 360 degrees clockwise and then 360 degrees counter-clockwise, + /// this property will return the value of 720 (as opposed to 0 for ). + /// + public float RotationAbsolute; + /// /// Whether currently in the correct time range to allow spinning. /// @@ -88,7 +101,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private float lastAngle; private float currentRotation; - public float RotationAbsolute; private int completeTick; private bool updateCompleteTick() => completeTick != (completeTick = (int)(RotationAbsolute / 360)); From c10cf2ef496544f9dfcd9c3a0533ae29c81176fa Mon Sep 17 00:00:00 2001 From: Joehu Date: Wed, 8 Jul 2020 19:01:12 -0700 Subject: [PATCH 724/871] Fix multi header title not aligning correctly when changing screens --- osu.Game/Screens/Multi/Header.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Multi/Header.cs b/osu.Game/Screens/Multi/Header.cs index e27fa154af..653cb3791a 100644 --- a/osu.Game/Screens/Multi/Header.cs +++ b/osu.Game/Screens/Multi/Header.cs @@ -95,22 +95,22 @@ namespace osu.Game.Screens.Multi { new OsuSpriteText { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: 24), Text = "Multiplayer" }, dot = new OsuSpriteText { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: 24), Text = "·" }, pageTitle = new OsuSpriteText { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: 24), Text = "Lounge" } From efb2c2f4aee0df8952d1efeac6817a49a6d1b391 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Jul 2020 12:01:00 +0900 Subject: [PATCH 725/871] Rename variable to be more clear on purpose --- osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs | 2 +- osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs | 8 ++++---- .../Objects/Drawables/DrawableSpinner.cs | 4 ++-- .../Objects/Drawables/Pieces/SpinnerDisc.cs | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs index 65bed071cd..8cb7f3f4b6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinner.cs @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Tests if (auto && !userTriggered && Time.Current > Spinner.StartTime + Spinner.Duration / 2 && Progress < 1) { // force completion only once to not break human interaction - Disc.RotationAbsolute = Spinner.SpinsRequired * 360; + Disc.CumulativeRotation = Spinner.SpinsRequired * 360; auto = false; } diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs index de06570d3c..6b1394d799 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSpinnerRotation.cs @@ -58,11 +58,11 @@ namespace osu.Game.Rulesets.Osu.Tests { addSeekStep(5000); AddAssert("is disc rotation not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Disc.Rotation, 0, 100)); - AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, 0, 100)); + AddAssert("is disc rotation absolute not almost 0", () => !Precision.AlmostEquals(drawableSpinner.Disc.CumulativeRotation, 0, 100)); addSeekStep(0); AddAssert("is disc rotation almost 0", () => Precision.AlmostEquals(drawableSpinner.Disc.Rotation, 0, 100)); - AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, 0, 100)); + AddAssert("is disc rotation absolute almost 0", () => Precision.AlmostEquals(drawableSpinner.Disc.CumulativeRotation, 0, 100)); } [Test] @@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Osu.Tests addSeekStep(5000); AddStep("retrieve disc relative rotation", () => finalRelativeDiscRotation = drawableSpinner.Disc.Rotation); - AddStep("retrieve disc absolute rotation", () => finalAbsoluteDiscRotation = drawableSpinner.Disc.RotationAbsolute); + AddStep("retrieve disc absolute rotation", () => finalAbsoluteDiscRotation = drawableSpinner.Disc.CumulativeRotation); AddStep("retrieve spinner symbol rotation", () => finalSpinnerSymbolRotation = spinnerSymbol.Rotation); addSeekStep(2500); @@ -88,7 +88,7 @@ namespace osu.Game.Rulesets.Osu.Tests AddAssert("is symbol rotation almost same", () => Precision.AlmostEquals(spinnerSymbol.Rotation, finalSpinnerSymbolRotation, 100)); AddAssert("is disc rotation absolute almost same", - () => Precision.AlmostEquals(drawableSpinner.Disc.RotationAbsolute, finalAbsoluteDiscRotation, 100)); + () => Precision.AlmostEquals(drawableSpinner.Disc.CumulativeRotation, finalAbsoluteDiscRotation, 100)); } [Test] diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs index 12034ad333..be6766509c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSpinner.cs @@ -138,7 +138,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables positionBindable.BindTo(HitObject.PositionBindable); } - public float Progress => Math.Clamp(Disc.RotationAbsolute / 360 / Spinner.SpinsRequired, 0, 1); + public float Progress => Math.Clamp(Disc.CumulativeRotation / 360 / Spinner.SpinsRequired, 0, 1); protected override void CheckForResult(bool userTriggered, double timeOffset) { @@ -191,7 +191,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables circle.Rotation = Disc.Rotation; Ticks.Rotation = Disc.Rotation; - SpmCounter.SetRotation(Disc.RotationAbsolute); + SpmCounter.SetRotation(Disc.CumulativeRotation); float relativeCircleScale = Spinner.Scale * circle.DrawHeight / mainContainer.DrawHeight; float targetScale = relativeCircleScale + (1 - relativeCircleScale) * Progress; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 408aba54d7..35819cd05e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -84,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces /// If the spinner is spun 360 degrees clockwise and then 360 degrees counter-clockwise, /// this property will return the value of 720 (as opposed to 0 for ). /// - public float RotationAbsolute; + public float CumulativeRotation; /// /// Whether currently in the correct time range to allow spinning. @@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces private float currentRotation; private int completeTick; - private bool updateCompleteTick() => completeTick != (completeTick = (int)(RotationAbsolute / 360)); + private bool updateCompleteTick() => completeTick != (completeTick = (int)(CumulativeRotation / 360)); private bool rotationTransferred; @@ -161,7 +161,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } currentRotation += angle; - RotationAbsolute += Math.Abs(angle) * Math.Sign(Clock.ElapsedFrameTime); + CumulativeRotation += Math.Abs(angle) * Math.Sign(Clock.ElapsedFrameTime); } } } From efdf179906dc810e04d444cbc028ce1d58591d17 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Jul 2020 12:31:20 +0900 Subject: [PATCH 726/871] Replace poo icon at disclaimer screen --- osu.Game/Screens/Menu/Disclaimer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Menu/Disclaimer.cs b/osu.Game/Screens/Menu/Disclaimer.cs index 35091028ae..986de1edf0 100644 --- a/osu.Game/Screens/Menu/Disclaimer.cs +++ b/osu.Game/Screens/Menu/Disclaimer.cs @@ -51,7 +51,7 @@ namespace osu.Game.Screens.Menu { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Icon = FontAwesome.Solid.Poo, + Icon = FontAwesome.Solid.Flask, Size = new Vector2(icon_size), Y = icon_y, }, From bbbe8d6f685215fcce28912f65f81077c128ce70 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Jul 2020 13:47:11 +0900 Subject: [PATCH 727/871] Remove group selector for now, tidy up code somewhat --- .../Graphics/UserInterface/OsuTabControl.cs | 4 +- osu.Game/Screens/Select/FilterControl.cs | 116 ++++++++---------- osu.Game/Screens/Select/SongSelect.cs | 1 - 3 files changed, 51 insertions(+), 70 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs index c2feca171b..61501b0cd8 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControl.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -23,6 +23,8 @@ namespace osu.Game.Graphics.UserInterface { private Color4 accentColour; + public const float HORIZONTAL_SPACING = 10; + public virtual Color4 AccentColour { get => accentColour; @@ -54,7 +56,7 @@ namespace osu.Game.Graphics.UserInterface public OsuTabControl() { - TabContainer.Spacing = new Vector2(10f, 0f); + TabContainer.Spacing = new Vector2(HORIZONTAL_SPACING, 0f); AddInternal(strip = new Box { diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index d613ce649a..a26664325e 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -2,21 +2,20 @@ // See the LICENCE file in the repository root for full licence text. using System; -using osuTK; -using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; -using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; -using osu.Game.Screens.Select.Filter; -using Container = osu.Framework.Graphics.Containers.Container; using osu.Framework.Graphics.Shapes; -using osu.Game.Configuration; -using osu.Game.Rulesets; using osu.Framework.Input.Events; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; +using osu.Game.Screens.Select.Filter; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Select { @@ -26,9 +25,7 @@ namespace osu.Game.Screens.Select public Action FilterChanged; - private readonly OsuTabControl sortTabs; - - private readonly TabControl groupTabs; + private OsuTabControl sortTabs; private Bindable sortMode; @@ -56,19 +53,39 @@ namespace osu.Game.Screens.Select return criteria; } - private readonly SeekLimitedSearchTextBox searchTextBox; + private SeekLimitedSearchTextBox searchTextBox; public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => - base.ReceivePositionalInputAt(screenSpacePos) || groupTabs.ReceivePositionalInputAt(screenSpacePos) || sortTabs.ReceivePositionalInputAt(screenSpacePos); + base.ReceivePositionalInputAt(screenSpacePos) || sortTabs.ReceivePositionalInputAt(screenSpacePos); - public FilterControl() + [BackgroundDependencyLoader(permitNulls: true)] + private void load(OsuColour colours, IBindable parentRuleset, OsuConfigManager config) { + config.BindWith(OsuSetting.ShowConvertedBeatmaps, showConverted); + showConverted.ValueChanged += _ => updateCriteria(); + + config.BindWith(OsuSetting.DisplayStarsMinimum, minimumStars); + minimumStars.ValueChanged += _ => updateCriteria(); + + config.BindWith(OsuSetting.DisplayStarsMaximum, maximumStars); + maximumStars.ValueChanged += _ => updateCriteria(); + + ruleset.BindTo(parentRuleset); + ruleset.BindValueChanged(_ => updateCriteria()); + + sortMode = config.GetBindable(OsuSetting.SongSelectSortingMode); + groupMode = config.GetBindable(OsuSetting.SongSelectGroupingMode); + + groupMode.BindValueChanged(_ => updateCriteria()); + sortMode.BindValueChanged(_ => updateCriteria()); + Children = new Drawable[] { - Background = new Box + new Box { Colour = Color4.Black, Alpha = 0.8f, + Width = 2, RelativeSizeAxes = Axes.Both, }, new Container @@ -96,33 +113,28 @@ namespace osu.Game.Screens.Select Direction = FillDirection.Horizontal, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Spacing = new Vector2(OsuTabControl.HORIZONTAL_SPACING, 0), Children = new Drawable[] { - groupTabs = new OsuTabControl - { - RelativeSizeAxes = Axes.X, - Height = 24, - Width = 0.5f, - AutoSort = true, - }, - //spriteText = new OsuSpriteText - //{ - // Font = @"Exo2.0-Bold", - // Text = "Sort results by", - // Size = 14, - // Margin = new MarginPadding - // { - // Top = 5, - // Bottom = 5 - // }, - //}, sortTabs = new OsuTabControl { RelativeSizeAxes = Axes.X, Width = 0.5f, Height = 24, AutoSort = true, - } + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + AccentColour = colours.GreenLight, + Current = { BindTarget = sortMode } + }, + new OsuSpriteText + { + Text = "Sort by", + Font = OsuFont.GetFont(size: 14), + Margin = new MarginPadding(5), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, } }, } @@ -131,8 +143,7 @@ namespace osu.Game.Screens.Select searchTextBox.Current.ValueChanged += _ => FilterChanged?.Invoke(CreateCriteria()); - groupTabs.PinItem(GroupMode.All); - groupTabs.PinItem(GroupMode.RecentlyPlayed); + updateCriteria(); } public void Deactivate() @@ -156,37 +167,6 @@ namespace osu.Game.Screens.Select private readonly Bindable minimumStars = new BindableDouble(); private readonly Bindable maximumStars = new BindableDouble(); - public readonly Box Background; - - [BackgroundDependencyLoader(permitNulls: true)] - private void load(OsuColour colours, IBindable parentRuleset, OsuConfigManager config) - { - sortTabs.AccentColour = colours.GreenLight; - - config.BindWith(OsuSetting.ShowConvertedBeatmaps, showConverted); - showConverted.ValueChanged += _ => updateCriteria(); - - config.BindWith(OsuSetting.DisplayStarsMinimum, minimumStars); - minimumStars.ValueChanged += _ => updateCriteria(); - - config.BindWith(OsuSetting.DisplayStarsMaximum, maximumStars); - maximumStars.ValueChanged += _ => updateCriteria(); - - ruleset.BindTo(parentRuleset); - ruleset.BindValueChanged(_ => updateCriteria()); - - sortMode = config.GetBindable(OsuSetting.SongSelectSortingMode); - groupMode = config.GetBindable(OsuSetting.SongSelectGroupingMode); - - sortTabs.Current.BindTo(sortMode); - groupTabs.Current.BindTo(groupMode); - - groupMode.BindValueChanged(_ => updateCriteria()); - sortMode.BindValueChanged(_ => updateCriteria()); - - updateCriteria(); - } - private void updateCriteria() => FilterChanged?.Invoke(CreateCriteria()); protected override bool OnClick(ClickEvent e) => true; diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index d613b0ae8d..e3705b15fa 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -173,7 +173,6 @@ namespace osu.Game.Screens.Select RelativeSizeAxes = Axes.X, Height = FilterControl.HEIGHT, FilterChanged = ApplyFilterToCarousel, - Background = { Width = 2 }, }, new GridContainer // used for max width implementation { From f231b5925f142d305c62482912502a93668401cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Jul 2020 13:47:23 +0900 Subject: [PATCH 728/871] Add "show converted" checkbox to song select for convenience --- osu.Game/Screens/Select/FilterControl.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index a26664325e..e111ec4b15 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -116,6 +116,13 @@ namespace osu.Game.Screens.Select Spacing = new Vector2(OsuTabControl.HORIZONTAL_SPACING, 0), Children = new Drawable[] { + new OsuTabControlCheckbox + { + Text = "Show converted", + Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, sortTabs = new OsuTabControl { RelativeSizeAxes = Axes.X, From 04ce436f6aad199ba7f07a440aaa740b85b64a17 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 9 Jul 2020 14:46:58 +0900 Subject: [PATCH 729/871] Dispose beatmap lookup task scheduler --- osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index d47d37806e..3106d1143e 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -183,6 +183,7 @@ namespace osu.Game.Beatmaps public void Dispose() { cacheDownloadRequest?.Dispose(); + updateScheduler?.Dispose(); } [Serializable] From 3a5784c4102a221440686bc30badcefb4eb3a2d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Jul 2020 15:08:03 +0900 Subject: [PATCH 730/871] Ensure directories are deleted before migration tests run --- .../NonVisual/CustomDataDirectoryTest.cs | 106 +++++++++++------- 1 file changed, 66 insertions(+), 40 deletions(-) diff --git a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs index 8ea0e34214..199e69a19d 100644 --- a/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs +++ b/osu.Game.Tests/NonVisual/CustomDataDirectoryTest.cs @@ -19,24 +19,18 @@ namespace osu.Game.Tests.NonVisual [TestFixture] public class CustomDataDirectoryTest { - [SetUp] - public void SetUp() - { - if (Directory.Exists(customPath)) - Directory.Delete(customPath, true); - } - [Test] public void TestDefaultDirectory() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestDefaultDirectory))) + using (HeadlessGameHost host = new CustomTestHeadlessGameHost(nameof(TestDefaultDirectory))) { try { + string defaultStorageLocation = getDefaultLocationFor(nameof(TestDefaultDirectory)); + var osu = loadOsu(host); var storage = osu.Dependencies.Get(); - string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestDefaultDirectory)); Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation)); } finally @@ -46,21 +40,14 @@ namespace osu.Game.Tests.NonVisual } } - private string customPath => Path.Combine(RuntimeInfo.StartupDirectory, "custom-path"); - [Test] public void TestCustomDirectory() { - using (var host = new HeadlessGameHost(nameof(TestCustomDirectory))) + string customPath = prepareCustomPath(); + + using (var host = new CustomTestHeadlessGameHost(nameof(TestCustomDirectory))) { - string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestCustomDirectory)); - - // need access before the game has constructed its own storage yet. - Storage storage = new DesktopStorage(defaultStorageLocation, host); - // manual cleaning so we can prepare a config file. - storage.DeleteDirectory(string.Empty); - - using (var storageConfig = new StorageConfigManager(storage)) + using (var storageConfig = new StorageConfigManager(host.InitialStorage)) storageConfig.Set(StorageConfig.FullPath, customPath); try @@ -68,7 +55,7 @@ namespace osu.Game.Tests.NonVisual var osu = loadOsu(host); // switch to DI'd storage - storage = osu.Dependencies.Get(); + var storage = osu.Dependencies.Get(); Assert.That(storage.GetFullPath("."), Is.EqualTo(customPath)); } @@ -82,16 +69,11 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestSubDirectoryLookup() { - using (var host = new HeadlessGameHost(nameof(TestSubDirectoryLookup))) + string customPath = prepareCustomPath(); + + using (var host = new CustomTestHeadlessGameHost(nameof(TestSubDirectoryLookup))) { - string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestSubDirectoryLookup)); - - // need access before the game has constructed its own storage yet. - Storage storage = new DesktopStorage(defaultStorageLocation, host); - // manual cleaning so we can prepare a config file. - storage.DeleteDirectory(string.Empty); - - using (var storageConfig = new StorageConfigManager(storage)) + using (var storageConfig = new StorageConfigManager(host.InitialStorage)) storageConfig.Set(StorageConfig.FullPath, customPath); try @@ -99,7 +81,7 @@ namespace osu.Game.Tests.NonVisual var osu = loadOsu(host); // switch to DI'd storage - storage = osu.Dependencies.Get(); + var storage = osu.Dependencies.Get(); string actualTestFile = Path.Combine(customPath, "rulesets", "test"); @@ -120,10 +102,14 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestMigration() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestMigration))) + string customPath = prepareCustomPath(); + + using (HeadlessGameHost host = new CustomTestHeadlessGameHost(nameof(TestMigration))) { try { + string defaultStorageLocation = getDefaultLocationFor(nameof(TestMigration)); + var osu = loadOsu(host); var storage = osu.Dependencies.Get(); @@ -139,8 +125,6 @@ namespace osu.Game.Tests.NonVisual // for testing nested files are not ignored (only top level) host.Storage.GetStorageForDirectory("test-nested").GetStorageForDirectory("cache"); - string defaultStorageLocation = Path.Combine(RuntimeInfo.StartupDirectory, "headless", nameof(TestMigration)); - Assert.That(storage.GetFullPath("."), Is.EqualTo(defaultStorageLocation)); osu.Migrate(customPath); @@ -178,14 +162,15 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestMigrationBetweenTwoTargets() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestMigrationBetweenTwoTargets))) + string customPath = prepareCustomPath(); + string customPath2 = prepareCustomPath("-2"); + + using (HeadlessGameHost host = new CustomTestHeadlessGameHost(nameof(TestMigrationBetweenTwoTargets))) { try { var osu = loadOsu(host); - string customPath2 = $"{customPath}-2"; - const string database_filename = "client.db"; Assert.DoesNotThrow(() => osu.Migrate(customPath)); @@ -207,7 +192,9 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestMigrationToSameTargetFails() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestMigrationToSameTargetFails))) + string customPath = prepareCustomPath(); + + using (HeadlessGameHost host = new CustomTestHeadlessGameHost(nameof(TestMigrationToSameTargetFails))) { try { @@ -226,7 +213,9 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestMigrationToNestedTargetFails() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestMigrationToNestedTargetFails))) + string customPath = prepareCustomPath(); + + using (HeadlessGameHost host = new CustomTestHeadlessGameHost(nameof(TestMigrationToNestedTargetFails))) { try { @@ -253,7 +242,9 @@ namespace osu.Game.Tests.NonVisual [Test] public void TestMigrationToSeeminglyNestedTarget() { - using (HeadlessGameHost host = new CleanRunHeadlessGameHost(nameof(TestMigrationToSeeminglyNestedTarget))) + string customPath = prepareCustomPath(); + + using (HeadlessGameHost host = new CustomTestHeadlessGameHost(nameof(TestMigrationToSeeminglyNestedTarget))) { try { @@ -282,6 +273,7 @@ namespace osu.Game.Tests.NonVisual var osu = new OsuGameBase(); Task.Run(() => host.Run(osu)); waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); + return osu; } @@ -294,5 +286,39 @@ namespace osu.Game.Tests.NonVisual Assert.IsTrue(task.Wait(timeout), failureMessage); } + + private static string getDefaultLocationFor(string testTypeName) + { + string path = Path.Combine(RuntimeInfo.StartupDirectory, "headless", testTypeName); + + if (Directory.Exists(path)) + Directory.Delete(path, true); + + return path; + } + + private string prepareCustomPath(string suffix = "") + { + string path = Path.Combine(RuntimeInfo.StartupDirectory, $"custom-path{suffix}"); + + if (Directory.Exists(path)) + Directory.Delete(path, true); + + return path; + } + + public class CustomTestHeadlessGameHost : HeadlessGameHost + { + public Storage InitialStorage { get; } + + public CustomTestHeadlessGameHost(string name) + : base(name) + { + string defaultStorageLocation = getDefaultLocationFor(name); + + InitialStorage = new DesktopStorage(defaultStorageLocation, this); + InitialStorage.DeleteDirectory(string.Empty); + } + } } } From 7d59825851258e972bf26a53a70551371b812483 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 9 Jul 2020 15:16:40 +0900 Subject: [PATCH 731/871] 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 0563e5319d..ff04c7f120 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4e6de77e86..e4753e7ee9 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index c31e28638f..91fa003604 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 69062a3ed1100844be3c69cca4091475465700c7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Jul 2020 17:43:26 +0900 Subject: [PATCH 732/871] Remove unused search container in lounge --- osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index d4b6a3b79f..9c2ed26b52 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Multi.Lounge public LoungeSubScreen() { - SearchContainer searchContainer; + RoomsContainer roomsContainer; InternalChildren = new Drawable[] { @@ -55,14 +55,9 @@ namespace osu.Game.Screens.Multi.Lounge RelativeSizeAxes = Axes.Both, ScrollbarOverlapsContent = false, Padding = new MarginPadding(10), - Child = searchContainer = new SearchContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Child = new RoomsContainer { JoinRequested = joinRequested } - }, + Child = roomsContainer = new RoomsContainer { JoinRequested = joinRequested } }, - loadingLayer = new LoadingLayer(searchContainer), + loadingLayer = new LoadingLayer(roomsContainer), } }, new RoomInspector From 80f6f87e0169b678ab22ff6ac16e4609820cd5f9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Jul 2020 17:28:22 +0900 Subject: [PATCH 733/871] Scroll selected room into view on selection --- .../Screens/Multi/Lounge/LoungeSubScreen.cs | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index 9c2ed26b52..f512b864a6 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.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.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -20,21 +21,23 @@ namespace osu.Game.Screens.Multi.Lounge { public override string Title => "Lounge"; - protected readonly FilterControl Filter; + protected FilterControl Filter; private readonly Bindable initialRoomsReceived = new Bindable(); - private readonly Container content; - private readonly LoadingLayer loadingLayer; + private Container content; + private LoadingLayer loadingLayer; [Resolved] private Bindable selectedRoom { get; set; } private bool joiningRoom; - public LoungeSubScreen() + [BackgroundDependencyLoader] + private void load() { RoomsContainer roomsContainer; + OsuScrollContainer scrollContainer; InternalChildren = new Drawable[] { @@ -50,7 +53,7 @@ namespace osu.Game.Screens.Multi.Lounge Width = 0.55f, Children = new Drawable[] { - new OsuScrollContainer + scrollContainer = new OsuScrollContainer { RelativeSizeAxes = Axes.Both, ScrollbarOverlapsContent = false, @@ -70,6 +73,14 @@ namespace osu.Game.Screens.Multi.Lounge }, }, }; + + // scroll selected room into view on selection. + selectedRoom.BindValueChanged(val => + { + var drawable = roomsContainer.Rooms.FirstOrDefault(r => r.Room == val.NewValue); + if (drawable != null) + scrollContainer.ScrollIntoView(drawable); + }); } protected override void LoadComplete() From 1ded94e5be049a9bd5eaba13bc02dc75131b83d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Jul 2020 18:07:34 +0900 Subject: [PATCH 734/871] Add test coverage --- .../Multiplayer/RoomManagerTestScene.cs | 60 ++++++++++++ .../Visual/Multiplayer/TestRoomManager.cs | 35 +++++++ .../TestSceneLoungeRoomsContainer.cs | 91 ++----------------- .../Multiplayer/TestSceneLoungeSubScreen.cs | 57 ++++++++++++ 4 files changed, 160 insertions(+), 83 deletions(-) create mode 100644 osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeSubScreen.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs new file mode 100644 index 0000000000..ef9bdd5f27 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs @@ -0,0 +1,60 @@ +// 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.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets; +using osu.Game.Screens.Multi; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class RoomManagerTestScene : MultiplayerTestScene + { + [Cached(Type = typeof(IRoomManager))] + protected TestRoomManager RoomManager { get; } = new TestRoomManager(); + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("clear rooms", () => RoomManager.Rooms.Clear()); + } + + protected void AddRooms(int count, RulesetInfo ruleset = null) + { + AddStep("add rooms", () => + { + for (int i = 0; i < count; i++) + { + var room = new Room + { + RoomID = { Value = i }, + Name = { Value = $"Room {i}" }, + Host = { Value = new User { Username = "Host" } }, + EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) } + }; + + if (ruleset != null) + { + room.Playlist.Add(new PlaylistItem + { + Ruleset = { Value = ruleset }, + Beatmap = + { + Value = new BeatmapInfo + { + Metadata = new BeatmapMetadata() + } + } + }); + } + + RoomManager.Rooms.Add(room); + } + }); + } + } +} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs b/osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs new file mode 100644 index 0000000000..67a53307fc --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestRoomManager.cs @@ -0,0 +1,35 @@ +// 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.Game.Online.Multiplayer; +using osu.Game.Screens.Multi; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestRoomManager : IRoomManager + { + public event Action RoomsUpdated + { + add { } + remove { } + } + + public readonly BindableList Rooms = new BindableList(); + + public Bindable InitialRoomsReceived { get; } = new Bindable(true); + + IBindableList IRoomManager.Rooms => Rooms; + + public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) => Rooms.Add(room); + + public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) + { + } + + public void PartRoom() + { + } + } +} diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 83f2297bd2..5cf3a9d320 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -1,30 +1,21 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.Multiplayer; -using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; -using osu.Game.Screens.Multi; using osu.Game.Screens.Multi.Lounge.Components; -using osu.Game.Users; using osuTK.Graphics; namespace osu.Game.Tests.Visual.Multiplayer { - public class TestSceneLoungeRoomsContainer : MultiplayerTestScene + public class TestSceneLoungeRoomsContainer : RoomManagerTestScene { - [Cached(Type = typeof(IRoomManager))] - private TestRoomManager roomManager = new TestRoomManager(); - private RoomsContainer container; [BackgroundDependencyLoader] @@ -39,34 +30,27 @@ namespace osu.Game.Tests.Visual.Multiplayer }; } - public override void SetUpSteps() - { - base.SetUpSteps(); - - AddStep("clear rooms", () => roomManager.Rooms.Clear()); - } - [Test] public void TestBasicListChanges() { - addRooms(3); + AddRooms(3); AddAssert("has 3 rooms", () => container.Rooms.Count == 3); - AddStep("remove first room", () => roomManager.Rooms.Remove(roomManager.Rooms.FirstOrDefault())); + AddStep("remove first room", () => RoomManager.Rooms.Remove(RoomManager.Rooms.FirstOrDefault())); AddAssert("has 2 rooms", () => container.Rooms.Count == 2); AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0)); AddStep("select first room", () => container.Rooms.First().Action?.Invoke()); - AddAssert("first room selected", () => Room == roomManager.Rooms.First()); + AddAssert("first room selected", () => Room == RoomManager.Rooms.First()); AddStep("join first room", () => container.Rooms.First().Action?.Invoke()); - AddAssert("first room joined", () => roomManager.Rooms.First().Status.Value is JoinedRoomStatus); + AddAssert("first room joined", () => RoomManager.Rooms.First().Status.Value is JoinedRoomStatus); } [Test] public void TestStringFiltering() { - addRooms(4); + AddRooms(4); AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4); @@ -82,8 +66,8 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestRulesetFiltering() { - addRooms(2, new OsuRuleset().RulesetInfo); - addRooms(3, new CatchRuleset().RulesetInfo); + AddRooms(2, new OsuRuleset().RulesetInfo); + AddRooms(3, new CatchRuleset().RulesetInfo); AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5); @@ -96,67 +80,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3); } - private void addRooms(int count, RulesetInfo ruleset = null) - { - AddStep("add rooms", () => - { - for (int i = 0; i < count; i++) - { - var room = new Room - { - RoomID = { Value = i }, - Name = { Value = $"Room {i}" }, - Host = { Value = new User { Username = "Host" } }, - EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) } - }; - - if (ruleset != null) - { - room.Playlist.Add(new PlaylistItem - { - Ruleset = { Value = ruleset }, - Beatmap = - { - Value = new BeatmapInfo - { - Metadata = new BeatmapMetadata() - } - } - }); - } - - roomManager.Rooms.Add(room); - } - }); - } - private void joinRequested(Room room) => room.Status.Value = new JoinedRoomStatus(); - private class TestRoomManager : IRoomManager - { - public event Action RoomsUpdated - { - add { } - remove { } - } - - public readonly BindableList Rooms = new BindableList(); - - public Bindable InitialRoomsReceived { get; } = new Bindable(true); - - IBindableList IRoomManager.Rooms => Rooms; - - public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) => Rooms.Add(room); - - public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) - { - } - - public void PartRoom() - { - } - } - private class JoinedRoomStatus : RoomStatus { public override string Message => "Joined"; diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeSubScreen.cs new file mode 100644 index 0000000000..475c39c9dc --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeSubScreen.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.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Screens; +using osu.Framework.Testing; +using osu.Game.Graphics.Containers; +using osu.Game.Screens.Multi.Lounge; +using osu.Game.Screens.Multi.Lounge.Components; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneLoungeSubScreen : RoomManagerTestScene + { + private LoungeSubScreen loungeScreen; + + [BackgroundDependencyLoader] + private void load() + { + Child = new ScreenStack(loungeScreen = new LoungeSubScreen + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.5f, + }); + } + + public override void SetUpSteps() + { + base.SetUpSteps(); + + AddStep("clear rooms", () => RoomManager.Rooms.Clear()); + } + + private RoomsContainer roomsContainer => loungeScreen.ChildrenOfType().First(); + + [Test] + public void TestScrollSelectedIntoView() + { + AddRooms(30); + + AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms.First())); + + AddStep("select last room", () => roomsContainer.Rooms.Last().Action?.Invoke()); + + AddUntilStep("first room is masked", () => !checkRoomVisible(roomsContainer.Rooms.First())); + AddUntilStep("last room is not masked", () => checkRoomVisible(roomsContainer.Rooms.Last())); + } + + private bool checkRoomVisible(DrawableRoom room) => + loungeScreen.ChildrenOfType().First().ScreenSpaceDrawQuad + .Contains(room.ScreenSpaceDrawQuad.Centre); + } +} From 95096cbf5ea87d5f8c70a4b8d247abafd803037a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Jul 2020 18:25:07 +0900 Subject: [PATCH 735/871] Use better screen load logic --- .../Visual/Multiplayer/TestSceneLoungeSubScreen.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeSubScreen.cs index 475c39c9dc..c4ec74859b 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeSubScreen.cs @@ -20,12 +20,6 @@ namespace osu.Game.Tests.Visual.Multiplayer [BackgroundDependencyLoader] private void load() { - Child = new ScreenStack(loungeScreen = new LoungeSubScreen - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Width = 0.5f, - }); } public override void SetUpSteps() @@ -33,6 +27,14 @@ namespace osu.Game.Tests.Visual.Multiplayer base.SetUpSteps(); AddStep("clear rooms", () => RoomManager.Rooms.Clear()); + AddStep("push screen", () => LoadScreen(loungeScreen = new LoungeSubScreen + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Width = 0.5f, + })); + + AddUntilStep("wait for present", () => loungeScreen.IsCurrentScreen()); } private RoomsContainer roomsContainer => loungeScreen.ChildrenOfType().First(); From 601101147eed5802151d7fd9c23aac3b040feec8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Jul 2020 17:15:16 +0900 Subject: [PATCH 736/871] Allow keyboard selection of rooms at the multiplayer lounge --- .../Multi/Lounge/Components/RoomsContainer.cs | 111 ++++++++++++++++-- 1 file changed, 101 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index f14aa5fd8c..e440c2225c 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -9,13 +9,17 @@ using osu.Framework.Bindables; using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Bindings; +using osu.Framework.Threading; +using osu.Game.Extensions; using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; using osu.Game.Online.Multiplayer; using osuTK; namespace osu.Game.Screens.Multi.Lounge.Components { - public class RoomsContainer : CompositeDrawable + public class RoomsContainer : CompositeDrawable, IKeyBindingHandler { public Action JoinRequested; @@ -88,8 +92,22 @@ namespace osu.Game.Screens.Multi.Lounge.Components private void addRooms(IEnumerable rooms) { - foreach (var r in rooms) - roomFlow.Add(new DrawableRoom(r) { Action = () => selectRoom(r) }); + foreach (var room in rooms) + { + roomFlow.Add(new DrawableRoom(room) + { + Action = () => + { + if (room == selectedRoom.Value) + { + JoinRequested?.Invoke(room); + return; + } + + selectRoom(room); + } + }); + } Filter(filter?.Value); } @@ -115,16 +133,89 @@ namespace osu.Game.Screens.Multi.Lounge.Components private void selectRoom(Room room) { - var drawable = roomFlow.FirstOrDefault(r => r.Room == room); - - if (drawable != null && drawable.State == SelectionState.Selected) - JoinRequested?.Invoke(room); - else - roomFlow.Children.ForEach(r => r.State = r.Room == room ? SelectionState.Selected : SelectionState.NotSelected); - + roomFlow.Children.ForEach(r => r.State = r.Room == room ? SelectionState.Selected : SelectionState.NotSelected); selectedRoom.Value = room; } + #region Key selection logic + + public bool OnPressed(GlobalAction action) + { + switch (action) + { + case GlobalAction.SelectNext: + beginRepeatSelection(() => selectNext(1), action); + return true; + + case GlobalAction.SelectPrevious: + beginRepeatSelection(() => selectNext(-1), action); + return true; + } + + return false; + } + + public void OnReleased(GlobalAction action) + { + switch (action) + { + case GlobalAction.SelectNext: + case GlobalAction.SelectPrevious: + endRepeatSelection(action); + break; + } + } + + private ScheduledDelegate repeatDelegate; + private object lastRepeatSource; + + /// + /// Begin repeating the specified selection action. + /// + /// The action to perform. + /// The source of the action. Used in conjunction with to only cancel the correct action (most recently pressed key). + private void beginRepeatSelection(Action action, object source) + { + endRepeatSelection(); + + lastRepeatSource = source; + repeatDelegate = this.BeginKeyRepeat(Scheduler, action); + } + + private void endRepeatSelection(object source = null) + { + // only the most recent source should be able to cancel the current action. + if (source != null && !EqualityComparer.Default.Equals(lastRepeatSource, source)) + return; + + repeatDelegate?.Cancel(); + repeatDelegate = null; + lastRepeatSource = null; + } + + private void selectNext(int direction) + { + var visibleRooms = Rooms.AsEnumerable().Where(r => r.IsPresent); + + Room room; + + if (selectedRoom.Value == null) + room = visibleRooms.FirstOrDefault()?.Room; + else + { + if (direction < 0) + visibleRooms = visibleRooms.Reverse(); + + room = visibleRooms.SkipWhile(r => r.Room != selectedRoom.Value).Skip(1).FirstOrDefault()?.Room; + } + + // we already have a valid selection only change selection if we still have a room to switch to. + if (room != null) + selectRoom(room); + } + + #endregion + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 115bb408166587431ea98a936298ea1f6e9df5ac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Jul 2020 17:33:02 +0900 Subject: [PATCH 737/871] Select via select action --- .../SearchableList/SearchableListFilterControl.cs | 2 -- .../Multi/Lounge/Components/RoomsContainer.cs | 15 +++++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs index d31470e685..de5e558943 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs @@ -136,8 +136,6 @@ namespace osu.Game.Overlays.SearchableList private class FilterSearchTextBox : SearchTextBox { - protected override bool AllowCommit => true; - [BackgroundDependencyLoader] private void load() { diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index e440c2225c..bf153b77df 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -100,7 +100,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components { if (room == selectedRoom.Value) { - JoinRequested?.Invoke(room); + joinSelected(); return; } @@ -137,12 +137,23 @@ namespace osu.Game.Screens.Multi.Lounge.Components selectedRoom.Value = room; } - #region Key selection logic + private void joinSelected() + { + if (selectedRoom.Value == null) return; + + JoinRequested?.Invoke(selectedRoom.Value); + } + + #region Key selection logic (shared with BeatmapCarousel) public bool OnPressed(GlobalAction action) { switch (action) { + case GlobalAction.Select: + joinSelected(); + return true; + case GlobalAction.SelectNext: beginRepeatSelection(() => selectNext(1), action); return true; From 25ddc5784ddd79df9ac9abe2379006b64aa07424 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Jul 2020 18:55:10 +0900 Subject: [PATCH 738/871] Change multiplayer tests to have null room by default --- .../Visual/Multiplayer/TestSceneLoungeRoomInfo.cs | 2 +- .../Multiplayer/TestSceneMatchBeatmapDetailArea.cs | 2 +- .../Visual/Multiplayer/TestSceneMatchHeader.cs | 1 + .../Visual/Multiplayer/TestSceneMatchLeaderboard.cs | 3 ++- .../Visual/Multiplayer/TestSceneMatchSongSelect.cs | 3 ++- .../Visual/Multiplayer/TestSceneMatchSubScreen.cs | 2 +- .../Multiplayer/TestSceneOverlinedParticipants.cs | 8 +++++--- .../Visual/Multiplayer/TestSceneOverlinedPlaylist.cs | 2 ++ .../Visual/Multiplayer/TestSceneParticipantsList.cs | 10 ++++++++-- osu.Game/Tests/Visual/MultiplayerTestScene.cs | 2 +- 10 files changed, 24 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs index 8b74eb5f27..cdad37a9ad 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUp] public void Setup() => Schedule(() => { - Room.CopyFrom(new Room()); + Room = new Room(); Child = new RoomInfo { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index 24d9f5ab12..01cd26fbe5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUp] public void Setup() => Schedule(() => { - Room.Playlist.Clear(); + Room = new Room(); Child = new MatchBeatmapDetailArea { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs index 38eb3181bf..e5943105b7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs @@ -14,6 +14,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public TestSceneMatchHeader() { + Room = new Room(); Room.Playlist.Add(new PlaylistItem { Beatmap = diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index 7ba1782a28..c24c6c4ba3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -6,6 +6,7 @@ using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Online.API; +using osu.Game.Online.Multiplayer; using osu.Game.Screens.Multi.Match.Components; using osu.Game.Users; using osuTK; @@ -18,7 +19,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public TestSceneMatchLeaderboard() { - Room.RoomID.Value = 3; + Room = new Room { RoomID = { Value = 3 } }; Add(new MatchLeaderboard { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs index 5cff2d7d05..c62479faa0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs @@ -14,6 +14,7 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Online.Multiplayer; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Multi.Components; @@ -95,7 +96,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUp] public void Setup() => Schedule(() => { - Room.Playlist.Clear(); + Room = new Room(); }); [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs index 66091f5679..2e22317539 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUp] public void Setup() => Schedule(() => { - Room.CopyFrom(new Room()); + Room = new Room(); }); [SetUpSteps] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedParticipants.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedParticipants.cs index 7ea3bba23f..2b4cac06bd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedParticipants.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedParticipants.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Game.Online.Multiplayer; using osu.Game.Screens.Multi.Components; using osuTK; @@ -12,10 +13,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { protected override bool UseOnlineAPI => true; - public TestSceneOverlinedParticipants() + [SetUp] + public void Setup() => Schedule(() => { - Room.RoomID.Value = 7; - } + Room = new Room { RoomID = { Value = 7 } }; + }); [Test] public void TestHorizontalLayout() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs index 14b7934dc7..88b2a6a4bc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs @@ -16,6 +16,8 @@ namespace osu.Game.Tests.Visual.Multiplayer public TestSceneOverlinedPlaylist() { + Room = new Room { RoomID = { Value = 7 } }; + for (int i = 0; i < 10; i++) { Room.Playlist.Add(new PlaylistItem diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneParticipantsList.cs index 9c4c45f94a..f71c5fc5d2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneParticipantsList.cs @@ -1,7 +1,9 @@ // 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.Graphics; +using osu.Game.Online.Multiplayer; using osu.Game.Screens.Multi.Components; namespace osu.Game.Tests.Visual.Multiplayer @@ -10,10 +12,14 @@ namespace osu.Game.Tests.Visual.Multiplayer { protected override bool UseOnlineAPI => true; + [SetUp] + public void Setup() => Schedule(() => + { + Room = new Room { RoomID = { Value = 7 } }; + }); + public TestSceneParticipantsList() { - Room.RoomID.Value = 7; - Add(new ParticipantsList { RelativeSizeAxes = Axes.Both }); } } diff --git a/osu.Game/Tests/Visual/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/MultiplayerTestScene.cs index ffb431b4d3..4d073f16f4 100644 --- a/osu.Game/Tests/Visual/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/MultiplayerTestScene.cs @@ -10,7 +10,7 @@ namespace osu.Game.Tests.Visual public abstract class MultiplayerTestScene : ScreenTestScene { [Cached] - private readonly Bindable currentRoom = new Bindable(new Room()); + private readonly Bindable currentRoom = new Bindable(); protected Room Room { From 0bc54528018961421a0dc4611791bc9629199ee3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Jul 2020 18:55:18 +0900 Subject: [PATCH 739/871] Add test coverage --- .../TestSceneLoungeRoomsContainer.cs | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 5cf3a9d320..b1f6ee3e3a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Multi.Lounge.Components; using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { @@ -41,12 +42,42 @@ namespace osu.Game.Tests.Visual.Multiplayer AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0)); AddStep("select first room", () => container.Rooms.First().Action?.Invoke()); - AddAssert("first room selected", () => Room == RoomManager.Rooms.First()); + AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First())); AddStep("join first room", () => container.Rooms.First().Action?.Invoke()); AddAssert("first room joined", () => RoomManager.Rooms.First().Status.Value is JoinedRoomStatus); } + [Test] + public void TestKeyboardNavigation() + { + AddRooms(3); + + AddAssert("no selection", () => checkRoomSelected(null)); + + press(Key.Down); + AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First())); + + press(Key.Up); + AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First())); + + press(Key.Down); + press(Key.Down); + AddAssert("last room selected", () => checkRoomSelected(RoomManager.Rooms.Last())); + + press(Key.Enter); + AddAssert("last room joined", () => RoomManager.Rooms.Last().Status.Value is JoinedRoomStatus); + } + + private void press(Key down) + { + AddStep($"press {down}", () => + { + InputManager.PressKey(down); + InputManager.ReleaseKey(down); + }); + } + [Test] public void TestStringFiltering() { @@ -80,6 +111,8 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3); } + private bool checkRoomSelected(Room room) => Room == room; + private void joinRequested(Room room) => room.Status.Value = new JoinedRoomStatus(); private class JoinedRoomStatus : RoomStatus From 43624381bf59cb1afcd96149ee626939b9b594d2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 9 Jul 2020 18:55:10 +0900 Subject: [PATCH 740/871] Change multiplayer tests to have null room by default --- .../Visual/Multiplayer/TestSceneLoungeRoomInfo.cs | 2 +- .../Multiplayer/TestSceneMatchBeatmapDetailArea.cs | 2 +- .../Visual/Multiplayer/TestSceneMatchHeader.cs | 1 + .../Visual/Multiplayer/TestSceneMatchLeaderboard.cs | 3 ++- .../Visual/Multiplayer/TestSceneMatchSongSelect.cs | 3 ++- .../Visual/Multiplayer/TestSceneMatchSubScreen.cs | 2 +- .../Multiplayer/TestSceneOverlinedParticipants.cs | 8 +++++--- .../Visual/Multiplayer/TestSceneOverlinedPlaylist.cs | 2 ++ .../Visual/Multiplayer/TestSceneParticipantsList.cs | 10 ++++++++-- osu.Game/Tests/Visual/MultiplayerTestScene.cs | 2 +- 10 files changed, 24 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs index 8b74eb5f27..cdad37a9ad 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomInfo.cs @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUp] public void Setup() => Schedule(() => { - Room.CopyFrom(new Room()); + Room = new Room(); Child = new RoomInfo { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs index 24d9f5ab12..01cd26fbe5 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchBeatmapDetailArea.cs @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUp] public void Setup() => Schedule(() => { - Room.Playlist.Clear(); + Room = new Room(); Child = new MatchBeatmapDetailArea { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs index 38eb3181bf..e5943105b7 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchHeader.cs @@ -14,6 +14,7 @@ namespace osu.Game.Tests.Visual.Multiplayer { public TestSceneMatchHeader() { + Room = new Room(); Room.Playlist.Add(new PlaylistItem { Beatmap = diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs index 7ba1782a28..c24c6c4ba3 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchLeaderboard.cs @@ -6,6 +6,7 @@ using Newtonsoft.Json; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Online.API; +using osu.Game.Online.Multiplayer; using osu.Game.Screens.Multi.Match.Components; using osu.Game.Users; using osuTK; @@ -18,7 +19,7 @@ namespace osu.Game.Tests.Visual.Multiplayer public TestSceneMatchLeaderboard() { - Room.RoomID.Value = 3; + Room = new Room { RoomID = { Value = 3 } }; Add(new MatchLeaderboard { diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs index 5cff2d7d05..c62479faa0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSongSelect.cs @@ -14,6 +14,7 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Utils; using osu.Game.Beatmaps; +using osu.Game.Online.Multiplayer; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; using osu.Game.Screens.Multi.Components; @@ -95,7 +96,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUp] public void Setup() => Schedule(() => { - Room.Playlist.Clear(); + Room = new Room(); }); [Test] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs index 66091f5679..2e22317539 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneMatchSubScreen.cs @@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.Multiplayer [SetUp] public void Setup() => Schedule(() => { - Room.CopyFrom(new Room()); + Room = new Room(); }); [SetUpSteps] diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedParticipants.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedParticipants.cs index 7ea3bba23f..2b4cac06bd 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedParticipants.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedParticipants.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Game.Online.Multiplayer; using osu.Game.Screens.Multi.Components; using osuTK; @@ -12,10 +13,11 @@ namespace osu.Game.Tests.Visual.Multiplayer { protected override bool UseOnlineAPI => true; - public TestSceneOverlinedParticipants() + [SetUp] + public void Setup() => Schedule(() => { - Room.RoomID.Value = 7; - } + Room = new Room { RoomID = { Value = 7 } }; + }); [Test] public void TestHorizontalLayout() diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs index 14b7934dc7..88b2a6a4bc 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneOverlinedPlaylist.cs @@ -16,6 +16,8 @@ namespace osu.Game.Tests.Visual.Multiplayer public TestSceneOverlinedPlaylist() { + Room = new Room { RoomID = { Value = 7 } }; + for (int i = 0; i < 10; i++) { Room.Playlist.Add(new PlaylistItem diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneParticipantsList.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneParticipantsList.cs index 9c4c45f94a..f71c5fc5d2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneParticipantsList.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneParticipantsList.cs @@ -1,7 +1,9 @@ // 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.Graphics; +using osu.Game.Online.Multiplayer; using osu.Game.Screens.Multi.Components; namespace osu.Game.Tests.Visual.Multiplayer @@ -10,10 +12,14 @@ namespace osu.Game.Tests.Visual.Multiplayer { protected override bool UseOnlineAPI => true; + [SetUp] + public void Setup() => Schedule(() => + { + Room = new Room { RoomID = { Value = 7 } }; + }); + public TestSceneParticipantsList() { - Room.RoomID.Value = 7; - Add(new ParticipantsList { RelativeSizeAxes = Axes.Both }); } } diff --git a/osu.Game/Tests/Visual/MultiplayerTestScene.cs b/osu.Game/Tests/Visual/MultiplayerTestScene.cs index ffb431b4d3..4d073f16f4 100644 --- a/osu.Game/Tests/Visual/MultiplayerTestScene.cs +++ b/osu.Game/Tests/Visual/MultiplayerTestScene.cs @@ -10,7 +10,7 @@ namespace osu.Game.Tests.Visual public abstract class MultiplayerTestScene : ScreenTestScene { [Cached] - private readonly Bindable currentRoom = new Bindable(new Room()); + private readonly Bindable currentRoom = new Bindable(); protected Room Room { From 4c24388fc0a6ad7d14ffb50142ea5124c76fad4f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Jul 2020 10:16:44 +0900 Subject: [PATCH 741/871] Apply review fixes --- osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs | 2 +- osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeSubScreen.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs index ef9bdd5f27..46bc279d5c 100644 --- a/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs @@ -11,7 +11,7 @@ using osu.Game.Users; namespace osu.Game.Tests.Visual.Multiplayer { - public class RoomManagerTestScene : MultiplayerTestScene + public abstract class RoomManagerTestScene : MultiplayerTestScene { [Cached(Type = typeof(IRoomManager))] protected TestRoomManager RoomManager { get; } = new TestRoomManager(); diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeSubScreen.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeSubScreen.cs index c4ec74859b..68987127d2 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeSubScreen.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeSubScreen.cs @@ -26,7 +26,6 @@ namespace osu.Game.Tests.Visual.Multiplayer { base.SetUpSteps(); - AddStep("clear rooms", () => RoomManager.Rooms.Clear()); AddStep("push screen", () => LoadScreen(loungeScreen = new LoungeSubScreen { Anchor = Anchor.Centre, From 1bcd673a55437e0c4945ad663b13c5ee1a6dd3d4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Jul 2020 12:07:17 +0900 Subject: [PATCH 742/871] Fix crash when switching rooms quickly --- osu.Game/Online/Multiplayer/Room.cs | 32 ++++++++++++++--------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index d074ac9775..66d5d8b3e0 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -16,54 +16,54 @@ namespace osu.Game.Online.Multiplayer { [Cached] [JsonProperty("id")] - public Bindable RoomID { get; private set; } = new Bindable(); + public readonly Bindable RoomID = new Bindable(); [Cached] [JsonProperty("name")] - public Bindable Name { get; private set; } = new Bindable(); + public readonly Bindable Name = new Bindable(); [Cached] [JsonProperty("host")] - public Bindable Host { get; private set; } = new Bindable(); + public readonly Bindable Host = new Bindable(); [Cached] [JsonProperty("playlist")] - public BindableList Playlist { get; private set; } = new BindableList(); + public readonly BindableList Playlist = new BindableList(); [Cached] [JsonProperty("channel_id")] - public Bindable ChannelId { get; private set; } = new Bindable(); + public readonly Bindable ChannelId = new Bindable(); [Cached] [JsonIgnore] - public Bindable Duration { get; private set; } = new Bindable(TimeSpan.FromMinutes(30)); + public readonly Bindable Duration = new Bindable(TimeSpan.FromMinutes(30)); [Cached] [JsonIgnore] - public Bindable MaxAttempts { get; private set; } = new Bindable(); + public readonly Bindable MaxAttempts = new Bindable(); [Cached] [JsonIgnore] - public Bindable Status { get; private set; } = new Bindable(new RoomStatusOpen()); + public readonly Bindable Status = new Bindable(new RoomStatusOpen()); [Cached] [JsonIgnore] - public Bindable Availability { get; private set; } = new Bindable(); + public readonly Bindable Availability = new Bindable(); [Cached] [JsonIgnore] - public Bindable Type { get; private set; } = new Bindable(new GameTypeTimeshift()); + public readonly Bindable Type = new Bindable(new GameTypeTimeshift()); [Cached] [JsonIgnore] - public Bindable MaxParticipants { get; private set; } = new Bindable(); + public readonly Bindable MaxParticipants = new Bindable(); [Cached] [JsonProperty("recent_participants")] - public BindableList RecentParticipants { get; private set; } = new BindableList(); + public readonly BindableList RecentParticipants = new BindableList(); [Cached] - public Bindable ParticipantCount { get; private set; } = new Bindable(); + public readonly Bindable ParticipantCount = new Bindable(); // todo: TEMPORARY [JsonProperty("participant_count")] @@ -83,7 +83,7 @@ namespace osu.Game.Online.Multiplayer // Only supports retrieval for now [Cached] [JsonProperty("ends_at")] - public Bindable EndDate { get; private set; } = new Bindable(); + public readonly Bindable EndDate = new Bindable(); // Todo: Find a better way to do this (https://github.com/ppy/osu-framework/issues/1930) [JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)] @@ -97,7 +97,7 @@ namespace osu.Game.Online.Multiplayer /// The position of this in the list. This is not read from or written to the API. /// [JsonIgnore] - public Bindable Position { get; private set; } = new Bindable(-1); + public readonly Bindable Position = new Bindable(-1); public void CopyFrom(Room other) { @@ -130,7 +130,7 @@ namespace osu.Game.Online.Multiplayer RecentParticipants.AddRange(other.RecentParticipants); } - Position = other.Position; + Position.Value = other.Position.Value; } public bool ShouldSerializeRoomID() => false; From e211ba5e7dc3c4c5c332ca9ec4105d77391dcd7b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 14:43:30 +0900 Subject: [PATCH 743/871] Fix cursor scale potentially not being updated if set too early --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs | 10 ++++++---- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs index 28600ef55b..5812e8cf75 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursorContainer.cs @@ -30,7 +30,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private readonly Drawable cursorTrail; - public Bindable CursorScale = new BindableFloat(1); + public IBindable CursorScale => cursorScale; + + private readonly Bindable cursorScale = new BindableFloat(1); private Bindable userCursorScale; private Bindable autoCursorScale; @@ -68,13 +70,13 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor autoCursorScale = config.GetBindable(OsuSetting.AutoCursorSize); autoCursorScale.ValueChanged += _ => calculateScale(); - CursorScale.ValueChanged += e => + CursorScale.BindValueChanged(e => { var newScale = new Vector2(e.NewValue); ActiveCursor.Scale = newScale; cursorTrail.Scale = newScale; - }; + }, true); calculateScale(); } @@ -95,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor scale *= GetScaleForCircleSize(beatmap.BeatmapInfo.BaseDifficulty.CircleSize); } - CursorScale.Value = scale; + cursorScale.Value = scale; var newScale = new Vector2(scale); diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index abba444c73..ec7751d2b4 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.UI private OsuClickToResumeCursor clickToResumeCursor; private OsuCursorContainer localCursorContainer; - private Bindable localCursorScale; + private IBindable localCursorScale; public override CursorContainer LocalCursor => State.Value == Visibility.Visible ? localCursorContainer : null; From a21c2422c5ec70285ec8f2235a275de1449109f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 14:44:20 +0900 Subject: [PATCH 744/871] Make cursor centre portion non-expanding and more visible with outline --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 34 +++++++++----------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index 4f3d07f208..ef05514146 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -115,24 +115,22 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor }, }, }, - new CircularContainer - { - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - RelativeSizeAxes = Axes.Both, - Scale = new Vector2(0.1f), - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, - }, - }, - } - } + }, + }, + new Circle + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Scale = new Vector2(0.14f), + Colour = new Color4(34, 93, 204, 255), + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Radius = 8, + Colour = Color4.White, + }, + }, }; } } From c562435267a6fcd2a650380b1a54f92356fe9015 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 14:44:30 +0900 Subject: [PATCH 745/871] Adjust cursor transforms for better feel --- osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs index ef05514146..eea45c6c80 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/OsuCursor.cs @@ -59,10 +59,10 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { if (!cursorExpand) return; - expandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 100, Easing.OutQuad); + expandTarget.ScaleTo(released_scale).ScaleTo(pressed_scale, 400, Easing.OutElasticHalf); } - public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); + public void Contract() => expandTarget.ScaleTo(released_scale, 400, Easing.OutQuad); private class DefaultCursor : OsuCursorSprite { From 13618915b7ff9ab4eb52bc3f0efd203a2450722f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 14:46:49 +0900 Subject: [PATCH 746/871] Don't show cursor guide in gameplay cursor test --- osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index 38c2bb9b95..16eedad465 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -87,6 +87,7 @@ namespace osu.Game.Rulesets.Osu.Tests public MovingCursorInputManager() { UseParentInput = false; + ShowVisualCursorGuide = false; } protected override void Update() From fee19753e12c27e5ea429c276f015159d5f1fe6d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 14:47:11 +0900 Subject: [PATCH 747/871] Fix animations not playing correctly in test scene due to too many calls to OnPressed --- .../TestSceneGameplayCursor.cs | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index 16eedad465..dcac3367db 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -69,16 +69,27 @@ namespace osu.Game.Rulesets.Osu.Tests private class ClickingCursorContainer : OsuCursorContainer { + private bool pressed; + + public bool Pressed + { + set + { + if (value == pressed) + return; + + pressed = value; + if (value) + OnPressed(OsuAction.LeftButton); + else + OnReleased(OsuAction.LeftButton); + } + } + protected override void Update() { base.Update(); - - double currentTime = Time.Current; - - if (((int)(currentTime / 1000)) % 2 == 0) - OnPressed(OsuAction.LeftButton); - else - OnReleased(OsuAction.LeftButton); + Pressed = ((int)(Time.Current / 1000)) % 2 == 0; } } From b68a2d885c4a5ad5b7f21c886b8b146c0cceecae Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 14:47:26 +0900 Subject: [PATCH 748/871] Add testability against different background colours / with user input --- .../TestSceneGameplayCursor.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs index dcac3367db..461779b185 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneGameplayCursor.cs @@ -5,7 +5,9 @@ using System; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; using osu.Framework.Testing.Input; +using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Screens.Play; @@ -24,9 +26,34 @@ namespace osu.Game.Rulesets.Osu.Tests [Resolved] private OsuConfigManager config { get; set; } + private Drawable background; + public TestSceneGameplayCursor() { gameplayBeatmap = new GameplayBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)); + + AddStep("change background colour", () => + { + background?.Expire(); + + Add(background = new Box + { + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue, + Colour = new Colour4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1) + }); + }); + + AddSliderStep("circle size", 0f, 10f, 0f, val => + { + config.Set(OsuSetting.AutoCursorSize, true); + gameplayBeatmap.BeatmapInfo.BaseDifficulty.CircleSize = val; + Scheduler.AddOnce(recreate); + }); + + AddStep("test cursor container", recreate); + + void recreate() => SetContents(() => new OsuInputManager(new OsuRuleset().RulesetInfo) { Child = new OsuCursorContainer() }); } [TestCase(1, 1)] From bd5957bc0a9eabd0843b2e1d201c126ca44e1d3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 14:49:44 +0900 Subject: [PATCH 749/871] Add dynamic compilation exclusion rules for ruleset types --- osu.Game.Rulesets.Catch/CatchRuleset.cs | 2 ++ osu.Game.Rulesets.Mania/ManiaRuleset.cs | 2 ++ osu.Game.Rulesets.Osu/OsuRuleset.cs | 2 ++ osu.Game.Rulesets.Taiko/TaikoRuleset.cs | 2 ++ 4 files changed, 8 insertions(+) diff --git a/osu.Game.Rulesets.Catch/CatchRuleset.cs b/osu.Game.Rulesets.Catch/CatchRuleset.cs index ca75a816f1..9437023c70 100644 --- a/osu.Game.Rulesets.Catch/CatchRuleset.cs +++ b/osu.Game.Rulesets.Catch/CatchRuleset.cs @@ -21,11 +21,13 @@ using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; using System; +using osu.Framework.Testing; using osu.Game.Rulesets.Catch.Skinning; using osu.Game.Skinning; namespace osu.Game.Rulesets.Catch { + [ExcludeFromDynamicCompile] public class CatchRuleset : Ruleset, ILegacyRuleset { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableCatchRuleset(this, beatmap, mods); diff --git a/osu.Game.Rulesets.Mania/ManiaRuleset.cs b/osu.Game.Rulesets.Mania/ManiaRuleset.cs index a27485dd06..68dce8b139 100644 --- a/osu.Game.Rulesets.Mania/ManiaRuleset.cs +++ b/osu.Game.Rulesets.Mania/ManiaRuleset.cs @@ -12,6 +12,7 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Bindings; +using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Replays.Types; @@ -34,6 +35,7 @@ using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Rulesets.Mania { + [ExcludeFromDynamicCompile] public class ManiaRuleset : Ruleset, ILegacyRuleset { /// diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index e488ba65c8..eaa5d8937a 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -30,12 +30,14 @@ using osu.Game.Scoring; using osu.Game.Skinning; using System; using System.Linq; +using osu.Framework.Testing; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Statistics; using osu.Game.Screens.Ranking.Statistics; namespace osu.Game.Rulesets.Osu { + [ExcludeFromDynamicCompile] public class OsuRuleset : Ruleset, ILegacyRuleset { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableOsuRuleset(this, beatmap, mods); diff --git a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs index 156905fa9c..2011842591 100644 --- a/osu.Game.Rulesets.Taiko/TaikoRuleset.cs +++ b/osu.Game.Rulesets.Taiko/TaikoRuleset.cs @@ -22,6 +22,7 @@ using osu.Game.Rulesets.Taiko.Scoring; using osu.Game.Scoring; using System; using System.Linq; +using osu.Framework.Testing; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Taiko.Edit; using osu.Game.Rulesets.Taiko.Objects; @@ -31,6 +32,7 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Taiko { + [ExcludeFromDynamicCompile] public class TaikoRuleset : Ruleset, ILegacyRuleset { public override DrawableRuleset CreateDrawableRulesetWith(IBeatmap beatmap, IReadOnlyList mods = null) => new DrawableTaikoRuleset(this, beatmap, mods); From a9faa11dcbfcd885abedf03a33ec621d7dc435b4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Jul 2020 15:37:08 +0900 Subject: [PATCH 750/871] Add back playlist header --- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index dffd6a0331..1233581575 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -134,6 +134,7 @@ namespace osu.Game.Screens.Multi.Match RelativeSizeAxes = Axes.Both, Content = new[] { + new Drawable[] { new OverlinedHeader("Playlist"), }, new Drawable[] { new DrawableRoomPlaylist(false, true) // Temporarily always allow selection @@ -156,6 +157,7 @@ namespace osu.Game.Screens.Multi.Match }, RowDimensions = new[] { + new Dimension(GridSizeMode.AutoSize), new Dimension(), new Dimension(GridSizeMode.Absolute, 5), new Dimension(GridSizeMode.AutoSize) From 2ed8d42d222b45f53f029ac0c4d93d06a0a71916 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Jul 2020 15:37:13 +0900 Subject: [PATCH 751/871] Remove whitespace --- osu.Game/Screens/Multi/Match/MatchSubScreen.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 1233581575..40a8427701 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -224,7 +224,6 @@ namespace osu.Game.Screens.Multi.Match } [Resolved] - private IAPIProvider api { get; set; } protected override void LoadComplete() From bc6f2199f3deb2c50fd0732aadd59e7111617e09 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 16:49:11 +0900 Subject: [PATCH 752/871] 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 ff04c7f120..0881861bdc 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e4753e7ee9..cba2d62bf5 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 91fa003604..45e0da36c1 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From 632f333ce2361af1e9b9cdb3c41ebfbe381b2656 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 16:33:20 +0900 Subject: [PATCH 753/871] Add ability to return protected beatmaps in GetAllUsable call --- osu.Game/Beatmaps/BeatmapManager.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 637833fb5d..b4b341634c 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -283,14 +283,16 @@ namespace osu.Game.Beatmaps /// Returns a list of all usable s. /// /// A list of available . - public List GetAllUsableBeatmapSets(IncludedDetails includes = IncludedDetails.All) => GetAllUsableBeatmapSetsEnumerable(includes).ToList(); + public List GetAllUsableBeatmapSets(IncludedDetails includes = IncludedDetails.All, bool includeProtected = false) => + GetAllUsableBeatmapSetsEnumerable(includes, includeProtected).ToList(); /// /// Returns a list of all usable s. Note that files are not populated. /// /// The level of detail to include in the returned objects. + /// Whether to include protected (system) beatmaps. These should not be included for gameplay playable use cases. /// A list of available . - public IEnumerable GetAllUsableBeatmapSetsEnumerable(IncludedDetails includes) + public IEnumerable GetAllUsableBeatmapSetsEnumerable(IncludedDetails includes, bool includeProtected = false) { IQueryable queryable; @@ -312,7 +314,7 @@ namespace osu.Game.Beatmaps // AsEnumerable used here to avoid applying the WHERE in sql. When done so, ef core 2.x uses an incorrect ORDER BY // clause which causes queries to take 5-10x longer. // TODO: remove if upgrading to EF core 3.x. - return queryable.AsEnumerable().Where(s => !s.DeletePending && !s.Protected); + return queryable.AsEnumerable().Where(s => !s.DeletePending && (includeProtected || !s.Protected)); } /// From 49b88971d1ef7e05887296003566a86216b0a901 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 16:33:31 +0900 Subject: [PATCH 754/871] Display all usable beatmaps in playlist, including protected --- osu.Game/Overlays/MusicController.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 92cf490be2..63e828a782 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Input.Bindings; @@ -71,7 +72,7 @@ namespace osu.Game.Overlays managerRemoved = beatmaps.ItemRemoved.GetBoundCopy(); managerRemoved.BindValueChanged(beatmapRemoved); - beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal).OrderBy(_ => RNG.Next())); + beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal, true).OrderBy(_ => RNG.Next())); } protected override void LoadComplete() @@ -135,6 +136,7 @@ namespace osu.Game.Overlays /// /// Start playing the current track (if not already playing). + /// Will select the next valid track if the current track is null or . /// /// Whether the operation was successful. public bool Play(bool restart = false) @@ -143,12 +145,12 @@ namespace osu.Game.Overlays IsUserPaused = false; - if (track == null) + if (track == null || track is TrackVirtual) { if (beatmap.Disabled) return false; - next(true); + next(); return true; } @@ -228,10 +230,9 @@ namespace osu.Game.Overlays /// public void NextTrack() => Schedule(() => next()); - private bool next(bool instant = false) + private bool next() { - if (!instant) - queuedDirection = TrackChangeDirection.Next; + queuedDirection = TrackChangeDirection.Next; var playable = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? BeatmapSets.FirstOrDefault(); From 44fdb5b82e040e1a129257f177f51e35ee8ff1a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 16:33:45 +0900 Subject: [PATCH 755/871] Ensure music starts when returning to lounge or main menu --- osu.Game/Screens/Menu/MainMenu.cs | 4 ++-- osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 76950982e6..41e2564141 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -260,8 +260,8 @@ namespace osu.Game.Screens.Menu // we may have consumed our preloaded instance, so let's make another. preloadSongSelect(); - if (Beatmap.Value.Track != null && music?.IsUserPaused != true) - Beatmap.Value.Track.Start(); + if (music?.IsUserPaused == false) + music.Play(); } public override bool OnExiting(IScreen next) diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index f512b864a6..e2ffb21153 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -11,6 +11,7 @@ using osu.Framework.Screens; using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; using osu.Game.Online.Multiplayer; +using osu.Game.Overlays; using osu.Game.Overlays.SearchableList; using osu.Game.Screens.Multi.Lounge.Components; using osu.Game.Screens.Multi.Match; @@ -31,6 +32,9 @@ namespace osu.Game.Screens.Multi.Lounge [Resolved] private Bindable selectedRoom { get; set; } + [Resolved(canBeNull: true)] + private MusicController music { get; set; } + private bool joiningRoom; [BackgroundDependencyLoader] @@ -122,6 +126,9 @@ namespace osu.Game.Screens.Multi.Lounge if (selectedRoom.Value?.RoomID.Value == null) selectedRoom.Value = new Room(); + if (music?.IsUserPaused == false) + music.Play(); + onReturning(); } From d0c2a1b9d30a1f3b37b59d59f7a02d9caf1e3693 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Jul 2020 17:25:07 +0900 Subject: [PATCH 756/871] Move dropdown out of display style selector --- .../SearchableList/DisplayStyleControl.cs | 38 ++++--------------- .../SearchableListFilterControl.cs | 32 ++++++++++++---- osu.Game/Overlays/SocialOverlay.cs | 4 +- 3 files changed, 35 insertions(+), 39 deletions(-) diff --git a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs b/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs index 5ecb477a2f..ffbc1c9586 100644 --- a/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs +++ b/osu.Game/Overlays/SearchableList/DisplayStyleControl.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Bindables; using osuTK; using osu.Framework.Graphics; @@ -11,44 +10,23 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Overlays.SearchableList { - public class DisplayStyleControl : Container - where T : struct, Enum + public class DisplayStyleControl : CompositeDrawable { - public readonly SlimEnumDropdown Dropdown; public readonly Bindable DisplayStyle = new Bindable(); public DisplayStyleControl() { AutoSizeAxes = Axes.Both; - Children = new[] + InternalChild = new FillFlowContainer { - new FillFlowContainer + AutoSizeAxes = Axes.Both, + Spacing = new Vector2(5f, 0f), + Direction = FillDirection.Horizontal, + Children = new[] { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, - Spacing = new Vector2(10f, 0f), - Direction = FillDirection.Horizontal, - Children = new Drawable[] - { - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Spacing = new Vector2(5f, 0f), - Direction = FillDirection.Horizontal, - Children = new[] - { - new DisplayStyleToggleButton(FontAwesome.Solid.ThLarge, PanelDisplayStyle.Grid, DisplayStyle), - new DisplayStyleToggleButton(FontAwesome.Solid.ListUl, PanelDisplayStyle.List, DisplayStyle), - }, - }, - Dropdown = new SlimEnumDropdown - { - RelativeSizeAxes = Axes.None, - Width = 160f, - }, - }, + new DisplayStyleToggleButton(FontAwesome.Solid.ThLarge, PanelDisplayStyle.Grid, DisplayStyle), + new DisplayStyleToggleButton(FontAwesome.Solid.ListUl, PanelDisplayStyle.List, DisplayStyle), }, }; diff --git a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs index de5e558943..3d0ff373b7 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs @@ -19,12 +19,14 @@ namespace osu.Game.Overlays.SearchableList { private const float padding = 10; - private readonly Container filterContainer; + private readonly Drawable filterContainer; + private readonly Drawable rightFilterContainer; private readonly Box tabStrip; public readonly SearchTextBox Search; public readonly PageTabControl Tabs; - public readonly DisplayStyleControl DisplayStyleControl; + public readonly SlimEnumDropdown Dropdown; + public readonly DisplayStyleControl DisplayStyleControl; protected abstract Color4 BackgroundColour { get; } protected abstract TTab DefaultTab { get; } @@ -42,7 +44,7 @@ namespace osu.Game.Overlays.SearchableList var controls = CreateSupplementaryControls(); Container controlsContainer; - Children = new Drawable[] + Children = new[] { filterContainer = new Container { @@ -104,11 +106,27 @@ namespace osu.Game.Overlays.SearchableList }, }, }, - DisplayStyleControl = new DisplayStyleControl + rightFilterContainer = new FillFlowContainer { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - }, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + Dropdown = new SlimEnumDropdown + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + RelativeSizeAxes = Axes.None, + Width = 160f, + }, + DisplayStyleControl = new DisplayStyleControl + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + } + } }; if (controls != null) controlsContainer.Children = new[] { controls }; @@ -116,8 +134,8 @@ namespace osu.Game.Overlays.SearchableList Tabs.Current.Value = DefaultTab; Tabs.Current.TriggerChange(); - DisplayStyleControl.Dropdown.Current.Value = DefaultCategory; - DisplayStyleControl.Dropdown.Current.TriggerChange(); + Dropdown.Current.Value = DefaultCategory; + Dropdown.Current.TriggerChange(); } [BackgroundDependencyLoader] diff --git a/osu.Game/Overlays/SocialOverlay.cs b/osu.Game/Overlays/SocialOverlay.cs index 9548573b4f..1b05142192 100644 --- a/osu.Game/Overlays/SocialOverlay.cs +++ b/osu.Game/Overlays/SocialOverlay.cs @@ -72,7 +72,7 @@ namespace osu.Game.Overlays Filter.Tabs.Current.ValueChanged += _ => onFilterUpdate(); Filter.DisplayStyleControl.DisplayStyle.ValueChanged += _ => recreatePanels(); - Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => recreatePanels(); + Filter.Dropdown.Current.ValueChanged += _ => recreatePanels(); currentQuery.BindTo(Filter.Search.Current); currentQuery.ValueChanged += query => @@ -155,7 +155,7 @@ namespace osu.Game.Overlays break; } - if (Filter.DisplayStyleControl.Dropdown.Current.Value == SortDirection.Descending) + if (Filter.Dropdown.Current.Value == SortDirection.Descending) sortedUsers = sortedUsers.Reverse(); var newPanels = new FillFlowContainer From ed926de77ffe739c2ea3fa07edd39ab1daba5ad3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Jul 2020 17:25:28 +0900 Subject: [PATCH 757/871] Fix up/improve dropdown styling/positioning --- osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs index 3d0ff373b7..e0163b5b0c 100644 --- a/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs +++ b/osu.Game/Overlays/SearchableList/SearchableListFilterControl.cs @@ -149,7 +149,7 @@ namespace osu.Game.Overlays.SearchableList base.Update(); Height = filterContainer.Height; - DisplayStyleControl.Margin = new MarginPadding { Top = filterContainer.Height - 35, Right = SearchableListOverlay.WIDTH_PADDING }; + rightFilterContainer.Margin = new MarginPadding { Top = filterContainer.Height - 30, Right = ContentHorizontalPadding }; } private class FilterSearchTextBox : SearchTextBox From 926279e39be2d81a6906d13fbeca6f45dfabdb6b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Jul 2020 17:26:42 +0900 Subject: [PATCH 758/871] Implement category dropdown for multiplayer --- .../TestSceneLoungeFilterControl.cs | 20 +++++++ .../Online/API/Requests/GetRoomsRequest.cs | 54 ++++++++++--------- .../Multi/Lounge/Components/FilterControl.cs | 19 +++---- .../Multi/Lounge/Components/FilterCriteria.cs | 4 +- .../Multi/Lounge/Components/RoomsContainer.cs | 8 --- osu.Game/Screens/Multi/RoomManager.cs | 2 +- 6 files changed, 63 insertions(+), 44 deletions(-) create mode 100644 osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeFilterControl.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeFilterControl.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeFilterControl.cs new file mode 100644 index 0000000000..7c0c2797f5 --- /dev/null +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeFilterControl.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.Framework.Graphics; +using osu.Game.Screens.Multi.Lounge.Components; + +namespace osu.Game.Tests.Visual.Multiplayer +{ + public class TestSceneLoungeFilterControl : OsuTestScene + { + public TestSceneLoungeFilterControl() + { + Child = new FilterControl + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + } + } +} diff --git a/osu.Game/Online/API/Requests/GetRoomsRequest.cs b/osu.Game/Online/API/Requests/GetRoomsRequest.cs index 8f1497ef33..4b90b04b51 100644 --- a/osu.Game/Online/API/Requests/GetRoomsRequest.cs +++ b/osu.Game/Online/API/Requests/GetRoomsRequest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using osu.Framework.IO.Network; using osu.Game.Online.Multiplayer; using osu.Game.Screens.Multi.Lounge.Components; @@ -9,39 +10,44 @@ namespace osu.Game.Online.API.Requests { public class GetRoomsRequest : APIRequest> { - private readonly PrimaryFilter primaryFilter; + private readonly RoomStatusFilter statusFilter; + private readonly RoomCategoryFilter categoryFilter; - public GetRoomsRequest(PrimaryFilter primaryFilter) + public GetRoomsRequest(RoomStatusFilter statusFilter, RoomCategoryFilter categoryFilter) { - this.primaryFilter = primaryFilter; + this.statusFilter = statusFilter; + this.categoryFilter = categoryFilter; } - protected override string Target + protected override WebRequest CreateWebRequest() { - get + var req = base.CreateWebRequest(); + + switch (statusFilter) { - string target = "rooms"; + case RoomStatusFilter.Owned: + req.AddParameter("mode", "owned"); + break; - switch (primaryFilter) - { - case PrimaryFilter.Open: - break; + case RoomStatusFilter.Participated: + req.AddParameter("mode", "participated"); + break; - case PrimaryFilter.Owned: - target += "/owned"; - break; - - case PrimaryFilter.Participated: - target += "/participated"; - break; - - case PrimaryFilter.RecentlyEnded: - target += "/ended"; - break; - } - - return target; + case RoomStatusFilter.RecentlyEnded: + req.AddParameter("mode", "ended"); + break; } + + switch (categoryFilter) + { + case RoomCategoryFilter.Spotlight: + req.AddParameter("category", "spotlight"); + break; + } + + return req; } + + protected override string Target => "rooms"; } } diff --git a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs index 2742ef3404..3a4ead44b7 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs @@ -12,11 +12,11 @@ using osuTK.Graphics; namespace osu.Game.Screens.Multi.Lounge.Components { - public class FilterControl : SearchableListFilterControl + public class FilterControl : SearchableListFilterControl { protected override Color4 BackgroundColour => Color4.Black.Opacity(0.5f); - protected override PrimaryFilter DefaultTab => PrimaryFilter.Open; - protected override SecondaryFilter DefaultCategory => SecondaryFilter.Public; + protected override RoomStatusFilter DefaultTab => RoomStatusFilter.Open; + protected override RoomCategoryFilter DefaultCategory => RoomCategoryFilter.Normal; protected override float ContentHorizontalPadding => base.ContentHorizontalPadding + OsuScreen.HORIZONTAL_OVERFLOW_PADDING; @@ -43,6 +43,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components ruleset.BindValueChanged(_ => updateFilter()); Search.Current.BindValueChanged(_ => scheduleUpdateFilter()); + Dropdown.Current.BindValueChanged(_ => updateFilter()); Tabs.Current.BindValueChanged(_ => updateFilter(), true); } @@ -61,14 +62,14 @@ namespace osu.Game.Screens.Multi.Lounge.Components filter.Value = new FilterCriteria { SearchString = Search.Current.Value ?? string.Empty, - PrimaryFilter = Tabs.Current.Value, - SecondaryFilter = DisplayStyleControl.Dropdown.Current.Value, + StatusFilter = Tabs.Current.Value, + RoomCategoryFilter = Dropdown.Current.Value, Ruleset = ruleset.Value }; } } - public enum PrimaryFilter + public enum RoomStatusFilter { Open, @@ -78,9 +79,9 @@ namespace osu.Game.Screens.Multi.Lounge.Components Owned, } - public enum SecondaryFilter + public enum RoomCategoryFilter { - Public, - //Private, + Normal, + Spotlight } } diff --git a/osu.Game/Screens/Multi/Lounge/Components/FilterCriteria.cs b/osu.Game/Screens/Multi/Lounge/Components/FilterCriteria.cs index 26d445e151..6d70225eec 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/FilterCriteria.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/FilterCriteria.cs @@ -8,8 +8,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components public class FilterCriteria { public string SearchString; - public PrimaryFilter PrimaryFilter; - public SecondaryFilter SecondaryFilter; + public RoomStatusFilter StatusFilter; + public RoomCategoryFilter RoomCategoryFilter; public RulesetInfo Ruleset; } } diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index bf153b77df..447c99039a 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -77,14 +77,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components if (!string.IsNullOrEmpty(criteria.SearchString)) matchingFilter &= r.FilterTerms.Any(term => term.IndexOf(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase) >= 0); - switch (criteria.SecondaryFilter) - { - default: - case SecondaryFilter.Public: - matchingFilter &= r.Room.Availability.Value == RoomAvailability.Public; - break; - } - r.MatchingFilter = matchingFilter; } }); diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index 642378d8d5..ac1f74b6a6 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -318,7 +318,7 @@ namespace osu.Game.Screens.Multi var tcs = new TaskCompletionSource(); pollReq?.Cancel(); - pollReq = new GetRoomsRequest(currentFilter.Value.PrimaryFilter); + pollReq = new GetRoomsRequest(currentFilter.Value.StatusFilter, currentFilter.Value.RoomCategoryFilter); pollReq.Success += result => { From 1760cc242730fb1998135a79bd74586f63fc79f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 18:03:56 +0900 Subject: [PATCH 759/871] Fix behavioural regression by splitting methods out --- osu.Game/Overlays/MusicController.cs | 34 ++++++++++++++----- osu.Game/Screens/Menu/MainMenu.cs | 3 +- .../Screens/Multi/Lounge/LoungeSubScreen.cs | 3 +- 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 63e828a782..09f2a66b47 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -134,9 +134,31 @@ namespace osu.Game.Overlays }); } + /// + /// Ensures music is playing, no matter what, unless the user has explicitly paused. + /// This means that if the current beatmap has a virtual track (see ) a new beatmap will be selected. + /// + public void EnsurePlayingSomething() + { + if (IsUserPaused) return; + + var track = current?.Track; + + if (track == null || track is TrackVirtual) + { + if (beatmap.Disabled) + return; + + next(); + } + else if (!IsPlaying) + { + Play(); + } + } + /// /// Start playing the current track (if not already playing). - /// Will select the next valid track if the current track is null or . /// /// Whether the operation was successful. public bool Play(bool restart = false) @@ -145,14 +167,8 @@ namespace osu.Game.Overlays IsUserPaused = false; - if (track == null || track is TrackVirtual) - { - if (beatmap.Disabled) - return false; - - next(); - return true; - } + if (track == null) + return false; if (restart) track.Restart(); diff --git a/osu.Game/Screens/Menu/MainMenu.cs b/osu.Game/Screens/Menu/MainMenu.cs index 41e2564141..57252d557e 100644 --- a/osu.Game/Screens/Menu/MainMenu.cs +++ b/osu.Game/Screens/Menu/MainMenu.cs @@ -260,8 +260,7 @@ namespace osu.Game.Screens.Menu // we may have consumed our preloaded instance, so let's make another. preloadSongSelect(); - if (music?.IsUserPaused == false) - music.Play(); + music.EnsurePlayingSomething(); } public override bool OnExiting(IScreen next) diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index e2ffb21153..ff7d56a95b 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -126,8 +126,7 @@ namespace osu.Game.Screens.Multi.Lounge if (selectedRoom.Value?.RoomID.Value == null) selectedRoom.Value = new Room(); - if (music?.IsUserPaused == false) - music.Play(); + music.EnsurePlayingSomething(); onReturning(); } From cb56b8e031a6bbb431963d46d28349ca318fbf59 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 18:13:58 +0900 Subject: [PATCH 760/871] Add test for menu playing music on return --- .../Navigation/TestSceneScreenNavigation.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index 9d603ac471..8ccaca8630 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -6,6 +6,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Overlays.Mods; @@ -70,6 +71,23 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("Ensure time wasn't reset to preview point", () => track().CurrentTime < beatmap().Metadata.PreviewTime); } + [Test] + public void TestMenuMakesMusic() + { + WorkingBeatmap beatmap() => Game.Beatmap.Value; + Track track() => beatmap().Track; + + TestSongSelect songSelect = null; + + PushAndConfirm(() => songSelect = new TestSongSelect()); + + AddUntilStep("wait for no track", () => track() is TrackVirtual); + + AddStep("return to menu", () => songSelect.Exit()); + + AddUntilStep("wait for track", () => !(track() is TrackVirtual) && track().IsRunning); + } + [Test] public void TestExitSongSelectWithClick() { From f699a34c77298ec0324903659d63cd9d4ab48808 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 18:19:18 +0900 Subject: [PATCH 761/871] Rename variable for potential future expansion --- osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs | 2 +- osu.Game/Graphics/UserInterface/OsuTextBox.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs index 11867cf103..ac6f5ceb1b 100644 --- a/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuPasswordTextBox.cs @@ -24,7 +24,7 @@ namespace osu.Game.Graphics.UserInterface Child = new PasswordMaskChar(CalculatedTextSize), }; - protected override bool AllowUpperCaseSamples => false; + protected override bool AllowUniqueCharacterSamples => false; protected override bool AllowClipboardExport => false; diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 753efcd16d..0d173e2d3e 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -30,10 +30,10 @@ namespace osu.Game.Graphics.UserInterface private SampleChannel caretMovedSample; /// - /// Whether to allow playing a different sample when inserting upper case text. - /// If set to false, same sample will be played for both letter cases. + /// Whether to allow playing a different samples based on the type of character. + /// If set to false, the same sample will be used for all characters. /// - protected virtual bool AllowUpperCaseSamples => true; + protected virtual bool AllowUniqueCharacterSamples => true; protected override float LeftRightPadding => 10; @@ -78,7 +78,7 @@ namespace osu.Game.Graphics.UserInterface { base.OnTextAdded(added); - if (added.Any(char.IsUpper) && AllowUpperCaseSamples) + if (added.Any(char.IsUpper) && AllowUniqueCharacterSamples) capsTextAddedSample?.Play(); else textAddedSamples[RNG.Next(0, 3)]?.Play(); From 8aff828dfe51a56be3c6eb9f7c5001830f351612 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 18:34:31 +0900 Subject: [PATCH 762/871] Move application of judgements to Apply method --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 4 +- .../Rulesets/Judgements/DrawableJudgement.cs | 47 ++++++++----------- 2 files changed, 22 insertions(+), 29 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 2eff99bd3e..291a572fdf 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.UI DrawableOsuJudgement explosion = poolDictionary[result.Type].Get(doj => { - doj.JudgedObject = judgedObject; + doj.Apply(result, judgedObject); // todo: move to JudgedObject property? doj.Position = osuObject.StackedEndPosition; @@ -143,7 +143,7 @@ namespace osu.Game.Rulesets.Osu.UI var judgement = base.CreateNewDrawable(); // just a placeholder to initialise the correct drawable hierarchy for this pool. - judgement.Result = new JudgementResult(new HitObject(), new Judgement()) { Type = result }; + judgement.Apply(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null); return judgement; } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 3ec5326299..1671e97db1 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -2,9 +2,9 @@ // See the LICENCE file in the repository root for full licence text. using System.Diagnostics; +using JetBrains.Annotations; using osuTK; using osu.Framework.Allocation; -using osu.Framework.Caching; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -28,24 +28,8 @@ namespace osu.Game.Rulesets.Judgements [Resolved] private OsuColour colours { get; set; } - private readonly Cached drawableCache = new Cached(); - - private JudgementResult result; - - public JudgementResult Result - { - get => result; - set - { - if (result?.Type == value.Type) - return; - - result = value; - drawableCache.Invalidate(); - } - } - - public DrawableHitObject JudgedObject; + public JudgementResult Result { get; private set; } + public DrawableHitObject JudgedObject { get; private set; } protected Container JudgementBody; protected SpriteText JudgementText; @@ -68,8 +52,7 @@ namespace osu.Game.Rulesets.Judgements public DrawableJudgement(JudgementResult result, DrawableHitObject judgedObject) : this() { - Result = result; - JudgedObject = judgedObject; + Apply(result, judgedObject); } public DrawableJudgement() @@ -78,6 +61,12 @@ namespace osu.Game.Rulesets.Judgements Origin = Anchor.Centre; } + [BackgroundDependencyLoader] + private void load() + { + prepareDrawables(); + } + protected virtual void ApplyHitAnimations() { JudgementBody.ScaleTo(0.9f); @@ -86,10 +75,10 @@ namespace osu.Game.Rulesets.Judgements this.Delay(FadeOutDelay).FadeOut(400); } - [BackgroundDependencyLoader] - private void load() + public void Apply([NotNull] JudgementResult result, [CanBeNull] DrawableHitObject judgedObject) { - prepareDrawables(); + Result = result; + JudgedObject = judgedObject; } protected override void PrepareForUse() @@ -98,8 +87,7 @@ namespace osu.Game.Rulesets.Judgements Debug.Assert(Result != null); - if (!drawableCache.IsValid) - prepareDrawables(); + prepareDrawables(); this.FadeInFromZero(FadeInDuration, Easing.OutQuint); JudgementBody.ScaleTo(1); @@ -129,10 +117,15 @@ namespace osu.Game.Rulesets.Judgements Expire(true); } + private HitResult? currentDrawableType; + private void prepareDrawables() { var type = Result?.Type ?? HitResult.Perfect; //TODO: better default type from ruleset + if (type == currentDrawableType) + return; + InternalChild = JudgementBody = new Container { Anchor = Anchor.Centre, @@ -147,7 +140,7 @@ namespace osu.Game.Rulesets.Judgements }, confineMode: ConfineMode.NoScaling) }; - drawableCache.Validate(); + currentDrawableType = type; } } } From f872343babd8ecc5285212d430abdef00be5664e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 18:35:20 +0900 Subject: [PATCH 763/871] Make Apply virtual to further simplify application process --- .../Objects/Drawables/DrawableOsuJudgement.cs | 11 +++++++++++ osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 11 +---------- osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 9d0c406295..fa980c7581 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -43,6 +43,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + public override void Apply(JudgementResult result, DrawableHitObject judgedObject) + { + base.Apply(result, judgedObject); + + if (judgedObject?.HitObject is OsuHitObject osuObject) + { + Position = osuObject.StackedPosition; + Scale = new Vector2(osuObject.Scale); + } + } + protected override void PrepareForUse() { base.PrepareForUse(); diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 291a572fdf..474e7c0f93 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -107,16 +107,7 @@ namespace osu.Game.Rulesets.Osu.UI if (!judgedObject.DisplayResult || !DisplayJudgements.Value) return; - var osuObject = (OsuHitObject)judgedObject.HitObject; - - DrawableOsuJudgement explosion = poolDictionary[result.Type].Get(doj => - { - doj.Apply(result, judgedObject); - - // todo: move to JudgedObject property? - doj.Position = osuObject.StackedEndPosition; - doj.Scale = new Vector2(osuObject.Scale); - }); + DrawableOsuJudgement explosion = poolDictionary[result.Type].Get(doj => doj.Apply(result, judgedObject)); judgementLayer.Add(explosion); } diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 1671e97db1..4e7f0018ef 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Judgements this.Delay(FadeOutDelay).FadeOut(400); } - public void Apply([NotNull] JudgementResult result, [CanBeNull] DrawableHitObject judgedObject) + public virtual void Apply([NotNull] JudgementResult result, [CanBeNull] DrawableHitObject judgedObject) { Result = result; JudgedObject = judgedObject; From 024fb52726d1d2b694caa6cc9fc5886dc76ae02e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 19:05:31 +0900 Subject: [PATCH 764/871] Fix unnecessary using --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 474e7c0f93..600efefca3 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -4,21 +4,20 @@ using System; using System.Collections.Generic; using System.Linq; -using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Pooling; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; -using osu.Game.Rulesets.UI; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Rulesets.Scoring; +using osu.Game.Rulesets.UI; using osu.Game.Skinning; +using osuTK; namespace osu.Game.Rulesets.Osu.UI { From cf3251a950a2f3318ce33b92f9b5b9a5c2655493 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 19:36:54 +0900 Subject: [PATCH 765/871] Default to showing all rooms --- osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs index 3a4ead44b7..f43763486c 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components { protected override Color4 BackgroundColour => Color4.Black.Opacity(0.5f); protected override RoomStatusFilter DefaultTab => RoomStatusFilter.Open; - protected override RoomCategoryFilter DefaultCategory => RoomCategoryFilter.Normal; + protected override RoomCategoryFilter DefaultCategory => RoomCategoryFilter.Any; protected override float ContentHorizontalPadding => base.ContentHorizontalPadding + OsuScreen.HORIZONTAL_OVERFLOW_PADDING; @@ -81,6 +81,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components public enum RoomCategoryFilter { + Any, Normal, Spotlight } From 64e8dce1ada23b4101ca49336cb382be1a76aff4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 19:37:27 +0900 Subject: [PATCH 766/871] Highlight spotlight rooms with a different colour --- .../Visual/Multiplayer/RoomManagerTestScene.cs | 3 ++- osu.Game/Online/Multiplayer/Room.cs | 4 ++++ osu.Game/Online/Multiplayer/RoomCategory.cs | 11 +++++++++++ .../Multi/Components/StatusColouredContainer.cs | 9 ++++++++- .../Screens/Multi/Lounge/Components/DrawableRoom.cs | 8 +++++--- 5 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 osu.Game/Online/Multiplayer/RoomCategory.cs diff --git a/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs index 46bc279d5c..8b7e0fd9da 100644 --- a/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/RoomManagerTestScene.cs @@ -34,7 +34,8 @@ namespace osu.Game.Tests.Visual.Multiplayer RoomID = { Value = i }, Name = { Value = $"Room {i}" }, Host = { Value = new User { Username = "Host" } }, - EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) } + EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }, + Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal } }; if (ruleset != null) diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index 66d5d8b3e0..34cf158442 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -34,6 +34,10 @@ namespace osu.Game.Online.Multiplayer [JsonProperty("channel_id")] public readonly Bindable ChannelId = new Bindable(); + [Cached] + [JsonProperty("category")] + public readonly Bindable Category = new Bindable(); + [Cached] [JsonIgnore] public readonly Bindable Duration = new Bindable(TimeSpan.FromMinutes(30)); diff --git a/osu.Game/Online/Multiplayer/RoomCategory.cs b/osu.Game/Online/Multiplayer/RoomCategory.cs new file mode 100644 index 0000000000..636a73a3e9 --- /dev/null +++ b/osu.Game/Online/Multiplayer/RoomCategory.cs @@ -0,0 +1,11 @@ +// 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.Online.Multiplayer +{ + public enum RoomCategory + { + Normal, + Spotlight + } +} diff --git a/osu.Game/Screens/Multi/Components/StatusColouredContainer.cs b/osu.Game/Screens/Multi/Components/StatusColouredContainer.cs index 97af6674bf..a115f06e7b 100644 --- a/osu.Game/Screens/Multi/Components/StatusColouredContainer.cs +++ b/osu.Game/Screens/Multi/Components/StatusColouredContainer.cs @@ -17,6 +17,9 @@ namespace osu.Game.Screens.Multi.Components [Resolved(typeof(Room), nameof(Room.Status))] private Bindable status { get; set; } + [Resolved(typeof(Room), nameof(Room.Category))] + private Bindable category { get; set; } + public StatusColouredContainer(double transitionDuration = 100) { this.transitionDuration = transitionDuration; @@ -25,7 +28,11 @@ namespace osu.Game.Screens.Multi.Components [BackgroundDependencyLoader] private void load(OsuColour colours) { - status.BindValueChanged(s => this.FadeColour(s.NewValue.GetAppropriateColour(colours), transitionDuration), true); + status.BindValueChanged(s => + { + this.FadeColour(category.Value == RoomCategory.Spotlight ? colours.Pink : s.NewValue.GetAppropriateColour(colours) + , transitionDuration); + }, true); } } } diff --git a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs index de02d779e1..3f5a2eb1d3 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs @@ -107,6 +107,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components [BackgroundDependencyLoader] private void load(OsuColour colours) { + float stripWidth = side_strip_width * (Room.Category.Value == RoomCategory.Spotlight ? 2 : 1); + Children = new Drawable[] { new StatusColouredContainer(transition_duration) @@ -139,7 +141,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components new StatusColouredContainer(transition_duration) { RelativeSizeAxes = Axes.Y, - Width = side_strip_width, + Width = stripWidth, Child = new Box { RelativeSizeAxes = Axes.Both } }, new Container @@ -147,7 +149,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components RelativeSizeAxes = Axes.Y, Width = cover_width, Masking = true, - Margin = new MarginPadding { Left = side_strip_width }, + Margin = new MarginPadding { Left = stripWidth }, Child = new MultiplayerBackgroundSprite(BeatmapSetCoverType.List) { RelativeSizeAxes = Axes.Both } }, new Container @@ -156,7 +158,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components Padding = new MarginPadding { Vertical = content_padding, - Left = side_strip_width + cover_width + content_padding, + Left = stripWidth + cover_width + content_padding, Right = content_padding, }, Children = new Drawable[] From fe585611e7169ebc615c2ad03932bcfef2504a7b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 10 Jul 2020 19:54:09 +0900 Subject: [PATCH 767/871] Fix + simplify web request --- .../Online/API/Requests/GetRoomsRequest.cs | 25 ++++--------------- .../Multi/Lounge/Components/FilterControl.cs | 2 +- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetRoomsRequest.cs b/osu.Game/Online/API/Requests/GetRoomsRequest.cs index 4b90b04b51..c47ed20909 100644 --- a/osu.Game/Online/API/Requests/GetRoomsRequest.cs +++ b/osu.Game/Online/API/Requests/GetRoomsRequest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using Humanizer; using osu.Framework.IO.Network; using osu.Game.Online.Multiplayer; using osu.Game.Screens.Multi.Lounge.Components; @@ -23,27 +24,11 @@ namespace osu.Game.Online.API.Requests { var req = base.CreateWebRequest(); - switch (statusFilter) - { - case RoomStatusFilter.Owned: - req.AddParameter("mode", "owned"); - break; + if (statusFilter != RoomStatusFilter.Open) + req.AddParameter("mode", statusFilter.ToString().Underscore().ToLowerInvariant()); - case RoomStatusFilter.Participated: - req.AddParameter("mode", "participated"); - break; - - case RoomStatusFilter.RecentlyEnded: - req.AddParameter("mode", "ended"); - break; - } - - switch (categoryFilter) - { - case RoomCategoryFilter.Spotlight: - req.AddParameter("category", "spotlight"); - break; - } + if (categoryFilter != RoomCategoryFilter.Any) + req.AddParameter("category", categoryFilter.ToString().Underscore().ToLowerInvariant()); return req; } diff --git a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs index f43763486c..be1083ce8d 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs @@ -74,7 +74,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components Open, [Description("Recently Ended")] - RecentlyEnded, + Ended, Participated, Owned, } From 9556166c1b963e60a82690e60e6cd0ef568d7c46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 19:37:27 +0900 Subject: [PATCH 768/871] Replace large "show results" button with embedded button each playlist item --- .../Screens/Multi/DrawableRoomPlaylistItem.cs | 49 ++++++++------ .../Multi/DrawableRoomPlaylistWithResults.cs | 66 +++++++++++++++++++ .../Screens/Multi/Match/MatchSubScreen.cs | 28 ++------ 3 files changed, 103 insertions(+), 40 deletions(-) create mode 100644 osu.Game/Screens/Multi/DrawableRoomPlaylistWithResults.cs diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index 414c1f5748..8086449401 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -48,7 +49,8 @@ namespace osu.Game.Screens.Multi private readonly Bindable ruleset = new Bindable(); private readonly BindableList requiredMods = new BindableList(); - private readonly PlaylistItem item; + public readonly PlaylistItem Item; + private readonly bool allowEdit; private readonly bool allowSelection; @@ -57,8 +59,11 @@ namespace osu.Game.Screens.Multi public DrawableRoomPlaylistItem(PlaylistItem item, bool allowEdit, bool allowSelection) : base(item) { - this.item = item; + Item = item; + + // TODO: edit support should be moved out into a derived class this.allowEdit = allowEdit; + this.allowSelection = allowSelection; beatmap.BindTo(item.Beatmap); @@ -91,6 +96,8 @@ namespace osu.Game.Screens.Multi private ScheduledDelegate scheduledRefresh; + public FillFlowContainer ButtonsContainer { get; private set; } + private void scheduleRefresh() { scheduledRefresh?.Cancel(); @@ -102,14 +109,14 @@ namespace osu.Game.Screens.Multi difficultyIconContainer.Child = new DifficultyIcon(beatmap.Value, ruleset.Value) { Size = new Vector2(32) }; beatmapText.Clear(); - beatmapText.AddLink(item.Beatmap.ToString(), LinkAction.OpenBeatmap, item.Beatmap.Value.OnlineBeatmapID.ToString()); + beatmapText.AddLink(Item.Beatmap.ToString(), LinkAction.OpenBeatmap, Item.Beatmap.Value.OnlineBeatmapID.ToString()); authorText.Clear(); - if (item.Beatmap?.Value?.Metadata?.Author != null) + if (Item.Beatmap?.Value?.Metadata?.Author != null) { authorText.AddText("mapped by "); - authorText.AddUserLink(item.Beatmap.Value?.Metadata.Author); + authorText.AddUserLink(Item.Beatmap.Value?.Metadata.Author); } modDisplay.Current.Value = requiredMods.ToArray(); @@ -180,29 +187,33 @@ namespace osu.Game.Screens.Multi } } }, - new Container + ButtonsContainer = new FillFlowContainer { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, + Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, X = -18, - Children = new Drawable[] - { - new PlaylistDownloadButton(item) - { - Size = new Vector2(50, 30) - }, - new IconButton - { - Icon = FontAwesome.Solid.MinusSquare, - Alpha = allowEdit ? 1 : 0, - Action = () => RequestDeletion?.Invoke(Model), - }, - } + ChildrenEnumerable = CreateButtons() } } }; + protected virtual IEnumerable CreateButtons() => + new Drawable[] + { + new PlaylistDownloadButton(Item) + { + Size = new Vector2(50, 30) + }, + new IconButton + { + Icon = FontAwesome.Solid.MinusSquare, + Alpha = allowEdit ? 1 : 0, + Action = () => RequestDeletion?.Invoke(Model), + }, + }; + protected override bool OnClick(ClickEvent e) { if (allowSelection) diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistWithResults.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistWithResults.cs new file mode 100644 index 0000000000..439aaaa275 --- /dev/null +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistWithResults.cs @@ -0,0 +1,66 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Multiplayer; + +namespace osu.Game.Screens.Multi +{ + public class DrawableRoomPlaylistWithResults : DrawableRoomPlaylist + { + public Action RequestShowResults; + + public DrawableRoomPlaylistWithResults() + : base(false, true) + { + } + + protected override OsuRearrangeableListItem CreateOsuDrawable(PlaylistItem item) => + new DrawableRoomPlaylistItemWithResults(item, false, true) + { + RequestShowResults = () => RequestShowResults(item), + SelectedItem = { BindTarget = SelectedItem }, + }; + + private class DrawableRoomPlaylistItemWithResults : DrawableRoomPlaylistItem + { + public Action RequestShowResults; + + public DrawableRoomPlaylistItemWithResults(PlaylistItem item, bool allowEdit, bool allowSelection) + : base(item, allowEdit, allowSelection) + { + } + + protected override IEnumerable CreateButtons() => + base.CreateButtons().Prepend(new FilledIconButton + { + Icon = FontAwesome.Solid.ChartPie, + Action = () => RequestShowResults?.Invoke(), + TooltipText = "View results" + }); + + private class FilledIconButton : IconButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Add(new Box + { + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue, + Colour = colours.Gray4, + }); + } + } + } + } +} diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 40a8427701..7c2d5cf85d 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Screens; using osu.Game.Audio; using osu.Game.Beatmaps; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer.GameTypes; @@ -137,30 +136,23 @@ namespace osu.Game.Screens.Multi.Match new Drawable[] { new OverlinedHeader("Playlist"), }, new Drawable[] { - new DrawableRoomPlaylist(false, true) // Temporarily always allow selection + new DrawableRoomPlaylistWithResults { RelativeSizeAxes = Axes.Both, Items = { BindTarget = playlist }, - SelectedItem = { BindTarget = SelectedItem } + SelectedItem = { BindTarget = SelectedItem }, + RequestShowResults = item => + { + Debug.Assert(roomId.Value != null); + multiplayer?.Push(new TimeshiftResultsScreen(null, roomId.Value.Value, item, false)); + } } }, - null, - new Drawable[] - { - new TriangleButton - { - RelativeSizeAxes = Axes.X, - Text = "Show beatmap results", - Action = showBeatmapResults - } - } }, RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), new Dimension(), - new Dimension(GridSizeMode.Absolute, 5), - new Dimension(GridSizeMode.AutoSize) } } }, @@ -296,11 +288,5 @@ namespace osu.Game.Screens.Multi.Match break; } } - - private void showBeatmapResults() - { - Debug.Assert(roomId.Value != null); - multiplayer?.Push(new TimeshiftResultsScreen(null, roomId.Value.Value, SelectedItem.Value, false)); - } } } From c7b5c5aef42689f2d0dfe6ab817189dc15a4448d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 20:22:51 +0900 Subject: [PATCH 769/871] Add tooltips to beatmap download button --- osu.Game/Graphics/UserInterface/DownloadButton.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/DownloadButton.cs b/osu.Game/Graphics/UserInterface/DownloadButton.cs index 86a5cb9aa6..bec5a45556 100644 --- a/osu.Game/Graphics/UserInterface/DownloadButton.cs +++ b/osu.Game/Graphics/UserInterface/DownloadButton.cs @@ -63,22 +63,26 @@ namespace osu.Game.Graphics.UserInterface background.FadeColour(colours.Gray4, 500, Easing.InOutExpo); icon.MoveToX(0, 500, Easing.InOutExpo); checkmark.ScaleTo(Vector2.Zero, 500, Easing.InOutExpo); + TooltipText = "Download"; break; case DownloadState.Downloading: background.FadeColour(colours.Blue, 500, Easing.InOutExpo); icon.MoveToX(0, 500, Easing.InOutExpo); checkmark.ScaleTo(Vector2.Zero, 500, Easing.InOutExpo); + TooltipText = "Downloading..."; break; case DownloadState.Downloaded: background.FadeColour(colours.Yellow, 500, Easing.InOutExpo); + TooltipText = "Importing"; break; case DownloadState.LocallyAvailable: background.FadeColour(colours.Green, 500, Easing.InOutExpo); icon.MoveToX(-8, 500, Easing.InOutExpo); checkmark.ScaleTo(new Vector2(13), 500, Easing.InOutExpo); + TooltipText = "Go to beatmap"; break; } } From 840380e0de4d068b5fe16af44a01b22ef948f7a3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 20:25:52 +0900 Subject: [PATCH 770/871] Fix LocallyAvailable state case getting cleared --- osu.Game/Graphics/UserInterface/DownloadButton.cs | 1 - .../BeatmapListing/Panels/BeatmapPanelDownloadButton.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/DownloadButton.cs b/osu.Game/Graphics/UserInterface/DownloadButton.cs index bec5a45556..da6c95299e 100644 --- a/osu.Game/Graphics/UserInterface/DownloadButton.cs +++ b/osu.Game/Graphics/UserInterface/DownloadButton.cs @@ -82,7 +82,6 @@ namespace osu.Game.Graphics.UserInterface background.FadeColour(colours.Green, 500, Easing.InOutExpo); icon.MoveToX(-8, 500, Easing.InOutExpo); checkmark.ScaleTo(new Vector2(13), 500, Easing.InOutExpo); - TooltipText = "Go to beatmap"; break; } } diff --git a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs index 67782dfe3f..001ca801d9 100644 --- a/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs +++ b/osu.Game/Overlays/BeatmapListing/Panels/BeatmapPanelDownloadButton.cs @@ -81,7 +81,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels { case DownloadState.LocallyAvailable: button.Enabled.Value = true; - button.TooltipText = string.Empty; + button.TooltipText = "Go to beatmap"; break; default: From d3367bb0f14597f959c7032beaecfd07383c6498 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 20:32:47 +0900 Subject: [PATCH 771/871] Remove unused public property --- osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs index 8086449401..c0892235f2 100644 --- a/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs +++ b/osu.Game/Screens/Multi/DrawableRoomPlaylistItem.cs @@ -96,8 +96,6 @@ namespace osu.Game.Screens.Multi private ScheduledDelegate scheduledRefresh; - public FillFlowContainer ButtonsContainer { get; private set; } - private void scheduleRefresh() { scheduledRefresh?.Cancel(); @@ -187,7 +185,7 @@ namespace osu.Game.Screens.Multi } } }, - ButtonsContainer = new FillFlowContainer + new FillFlowContainer { Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, From 13205319f3c910a12e8854da1607244d27da65f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 22:37:29 +0900 Subject: [PATCH 772/871] Fix null reference if hit lighting is disabled --- .../Objects/Drawables/DrawableOsuJudgement.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index fa980c7581..f32ce2c4cd 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -60,10 +60,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables lightingColour?.UnbindAll(); - if (JudgedObject != null) + if (JudgedObject != null && lighting != null) { lightingColour = JudgedObject.AccentColour.GetBoundCopy(); - lightingColour.BindValueChanged(colour => lighting.Colour = Result.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true); + lightingColour.BindValueChanged(colour => lighting.Colour = Result?.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true); } else { From 0a61f80c8b1ab7cf1d3b1060c28831c2547027d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 22:39:35 +0900 Subject: [PATCH 773/871] Remove result nullable check --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index f32ce2c4cd..33ad674679 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (JudgedObject != null && lighting != null) { lightingColour = JudgedObject.AccentColour.GetBoundCopy(); - lightingColour.BindValueChanged(colour => lighting.Colour = Result?.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true); + lightingColour.BindValueChanged(colour => lighting.Colour = Result.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true); } else { From dd025262d07a7e6e94fc448c094ae7dc5179bd78 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 10 Jul 2020 22:48:34 +0900 Subject: [PATCH 774/871] Fix one more nullref --- .../Objects/Drawables/DrawableOsuJudgement.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index 33ad674679..cfe969d1cc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -60,14 +60,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables lightingColour?.UnbindAll(); - if (JudgedObject != null && lighting != null) + if (lighting != null) { - lightingColour = JudgedObject.AccentColour.GetBoundCopy(); - lightingColour.BindValueChanged(colour => lighting.Colour = Result.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true); - } - else - { - lighting.Colour = Color4.White; + if (JudgedObject != null) + { + lightingColour = JudgedObject.AccentColour.GetBoundCopy(); + lightingColour.BindValueChanged(colour => lighting.Colour = Result.Type == HitResult.Miss ? Color4.Transparent : colour.NewValue, true); + } + else + { + lighting.Colour = Color4.White; + } } } From fa0c2d7f84985060a047be059fc0721e8c2fce0a Mon Sep 17 00:00:00 2001 From: Joehu Date: Fri, 10 Jul 2020 10:42:41 -0700 Subject: [PATCH 775/871] Fix room name overflowing on multiplayer lounge --- .../Multi/Lounge/Components/RoomInfo.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomInfo.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomInfo.cs index 02f2667802..e6f6ce5ed2 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomInfo.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomInfo.cs @@ -4,9 +4,8 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.Containers; using osu.Game.Screens.Multi.Components; using osuTK; @@ -15,7 +14,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components public class RoomInfo : MultiplayerComposite { private readonly List statusElements = new List(); - private readonly SpriteText roomName; + private readonly OsuTextFlowContainer roomName; public RoomInfo() { @@ -43,18 +42,23 @@ namespace osu.Game.Screens.Multi.Lounge.Components { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Children = new Drawable[] { - roomName = new OsuSpriteText { Font = OsuFont.GetFont(size: 30) }, + roomName = new OsuTextFlowContainer(t => t.Font = OsuFont.GetFont(size: 30)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + }, statusInfo = new RoomStatusInfo(), } }, typeInfo = new ModeTypeInfo { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight } } }, From 789c921af1869435142f77c82fbdf5227c3a7af5 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 11 Jul 2020 06:47:52 +0300 Subject: [PATCH 776/871] Move replies button to a new line --- osu.Game/Overlays/Comments/DrawableComment.cs | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 46f600615a..2a63060385 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -163,20 +163,28 @@ namespace osu.Game.Overlays.Comments AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Right = 40 } }, - info = new FillFlowContainer + new FillFlowContainer { AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), + Direction = FillDirection.Vertical, Children = new Drawable[] { - new OsuSpriteText + info = new FillFlowContainer { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: 12), - Colour = OsuColour.Gray(0.7f), - Text = HumanizerUtils.Humanize(Comment.CreatedAt) + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 12), + Colour = OsuColour.Gray(0.7f), + Text = HumanizerUtils.Humanize(Comment.CreatedAt) + }, + } }, repliesButton = new RepliesButton(Comment.RepliesCount) { From da249abd19382e9c3c6c0deeab0529f510314bd9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 11 Jul 2020 07:47:17 +0300 Subject: [PATCH 777/871] Implement CommentRepliesButton --- .../TestSceneCommentRepliesButton.cs | 54 ++++++++++ .../Comments/Buttons/CommentRepliesButton.cs | 100 ++++++++++++++++++ .../Comments/Buttons/LoadRepliesButton.cs | 10 ++ .../Comments/Buttons/ShowRepliesButton.cs | 19 ++++ 4 files changed, 183 insertions(+) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs create mode 100644 osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs create mode 100644 osu.Game/Overlays/Comments/Buttons/LoadRepliesButton.cs create mode 100644 osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs new file mode 100644 index 0000000000..b4f518a5d0 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.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.Game.Overlays.Comments.Buttons; +using osu.Framework.Graphics; +using osu.Framework.Allocation; +using osu.Game.Overlays; +using osu.Framework.Graphics.Containers; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneCommentRepliesButton : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + + public TestSceneCommentRepliesButton() + { + Child = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 10), + Children = new Drawable[] + { + new TestButton + { + Action = () => { } + }, + new LoadRepliesButton + { + Action = () => { } + }, + new ShowRepliesButton(1) + { + Action = () => { } + }, + new ShowRepliesButton(2) + { + Action = () => { } + } + } + }; + } + + private class TestButton : CommentRepliesButton + { + protected override string GetText() => "sample text"; + } + } +} diff --git a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs new file mode 100644 index 0000000000..13924200c2 --- /dev/null +++ b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs @@ -0,0 +1,100 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Overlays.Comments.Buttons +{ + public abstract class CommentRepliesButton : OsuHoverContainer + { + protected override IEnumerable EffectTargets => new[] { background }; + + protected ChevronIcon Icon; + private Box background; + + public CommentRepliesButton() + { + AutoSizeAxes = Axes.Both; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Add(new CircularContainer + { + AutoSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new Container + { + AutoSizeAxes = Axes.Both, + Margin = new MarginPadding + { + Vertical = 5, + Horizontal = 10, + }, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(15, 0), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Text = GetText() + }, + Icon = new ChevronIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + } + } + } + } + } + }); + + IdleColour = colourProvider.Background2; + HoverColour = colourProvider.Background1; + } + + protected abstract string GetText(); + + protected class ChevronIcon : SpriteIcon + { + public ChevronIcon() + { + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + Size = new Vector2(7.5f); + Icon = FontAwesome.Solid.ChevronDown; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + Colour = colourProvider.Foreground1; + } + } + } +} diff --git a/osu.Game/Overlays/Comments/Buttons/LoadRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/LoadRepliesButton.cs new file mode 100644 index 0000000000..41cce72272 --- /dev/null +++ b/osu.Game/Overlays/Comments/Buttons/LoadRepliesButton.cs @@ -0,0 +1,10 @@ +// 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.Overlays.Comments.Buttons +{ + public class LoadRepliesButton : CommentRepliesButton + { + protected override string GetText() => "load replies"; + } +} diff --git a/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs new file mode 100644 index 0000000000..1e8c732453 --- /dev/null +++ b/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs @@ -0,0 +1,19 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Humanizer; + +namespace osu.Game.Overlays.Comments.Buttons +{ + public class ShowRepliesButton : CommentRepliesButton + { + private readonly int count; + + public ShowRepliesButton(int count) + { + this.count = count; + } + + protected override string GetText() => "reply".ToQuantity(count); + } +} From 0861ee0c8e985bb413681037ab54ea00f1910d82 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 11 Jul 2020 07:54:37 +0300 Subject: [PATCH 778/871] Make Icon rotate when clicking ShowRepliesButton --- .../Comments/Buttons/ShowRepliesButton.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs index 1e8c732453..2381727431 100644 --- a/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs @@ -2,11 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using Humanizer; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osuTK; namespace osu.Game.Overlays.Comments.Buttons { public class ShowRepliesButton : CommentRepliesButton { + public readonly BindableBool Expanded = new BindableBool(true); + private readonly int count; public ShowRepliesButton(int count) @@ -14,6 +20,23 @@ namespace osu.Game.Overlays.Comments.Buttons this.count = count; } + protected override void LoadComplete() + { + base.LoadComplete(); + Expanded.BindValueChanged(onExpandedChanged, true); + } + + private void onExpandedChanged(ValueChangedEvent expanded) + { + Icon.ScaleTo(new Vector2(1, expanded.NewValue ? -1 : 1)); + } + + protected override bool OnClick(ClickEvent e) + { + Expanded.Toggle(); + return base.OnClick(e); + } + protected override string GetText() => "reply".ToQuantity(count); } } From 42d3288f176d312833cad45f23cb69e73718e48c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 11 Jul 2020 08:01:11 +0300 Subject: [PATCH 779/871] Update old buttons usage --- osu.Game/Overlays/Comments/DrawableComment.cs | 50 ++++--------------- 1 file changed, 10 insertions(+), 40 deletions(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 2a63060385..7bd5e22038 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -16,12 +16,12 @@ using System.Linq; using osu.Game.Graphics.Sprites; using osu.Game.Online.Chat; using osu.Framework.Allocation; -using osuTK.Graphics; using System.Collections.Generic; using System; using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.IEnumerableExtensions; using System.Collections.Specialized; +using osu.Game.Overlays.Comments.Buttons; namespace osu.Game.Overlays.Comments { @@ -46,9 +46,9 @@ namespace osu.Game.Overlays.Comments private FillFlowContainer childCommentsVisibilityContainer; private FillFlowContainer childCommentsContainer; - private LoadMoreCommentsButton loadMoreCommentsButton; + private LoadRepliesButton loadRepliesButton; private ShowMoreButton showMoreButton; - private RepliesButton repliesButton; + private ShowRepliesButton showRepliesButton; private ChevronButton chevronButton; private DeletedCommentsCounter deletedCommentsCounter; @@ -186,11 +186,11 @@ namespace osu.Game.Overlays.Comments }, } }, - repliesButton = new RepliesButton(Comment.RepliesCount) + showRepliesButton = new ShowRepliesButton(Comment.RepliesCount) { Expanded = { BindTarget = childrenExpanded } }, - loadMoreCommentsButton = new LoadMoreCommentsButton + loadRepliesButton = new LoadRepliesButton { Action = () => RepliesRequested(this, ++currentPage) } @@ -347,14 +347,16 @@ namespace osu.Game.Overlays.Comments var loadedReplesCount = loadedReplies.Count; var hasUnloadedReplies = loadedReplesCount != Comment.RepliesCount; - loadMoreCommentsButton.FadeTo(hasUnloadedReplies && loadedReplesCount == 0 ? 1 : 0); + loadRepliesButton.FadeTo(hasUnloadedReplies && loadedReplesCount == 0 ? 1 : 0); showMoreButton.FadeTo(hasUnloadedReplies && loadedReplesCount > 0 ? 1 : 0); - repliesButton.FadeTo(loadedReplesCount != 0 ? 1 : 0); + showRepliesButton.FadeTo(loadedReplesCount != 0 ? 1 : 0); if (Comment.IsTopLevel) chevronButton.FadeTo(loadedReplesCount != 0 ? 1 : 0); - showMoreButton.IsLoading = loadMoreCommentsButton.IsLoading = false; + showMoreButton.IsLoading = false; + + //loadRepliesButton.IsLoading = false; } private class ChevronButton : ShowChildrenButton @@ -375,38 +377,6 @@ namespace osu.Game.Overlays.Comments } } - private class RepliesButton : ShowChildrenButton - { - private readonly SpriteText text; - private readonly int count; - - public RepliesButton(int count) - { - this.count = count; - - Child = text = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - }; - } - - protected override void OnExpandedChanged(ValueChangedEvent expanded) - { - text.Text = $@"{(expanded.NewValue ? "[-]" : "[+]")} replies ({count})"; - } - } - - private class LoadMoreCommentsButton : GetCommentRepliesButton - { - public LoadMoreCommentsButton() - { - IdleColour = OsuColour.Gray(0.7f); - HoverColour = Color4.White; - } - - protected override string GetText() => @"[+] load replies"; - } - private class ShowMoreButton : GetCommentRepliesButton { [BackgroundDependencyLoader] From b1b2e961bc0027b6305751ad469bf60e69186ee2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 11 Jul 2020 08:13:11 +0300 Subject: [PATCH 780/871] Update arrow colour on hover --- .../TestSceneCommentRepliesButton.cs | 18 +-- .../Comments/Buttons/CommentRepliesButton.cs | 125 ++++++++++-------- 2 files changed, 70 insertions(+), 73 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs index b4f518a5d0..e62092a180 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs @@ -26,22 +26,10 @@ namespace osu.Game.Tests.Visual.UserInterface Spacing = new Vector2(0, 10), Children = new Drawable[] { - new TestButton - { - Action = () => { } - }, - new LoadRepliesButton - { - Action = () => { } - }, - new ShowRepliesButton(1) - { - Action = () => { } - }, + new TestButton(), + new LoadRepliesButton(), + new ShowRepliesButton(1), new ShowRepliesButton(2) - { - Action = () => { } - } } }; } diff --git a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs index 13924200c2..7ea256d113 100644 --- a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs @@ -1,100 +1,109 @@ // 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; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osuTK; namespace osu.Game.Overlays.Comments.Buttons { - public abstract class CommentRepliesButton : OsuHoverContainer + public abstract class CommentRepliesButton : CompositeDrawable { - protected override IEnumerable EffectTargets => new[] { background }; + public Action Action { get; set; } - protected ChevronIcon Icon; + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + protected SpriteIcon Icon; private Box background; - public CommentRepliesButton() + [BackgroundDependencyLoader] + private void load() { AutoSizeAxes = Axes.Both; - } - - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - Add(new CircularContainer + InternalChildren = new Drawable[] { - AutoSizeAxes = Axes.Both, - Masking = true, - Children = new Drawable[] + new CircularContainer { - background = new Box + AutoSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both - }, - new Container - { - AutoSizeAxes = Axes.Both, - Margin = new MarginPadding + background = new Box { - Vertical = 5, - Horizontal = 10, + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background2 }, - Child = new FillFlowContainer + new Container { AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(15, 0), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] + Margin = new MarginPadding { - new OsuSpriteText + Vertical = 5, + Horizontal = 10, + }, + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(15, 0), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Text = GetText() - }, - Icon = new ChevronIcon - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), + Text = GetText() + }, + Icon = new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(7.5f), + Icon = FontAwesome.Solid.ChevronDown, + Colour = colourProvider.Foreground1 + } } } } } - } - }); - - IdleColour = colourProvider.Background2; - HoverColour = colourProvider.Background1; + }, + new HoverClickSounds(), + }; } protected abstract string GetText(); - protected class ChevronIcon : SpriteIcon + protected override bool OnHover(HoverEvent e) { - public ChevronIcon() - { - Anchor = Anchor.Centre; - Origin = Anchor.Centre; - Size = new Vector2(7.5f); - Icon = FontAwesome.Solid.ChevronDown; - } + base.OnHover(e); + background.FadeColour(colourProvider.Background1, 200, Easing.OutQuint); + Icon.FadeColour(colourProvider.Light1, 200, Easing.OutQuint); + return true; + } - [BackgroundDependencyLoader] - private void load(OverlayColourProvider colourProvider) - { - Colour = colourProvider.Foreground1; - } + protected override void OnHoverLost(HoverLostEvent e) + { + base.OnHoverLost(e); + background.FadeColour(colourProvider.Background2, 200, Easing.OutQuint); + Icon.FadeColour(colourProvider.Foreground1, 200, Easing.OutQuint); + } + + protected override bool OnClick(ClickEvent e) + { + Action?.Invoke(); + return base.OnClick(e); } } } From 84392d0d130242616ded329ac711355bbc7c8c0c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 11 Jul 2020 08:50:35 +0300 Subject: [PATCH 781/871] Add loading spinner --- osu.Game/Overlays/Comments/DrawableComment.cs | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 7bd5e22038..fef8194712 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -22,6 +22,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.IEnumerableExtensions; using System.Collections.Specialized; using osu.Game.Overlays.Comments.Buttons; +using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Comments { @@ -51,6 +52,7 @@ namespace osu.Game.Overlays.Comments private ShowRepliesButton showRepliesButton; private ChevronButton chevronButton; private DeletedCommentsCounter deletedCommentsCounter; + private Loading loading; public DrawableComment(Comment comment) { @@ -192,7 +194,12 @@ namespace osu.Game.Overlays.Comments }, loadRepliesButton = new LoadRepliesButton { - Action = () => RepliesRequested(this, ++currentPage) + Action = () => + { + RepliesRequested(this, ++currentPage); + loadRepliesButton.Hide(); + loading.Show(); + } } } } @@ -202,6 +209,11 @@ namespace osu.Game.Overlays.Comments } } }, + loading = new Loading + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + }, childCommentsVisibilityContainer = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -355,8 +367,7 @@ namespace osu.Game.Overlays.Comments chevronButton.FadeTo(loadedReplesCount != 0 ? 1 : 0); showMoreButton.IsLoading = false; - - //loadRepliesButton.IsLoading = false; + loading.Hide(); } private class ChevronButton : ShowChildrenButton @@ -427,5 +438,31 @@ namespace osu.Game.Overlays.Comments return parentComment.HasMessage ? parentComment.Message : parentComment.IsDeleted ? @"deleted" : string.Empty; } } + + private class Loading : Container + { + private readonly LoadingSpinner loading; + + public Loading() + { + Child = loading = new LoadingSpinner + { + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Size = new Vector2(15), + Margin = new MarginPadding + { + Vertical = 5, + Left = 80 + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + loading.Show(); + } + } } } From 024ccc75eefcec4e62b5a6a79d658df8a421657b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 11 Jul 2020 09:03:03 +0300 Subject: [PATCH 782/871] Adjust margins/paddings --- osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs | 4 ++++ osu.Game/Overlays/Comments/DrawableComment.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs index 7ea256d113..f4bab3d9d7 100644 --- a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs @@ -29,6 +29,10 @@ namespace osu.Game.Overlays.Comments.Buttons private void load() { AutoSizeAxes = Axes.Both; + Margin = new MarginPadding + { + Vertical = 2 + }; InternalChildren = new Drawable[] { new CircularContainer diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index fef8194712..813540b28d 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Comments { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding(margin) { Left = margin + 5 }, + Padding = new MarginPadding(margin) { Left = margin + 5, Top = Comment.IsTopLevel ? 10 : 0 }, Child = content = new GridContainer { RelativeSizeAxes = Axes.X, From acfb6eecc65b5a3b66af8f44d155178f7b0cbcf7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 11 Jul 2020 20:17:40 +0900 Subject: [PATCH 783/871] Fix bonus judgements being required toward HP --- .../TestSceneDrainingHealthProcessor.cs | 37 ++++++++++++++++++- .../Scoring/DrainingHealthProcessor.cs | 4 +- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs index e50b2231bf..460ad1b898 100644 --- a/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs +++ b/osu.Game.Tests/Gameplay/TestSceneDrainingHealthProcessor.cs @@ -157,6 +157,24 @@ namespace osu.Game.Tests.Gameplay assertHealthNotEqualTo(1); } + [Test] + public void TestBonusObjectsExcludedFromDrain() + { + var beatmap = new Beatmap + { + BeatmapInfo = { BaseDifficulty = { DrainRate = 10 } }, + }; + + beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = 0 }); + for (double time = 0; time < 5000; time += 100) + beatmap.HitObjects.Add(new JudgeableHitObject(false) { StartTime = time }); + beatmap.HitObjects.Add(new JudgeableHitObject { StartTime = 5000 }); + + createProcessor(beatmap); + setTime(4900); // Get close to the second combo-affecting object + assertHealthNotEqualTo(0); + } + private Beatmap createBeatmap(double startTime, double endTime, params BreakPeriod[] breaks) { var beatmap = new Beatmap @@ -197,8 +215,25 @@ namespace osu.Game.Tests.Gameplay private class JudgeableHitObject : HitObject { - public override Judgement CreateJudgement() => new Judgement(); + private readonly bool affectsCombo; + + public JudgeableHitObject(bool affectsCombo = true) + { + this.affectsCombo = affectsCombo; + } + + public override Judgement CreateJudgement() => new TestJudgement(affectsCombo); protected override HitWindows CreateHitWindows() => new HitWindows(); + + private class TestJudgement : Judgement + { + public override bool AffectsCombo { get; } + + public TestJudgement(bool affectsCombo) + { + AffectsCombo = affectsCombo; + } + } } } } diff --git a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs index ef341575fa..130907b242 100644 --- a/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs +++ b/osu.Game/Rulesets/Scoring/DrainingHealthProcessor.cs @@ -114,7 +114,9 @@ namespace osu.Game.Rulesets.Scoring protected override void ApplyResultInternal(JudgementResult result) { base.ApplyResultInternal(result); - healthIncreases.Add((result.HitObject.GetEndTime() + result.TimeOffset, GetHealthIncreaseFor(result))); + + if (!result.Judgement.IsBonus) + healthIncreases.Add((result.HitObject.GetEndTime() + result.TimeOffset, GetHealthIncreaseFor(result))); } protected override void Reset(bool storeResults) From ede4235884d852989275ba305411991dc7a3806e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Sat, 11 Jul 2020 20:33:42 +0900 Subject: [PATCH 784/871] Increase HP gain of bananas --- osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs index fc030877f1..a7449ba4e1 100644 --- a/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs +++ b/osu.Game.Rulesets.Catch/Judgements/CatchBananaJudgement.cs @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Catch.Judgements return 0; case HitResult.Perfect: - return 0.01; + return DEFAULT_MAX_HEALTH_INCREASE * 0.75; } } From 2bb0283a6855c10599e58f97eea8ca6484553a8e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 12 Jul 2020 00:52:55 +0900 Subject: [PATCH 785/871] Clean up HitEvents after use to avoid near-permanent memory retention --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index 9c1bc35169..eb49638d59 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -252,6 +252,12 @@ namespace osu.Game.Rulesets.Scoring HighestCombo.Value = 0; } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + hitEvents.Clear(); + } + /// /// Retrieve a score populated with data for the current play this processor is responsible for. /// @@ -269,7 +275,7 @@ namespace osu.Game.Rulesets.Scoring foreach (var result in Enum.GetValues(typeof(HitResult)).OfType().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r))) score.Statistics[result] = GetStatistic(result); - score.HitEvents = new List(hitEvents); + score.HitEvents = hitEvents; } /// From 9b4bed2ab223e8e81aafc49d9ee0d9012aa0e740 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 11 Jul 2020 16:02:47 -0700 Subject: [PATCH 786/871] Add ability to select mods from a specific score --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 13 +++++++++++++ osu.Game/Overlays/Mods/ModSection.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 +++--- osu.Game/Screens/Select/SongSelect.cs | 2 +- 4 files changed, 18 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 1469f29874..c1771fbf3d 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -58,6 +58,9 @@ namespace osu.Game.Online.Leaderboards [Resolved(CanBeNull = true)] private DialogOverlay dialogOverlay { get; set; } + [Resolved(CanBeNull = true)] + private SongSelect songSelect { get; set; } + public LeaderboardScore(ScoreInfo score, int? rank, bool allowHighlight = true) { this.score = score; @@ -373,11 +376,21 @@ namespace osu.Game.Online.Leaderboards { List items = new List(); + if (score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered)) + items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => getMods())); + if (score.ID != 0) items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score)))); return items.ToArray(); } } + + private void getMods() + { + songSelect.ModSelect.DeselectAll(true); + + songSelect.Mods.Value = score.Mods; + } } } diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 7235a18a23..45fae90a1e 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.Mods return base.OnKeyDown(e); } - public void DeselectAll() => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); + public void DeselectAll(bool immediate = false) => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null), immediate); /// /// Deselect one or more mods in this section. diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 3d0ad1a594..d83dd77401 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -231,7 +231,7 @@ namespace osu.Game.Overlays.Mods { Width = 180, Text = "Deselect All", - Action = DeselectAll, + Action = () => DeselectAll(), Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }, @@ -328,10 +328,10 @@ namespace osu.Game.Overlays.Mods sampleOff = audio.Samples.Get(@"UI/check-off"); } - public void DeselectAll() + public void DeselectAll(bool immediate = false) { foreach (var section in ModSectionsContainer.Children) - section.DeselectAll(); + section.DeselectAll(immediate); refreshSelectedMods(); } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index e3705b15fa..23fa3cd724 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Select [Resolved] private BeatmapManager beatmaps { get; set; } - protected ModSelectOverlay ModSelect { get; private set; } + public ModSelectOverlay ModSelect { get; private set; } protected SampleChannel SampleConfirm { get; private set; } From 0d26ad9ddb11c46e64377eadec5fded63de37a9a Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 11 Jul 2020 16:22:01 -0700 Subject: [PATCH 787/871] Fix user top score not having a context menu --- osu.Game/Online/Leaderboards/Leaderboard.cs | 38 ++++++++++----------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index e2a817aaff..d51bf963fc 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -170,36 +170,36 @@ namespace osu.Game.Online.Leaderboards { InternalChildren = new Drawable[] { - new GridContainer + new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, - RowDimensions = new[] + Child = new GridContainer { - new Dimension(), - new Dimension(GridSizeMode.AutoSize), - }, - Content = new[] - { - new Drawable[] + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - new OsuContextMenuContainer + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.Both, - Child = scrollContainer = new OsuScrollContainer + scrollContainer = new OsuScrollContainer { RelativeSizeAxes = Axes.Both, ScrollbarVisible = false, } + }, + new Drawable[] + { + content = new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }, } }, - new Drawable[] - { - content = new Container - { - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, - }, - } }, }, loading = new LoadingSpinner(), From 25d2d9ba5c19252574371d41f3699edd7ec27a33 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 11 Jul 2020 16:24:57 -0700 Subject: [PATCH 788/871] Convert getMods reference to method group --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index c1771fbf3d..7203683711 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -377,7 +377,7 @@ namespace osu.Game.Online.Leaderboards List items = new List(); if (score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered)) - items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => getMods())); + items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, getMods)); if (score.ID != 0) items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score)))); From 4d7dc9f5eb9e0cd2e34b4ecc7296b59c081d1beb Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 11 Jul 2020 18:27:47 -0700 Subject: [PATCH 789/871] Fix color and underline of tab control checkboxes when initially checked --- .../Graphics/UserInterface/OsuTabControlCheckbox.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index 544acc7eb2..b9afc2fa1f 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -32,12 +32,6 @@ namespace osu.Game.Graphics.UserInterface { accentColour = value; - if (Current.Value) - { - text.Colour = AccentColour; - icon.Colour = AccentColour; - } - updateFade(); } } @@ -89,6 +83,8 @@ namespace osu.Game.Graphics.UserInterface { icon.Icon = selected.NewValue ? FontAwesome.Regular.CheckCircle : FontAwesome.Regular.Circle; text.Font = text.Font.With(weight: selected.NewValue ? FontWeight.Bold : FontWeight.Medium); + + updateFade(); }; } @@ -115,8 +111,8 @@ namespace osu.Game.Graphics.UserInterface private void updateFade() { - box.FadeTo(IsHovered ? 1 : 0, transition_length, Easing.OutQuint); - text.FadeColour(IsHovered ? Color4.White : AccentColour, transition_length, Easing.OutQuint); + box.FadeTo(Current.Value || IsHovered ? 1 : 0, transition_length, Easing.OutQuint); + text.FadeColour(Current.Value || IsHovered ? Color4.White : AccentColour, transition_length, Easing.OutQuint); } } } From 681f0015255c0b269a305d6aff3308e3da9c6ab0 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sat, 11 Jul 2020 19:19:34 -0700 Subject: [PATCH 790/871] Convert icon to local variable --- osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs index b9afc2fa1f..bdc95ee048 100644 --- a/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTabControlCheckbox.cs @@ -21,7 +21,6 @@ namespace osu.Game.Graphics.UserInterface { private readonly Box box; private readonly SpriteText text; - private readonly SpriteIcon icon; private Color4? accentColour; @@ -46,6 +45,8 @@ namespace osu.Game.Graphics.UserInterface public OsuTabControlCheckbox() { + SpriteIcon icon; + AutoSizeAxes = Axes.Both; Children = new Drawable[] From da40f29b4405990a3db8150fbd0fafa4f34f9f83 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 12 Jul 2020 12:27:26 +0300 Subject: [PATCH 791/871] Make button text a property --- .../TestSceneCommentRepliesButton.cs | 5 ++- .../Comments/Buttons/CommentRepliesButton.cs | 32 ++++++++++++------- .../Comments/Buttons/LoadRepliesButton.cs | 5 ++- .../Comments/Buttons/ShowRepliesButton.cs | 6 +--- 4 files changed, 29 insertions(+), 19 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs index e62092a180..baeb1ae822 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs @@ -36,7 +36,10 @@ namespace osu.Game.Tests.Visual.UserInterface private class TestButton : CommentRepliesButton { - protected override string GetText() => "sample text"; + public TestButton() + { + Text = "sample text"; + } } } } diff --git a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs index f4bab3d9d7..1121ac5327 100644 --- a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs @@ -19,14 +19,20 @@ namespace osu.Game.Overlays.Comments.Buttons { public Action Action { get; set; } + protected string Text + { + get => text.Text; + set => text.Text = value; + } + [Resolved] private OverlayColourProvider colourProvider { get; set; } - protected SpriteIcon Icon; - private Box background; + protected readonly SpriteIcon Icon; + private readonly Box background; + private readonly OsuSpriteText text; - [BackgroundDependencyLoader] - private void load() + public CommentRepliesButton() { AutoSizeAxes = Axes.Both; Margin = new MarginPadding @@ -43,8 +49,7 @@ namespace osu.Game.Overlays.Comments.Buttons { background = new Box { - RelativeSizeAxes = Axes.Both, - Colour = colourProvider.Background2 + RelativeSizeAxes = Axes.Both }, new Container { @@ -63,20 +68,18 @@ namespace osu.Game.Overlays.Comments.Buttons Origin = Anchor.Centre, Children = new Drawable[] { - new OsuSpriteText + text = new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), - Text = GetText() + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) }, Icon = new SpriteIcon { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Size = new Vector2(7.5f), - Icon = FontAwesome.Solid.ChevronDown, - Colour = colourProvider.Foreground1 + Icon = FontAwesome.Solid.ChevronDown } } } @@ -87,7 +90,12 @@ namespace osu.Game.Overlays.Comments.Buttons }; } - protected abstract string GetText(); + [BackgroundDependencyLoader] + private void load() + { + background.Colour = colourProvider.Background2; + Icon.Colour = colourProvider.Foreground1; + } protected override bool OnHover(HoverEvent e) { diff --git a/osu.Game/Overlays/Comments/Buttons/LoadRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/LoadRepliesButton.cs index 41cce72272..9387c95758 100644 --- a/osu.Game/Overlays/Comments/Buttons/LoadRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/LoadRepliesButton.cs @@ -5,6 +5,9 @@ namespace osu.Game.Overlays.Comments.Buttons { public class LoadRepliesButton : CommentRepliesButton { - protected override string GetText() => "load replies"; + public LoadRepliesButton() + { + Text = "load replies"; + } } } diff --git a/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs index 2381727431..01c2e8a7a7 100644 --- a/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs @@ -13,11 +13,9 @@ namespace osu.Game.Overlays.Comments.Buttons { public readonly BindableBool Expanded = new BindableBool(true); - private readonly int count; - public ShowRepliesButton(int count) { - this.count = count; + Text = "reply".ToQuantity(count); } protected override void LoadComplete() @@ -36,7 +34,5 @@ namespace osu.Game.Overlays.Comments.Buttons Expanded.Toggle(); return base.OnClick(e); } - - protected override string GetText() => "reply".ToQuantity(count); } } From c3524cbe57f60e826df8b12fdcdf5bb178497b80 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 12 Jul 2020 12:32:26 +0300 Subject: [PATCH 792/871] Make icon private but expose a protected method --- .../Comments/Buttons/CommentRepliesButton.cs | 12 +++++++----- .../Overlays/Comments/Buttons/ShowRepliesButton.cs | 9 +-------- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs index 1121ac5327..dd02a35a06 100644 --- a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs @@ -28,7 +28,7 @@ namespace osu.Game.Overlays.Comments.Buttons [Resolved] private OverlayColourProvider colourProvider { get; set; } - protected readonly SpriteIcon Icon; + private readonly SpriteIcon icon; private readonly Box background; private readonly OsuSpriteText text; @@ -74,7 +74,7 @@ namespace osu.Game.Overlays.Comments.Buttons Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) }, - Icon = new SpriteIcon + icon = new SpriteIcon { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, @@ -94,14 +94,16 @@ namespace osu.Game.Overlays.Comments.Buttons private void load() { background.Colour = colourProvider.Background2; - Icon.Colour = colourProvider.Foreground1; + icon.Colour = colourProvider.Foreground1; } + protected void ToggleIcon(bool upwards) => icon.ScaleTo(new Vector2(1, upwards ? -1 : 1)); + protected override bool OnHover(HoverEvent e) { base.OnHover(e); background.FadeColour(colourProvider.Background1, 200, Easing.OutQuint); - Icon.FadeColour(colourProvider.Light1, 200, Easing.OutQuint); + icon.FadeColour(colourProvider.Light1, 200, Easing.OutQuint); return true; } @@ -109,7 +111,7 @@ namespace osu.Game.Overlays.Comments.Buttons { base.OnHoverLost(e); background.FadeColour(colourProvider.Background2, 200, Easing.OutQuint); - Icon.FadeColour(colourProvider.Foreground1, 200, Easing.OutQuint); + icon.FadeColour(colourProvider.Foreground1, 200, Easing.OutQuint); } protected override bool OnClick(ClickEvent e) diff --git a/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs index 01c2e8a7a7..118cac5b4c 100644 --- a/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs @@ -3,9 +3,7 @@ using Humanizer; using osu.Framework.Bindables; -using osu.Framework.Graphics; using osu.Framework.Input.Events; -using osuTK; namespace osu.Game.Overlays.Comments.Buttons { @@ -21,12 +19,7 @@ namespace osu.Game.Overlays.Comments.Buttons protected override void LoadComplete() { base.LoadComplete(); - Expanded.BindValueChanged(onExpandedChanged, true); - } - - private void onExpandedChanged(ValueChangedEvent expanded) - { - Icon.ScaleTo(new Vector2(1, expanded.NewValue ? -1 : 1)); + Expanded.BindValueChanged(expanded => ToggleIcon(expanded.NewValue), true); } protected override bool OnClick(ClickEvent e) From be36a4b7686a120641a7ec29f727a2898dd54053 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 12 Jul 2020 12:39:48 +0300 Subject: [PATCH 793/871] Make ctor protected --- osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs index dd02a35a06..65648f6751 100644 --- a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs @@ -32,7 +32,7 @@ namespace osu.Game.Overlays.Comments.Buttons private readonly Box background; private readonly OsuSpriteText text; - public CommentRepliesButton() + protected CommentRepliesButton() { AutoSizeAxes = Axes.Both; Margin = new MarginPadding From c9d21894e5aeba8e2ddbf7c97779584bbc95ac93 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 12 Jul 2020 13:19:28 +0300 Subject: [PATCH 794/871] Add test for icon toggle --- .../TestSceneCommentRepliesButton.cs | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs index baeb1ae822..73fe66d6eb 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs @@ -7,6 +7,9 @@ using osu.Framework.Allocation; using osu.Game.Overlays; using osu.Framework.Graphics.Containers; using osuTK; +using NUnit.Framework; +using System.Linq; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Tests.Visual.UserInterface { @@ -15,6 +18,8 @@ namespace osu.Game.Tests.Visual.UserInterface [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue); + private readonly TestButton button; + public TestSceneCommentRepliesButton() { Child = new FillFlowContainer @@ -26,7 +31,7 @@ namespace osu.Game.Tests.Visual.UserInterface Spacing = new Vector2(0, 10), Children = new Drawable[] { - new TestButton(), + button = new TestButton(), new LoadRepliesButton(), new ShowRepliesButton(1), new ShowRepliesButton(2) @@ -34,12 +39,25 @@ namespace osu.Game.Tests.Visual.UserInterface }; } + [Test] + public void TestArrowRotation() + { + AddStep("Toggle icon up", () => button.ToggleIcon(true)); + AddAssert("Icon facing upwards", () => button.Icon.Scale.Y == -1); + AddStep("Toggle icon down", () => button.ToggleIcon(false)); + AddAssert("Icon facing downwards", () => button.Icon.Scale.Y == 1); + } + private class TestButton : CommentRepliesButton { + public SpriteIcon Icon => InternalChildren.OfType().First().Children.OfType().First().Children.OfType().First().Children.OfType().First(); + public TestButton() { Text = "sample text"; } + + public new void ToggleIcon(bool upwards) => base.ToggleIcon(upwards); } } } From 0d6dbb652315f7f8e341ddf2478617308e3ac59f Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 12 Jul 2020 13:36:42 +0300 Subject: [PATCH 795/871] Clean up exposed icon for tests --- .../Visual/UserInterface/TestSceneCommentRepliesButton.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs index 73fe66d6eb..7f5806705e 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs @@ -10,6 +10,7 @@ using osuTK; using NUnit.Framework; using System.Linq; using osu.Framework.Graphics.Sprites; +using osu.Framework.Testing; namespace osu.Game.Tests.Visual.UserInterface { @@ -50,7 +51,7 @@ namespace osu.Game.Tests.Visual.UserInterface private class TestButton : CommentRepliesButton { - public SpriteIcon Icon => InternalChildren.OfType().First().Children.OfType().First().Children.OfType().First().Children.OfType().First(); + public SpriteIcon Icon => this.ChildrenOfType().First(); public TestButton() { From d18609e9003548a2f21af720d3c3357708bdc6f7 Mon Sep 17 00:00:00 2001 From: vntxx <38376434+vntxx@users.noreply.github.com> Date: Fri, 10 Jul 2020 21:05:23 +0200 Subject: [PATCH 796/871] Added notifications keybinding Implementation of #9502 --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 4 ++++ osu.Game/OsuGame.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 618798a6d8..8a5acbadbc 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -35,6 +35,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings), new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.ToggleDirect), + new KeyBinding(new[] { InputKey.Control, InputKey.N }, GlobalAction.ToggleNotifications), new KeyBinding(InputKey.Escape, GlobalAction.Back), new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back), @@ -157,5 +158,8 @@ namespace osu.Game.Input.Bindings [Description("Home")] Home, + + [Description("Toggle notifications")] + ToggleNotifications } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 92233f143d..47a7c2ae11 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -890,6 +890,10 @@ namespace osu.Game beatmapListing.ToggleVisibility(); return true; + case GlobalAction.ToggleNotifications: + notifications.ToggleVisibility(); + return true; + case GlobalAction.ToggleGameplayMouseButtons: LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get(OsuSetting.MouseDisableButtons)); return true; From ab11a112b7078c538e5b23d57603294a02c04749 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 12 Jul 2020 22:21:16 +0900 Subject: [PATCH 797/871] Fix correct filter criteria not being applied to beatmap carousel if beatmaps take too long to load --- osu.Game/Screens/Select/BeatmapCarousel.cs | 29 ++++++++++++++-------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 6f913a3177..5a4160960a 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -95,7 +95,6 @@ namespace osu.Game.Screens.Select CarouselRoot newRoot = new CarouselRoot(this); beatmapSets.Select(createCarouselSet).Where(g => g != null).ForEach(newRoot.AddChild); - newRoot.Filter(activeCriteria); // preload drawables as the ctor overhead is quite high currently. _ = newRoot.Drawables; @@ -108,6 +107,9 @@ namespace osu.Game.Screens.Select itemsCache.Invalidate(); scrollPositionCache.Invalidate(); + // the filter criteria may have changed since the above filter operation. + FlushPendingFilterOperations(); + // Run on late scheduler want to ensure this runs after all pending UpdateBeatmapSet / RemoveBeatmapSet operations are run. SchedulerAfterChildren.Add(() => { @@ -321,6 +323,9 @@ namespace osu.Game.Screens.Select /// True if a selection could be made, else False. public bool SelectNextRandom() { + if (!AllowSelection) + return false; + var visibleSets = beatmapSets.Where(s => !s.Filtered.Value).ToList(); if (!visibleSets.Any()) return false; @@ -427,7 +432,19 @@ namespace osu.Game.Screens.Select private void applyActiveCriteria(bool debounce, bool alwaysResetScrollPosition = true) { - if (root.Children.Any() != true) return; + PendingFilter?.Cancel(); + PendingFilter = null; + + if (debounce) + PendingFilter = Scheduler.AddDelayed(perform, 250); + else + { + // if initial load is not yet finished, this will be run inline in loadBeatmapSets to ensure correct order of operation. + if (!BeatmapSetsLoaded) + PendingFilter = Schedule(perform); + else + perform(); + } void perform() { @@ -439,14 +456,6 @@ namespace osu.Game.Screens.Select if (alwaysResetScrollPosition || !scroll.UserScrolling) ScrollToSelected(); } - - PendingFilter?.Cancel(); - PendingFilter = null; - - if (debounce) - PendingFilter = Scheduler.AddDelayed(perform, 250); - else - perform(); } private float? scrollTarget; From 08696b9bca23ce188b3e6bb90fc1d11bc36cbdef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 12 Jul 2020 23:03:03 +0900 Subject: [PATCH 798/871] Allow pausing gameplay via middle mouse button --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 4 ++++ osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 1 + 2 files changed, 5 insertions(+) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 618798a6d8..567c81c018 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -56,6 +56,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Tilde }, GlobalAction.QuickExit), new KeyBinding(new[] { InputKey.Control, InputKey.Plus }, GlobalAction.IncreaseScrollSpeed), new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed), + new KeyBinding(InputKey.MouseMiddle, GlobalAction.PauseGameplay), }; public IEnumerable AudioControlKeyBindings => new[] @@ -157,5 +158,8 @@ namespace osu.Game.Input.Bindings [Description("Home")] Home, + + [Description("Pause")] + PauseGameplay, } } diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 684834123b..81ad29107f 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -251,6 +251,7 @@ namespace osu.Game.Screens.Play.HUD switch (action) { case GlobalAction.Back: + case GlobalAction.PauseGameplay: if (!pendingAnimation) BeginConfirm(); return true; From c1aafe83fa042ac01e28631bf9bda2695a71a100 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 12 Jul 2020 23:05:47 +0900 Subject: [PATCH 799/871] Add note about future behaviour --- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 81ad29107f..74064c507f 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -251,7 +251,7 @@ namespace osu.Game.Screens.Play.HUD switch (action) { case GlobalAction.Back: - case GlobalAction.PauseGameplay: + case GlobalAction.PauseGameplay: // in the future this behaviour will differ for replays etc. if (!pendingAnimation) BeginConfirm(); return true; From 9c039848bc0fe48bae87214d6871c43c4b9a0a9c Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 12 Jul 2020 12:04:53 -0700 Subject: [PATCH 800/871] Simplify and add null check --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 5 ++--- osu.Game/Overlays/Mods/ModSection.cs | 2 +- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 6 +++--- osu.Game/Screens/Select/SongSelect.cs | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 7203683711..40323c325e 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -388,9 +388,8 @@ namespace osu.Game.Online.Leaderboards private void getMods() { - songSelect.ModSelect.DeselectAll(true); - - songSelect.Mods.Value = score.Mods; + if (songSelect != null) + songSelect.Mods.Value = score.Mods; } } } diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 45fae90a1e..7235a18a23 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -95,7 +95,7 @@ namespace osu.Game.Overlays.Mods return base.OnKeyDown(e); } - public void DeselectAll(bool immediate = false) => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null), immediate); + public void DeselectAll() => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null)); /// /// Deselect one or more mods in this section. diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index d83dd77401..3d0ad1a594 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -231,7 +231,7 @@ namespace osu.Game.Overlays.Mods { Width = 180, Text = "Deselect All", - Action = () => DeselectAll(), + Action = DeselectAll, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }, @@ -328,10 +328,10 @@ namespace osu.Game.Overlays.Mods sampleOff = audio.Samples.Get(@"UI/check-off"); } - public void DeselectAll(bool immediate = false) + public void DeselectAll() { foreach (var section in ModSectionsContainer.Children) - section.DeselectAll(immediate); + section.DeselectAll(); refreshSelectedMods(); } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 23fa3cd724..e3705b15fa 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Select [Resolved] private BeatmapManager beatmaps { get; set; } - public ModSelectOverlay ModSelect { get; private set; } + protected ModSelectOverlay ModSelect { get; private set; } protected SampleChannel SampleConfirm { get; private set; } From 6eec2f9429bd3548d20a1c3bb1b0d21119b77db0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Mon, 13 Jul 2020 01:22:05 +0300 Subject: [PATCH 801/871] Implement RankingsSortTabControl component --- .../TestSceneRankingsSortTabControl.cs | 25 +++++++++++++++++++ osu.Game/Overlays/Comments/CommentsHeader.cs | 2 +- osu.Game/Overlays/OverlaySortTabControl.cs | 16 +++++++++--- .../Rankings/RankingsSortTabControl.cs | 19 ++++++++++++++ 4 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneRankingsSortTabControl.cs create mode 100644 osu.Game/Overlays/Rankings/RankingsSortTabControl.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneRankingsSortTabControl.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneRankingsSortTabControl.cs new file mode 100644 index 0000000000..24bc0dbc97 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneRankingsSortTabControl.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 osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Game.Overlays; +using osu.Game.Overlays.Rankings; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneRankingsSortTabControl : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green); + + public TestSceneRankingsSortTabControl() + { + Child = new RankingsSortTabControl + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }; + } + } +} diff --git a/osu.Game/Overlays/Comments/CommentsHeader.cs b/osu.Game/Overlays/Comments/CommentsHeader.cs index 83f44ccd80..0dd68bbd41 100644 --- a/osu.Game/Overlays/Comments/CommentsHeader.cs +++ b/osu.Game/Overlays/Comments/CommentsHeader.cs @@ -86,7 +86,7 @@ namespace osu.Game.Overlays.Comments { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: 12), + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), Text = @"Show deleted" } }, diff --git a/osu.Game/Overlays/OverlaySortTabControl.cs b/osu.Game/Overlays/OverlaySortTabControl.cs index 395f3aec4c..b2212336ef 100644 --- a/osu.Game/Overlays/OverlaySortTabControl.cs +++ b/osu.Game/Overlays/OverlaySortTabControl.cs @@ -30,6 +30,14 @@ namespace osu.Game.Overlays set => current.Current = value; } + public string Title + { + get => text.Text; + set => text.Text = value; + } + + private readonly OsuSpriteText text; + public OverlaySortTabControl() { AutoSizeAxes = Axes.Both; @@ -40,11 +48,11 @@ namespace osu.Game.Overlays Spacing = new Vector2(10, 0), Children = new Drawable[] { - new OsuSpriteText + text = new OsuSpriteText { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Font = OsuFont.GetFont(size: 12), + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), Text = @"Sort by" }, CreateControl().With(c => @@ -133,7 +141,7 @@ namespace osu.Game.Overlays { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Font = OsuFont.GetFont(size: 12), + Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold), Text = (value as Enum)?.GetDescription() ?? value.ToString() } } @@ -163,7 +171,7 @@ namespace osu.Game.Overlays ContentColour = Active.Value && !IsHovered ? colourProvider.Light1 : Color4.White; - text.Font = text.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Medium); + text.Font = text.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.SemiBold); } } } diff --git a/osu.Game/Overlays/Rankings/RankingsSortTabControl.cs b/osu.Game/Overlays/Rankings/RankingsSortTabControl.cs new file mode 100644 index 0000000000..c0bbf46e30 --- /dev/null +++ b/osu.Game/Overlays/Rankings/RankingsSortTabControl.cs @@ -0,0 +1,19 @@ +// 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.Overlays.Rankings +{ + public class RankingsSortTabControl : OverlaySortTabControl + { + public RankingsSortTabControl() + { + Title = "Show"; + } + } + + public enum RankingsSortCriteria + { + All, + Friends + } +} From f442df75a9bf16d463a898bc67f15acb539960dd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Jul 2020 09:00:10 +0900 Subject: [PATCH 802/871] Add missing released conditional --- osu.Game/Screens/Play/HUD/HoldForMenuButton.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs index 74064c507f..387c0e587b 100644 --- a/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs +++ b/osu.Game/Screens/Play/HUD/HoldForMenuButton.cs @@ -265,6 +265,7 @@ namespace osu.Game.Screens.Play.HUD switch (action) { case GlobalAction.Back: + case GlobalAction.PauseGameplay: AbortConfirm(); break; } From 0718e9e4b64fceae400129de0a3a6a6ae5627f37 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Jul 2020 13:08:41 +0900 Subject: [PATCH 803/871] Update outdated comment --- osu.Game/Screens/Select/BeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 5a4160960a..5f6f859d66 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -107,7 +107,7 @@ namespace osu.Game.Screens.Select itemsCache.Invalidate(); scrollPositionCache.Invalidate(); - // the filter criteria may have changed since the above filter operation. + // apply any pending filter operation that may have been delayed (see applyActiveCriteria's scheduling behaviour when BeatmapSetsLoaded is false). FlushPendingFilterOperations(); // Run on late scheduler want to ensure this runs after all pending UpdateBeatmapSet / RemoveBeatmapSet operations are run. From ac7252e152ce53b7d0f77654c4159193e4872d87 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 12 Jul 2020 22:04:00 -0700 Subject: [PATCH 804/871] Fix context menu not masking outside of leaderboard area --- osu.Game/Online/Leaderboards/Leaderboard.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index d51bf963fc..800029ceb9 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -173,6 +173,7 @@ namespace osu.Game.Online.Leaderboards new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, + Masking = true, Child = new GridContainer { RelativeSizeAxes = Axes.Both, From db6a9c97172ec8418633f62fe2e8ebcc901b6605 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 12 Jul 2020 22:06:17 -0700 Subject: [PATCH 805/871] Move null check to menu item addition --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 40323c325e..b60d71cfe7 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -376,8 +376,8 @@ namespace osu.Game.Online.Leaderboards { List items = new List(); - if (score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered)) - items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, getMods)); + if (score.Mods.Length > 0 && modsContainer.Any(s => s.IsHovered) && songSelect != null) + items.Add(new OsuMenuItem("Use these mods", MenuItemType.Highlighted, () => songSelect.Mods.Value = score.Mods)); if (score.ID != 0) items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score)))); @@ -385,11 +385,5 @@ namespace osu.Game.Online.Leaderboards return items.ToArray(); } } - - private void getMods() - { - if (songSelect != null) - songSelect.Mods.Value = score.Mods; - } } } From 79ea6dbd5cad12e26310d4c9f1fce87b767daa71 Mon Sep 17 00:00:00 2001 From: Joehu Date: Sun, 12 Jul 2020 22:24:11 -0700 Subject: [PATCH 806/871] Only allow link clicking and tooltips of multi room drawables when selected --- osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs index 3f5a2eb1d3..8dd1b239e8 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs @@ -219,6 +219,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components Alpha = 0; } + protected override bool ShouldBeConsideredForInput(Drawable child) => state == SelectionState.Selected; + private class RoomName : OsuSpriteText { [Resolved(typeof(Room), nameof(Online.Multiplayer.Room.Name))] From 352f59942e5aff3523582893ebad5bd78349f5e9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 13 Jul 2020 16:50:54 +0900 Subject: [PATCH 807/871] Fix incorrect time delta in taiko strain --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs index c6fe273b50..99975d9174 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills double addition = 1; // We get an extra addition if we are not a slider or spinner - if (current.LastObject is Hit && current.BaseObject is Hit && current.DeltaTime < 1000) + if (current.LastObject is Hit && current.BaseObject is Hit && (current.BaseObject.StartTime - current.LastObject.StartTime) < 1000) { if (hasColourChange(current)) addition += 0.75; From 1116703e92c1a6ac82d4b63298780d5ea584ace8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 13 Jul 2020 16:52:05 +0900 Subject: [PATCH 808/871] Fix potential out-of-order objects after conversion --- osu.Game/Beatmaps/BeatmapConverter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapConverter.cs b/osu.Game/Beatmaps/BeatmapConverter.cs index 99e0bf4e33..11fee030f8 100644 --- a/osu.Game/Beatmaps/BeatmapConverter.cs +++ b/osu.Game/Beatmaps/BeatmapConverter.cs @@ -57,7 +57,7 @@ namespace osu.Game.Beatmaps beatmap.BeatmapInfo = original.BeatmapInfo; beatmap.ControlPointInfo = original.ControlPointInfo; - beatmap.HitObjects = convertHitObjects(original.HitObjects, original); + beatmap.HitObjects = convertHitObjects(original.HitObjects, original).OrderBy(s => s.StartTime).ToList(); beatmap.Breaks = original.Breaks; return beatmap; From cd3500510e71ef1d7de641e648d1cb7b7e64eff5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Jul 2020 17:05:29 +0900 Subject: [PATCH 809/871] Fix carousel tests relying on initial selection being null --- .../SongSelect/TestSceneBeatmapCarousel.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 073d75692e..70eafcb2a7 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -80,9 +80,9 @@ namespace osu.Game.Tests.Visual.SongSelect [Test] public void TestRecommendedSelection() { - loadBeatmaps(); + loadBeatmaps(carouselAdjust: carousel => carousel.GetRecommendedBeatmap = beatmaps => beatmaps.LastOrDefault()); - AddStep("set recommendation function", () => carousel.GetRecommendedBeatmap = beatmaps => beatmaps.LastOrDefault()); + AddStep("select last", () => carousel.SelectBeatmap(carousel.BeatmapSets.Last().Beatmaps.Last())); // check recommended was selected advanceSelection(direction: 1, diff: false); @@ -114,7 +114,7 @@ namespace osu.Game.Tests.Visual.SongSelect { loadBeatmaps(); - advanceSelection(direction: 1, diff: false); + AddStep("select last", () => carousel.SelectBeatmap(carousel.BeatmapSets.First().Beatmaps.First())); waitForSelection(1, 1); advanceSelection(direction: 1, diff: true); @@ -707,9 +707,9 @@ namespace osu.Game.Tests.Visual.SongSelect checkVisibleItemCount(true, 15); } - private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null) + private void loadBeatmaps(List beatmapSets = null, Func initialCriteria = null, Action carouselAdjust = null) { - createCarousel(); + createCarousel(carouselAdjust); if (beatmapSets == null) { @@ -730,17 +730,21 @@ namespace osu.Game.Tests.Visual.SongSelect AddUntilStep("Wait for load", () => changed); } - private void createCarousel(Container target = null) + private void createCarousel(Action carouselAdjust = null, Container target = null) { AddStep("Create carousel", () => { selectedSets.Clear(); eagerSelectedIDs.Clear(); - (target ?? this).Child = carousel = new TestBeatmapCarousel + carousel = new TestBeatmapCarousel { RelativeSizeAxes = Axes.Both, }; + + carouselAdjust?.Invoke(carousel); + + (target ?? this).Child = carousel; }); } From 0ea13dea55caca5ba2d52d330c889880c924c150 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 13 Jul 2020 17:06:00 +0900 Subject: [PATCH 810/871] Introduce legacy timing point fp errors --- .../Beatmaps/TaikoBeatmapConverter.cs | 79 +++++++++++-------- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 4 +- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 13 ++- 3 files changed, 62 insertions(+), 34 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs b/osu.Game.Rulesets.Taiko/Beatmaps/TaikoBeatmapConverter.cs index 78550ed270..2a1aa5d1df 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.Game.Audio; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Beatmaps.Formats; namespace osu.Game.Rulesets.Taiko.Beatmaps { @@ -82,37 +83,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps { case IHasDistance distanceData: { - // Number of spans of the object - one for the initial length and for each repeat - int spans = (obj as IHasRepeats)?.SpanCount() ?? 1; - - TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); - DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime); - - double speedAdjustment = difficultyPoint.SpeedMultiplier; - double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment; - - // The true distance, accounting for any repeats. This ends up being the drum roll distance later - double distance = distanceData.Distance * spans * LEGACY_VELOCITY_MULTIPLIER; - - // The velocity of the taiko hit object - calculated as the velocity of a drum roll - double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength; - // The duration of the taiko hit object - double taikoDuration = distance / taikoVelocity; - - // The velocity of the osu! hit object - calculated as the velocity of a slider - double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength; - // The duration of the osu! hit object - double osuDuration = distance / osuVelocity; - - // osu-stable always uses the speed-adjusted beatlength to determine the velocities, but - // only uses it for tick rate if beatmap version < 8 - if (beatmap.BeatmapInfo.BeatmapVersion >= 8) - speedAdjustedBeatLength *= speedAdjustment; - - // If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat - double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate, taikoDuration / spans); - - if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength) + if (shouldConvertSliderToHits(obj, beatmap, distanceData, out var taikoDuration, out var tickSpacing)) { List> allSamples = obj is IHasPathWithRepeats curveData ? curveData.NodeSamples : new List>(new[] { samples }); @@ -184,6 +155,52 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps } } + private bool shouldConvertSliderToHits(HitObject obj, IBeatmap beatmap, IHasDistance distanceData, out double taikoDuration, out double tickSpacing) + { + // DO NOT CHANGE OR REFACTOR ANYTHING IN HERE WITHOUT TESTING AGAINST _ALL_ BEATMAPS. + // Some of these calculations look redundant, but they are not - extremely small floating point errors are introduced to maintain 1:1 compatibility with stable. + // Rounding cannot be used as an alternative since the error deltas have been observed to be between 1e-2 and 1e-6. + + // The true distance, accounting for any repeats. This ends up being the drum roll distance later + int spans = (obj as IHasRepeats)?.SpanCount() ?? 1; + double distance = distanceData.Distance * spans * LEGACY_VELOCITY_MULTIPLIER; + + TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(obj.StartTime); + DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime); + + double beatLength; +#pragma warning disable 618 + if (difficultyPoint is LegacyBeatmapDecoder.LegacyDifficultyControlPoint legacyDifficultyPoint) +#pragma warning restore 618 + beatLength = timingPoint.BeatLength * legacyDifficultyPoint.BpmMultiplier; + else + beatLength = timingPoint.BeatLength / difficultyPoint.SpeedMultiplier; + + double sliderScoringPointDistance = osu_base_scoring_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / beatmap.BeatmapInfo.BaseDifficulty.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; + taikoDuration = distance / taikoVelocity * beatLength; + + if (isForCurrentRuleset) + { + tickSpacing = 0; + return false; + } + + double osuVelocity = taikoVelocity * (1000f / beatLength); + + // osu-stable always uses the speed-adjusted beatlength to determine the osu! velocity, but only uses it for conversion if beatmap version < 8 + if (beatmap.BeatmapInfo.BeatmapVersion >= 8) + 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, taikoDuration / spans); + + return tickSpacing > 0 + && distance / osuVelocity * 1000 < 2 * beatLength; + } + protected override Beatmap CreateBeatmap() => new TaikoBeatmap(); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index be5cd78dc8..b30ec0ca2c 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -369,7 +369,9 @@ namespace osu.Game.Beatmaps.Formats addControlPoint(time, controlPoint, true); } - addControlPoint(time, new LegacyDifficultyControlPoint +#pragma warning disable 618 + addControlPoint(time, new LegacyDifficultyControlPoint(beatLength) +#pragma warning restore 618 { SpeedMultiplier = speedMultiplier, }, timingChange); diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index a0e83554a3..44ef9bcacc 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -159,11 +159,20 @@ namespace osu.Game.Beatmaps.Formats Mania, } - internal class LegacyDifficultyControlPoint : DifficultyControlPoint + [Obsolete("Do not use unless you're a legacy ruleset and 100% sure.")] + public class LegacyDifficultyControlPoint : DifficultyControlPoint { - public LegacyDifficultyControlPoint() + /// + /// Legacy BPM multiplier that introduces floating-point errors for rulesets that depend on it. + /// DO NOT USE THIS UNLESS 100% SURE. + /// + public readonly float BpmMultiplier; + + public LegacyDifficultyControlPoint(double beatLength) { SpeedMultiplierBindable.Precision = double.Epsilon; + + BpmMultiplier = beatLength < 0 ? Math.Clamp((float)-beatLength, 10, 10000) / 100f : 1; } } From 7b7b92aa10ab0f65f21e338d34d015ed4fe45f6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 13 Jul 2020 17:28:16 +0900 Subject: [PATCH 811/871] Fix potential crash when trying to ensure music is playing --- osu.Game/Overlays/MusicController.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/MusicController.cs b/osu.Game/Overlays/MusicController.cs index 09f2a66b47..546f7a1ec4 100644 --- a/osu.Game/Overlays/MusicController.cs +++ b/osu.Game/Overlays/MusicController.cs @@ -149,7 +149,7 @@ namespace osu.Game.Overlays if (beatmap.Disabled) return; - next(); + NextTrack(); } else if (!IsPlaying) { @@ -217,6 +217,9 @@ namespace osu.Game.Overlays /// The that indicate the decided action. private PreviousTrackResult prev() { + if (beatmap.Disabled) + return PreviousTrackResult.None; + var currentTrackPosition = current?.Track.CurrentTime; if (currentTrackPosition >= restart_cutoff_point) @@ -248,6 +251,9 @@ namespace osu.Game.Overlays private bool next() { + if (beatmap.Disabled) + return false; + queuedDirection = TrackChangeDirection.Next; var playable = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? BeatmapSets.FirstOrDefault(); From 69548447a7e3bf9ac63c2e1e05ceff17123ed6ed Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Mon, 13 Jul 2020 20:03:07 +0900 Subject: [PATCH 812/871] Adjust step name --- osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs index 70eafcb2a7..a3ea4619cc 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarousel.cs @@ -114,7 +114,7 @@ namespace osu.Game.Tests.Visual.SongSelect { loadBeatmaps(); - AddStep("select last", () => carousel.SelectBeatmap(carousel.BeatmapSets.First().Beatmaps.First())); + AddStep("select first", () => carousel.SelectBeatmap(carousel.BeatmapSets.First().Beatmaps.First())); waitForSelection(1, 1); advanceSelection(direction: 1, diff: true); From 8a3cadc111cfadb6744a5e22b4f5d2bd4abe3550 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 13 Jul 2020 20:12:50 +0900 Subject: [PATCH 813/871] Fix judgement animations not resetting on use --- .../Objects/Drawables/DrawableOsuJudgement.cs | 2 ++ osu.Game/Rulesets/Judgements/DrawableJudgement.cs | 10 +++++++--- osu.Game/Skinning/SkinnableDrawable.cs | 6 ++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs index cfe969d1cc..1493ddfcf3 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableOsuJudgement.cs @@ -62,6 +62,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables if (lighting != null) { + lighting.ResetAnimation(); + if (JudgedObject != null) { lightingColour = JudgedObject.AccentColour.GetBoundCopy(); diff --git a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs index 4e7f0018ef..052aaa3c65 100644 --- a/osu.Game/Rulesets/Judgements/DrawableJudgement.cs +++ b/osu.Game/Rulesets/Judgements/DrawableJudgement.cs @@ -31,8 +31,10 @@ namespace osu.Game.Rulesets.Judgements public JudgementResult Result { get; private set; } public DrawableHitObject JudgedObject { get; private set; } - protected Container JudgementBody; - protected SpriteText JudgementText; + protected Container JudgementBody { get; private set; } + protected SpriteText JudgementText { get; private set; } + + private SkinnableDrawable bodyDrawable; /// /// Duration of initial fade in. @@ -89,6 +91,8 @@ namespace osu.Game.Rulesets.Judgements prepareDrawables(); + bodyDrawable.ResetAnimation(); + this.FadeInFromZero(FadeInDuration, Easing.OutQuint); JudgementBody.ScaleTo(1); JudgementBody.RotateTo(0); @@ -131,7 +135,7 @@ namespace osu.Game.Rulesets.Judgements Anchor = Anchor.Centre, Origin = Anchor.Centre, RelativeSizeAxes = Axes.Both, - Child = new SkinnableDrawable(new GameplaySkinComponent(type), _ => JudgementText = new OsuSpriteText + Child = bodyDrawable = new SkinnableDrawable(new GameplaySkinComponent(type), _ => JudgementText = new OsuSpriteText { Text = type.GetDescription().ToUpperInvariant(), Font = OsuFont.Numeric.With(size: 20), diff --git a/osu.Game/Skinning/SkinnableDrawable.cs b/osu.Game/Skinning/SkinnableDrawable.cs index 0f0d3da5aa..d9a5036649 100644 --- a/osu.Game/Skinning/SkinnableDrawable.cs +++ b/osu.Game/Skinning/SkinnableDrawable.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Caching; using osu.Framework.Graphics; +using osu.Framework.Graphics.Animations; using osuTK; namespace osu.Game.Skinning @@ -50,6 +51,11 @@ namespace osu.Game.Skinning RelativeSizeAxes = Axes.Both; } + /// + /// Seeks to the 0-th frame if the content of this is an . + /// + public void ResetAnimation() => (Drawable as IFramedAnimation)?.GotoFrame(0); + private readonly Func createDefault; private readonly Cached scaling = new Cached(); From 53520ec7c45b65961b9653c3c7f1b0927ca23889 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 13 Jul 2020 20:55:55 +0900 Subject: [PATCH 814/871] Add test --- .../TestSceneDrawableJudgement.cs | 45 ++++++++++++++++--- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs index c81edf4e07..f08f994b07 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneDrawableJudgement.cs @@ -2,9 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.Linq; using osu.Framework.Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -16,14 +19,46 @@ namespace osu.Game.Rulesets.Osu.Tests { public TestSceneDrawableJudgement() { + var pools = new List>(); + foreach (HitResult result in Enum.GetValues(typeof(HitResult)).OfType().Skip(1)) { - AddStep("Show " + result.GetDescription(), () => SetContents(() => - new DrawableOsuJudgement(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null) + AddStep("Show " + result.GetDescription(), () => + { + int poolIndex = 0; + + SetContents(() => { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - })); + DrawablePool pool; + + if (poolIndex >= pools.Count) + pools.Add(pool = new DrawablePool(1)); + else + { + pool = pools[poolIndex]; + + // We need to make sure neither the pool nor the judgement get disposed when new content is set, and they both share the same parent. + ((Container)pool.Parent).Clear(false); + } + + var container = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + pool, + pool.Get(j => j.Apply(new JudgementResult(new HitObject(), new Judgement()) { Type = result }, null)).With(j => + { + j.Anchor = Anchor.Centre; + j.Origin = Anchor.Centre; + }) + } + }; + + poolIndex++; + return container; + }); + }); } } } From 8087a75c3506e1906af24c3fc9f0639048e8b9d8 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Mon, 13 Jul 2020 17:22:39 +0000 Subject: [PATCH 815/871] Bump NUnit3TestAdapter from 3.15.1 to 3.17.0 Bumps [NUnit3TestAdapter](https://github.com/nunit/nunit3-vs-adapter) from 3.15.1 to 3.17.0. - [Release notes](https://github.com/nunit/nunit3-vs-adapter/releases) - [Commits](https://github.com/nunit/nunit3-vs-adapter/compare/V3.15.1...V3.17) Signed-off-by: dependabot-preview[bot] --- osu.Game.Benchmarks/osu.Game.Benchmarks.csproj | 2 +- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 41e726e05c..ff26f4afaa 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -9,7 +9,7 @@ - + diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index cbd3dc5518..7c0b73e8c3 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index 77c871718b..972cbec4a2 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 2fcfa1deb7..d6a68abaf2 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 28b8476a22..ada7ac5d74 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -4,7 +4,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 5ee887cb64..4b0506d818 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -5,7 +5,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index aa37326a49..f256b8e4e9 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -7,7 +7,7 @@ - + WinExe From 38e9b882b813f68fc350ae4538ab1e6c5abf806f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Jul 2020 07:04:30 +0900 Subject: [PATCH 816/871] 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 0881861bdc..1d1583c55a 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index cba2d62bf5..4295e02d24 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 45e0da36c1..3627cc032e 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From b59e4f8a7ef40ece6455b20a612ff942e18917c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Jul 2020 08:15:14 +0900 Subject: [PATCH 817/871] Change difficulty adjust mod to match stable range of 0-10 --- osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs | 4 ++-- osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs index 8228161008..ff995e38ce 100644 --- a/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs +++ b/osu.Game.Rulesets.Osu/Mods/OsuModDifficultyAdjust.cs @@ -15,7 +15,7 @@ namespace osu.Game.Rulesets.Osu.Mods public BindableNumber CircleSize { get; } = new BindableFloat { Precision = 0.1f, - MinValue = 1, + MinValue = 0, MaxValue = 10, Default = 5, Value = 5, @@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Mods public BindableNumber ApproachRate { get; } = new BindableFloat { Precision = 0.1f, - MinValue = 1, + MinValue = 0, MaxValue = 10, Default = 5, Value = 5, diff --git a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs index c3a8efdd66..165644edbe 100644 --- a/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs +++ b/osu.Game/Rulesets/Mods/ModDifficultyAdjust.cs @@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mods public BindableNumber DrainRate { get; } = new BindableFloat { Precision = 0.1f, - MinValue = 1, + MinValue = 0, MaxValue = 10, Default = 5, Value = 5, @@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mods public BindableNumber OverallDifficulty { get; } = new BindableFloat { Precision = 0.1f, - MinValue = 1, + MinValue = 0, MaxValue = 10, Default = 5, Value = 5, From 56349e65f3929032561d3059622a201f87afc604 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 14 Jul 2020 03:01:14 +0300 Subject: [PATCH 818/871] Rename arrow direction method --- .../Visual/UserInterface/TestSceneCommentRepliesButton.cs | 8 ++++---- .../Overlays/Comments/Buttons/CommentRepliesButton.cs | 2 +- osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs index 7f5806705e..c2dc804385 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs @@ -41,11 +41,11 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestArrowRotation() + public void TestArrowDirection() { - AddStep("Toggle icon up", () => button.ToggleIcon(true)); + AddStep("Set upwards", () => button.SetIconDirection(true)); AddAssert("Icon facing upwards", () => button.Icon.Scale.Y == -1); - AddStep("Toggle icon down", () => button.ToggleIcon(false)); + AddStep("Set downwards", () => button.SetIconDirection(false)); AddAssert("Icon facing downwards", () => button.Icon.Scale.Y == 1); } @@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.UserInterface Text = "sample text"; } - public new void ToggleIcon(bool upwards) => base.ToggleIcon(upwards); + public new void SetIconDirection(bool upwards) => base.SetIconDirection(upwards); } } } diff --git a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs index 65648f6751..abe80722e2 100644 --- a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs @@ -97,7 +97,7 @@ namespace osu.Game.Overlays.Comments.Buttons icon.Colour = colourProvider.Foreground1; } - protected void ToggleIcon(bool upwards) => icon.ScaleTo(new Vector2(1, upwards ? -1 : 1)); + protected void SetIconDirection(bool upwards) => icon.ScaleTo(new Vector2(1, upwards ? -1 : 1)); protected override bool OnHover(HoverEvent e) { diff --git a/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs index 118cac5b4c..e2023c2f58 100644 --- a/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs @@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Comments.Buttons protected override void LoadComplete() { base.LoadComplete(); - Expanded.BindValueChanged(expanded => ToggleIcon(expanded.NewValue), true); + Expanded.BindValueChanged(expanded => SetIconDirection(expanded.NewValue), true); } protected override bool OnClick(ClickEvent e) From 7c71cc6b6174a6a62a0f78cd9029bb12943ff82b Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 14 Jul 2020 03:06:51 +0300 Subject: [PATCH 819/871] Remove unneeded class from DrawableComment --- osu.Game/Overlays/Comments/DrawableComment.cs | 41 +++++-------------- 1 file changed, 11 insertions(+), 30 deletions(-) diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 813540b28d..731ebe7104 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -52,7 +52,7 @@ namespace osu.Game.Overlays.Comments private ShowRepliesButton showRepliesButton; private ChevronButton chevronButton; private DeletedCommentsCounter deletedCommentsCounter; - private Loading loading; + private LoadingSpinner loading; public DrawableComment(Comment comment) { @@ -209,10 +209,16 @@ namespace osu.Game.Overlays.Comments } } }, - loading = new Loading + loading = new LoadingSpinner { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Size = new Vector2(15), + Margin = new MarginPadding + { + Vertical = 5, + Left = 80 + } }, childCommentsVisibilityContainer = new FillFlowContainer { @@ -368,6 +374,7 @@ namespace osu.Game.Overlays.Comments showMoreButton.IsLoading = false; loading.Hide(); + loading.FinishTransforms(); } private class ChevronButton : ShowChildrenButton @@ -438,31 +445,5 @@ namespace osu.Game.Overlays.Comments return parentComment.HasMessage ? parentComment.Message : parentComment.IsDeleted ? @"deleted" : string.Empty; } } - - private class Loading : Container - { - private readonly LoadingSpinner loading; - - public Loading() - { - Child = loading = new LoadingSpinner - { - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - Size = new Vector2(15), - Margin = new MarginPadding - { - Vertical = 5, - Left = 80 - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - loading.Show(); - } - } } } From 8a77a3621e71961bbcccb0a3b38eb2e1d9e96561 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Jul 2020 11:03:55 +0900 Subject: [PATCH 820/871] 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 1d1583c55a..8510632d45 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 4295e02d24..05d6f27d40 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -25,7 +25,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 3627cc032e..af779b32fd 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From e35e9df4e1a6acdeaae0a2022363d8240f9cb390 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Jul 2020 12:02:03 +0900 Subject: [PATCH 821/871] Fix local online cache database not being used when offline / logged out --- osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index 3106d1143e..4de4e21b15 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -48,9 +48,6 @@ namespace osu.Game.Beatmaps public Task UpdateAsync(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken) { - if (api?.State != APIState.Online) - return Task.CompletedTask; - LogForModel(beatmapSet, "Performing online lookups..."); return Task.WhenAll(beatmapSet.Beatmaps.Select(b => UpdateAsync(beatmapSet, b, cancellationToken)).ToArray()); } From 7fe69bb1996e83fd70417670a709a67d3cb2a1c1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Jul 2020 13:07:17 +0900 Subject: [PATCH 822/871] Fix some web requests retrieving the user too early --- osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs | 7 +++---- osu.Game/Online/API/APIRequest.cs | 7 +++++++ osu.Game/Online/API/Requests/JoinChannelRequest.cs | 7 ++----- osu.Game/Online/API/Requests/JoinRoomRequest.cs | 7 ++----- osu.Game/Online/API/Requests/LeaveChannelRequest.cs | 7 ++----- osu.Game/Online/API/Requests/PartRoomRequest.cs | 7 ++----- osu.Game/Online/Chat/ChannelManager.cs | 4 ++-- osu.Game/Screens/Multi/RoomManager.cs | 4 ++-- 8 files changed, 22 insertions(+), 28 deletions(-) diff --git a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs index 1e77d50115..42948c3731 100644 --- a/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs +++ b/osu.Game.Tests/Online/TestDummyAPIRequestHandling.cs @@ -8,7 +8,6 @@ using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osu.Game.Tests.Visual; -using osu.Game.Users; namespace osu.Game.Tests.Online { @@ -55,7 +54,7 @@ namespace osu.Game.Tests.Online AddStep("fire request", () => { gotResponse = false; - request = new LeaveChannelRequest(new Channel(), new User()); + request = new LeaveChannelRequest(new Channel()); request.Success += () => gotResponse = true; API.Queue(request); }); @@ -74,7 +73,7 @@ namespace osu.Game.Tests.Online AddStep("fire request", () => { gotResponse = false; - request = new LeaveChannelRequest(new Channel(), new User()); + request = new LeaveChannelRequest(new Channel()); request.Success += () => gotResponse = true; API.Perform(request); }); @@ -93,7 +92,7 @@ namespace osu.Game.Tests.Online AddStep("fire request", () => { gotResponse = false; - request = new LeaveChannelRequest(new Channel(), new User()); + request = new LeaveChannelRequest(new Channel()); request.Success += () => gotResponse = true; API.PerformAsync(request); }); diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index 0f8acbb7af..2115326cc2 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -5,6 +5,7 @@ using System; using Newtonsoft.Json; using osu.Framework.IO.Network; using osu.Framework.Logging; +using osu.Game.Users; namespace osu.Game.Online.API { @@ -61,6 +62,11 @@ namespace osu.Game.Online.API protected APIAccess API; protected WebRequest WebRequest; + /// + /// The currently logged in user. Note that this will only be populated during . + /// + protected User User { get; private set; } + /// /// Invoked on successful completion of an API request. /// This will be scheduled to the API's internal scheduler (run on update thread automatically). @@ -86,6 +92,7 @@ namespace osu.Game.Online.API } API = apiAccess; + User = apiAccess.LocalUser.Value; if (checkAndScheduleFailure()) return; diff --git a/osu.Game/Online/API/Requests/JoinChannelRequest.cs b/osu.Game/Online/API/Requests/JoinChannelRequest.cs index f6ed5f22c9..33eab7e355 100644 --- a/osu.Game/Online/API/Requests/JoinChannelRequest.cs +++ b/osu.Game/Online/API/Requests/JoinChannelRequest.cs @@ -4,19 +4,16 @@ using System.Net.Http; using osu.Framework.IO.Network; using osu.Game.Online.Chat; -using osu.Game.Users; namespace osu.Game.Online.API.Requests { public class JoinChannelRequest : APIRequest { private readonly Channel channel; - private readonly User user; - public JoinChannelRequest(Channel channel, User user) + public JoinChannelRequest(Channel channel) { this.channel = channel; - this.user = user; } protected override WebRequest CreateWebRequest() @@ -26,6 +23,6 @@ namespace osu.Game.Online.API.Requests return req; } - protected override string Target => $@"chat/channels/{channel.Id}/users/{user.Id}"; + protected override string Target => $@"chat/channels/{channel.Id}/users/{User.Id}"; } } diff --git a/osu.Game/Online/API/Requests/JoinRoomRequest.cs b/osu.Game/Online/API/Requests/JoinRoomRequest.cs index 36b275236c..b0808afa45 100644 --- a/osu.Game/Online/API/Requests/JoinRoomRequest.cs +++ b/osu.Game/Online/API/Requests/JoinRoomRequest.cs @@ -4,19 +4,16 @@ using System.Net.Http; using osu.Framework.IO.Network; using osu.Game.Online.Multiplayer; -using osu.Game.Users; namespace osu.Game.Online.API.Requests { public class JoinRoomRequest : APIRequest { private readonly Room room; - private readonly User user; - public JoinRoomRequest(Room room, User user) + public JoinRoomRequest(Room room) { this.room = room; - this.user = user; } protected override WebRequest CreateWebRequest() @@ -26,6 +23,6 @@ namespace osu.Game.Online.API.Requests return req; } - protected override string Target => $"rooms/{room.RoomID.Value}/users/{user.Id}"; + protected override string Target => $"rooms/{room.RoomID.Value}/users/{User.Id}"; } } diff --git a/osu.Game/Online/API/Requests/LeaveChannelRequest.cs b/osu.Game/Online/API/Requests/LeaveChannelRequest.cs index f2ae3926bd..7dfc9a0aed 100644 --- a/osu.Game/Online/API/Requests/LeaveChannelRequest.cs +++ b/osu.Game/Online/API/Requests/LeaveChannelRequest.cs @@ -4,19 +4,16 @@ using System.Net.Http; using osu.Framework.IO.Network; using osu.Game.Online.Chat; -using osu.Game.Users; namespace osu.Game.Online.API.Requests { public class LeaveChannelRequest : APIRequest { private readonly Channel channel; - private readonly User user; - public LeaveChannelRequest(Channel channel, User user) + public LeaveChannelRequest(Channel channel) { this.channel = channel; - this.user = user; } protected override WebRequest CreateWebRequest() @@ -26,6 +23,6 @@ namespace osu.Game.Online.API.Requests return req; } - protected override string Target => $@"chat/channels/{channel.Id}/users/{user.Id}"; + protected override string Target => $@"chat/channels/{channel.Id}/users/{User.Id}"; } } diff --git a/osu.Game/Online/API/Requests/PartRoomRequest.cs b/osu.Game/Online/API/Requests/PartRoomRequest.cs index e1550cb2e0..c988cd5c9e 100644 --- a/osu.Game/Online/API/Requests/PartRoomRequest.cs +++ b/osu.Game/Online/API/Requests/PartRoomRequest.cs @@ -4,19 +4,16 @@ using System.Net.Http; using osu.Framework.IO.Network; using osu.Game.Online.Multiplayer; -using osu.Game.Users; namespace osu.Game.Online.API.Requests { public class PartRoomRequest : APIRequest { private readonly Room room; - private readonly User user; - public PartRoomRequest(Room room, User user) + public PartRoomRequest(Room room) { this.room = room; - this.user = user; } protected override WebRequest CreateWebRequest() @@ -26,6 +23,6 @@ namespace osu.Game.Online.API.Requests return req; } - protected override string Target => $"rooms/{room.RoomID.Value}/users/{user.Id}"; + protected override string Target => $"rooms/{room.RoomID.Value}/users/{User.Id}"; } } diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 3b336fef4f..f7ed57f207 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -381,7 +381,7 @@ namespace osu.Game.Online.Chat break; default: - var req = new JoinChannelRequest(channel, api.LocalUser.Value); + var req = new JoinChannelRequest(channel); req.Success += () => joinChannel(channel, fetchInitialMessages); req.Failure += ex => LeaveChannel(channel); api.Queue(req); @@ -410,7 +410,7 @@ namespace osu.Game.Online.Chat if (channel.Joined.Value) { - api.Queue(new LeaveChannelRequest(channel, api.LocalUser.Value)); + api.Queue(new LeaveChannelRequest(channel)); channel.Joined.Value = false; } } diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index ac1f74b6a6..491be2e946 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -114,7 +114,7 @@ namespace osu.Game.Screens.Multi public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) { currentJoinRoomRequest?.Cancel(); - currentJoinRoomRequest = new JoinRoomRequest(room, api.LocalUser.Value); + currentJoinRoomRequest = new JoinRoomRequest(room); currentJoinRoomRequest.Success += () => { @@ -139,7 +139,7 @@ namespace osu.Game.Screens.Multi if (joinedRoom == null) return; - api.Queue(new PartRoomRequest(joinedRoom, api.LocalUser.Value)); + api.Queue(new PartRoomRequest(joinedRoom)); joinedRoom = null; } From 1a2f5cb477a2c13d095fd63b998a4ea3f53b8829 Mon Sep 17 00:00:00 2001 From: Joehu Date: Mon, 13 Jul 2020 23:59:20 -0700 Subject: [PATCH 823/871] Add OnBackButton bool to OsuScreen --- osu.Game/OsuGame.cs | 4 +++- osu.Game/Screens/IOsuScreen.cs | 2 ++ osu.Game/Screens/OsuScreen.cs | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 47a7c2ae11..618049e72c 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -573,7 +573,9 @@ namespace osu.Game Origin = Anchor.BottomLeft, Action = () => { - if ((ScreenStack.CurrentScreen as IOsuScreen)?.AllowBackButton == true) + var currentScreen = ScreenStack.CurrentScreen as IOsuScreen; + + if (currentScreen?.AllowBackButton == true && !currentScreen.OnBackButton()) ScreenStack.Exit(); } }, diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index 22fe0ad816..6ed04291ce 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -56,5 +56,7 @@ namespace osu.Game.Screens /// Whether mod rate adjustments are allowed to be applied. /// bool AllowRateAdjustments { get; } + + bool OnBackButton(); } } diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 35bb4fa34f..872a1cd39a 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -258,5 +258,7 @@ namespace osu.Game.Screens /// Note that the instance created may not be the used instance if it matches the BackgroundMode equality clause. /// protected virtual BackgroundScreen CreateBackground() => null; + + public virtual bool OnBackButton() => false; } } From 4caf4d31d4a9fb0b56ce528c3b5fac6194718ca3 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 14 Jul 2020 00:00:10 -0700 Subject: [PATCH 824/871] Fix mod select blocking home and alt f4 in song select --- osu.Game/Screens/Select/SongSelect.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index e3705b15fa..74a5ee8309 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -599,12 +599,6 @@ namespace osu.Game.Screens.Select public override bool OnExiting(IScreen next) { - if (ModSelect.State.Value == Visibility.Visible) - { - ModSelect.Hide(); - return true; - } - if (base.OnExiting(next)) return true; @@ -620,6 +614,17 @@ namespace osu.Game.Screens.Select return false; } + public override bool OnBackButton() + { + if (ModSelect.State.Value == Visibility.Visible) + { + ModSelect.Hide(); + return true; + } + + return false; + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From daa7430fd8d07befdcf2544143ccfe7ef1cd6316 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 14 Jul 2020 00:00:43 -0700 Subject: [PATCH 825/871] Fix statistics screen blocking retry, home, and alt f4 --- osu.Game/Screens/Ranking/ResultsScreen.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 49ce07b708..44458d8c8e 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -194,6 +194,13 @@ namespace osu.Game.Screens.Ranking } public override bool OnExiting(IScreen next) + { + Background.FadeTo(1, 250); + + return base.OnExiting(next); + } + + public override bool OnBackButton() { if (statisticsPanel.State.Value == Visibility.Visible) { @@ -201,9 +208,7 @@ namespace osu.Game.Screens.Ranking return true; } - Background.FadeTo(1, 250); - - return base.OnExiting(next); + return false; } private void addScore(ScoreInfo score) From 3573460d9c05aebe54995c72ea16e231f0e91dc8 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 14 Jul 2020 00:02:01 -0700 Subject: [PATCH 826/871] Fix multiplayer screens blocking home and alt f4 --- osu.Game/Screens/Multi/Multiplayer.cs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 3178e35581..067a42d57d 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -250,12 +250,6 @@ namespace osu.Game.Screens.Multi { roomManager.PartRoom(); - if (screenStack.CurrentScreen != null && !(screenStack.CurrentScreen is LoungeSubScreen)) - { - screenStack.Exit(); - return true; - } - waves.Hide(); this.Delay(WaveContainer.DISAPPEAR_DURATION).FadeOut(); @@ -269,6 +263,17 @@ namespace osu.Game.Screens.Multi return false; } + public override bool OnBackButton() + { + if (screenStack.CurrentScreen != null && !(screenStack.CurrentScreen is LoungeSubScreen)) + { + screenStack.Exit(); + return true; + } + + return false; + } + protected override void LogoExiting(OsuLogo logo) { base.LogoExiting(logo); From 8ace06fcc5c47b1c5f67c659cd8e205a1954f2a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Jul 2020 16:03:40 +0900 Subject: [PATCH 827/871] Fix continuations attaching to the BeatmapOnlineLookupQueue scheduler --- osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs index 4de4e21b15..16207c7d2a 100644 --- a/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs +++ b/osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs @@ -54,7 +54,7 @@ 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, updateScheduler); + => Task.Factory.StartNew(() => lookup(beatmapSet, beatmap), cancellationToken, TaskCreationOptions.HideScheduler | TaskCreationOptions.RunContinuationsAsynchronously, updateScheduler); private void lookup(BeatmapSetInfo set, BeatmapInfo beatmap) { From 36041fc2453832976cad0b6c89a0ec0979dae68b Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 14 Jul 2020 00:29:57 -0700 Subject: [PATCH 828/871] Fix back button not working correctly with multi song select's mod select --- osu.Game/Screens/Multi/Multiplayer.cs | 12 ++++++++++-- osu.Game/Screens/Select/SongSelect.cs | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 067a42d57d..2a73b53199 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -20,9 +20,9 @@ using osu.Game.Online.Multiplayer; using osu.Game.Screens.Menu; using osu.Game.Screens.Multi.Components; using osu.Game.Screens.Multi.Lounge; -using osu.Game.Screens.Multi.Lounge.Components; using osu.Game.Screens.Multi.Match; using osu.Game.Screens.Multi.Match.Components; +using osu.Game.Screens.Select; using osuTK; namespace osu.Game.Screens.Multi @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Multi private readonly Bindable selectedRoom = new Bindable(); [Cached] - private readonly Bindable currentFilter = new Bindable(new FilterCriteria()); + private readonly Bindable currentFilter = new Bindable(new Lounge.Components.FilterCriteria()); [Cached(Type = typeof(IRoomManager))] private RoomManager roomManager; @@ -265,6 +265,14 @@ namespace osu.Game.Screens.Multi public override bool OnBackButton() { + var songSelect = screenStack.CurrentScreen as MatchSongSelect; + + if (songSelect?.ModSelect.State.Value == Visibility.Visible) + { + songSelect.ModSelect.Hide(); + return true; + } + if (screenStack.CurrentScreen != null && !(screenStack.CurrentScreen is LoungeSubScreen)) { screenStack.Exit(); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 74a5ee8309..87fad66b66 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Select [Resolved] private BeatmapManager beatmaps { get; set; } - protected ModSelectOverlay ModSelect { get; private set; } + public ModSelectOverlay ModSelect { get; private set; } protected SampleChannel SampleConfirm { get; private set; } From 304e518f7ba48d32dc426a0bf959d5481cb30f12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Jul 2020 17:30:20 +0900 Subject: [PATCH 829/871] 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 8510632d45..85d154f2e2 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,6 +52,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 05d6f27d40..b8e73262c4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -24,7 +24,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index af779b32fd..1faf60b1d9 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -70,7 +70,7 @@ - + @@ -80,7 +80,7 @@ - + From c7d3b025ada6692d159182bc42fbd9d1e844931f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Jul 2020 20:11:54 +0900 Subject: [PATCH 830/871] Rename variable and change default to true --- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- .../Overlays/Settings/Sections/Gameplay/GeneralSettings.cs | 2 +- osu.Game/Screens/Play/ComboEffects.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 10d11f967e..268328272c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -91,7 +91,7 @@ namespace osu.Game.Configuration Set(OsuSetting.FadePlayfieldWhenHealthLow, true); Set(OsuSetting.KeyOverlay, false); Set(OsuSetting.PositionalHitSounds, true); - Set(OsuSetting.AlwaysPlayComboBreak, false); + Set(OsuSetting.AlwaysPlayFirstComboBreak, true); Set(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth); Set(OsuSetting.FloatingComments, false); @@ -181,7 +181,7 @@ namespace osu.Game.Configuration ShowStoryboard, KeyOverlay, PositionalHitSounds, - AlwaysPlayComboBreak, + AlwaysPlayFirstComboBreak, ScoreMeter, FloatingComments, ShowInterface, diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index f3534e4625..d79f1ba583 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -71,7 +71,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay { LabelText = "Always play first combo break sound", Keywords = new[] { "regardless", "combobreak.wav" }, - Bindable = config.GetBindable(OsuSetting.AlwaysPlayComboBreak) + Bindable = config.GetBindable(OsuSetting.AlwaysPlayFirstComboBreak) }, new SettingsEnumDropdown { diff --git a/osu.Game/Screens/Play/ComboEffects.cs b/osu.Game/Screens/Play/ComboEffects.cs index c56ee35cec..a836b6137e 100644 --- a/osu.Game/Screens/Play/ComboEffects.cs +++ b/osu.Game/Screens/Play/ComboEffects.cs @@ -29,7 +29,7 @@ namespace osu.Game.Screens.Play private void load(OsuConfigManager config) { InternalChild = comboBreakSample = new SkinnableSound(new SampleInfo("combobreak")); - alwaysPlay = config.GetBindable(OsuSetting.AlwaysPlayComboBreak); + alwaysPlay = config.GetBindable(OsuSetting.AlwaysPlayFirstComboBreak); } protected override void LoadComplete() From 956980ee9055738a73dd9791ca52ff1a3de2ab18 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Jul 2020 20:12:58 +0900 Subject: [PATCH 831/871] Remove setting from gameplay settings screen --- .../Overlays/Settings/Sections/Gameplay/GeneralSettings.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs index d79f1ba583..93a02ea0e4 100644 --- a/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Gameplay/GeneralSettings.cs @@ -67,12 +67,6 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay LabelText = "Positional hitsounds", Bindable = config.GetBindable(OsuSetting.PositionalHitSounds) }, - new SettingsCheckbox - { - LabelText = "Always play first combo break sound", - Keywords = new[] { "regardless", "combobreak.wav" }, - Bindable = config.GetBindable(OsuSetting.AlwaysPlayFirstComboBreak) - }, new SettingsEnumDropdown { LabelText = "Score meter type", From 2626ab41c3e38b775896c652c59f7b4b0ee335d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 14 Jul 2020 20:15:29 +0900 Subject: [PATCH 832/871] Add implicit braces for clarity --- osu.Game/Screens/Play/ComboEffects.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/ComboEffects.cs b/osu.Game/Screens/Play/ComboEffects.cs index a836b6137e..5bcda50399 100644 --- a/osu.Game/Screens/Play/ComboEffects.cs +++ b/osu.Game/Screens/Play/ComboEffects.cs @@ -40,7 +40,7 @@ namespace osu.Game.Screens.Play private void onComboChange(ValueChangedEvent combo) { - if (combo.NewValue == 0 && (combo.OldValue > 20 || alwaysPlay.Value && firstTime)) + if (combo.NewValue == 0 && (combo.OldValue > 20 || (alwaysPlay.Value && firstTime))) { comboBreakSample?.Play(); firstTime = false; From b64ddf061ea5ba2aa1aa479ccbbba148df5018d9 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 14 Jul 2020 20:37:21 +0900 Subject: [PATCH 833/871] Remove whitespace --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 08b2acc2ef..6ae420b162 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -162,7 +162,7 @@ namespace osu.Game.Input.Bindings [Description("Toggle notifications")] ToggleNotifications, - + [Description("Pause")] PauseGameplay, } From 4c2294f0cd65ebce410d979af5f3b7b395da64ef Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 14 Jul 2020 15:02:29 +0300 Subject: [PATCH 834/871] Refactor LoadRepliesButton to inherit LoadingButton --- .../TestSceneCommentRepliesButton.cs | 5 +++- .../Comments/Buttons/CommentRepliesButton.cs | 12 +++------- .../Comments/Buttons/LoadRepliesButton.cs | 23 ++++++++++++++++-- .../Comments/Buttons/ShowRepliesButton.cs | 4 ++++ osu.Game/Overlays/Comments/DrawableComment.cs | 24 ++----------------- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs index c2dc804385..c2ac5179c9 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneCommentRepliesButton.cs @@ -33,7 +33,10 @@ namespace osu.Game.Tests.Visual.UserInterface Children = new Drawable[] { button = new TestButton(), - new LoadRepliesButton(), + new LoadRepliesButton + { + Action = () => { } + }, new ShowRepliesButton(1), new ShowRepliesButton(2) } diff --git a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs index abe80722e2..f7e0cb0a6c 100644 --- a/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/CommentRepliesButton.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -17,8 +16,6 @@ namespace osu.Game.Overlays.Comments.Buttons { public abstract class CommentRepliesButton : CompositeDrawable { - public Action Action { get; set; } - protected string Text { get => text.Text; @@ -72,6 +69,7 @@ namespace osu.Game.Overlays.Comments.Buttons { Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + AlwaysPresent = true, Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold) }, icon = new SpriteIcon @@ -99,6 +97,8 @@ namespace osu.Game.Overlays.Comments.Buttons protected void SetIconDirection(bool upwards) => icon.ScaleTo(new Vector2(1, upwards ? -1 : 1)); + public void ToggleTextVisibility(bool visible) => text.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint); + protected override bool OnHover(HoverEvent e) { base.OnHover(e); @@ -113,11 +113,5 @@ namespace osu.Game.Overlays.Comments.Buttons background.FadeColour(colourProvider.Background2, 200, Easing.OutQuint); icon.FadeColour(colourProvider.Foreground1, 200, Easing.OutQuint); } - - protected override bool OnClick(ClickEvent e) - { - Action?.Invoke(); - return base.OnClick(e); - } } } diff --git a/osu.Game/Overlays/Comments/Buttons/LoadRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/LoadRepliesButton.cs index 9387c95758..4998e5391e 100644 --- a/osu.Game/Overlays/Comments/Buttons/LoadRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/LoadRepliesButton.cs @@ -1,13 +1,32 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; + namespace osu.Game.Overlays.Comments.Buttons { - public class LoadRepliesButton : CommentRepliesButton + public class LoadRepliesButton : LoadingButton { + private ButtonContent content; + public LoadRepliesButton() { - Text = "load replies"; + AutoSizeAxes = Axes.Both; + } + + protected override Drawable CreateContent() => content = new ButtonContent(); + + protected override void OnLoadStarted() => content.ToggleTextVisibility(false); + + protected override void OnLoadFinished() => content.ToggleTextVisibility(true); + + private class ButtonContent : CommentRepliesButton + { + public ButtonContent() + { + Text = "load replies"; + } } } } diff --git a/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs index e2023c2f58..aeb33e6756 100644 --- a/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.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 Humanizer; using osu.Framework.Bindables; using osu.Framework.Input.Events; @@ -9,6 +10,8 @@ namespace osu.Game.Overlays.Comments.Buttons { public class ShowRepliesButton : CommentRepliesButton { + public Action Action; + public readonly BindableBool Expanded = new BindableBool(true); public ShowRepliesButton(int count) @@ -25,6 +28,7 @@ namespace osu.Game.Overlays.Comments.Buttons protected override bool OnClick(ClickEvent e) { Expanded.Toggle(); + Action?.Invoke(); return base.OnClick(e); } } diff --git a/osu.Game/Overlays/Comments/DrawableComment.cs b/osu.Game/Overlays/Comments/DrawableComment.cs index 731ebe7104..3cdc0a0cbd 100644 --- a/osu.Game/Overlays/Comments/DrawableComment.cs +++ b/osu.Game/Overlays/Comments/DrawableComment.cs @@ -22,7 +22,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Extensions.IEnumerableExtensions; using System.Collections.Specialized; using osu.Game.Overlays.Comments.Buttons; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Comments { @@ -52,7 +51,6 @@ namespace osu.Game.Overlays.Comments private ShowRepliesButton showRepliesButton; private ChevronButton chevronButton; private DeletedCommentsCounter deletedCommentsCounter; - private LoadingSpinner loading; public DrawableComment(Comment comment) { @@ -194,12 +192,7 @@ namespace osu.Game.Overlays.Comments }, loadRepliesButton = new LoadRepliesButton { - Action = () => - { - RepliesRequested(this, ++currentPage); - loadRepliesButton.Hide(); - loading.Show(); - } + Action = () => RepliesRequested(this, ++currentPage) } } } @@ -209,17 +202,6 @@ namespace osu.Game.Overlays.Comments } } }, - loading = new LoadingSpinner - { - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - Size = new Vector2(15), - Margin = new MarginPadding - { - Vertical = 5, - Left = 80 - } - }, childCommentsVisibilityContainer = new FillFlowContainer { RelativeSizeAxes = Axes.X, @@ -372,9 +354,7 @@ namespace osu.Game.Overlays.Comments if (Comment.IsTopLevel) chevronButton.FadeTo(loadedReplesCount != 0 ? 1 : 0); - showMoreButton.IsLoading = false; - loading.Hide(); - loading.FinishTransforms(); + showMoreButton.IsLoading = loadRepliesButton.IsLoading = false; } private class ChevronButton : ShowChildrenButton From 28006ac33f7f71fefd1dafdf490a760d260f661a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 14 Jul 2020 15:12:18 +0300 Subject: [PATCH 835/871] Remove unnecessary action from ShowRepliesButton --- osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs index aeb33e6756..e2023c2f58 100644 --- a/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using Humanizer; using osu.Framework.Bindables; using osu.Framework.Input.Events; @@ -10,8 +9,6 @@ namespace osu.Game.Overlays.Comments.Buttons { public class ShowRepliesButton : CommentRepliesButton { - public Action Action; - public readonly BindableBool Expanded = new BindableBool(true); public ShowRepliesButton(int count) @@ -28,7 +25,6 @@ namespace osu.Game.Overlays.Comments.Buttons protected override bool OnClick(ClickEvent e) { Expanded.Toggle(); - Action?.Invoke(); return base.OnClick(e); } } From c2c80d2a9891acd9959f3598249ca5cfabaf1b9c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 14 Jul 2020 17:34:01 +0300 Subject: [PATCH 836/871] Refactor SpotlightSelector layout --- .../Overlays/Rankings/SpotlightSelector.cs | 75 ++++++++++++------- 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index f019b50ae8..4d07d6c118 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -50,10 +50,11 @@ namespace osu.Game.Overlays.Rankings public SpotlightSelector() { RelativeSizeAxes = Axes.X; - Height = 100; + AutoSizeAxes = Axes.Y; Add(content = new Container { - RelativeSizeAxes = Axes.Both, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, Children = new Drawable[] { background = new Box @@ -62,31 +63,52 @@ namespace osu.Game.Overlays.Rankings }, new Container { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, - Children = new Drawable[] + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN }, + Child = new FillFlowContainer { - dropdown = new SpotlightsDropdown + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - RelativeSizeAxes = Axes.X, - Current = Current, - Depth = -float.MaxValue - }, - new FillFlowContainer - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(15, 0), - Children = new Drawable[] + new Container { - startDateColumn = new InfoColumn(@"Start Date"), - endDateColumn = new InfoColumn(@"End Date"), - mapCountColumn = new InfoColumn(@"Map Count"), - participantsColumn = new InfoColumn(@"Participants") + Margin = new MarginPadding { Vertical = 20 }, + RelativeSizeAxes = Axes.X, + Height = 40, + Depth = -float.MaxValue, + Child = dropdown = new SpotlightsDropdown + { + RelativeSizeAxes = Axes.X, + Current = Current + } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Margin = new MarginPadding { Vertical = 5 }, + Children = new Drawable[] + { + startDateColumn = new InfoColumn(@"Start Date"), + endDateColumn = new InfoColumn(@"End Date"), + mapCountColumn = new InfoColumn(@"Map Count"), + participantsColumn = new InfoColumn(@"Participants") + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Vertical = 20 }, + Child = new RankingsSortTabControl + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight + } } } } @@ -128,12 +150,13 @@ namespace osu.Game.Overlays.Rankings { AutoSizeAxes = Axes.Both; Direction = FillDirection.Vertical; + Margin = new MarginPadding { Vertical = 10 }; Children = new Drawable[] { new OsuSpriteText { Text = name, - Font = OsuFont.GetFont(size: 10), + Font = OsuFont.GetFont(size: 10, weight: FontWeight.Regular), }, new Container { @@ -143,7 +166,7 @@ namespace osu.Game.Overlays.Rankings { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Font = OsuFont.GetFont(size: 18, weight: FontWeight.Light), + Font = OsuFont.GetFont(size: 20, weight: FontWeight.Light), } } }; From 3d9e174ae8369f6a655e7781ea4af73470a585bb Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 14 Jul 2020 18:09:09 +0300 Subject: [PATCH 837/871] Make sort changes affect request result --- .../Visual/Online/TestSceneRankingsTables.cs | 3 ++- .../API/Requests/GetSpotlightRankingsRequest.cs | 6 +++++- osu.Game/Overlays/Rankings/SpotlightSelector.cs | 11 ++++++----- osu.Game/Overlays/Rankings/SpotlightsLayout.cs | 10 +++++++--- 4 files changed, 20 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs index a3b102dc76..ee109189c7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankingsTables.cs @@ -15,6 +15,7 @@ using osu.Game.Rulesets.Catch; using osu.Framework.Allocation; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays; +using osu.Game.Overlays.Rankings; namespace osu.Game.Tests.Visual.Online { @@ -105,7 +106,7 @@ namespace osu.Game.Tests.Visual.Online { onLoadStarted(); - request = new GetSpotlightRankingsRequest(ruleset, spotlight); + request = new GetSpotlightRankingsRequest(ruleset, spotlight, RankingsSortCriteria.All); ((GetSpotlightRankingsRequest)request).Success += rankings => Schedule(() => { var table = new ScoresTable(1, rankings.Users); diff --git a/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs b/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs index a279db134f..25e6b3f1af 100644 --- a/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs +++ b/osu.Game/Online/API/Requests/GetSpotlightRankingsRequest.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.IO.Network; +using osu.Game.Overlays.Rankings; using osu.Game.Rulesets; namespace osu.Game.Online.API.Requests @@ -9,11 +10,13 @@ namespace osu.Game.Online.API.Requests public class GetSpotlightRankingsRequest : GetRankingsRequest { private readonly int spotlight; + private readonly RankingsSortCriteria sort; - public GetSpotlightRankingsRequest(RulesetInfo ruleset, int spotlight) + public GetSpotlightRankingsRequest(RulesetInfo ruleset, int spotlight, RankingsSortCriteria sort) : base(ruleset, 1) { this.spotlight = spotlight; + this.sort = sort; } protected override WebRequest CreateWebRequest() @@ -21,6 +24,7 @@ namespace osu.Game.Online.API.Requests var req = base.CreateWebRequest(); req.AddParameter("spotlight", spotlight.ToString()); + req.AddParameter("filter", sort.ToString().ToLower()); return req; } diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index 4d07d6c118..fbea53782b 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -22,10 +22,8 @@ namespace osu.Game.Overlays.Rankings { private const int duration = 300; - private readonly Box background; - private readonly SpotlightsDropdown dropdown; - private readonly BindableWithCurrent current = new BindableWithCurrent(); + public readonly Bindable Sort = new Bindable(); public Bindable Current { @@ -41,11 +39,13 @@ namespace osu.Game.Overlays.Rankings protected override bool StartHidden => true; + private readonly Box background; + private readonly Container content; + private readonly SpotlightsDropdown dropdown; private readonly InfoColumn startDateColumn; private readonly InfoColumn endDateColumn; private readonly InfoColumn mapCountColumn; private readonly InfoColumn participantsColumn; - private readonly Container content; public SpotlightSelector() { @@ -107,7 +107,8 @@ namespace osu.Game.Overlays.Rankings Child = new RankingsSortTabControl { Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight + Origin = Anchor.CentreRight, + Current = Sort } } } diff --git a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs index 917509e842..0f9b07bf89 100644 --- a/osu.Game/Overlays/Rankings/SpotlightsLayout.cs +++ b/osu.Game/Overlays/Rankings/SpotlightsLayout.cs @@ -24,6 +24,7 @@ namespace osu.Game.Overlays.Rankings public readonly Bindable Ruleset = new Bindable(); private readonly Bindable selectedSpotlight = new Bindable(); + private readonly Bindable sort = new Bindable(); [Resolved] private IAPIProvider api { get; set; } @@ -72,6 +73,8 @@ namespace osu.Game.Overlays.Rankings } } }; + + sort.BindTo(selector.Sort); } protected override void LoadComplete() @@ -80,7 +83,8 @@ namespace osu.Game.Overlays.Rankings selector.Show(); - selectedSpotlight.BindValueChanged(onSpotlightChanged); + selectedSpotlight.BindValueChanged(_ => onSpotlightChanged()); + sort.BindValueChanged(_ => onSpotlightChanged()); Ruleset.BindValueChanged(onRulesetChanged); getSpotlights(); @@ -101,14 +105,14 @@ namespace osu.Game.Overlays.Rankings selectedSpotlight.TriggerChange(); } - private void onSpotlightChanged(ValueChangedEvent spotlight) + private void onSpotlightChanged() { loading.Show(); cancellationToken?.Cancel(); getRankingsRequest?.Cancel(); - getRankingsRequest = new GetSpotlightRankingsRequest(Ruleset.Value, spotlight.NewValue.Id); + getRankingsRequest = new GetSpotlightRankingsRequest(Ruleset.Value, selectedSpotlight.Value.Id, sort.Value); getRankingsRequest.Success += onSuccess; api.Queue(getRankingsRequest); } From 85c875757219da92b49e4d3582def1653ae2c8f6 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 14 Jul 2020 21:18:46 +0300 Subject: [PATCH 838/871] Return true on click --- osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs b/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs index e2023c2f58..04e7e25cc5 100644 --- a/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs +++ b/osu.Game/Overlays/Comments/Buttons/ShowRepliesButton.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Comments.Buttons protected override bool OnClick(ClickEvent e) { Expanded.Toggle(); - return base.OnClick(e); + return true; } } } From 21ed9df1ea49fe4c97b9156f737ab62adea4711d Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 14 Jul 2020 13:14:59 -0700 Subject: [PATCH 839/871] Add xmldoc for OnBackButton --- osu.Game/Screens/IOsuScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index 6ed04291ce..5f9f611a24 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -57,6 +57,9 @@ namespace osu.Game.Screens /// bool AllowRateAdjustments { get; } + /// + /// Whether there are sub overlays/screens that need closing with the back button before this can be exited. + /// bool OnBackButton(); } } From 73e1bf0d89ec605011cb6e7270cc4c2b3ca31307 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 14 Jul 2020 13:19:48 -0700 Subject: [PATCH 840/871] Check sub screen's OnBackButton instead --- osu.Game/Screens/Multi/Multiplayer.cs | 11 +++-------- osu.Game/Screens/Select/SongSelect.cs | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 2a73b53199..951f21dc2d 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -20,9 +20,9 @@ using osu.Game.Online.Multiplayer; using osu.Game.Screens.Menu; using osu.Game.Screens.Multi.Components; using osu.Game.Screens.Multi.Lounge; +using osu.Game.Screens.Multi.Lounge.Components; using osu.Game.Screens.Multi.Match; using osu.Game.Screens.Multi.Match.Components; -using osu.Game.Screens.Select; using osuTK; namespace osu.Game.Screens.Multi @@ -48,7 +48,7 @@ namespace osu.Game.Screens.Multi private readonly Bindable selectedRoom = new Bindable(); [Cached] - private readonly Bindable currentFilter = new Bindable(new Lounge.Components.FilterCriteria()); + private readonly Bindable currentFilter = new Bindable(new FilterCriteria()); [Cached(Type = typeof(IRoomManager))] private RoomManager roomManager; @@ -265,13 +265,8 @@ namespace osu.Game.Screens.Multi public override bool OnBackButton() { - var songSelect = screenStack.CurrentScreen as MatchSongSelect; - - if (songSelect?.ModSelect.State.Value == Visibility.Visible) - { - songSelect.ModSelect.Hide(); + if ((screenStack.CurrentScreen as IMultiplayerSubScreen).OnBackButton()) return true; - } if (screenStack.CurrentScreen != null && !(screenStack.CurrentScreen is LoungeSubScreen)) { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 87fad66b66..74a5ee8309 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -86,7 +86,7 @@ namespace osu.Game.Screens.Select [Resolved] private BeatmapManager beatmaps { get; set; } - public ModSelectOverlay ModSelect { get; private set; } + protected ModSelectOverlay ModSelect { get; private set; } protected SampleChannel SampleConfirm { get; private set; } From 79f6092344f01b3bb263c769af4d281edb6dd213 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 14 Jul 2020 13:31:15 -0700 Subject: [PATCH 841/871] Fix back button not glowing when closing mod select with escape --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index 3d0ad1a594..c4a59b57cb 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics.Backgrounds; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; using osu.Game.Overlays.Mods.Sections; using osu.Game.Rulesets.Mods; using osu.Game.Screens; @@ -489,5 +490,7 @@ namespace osu.Game.Overlays.Mods } #endregion + + public override bool OnPressed(GlobalAction action) => false; // handled by back button } } From ecbd8067e9f513682a5ae9cb1fcf9cf1d3dc7a57 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 14 Jul 2020 20:18:47 -0700 Subject: [PATCH 842/871] Add ability to seek replays/auto with arrow keys --- osu.Game/Screens/Play/SongProgressBar.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs index 5052b32335..939b5fad1f 100644 --- a/osu.Game/Screens/Play/SongProgressBar.cs +++ b/osu.Game/Screens/Play/SongProgressBar.cs @@ -57,6 +57,8 @@ namespace osu.Game.Screens.Play set => CurrentNumber.Value = value; } + protected override bool AllowKeyboardInputWhenNotHovered => true; + public SongProgressBar(float barHeight, float handleBarHeight, Vector2 handleSize) { CurrentNumber.MinValue = 0; From 350a4a153bc88948bf47763ee42d635b19d642fd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 15 Jul 2020 12:59:31 +0900 Subject: [PATCH 843/871] Fix possible nullref --- osu.Game/Screens/Multi/Multiplayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 951f21dc2d..269eab5772 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -265,7 +265,7 @@ namespace osu.Game.Screens.Multi public override bool OnBackButton() { - if ((screenStack.CurrentScreen as IMultiplayerSubScreen).OnBackButton()) + if ((screenStack.CurrentScreen as IMultiplayerSubScreen)?.OnBackButton() == true) return true; if (screenStack.CurrentScreen != null && !(screenStack.CurrentScreen is LoungeSubScreen)) From e2c043737dbe4c95f8273488cfdac437474072fd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 15 Jul 2020 13:08:57 +0900 Subject: [PATCH 844/871] Reword xmldoc to specify intended usage --- osu.Game/Screens/IOsuScreen.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/IOsuScreen.cs b/osu.Game/Screens/IOsuScreen.cs index 5f9f611a24..761f842c22 100644 --- a/osu.Game/Screens/IOsuScreen.cs +++ b/osu.Game/Screens/IOsuScreen.cs @@ -58,8 +58,12 @@ namespace osu.Game.Screens bool AllowRateAdjustments { get; } /// - /// Whether there are sub overlays/screens that need closing with the back button before this can be exited. + /// Invoked when the back button has been pressed to close any overlays before exiting this . /// + /// + /// Return true to block this from being exited after closing an overlay. + /// Return false if this should continue exiting. + /// bool OnBackButton(); } } From d8ebb8e3eb0385fb6046e78b41268ebf56c261c1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 15 Jul 2020 13:17:22 +0900 Subject: [PATCH 845/871] Move override to a bit better location --- osu.Game/Overlays/Mods/ModSelectOverlay.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Mods/ModSelectOverlay.cs b/osu.Game/Overlays/Mods/ModSelectOverlay.cs index c4a59b57cb..8a5e4d2683 100644 --- a/osu.Game/Overlays/Mods/ModSelectOverlay.cs +++ b/osu.Game/Overlays/Mods/ModSelectOverlay.cs @@ -404,6 +404,8 @@ namespace osu.Game.Overlays.Mods return base.OnKeyDown(e); } + public override bool OnPressed(GlobalAction action) => false; // handled by back button + private void availableModsChanged(ValueChangedEvent>> mods) { if (mods.NewValue == null) return; @@ -490,7 +492,5 @@ namespace osu.Game.Overlays.Mods } #endregion - - public override bool OnPressed(GlobalAction action) => false; // handled by back button } } From d1aedd15c4817bce96567768e33ae9e3606357bb Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 15 Jul 2020 13:35:40 +0900 Subject: [PATCH 846/871] Add noto-thai font --- osu.Game/OsuGameBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index c79f710151..dd120937af 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -152,6 +152,7 @@ namespace osu.Game AddFont(Resources, @"Fonts/Noto-Hangul"); AddFont(Resources, @"Fonts/Noto-CJK-Basic"); AddFont(Resources, @"Fonts/Noto-CJK-Compatibility"); + AddFont(Resources, @"Fonts/Noto-Thai"); AddFont(Resources, @"Fonts/Venera-Light"); AddFont(Resources, @"Fonts/Venera-Bold"); From ec3fe8d34660ea5bc4d8cb3187f925c7423dfcb0 Mon Sep 17 00:00:00 2001 From: Joehu Date: Tue, 14 Jul 2020 21:59:26 -0700 Subject: [PATCH 847/871] Add test for arrow key seeking --- osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 0be949650e..067fa5ae96 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -11,6 +11,7 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Screens.Play; using osu.Game.Screens.Play.Break; using osu.Game.Screens.Ranking; +using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { @@ -34,6 +35,8 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType().Single().AccuracyDisplay.Current.Value == 1); AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000)); AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); + AddStep("seek with right arrow key", () => press(Key.Right)); + AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2)); seekToBreak(0); seekToBreak(1); @@ -54,5 +57,11 @@ namespace osu.Game.Tests.Visual.Gameplay BreakPeriod destBreak() => Beatmap.Value.Beatmap.Breaks.ElementAt(breakIndex); } + + private void press(Key key) + { + InputManager.PressKey(key); + InputManager.ReleaseKey(key); + } } } From 0043bd74bac5e8b0e68b7cb68b7d6ecd8be3e492 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 15 Jul 2020 08:27:46 +0300 Subject: [PATCH 848/871] Rework SpotlightSelector header layout --- .../Overlays/Rankings/SpotlightSelector.cs | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/Rankings/SpotlightSelector.cs b/osu.Game/Overlays/Rankings/SpotlightSelector.cs index fbea53782b..f112c1ec43 100644 --- a/osu.Game/Overlays/Rankings/SpotlightSelector.cs +++ b/osu.Game/Overlays/Rankings/SpotlightSelector.cs @@ -85,30 +85,32 @@ namespace osu.Game.Overlays.Rankings Current = Current } }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Margin = new MarginPadding { Vertical = 5 }, - Children = new Drawable[] - { - startDateColumn = new InfoColumn(@"Start Date"), - endDateColumn = new InfoColumn(@"End Date"), - mapCountColumn = new InfoColumn(@"Map Count"), - participantsColumn = new InfoColumn(@"Participants") - } - }, new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Vertical = 20 }, - Child = new RankingsSortTabControl + Children = new Drawable[] { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Current = Sort + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Margin = new MarginPadding { Bottom = 5 }, + Children = new Drawable[] + { + startDateColumn = new InfoColumn(@"Start Date"), + endDateColumn = new InfoColumn(@"End Date"), + mapCountColumn = new InfoColumn(@"Map Count"), + participantsColumn = new InfoColumn(@"Participants") + } + }, + new RankingsSortTabControl + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Current = Sort + } } } } From 0125a7ef3b5dc15c3b49de6990b1b04bc5809bbe Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Jul 2020 15:36:17 +0900 Subject: [PATCH 849/871] Fix single-frame glitch in SS grade display animation --- .../Screens/Ranking/Expanded/Accuracy/RankText.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/Expanded/Accuracy/RankText.cs b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankText.cs index 8343716e7e..cc732382f4 100644 --- a/osu.Game/Screens/Ranking/Expanded/Accuracy/RankText.cs +++ b/osu.Game/Screens/Ranking/Expanded/Accuracy/RankText.cs @@ -77,11 +77,10 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy Origin = Anchor.Centre, BlurSigma = new Vector2(35), BypassAutoSizeAxes = Axes.Both, - RelativeSizeAxes = Axes.Both, + Size = new Vector2(200), CacheDrawnFrameBuffer = true, Blending = BlendingParameters.Additive, Alpha = 0, - Size = new Vector2(2f), // increase buffer size to allow for scale Scale = new Vector2(1.8f), Children = new[] { @@ -122,15 +121,18 @@ namespace osu.Game.Screens.Ranking.Expanded.Accuracy } flash.Colour = OsuColour.ForRank(rank); - flash.FadeIn().Then().FadeOut(1200, Easing.OutQuint); if (rank >= ScoreRank.S) rankText.ScaleTo(1.05f).ScaleTo(1, 3000, Easing.OutQuint); if (rank >= ScoreRank.X) { - flash.FadeIn().Then().FadeOut(3000); - superFlash.FadeIn().Then().FadeOut(800, Easing.OutQuint); + flash.FadeOutFromOne(3000); + superFlash.FadeOutFromOne(800, Easing.OutQuint); + } + else + { + flash.FadeOutFromOne(1200, Easing.OutQuint); } } } From fa407d2c7b5312d274334be3567a4f52feb95708 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Jul 2020 16:42:37 +0900 Subject: [PATCH 850/871] Make tests better --- osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs index 067fa5ae96..4743317fdd 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneAutoplay.cs @@ -35,8 +35,18 @@ namespace osu.Game.Tests.Visual.Gameplay AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType().Single().AccuracyDisplay.Current.Value == 1); AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000)); AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Children.All(kc => kc.CountPresses == 0)); + + double? time = null; + + AddStep("store time", () => time = Player.GameplayClockContainer.GameplayClock.CurrentTime); + + // test seek via keyboard AddStep("seek with right arrow key", () => press(Key.Right)); - AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Children.Any(kc => kc.CountPresses > 2)); + AddAssert("time seeked forward", () => Player.GameplayClockContainer.GameplayClock.CurrentTime > time + 2000); + + AddStep("store time", () => time = Player.GameplayClockContainer.GameplayClock.CurrentTime); + AddStep("seek with left arrow key", () => press(Key.Left)); + AddAssert("time seeked backward", () => Player.GameplayClockContainer.GameplayClock.CurrentTime < time); seekToBreak(0); seekToBreak(1); From e95a1beaef5cda4fab4977612546f4211d04accd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 15 Jul 2020 16:53:27 +0900 Subject: [PATCH 851/871] Update state after applying hitobject --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 44afb7a227..b633cb0860 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -147,8 +147,9 @@ namespace osu.Game.Rulesets.Objects.Drawables samplesBindable = HitObject.SamplesBindable.GetBoundCopy(); samplesBindable.CollectionChanged += (_, __) => loadSamples(); - updateState(ArmedState.Idle, true); apply(HitObject); + + updateState(ArmedState.Idle, true); } private void loadSamples() From f13bde68e665088021ee1db2c43565557b30b6e8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 15 Jul 2020 16:53:36 +0900 Subject: [PATCH 852/871] Add test for catch hidden mod --- .../TestSceneCatchModHidden.cs | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs new file mode 100644 index 0000000000..f15da29993 --- /dev/null +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneCatchModHidden.cs @@ -0,0 +1,56 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Testing; +using osu.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Rulesets.Catch.Mods; +using osu.Game.Rulesets.Catch.Objects; +using osu.Game.Rulesets.Catch.Objects.Drawables; +using osu.Game.Rulesets.Catch.UI; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Tests.Visual; +using osuTK; + +namespace osu.Game.Rulesets.Catch.Tests +{ + public class TestSceneCatchModHidden : ModTestScene + { + [BackgroundDependencyLoader] + private void load() + { + LocalConfig.Set(OsuSetting.IncreaseFirstObjectVisibility, false); + } + + [Test] + public void TestJuiceStream() + { + CreateModTest(new ModTestData + { + Beatmap = new Beatmap + { + HitObjects = new List + { + new JuiceStream + { + StartTime = 1000, + Path = new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(0, -192) }), + X = CatchPlayfield.WIDTH / 2 + } + } + }, + Mod = new CatchModHidden(), + PassCondition = () => Player.Results.Count > 0 + && Player.ChildrenOfType().Single().Alpha > 0 + && Player.ChildrenOfType().Last().Alpha > 0 + }); + } + + protected override Ruleset CreatePlayerRuleset() => new CatchRuleset(); + } +} From 19ab973bb99bdcfc0cb0ff25bc64fcf0875d417d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Jul 2020 18:48:20 +0900 Subject: [PATCH 853/871] Add second layer to test scene --- .../UserInterface/TestSceneHueAnimation.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs index b341291c58..9c5888d072 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHueAnimation.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Textures; +using osu.Game.Graphics; using osu.Game.Graphics.Sprites; namespace osu.Game.Tests.Visual.UserInterface @@ -15,6 +16,16 @@ namespace osu.Game.Tests.Visual.UserInterface [BackgroundDependencyLoader] private void load(LargeTextureStore textures) { + HueAnimation anim2; + + Add(anim2 = new HueAnimation + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fit, + Texture = textures.Get("Intro/Triangles/logo-highlight"), + Colour = Colour4.White, + }); + HueAnimation anim; Add(anim = new HueAnimation @@ -22,10 +33,14 @@ namespace osu.Game.Tests.Visual.UserInterface RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, Texture = textures.Get("Intro/Triangles/logo-background"), - Colour = Colour4.White, + Colour = OsuColour.Gray(0.6f), }); - AddSliderStep("Progress", 0f, 1f, 0f, newValue => anim.AnimationProgress = newValue); + AddSliderStep("Progress", 0f, 1f, 0f, newValue => + { + anim2.AnimationProgress = newValue; + anim.AnimationProgress = newValue; + }); } } } From 675544ec2f63d06994659eb259fc39dac4e108d4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Jul 2020 19:03:59 +0900 Subject: [PATCH 854/871] Tidy up colour and variable usage --- osu.Game/Screens/Menu/IntroTriangles.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 38da98220d..5c8e7049f0 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -283,21 +283,18 @@ namespace osu.Game.Screens.Menu [BackgroundDependencyLoader] private void load(TextureStore textures) { - const string lazer_logo_background = @"Intro/Triangles/logo-background"; - const string lazer_logo_highlight = @"Intro/Triangles/logo-highlight"; - InternalChildren = new Drawable[] { highlight = new HueAnimation { RelativeSizeAxes = Axes.Both, - Texture = textures.Get(lazer_logo_highlight), - Colour = OsuColour.Gray(1f), + Texture = textures.Get(@"Intro/Triangles/logo-highlight"), + Colour = Color4.White, }, background = new HueAnimation { RelativeSizeAxes = Axes.Both, - Texture = textures.Get(lazer_logo_background), + Texture = textures.Get(@"Intro/Triangles/logo-background"), Colour = OsuColour.Gray(0.6f), }, }; From 53a46400f50a47b3d5399ffb0444ffc84ffbbe52 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Jul 2020 19:06:40 +0900 Subject: [PATCH 855/871] 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 85d154f2e2..71d4e5aacf 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 b8e73262c4..2f3d08c528 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -25,7 +25,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 1faf60b1d9..2bb3914c25 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -71,7 +71,7 @@ - + From 94834e4920200417848944b318f58e5e46c1f043 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 15 Jul 2020 19:35:52 +0900 Subject: [PATCH 856/871] Select mods via exact types --- osu.Game/Overlays/Mods/ModSection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Mods/ModSection.cs b/osu.Game/Overlays/Mods/ModSection.cs index 7235a18a23..3701f9ecab 100644 --- a/osu.Game/Overlays/Mods/ModSection.cs +++ b/osu.Game/Overlays/Mods/ModSection.cs @@ -132,7 +132,7 @@ namespace osu.Game.Overlays.Mods { foreach (var button in buttons) { - int i = Array.FindIndex(button.Mods, m => modTypes.Any(t => t.IsInstanceOfType(m))); + int i = Array.FindIndex(button.Mods, m => modTypes.Any(t => t == m.GetType())); if (i >= 0) button.SelectAt(i); From 87f7d8744de0bd3219e728f013942616c2138aa1 Mon Sep 17 00:00:00 2001 From: jorolf Date: Wed, 15 Jul 2020 12:40:46 +0200 Subject: [PATCH 857/871] simplify transform --- osu.Game/Screens/Menu/IntroTriangles.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs index 5c8e7049f0..b56ba6c8a4 100644 --- a/osu.Game/Screens/Menu/IntroTriangles.cs +++ b/osu.Game/Screens/Menu/IntroTriangles.cs @@ -217,8 +217,7 @@ namespace osu.Game.Screens.Menu // matching flyte curve y = 0.25x^2 + (max(0, x - 0.7) / 0.3) ^ 5 lazerLogo.FadeIn().ScaleTo(scale_start).Then().Delay(logo_scale_duration * 0.7f).ScaleTo(scale_start - scale_adjust, logo_scale_duration * 0.3f, Easing.InQuint); - lazerLogo.TransformTo(nameof(LazerLogo.Background), 1f, logo_scale_duration); - lazerLogo.TransformTo(nameof(LazerLogo.Highlight), 1f, logo_scale_duration); + lazerLogo.TransformTo(nameof(LazerLogo.Progress), 1f, logo_scale_duration); logoContainerSecondary.ScaleTo(scale_start).Then().ScaleTo(scale_start - scale_adjust * 0.25f, logo_scale_duration, Easing.InQuad); } @@ -263,16 +262,14 @@ namespace osu.Game.Screens.Menu { private HueAnimation highlight, background; - public float Highlight - { - get => highlight.AnimationProgress; - set => highlight.AnimationProgress = value; - } - - public float Background + public float Progress { get => background.AnimationProgress; - set => background.AnimationProgress = value; + set + { + background.AnimationProgress = value; + highlight.AnimationProgress = value; + } } public LazerLogo() From 1a6ae3194e795bf011b903ff41f4e994744ad691 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 15 Jul 2020 19:45:48 +0900 Subject: [PATCH 858/871] Add test --- .../Mods/ManiaModFadeIn.cs | 9 +-------- .../TestSceneModSelectOverlay.cs | 20 +++++++++++++++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs index 4c125ad6ef..bdc8cb31e5 100644 --- a/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs +++ b/osu.Game.Rulesets.Mania/Mods/ManiaModFadeIn.cs @@ -1,23 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; -using osu.Game.Rulesets.Mania.Objects; -using osu.Game.Rulesets.Mods; namespace osu.Game.Rulesets.Mania.Mods { - public class ManiaModFadeIn : Mod + public class ManiaModFadeIn : ManiaModHidden { public override string Name => "Fade In"; public override string Acronym => "FI"; public override IconUsage? Icon => OsuIcon.ModHidden; - public override ModType Type => ModType.DifficultyIncrease; public override string Description => @"Keys appear out of nowhere!"; - public override double ScoreMultiplier => 1; - public override bool Ranked => true; - public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight) }; } } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs index ce691bff70..6f083f4ab6 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneModSelectOverlay.cs @@ -13,6 +13,8 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Mods; using osu.Game.Rulesets; +using osu.Game.Rulesets.Mania; +using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Mods; @@ -99,6 +101,12 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestManiaMods() { changeRuleset(3); + + var mania = new ManiaRuleset(); + + testModsWithSameBaseType( + mania.GetAllMods().Single(m => m.GetType() == typeof(ManiaModFadeIn)), + mania.GetAllMods().Single(m => m.GetType() == typeof(ManiaModHidden))); } [Test] @@ -197,6 +205,18 @@ namespace osu.Game.Tests.Visual.UserInterface checkLabelColor(() => Color4.White); } + private void testModsWithSameBaseType(Mod modA, Mod modB) + { + selectNext(modA); + checkSelected(modA); + selectNext(modB); + checkSelected(modB); + + // Backwards + selectPrevious(modA); + checkSelected(modA); + } + private void selectNext(Mod mod) => AddStep($"left click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(1)); private void selectPrevious(Mod mod) => AddStep($"right click {mod.Name}", () => modSelect.GetModButton(mod)?.SelectNext(-1)); From 2624862e32c6310e16a400bd9ad97a96a72bf0ef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Jul 2020 20:58:09 +0900 Subject: [PATCH 859/871] Fix osu!catch dropping fruit appearing above the plate instead of behind --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 29 +++++++++++--------- osu.Game.Rulesets.Catch/UI/Catcher.cs | 26 +++++++++--------- osu.Game.Rulesets.Catch/UI/CatcherArea.cs | 4 +-- 3 files changed, 31 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index d034f3c7d4..63751ecb0d 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -35,22 +35,25 @@ namespace osu.Game.Rulesets.Catch.UI public CatchPlayfield(BeatmapDifficulty difficulty, Func> createDrawableRepresentation) { - Container explodingFruitContainer; + var explodingFruitContainer = new Container + { + RelativeSizeAxes = Axes.Both, + }; + + CatcherArea = new CatcherArea(difficulty) + { + CreateDrawableRepresentation = createDrawableRepresentation, + ExplodingFruitTarget = explodingFruitContainer, + Anchor = Anchor.BottomLeft, + Origin = Anchor.TopLeft, + }; InternalChildren = new Drawable[] { - explodingFruitContainer = new Container - { - RelativeSizeAxes = Axes.Both, - }, - CatcherArea = new CatcherArea(difficulty) - { - CreateDrawableRepresentation = createDrawableRepresentation, - ExplodingFruitTarget = explodingFruitContainer, - Anchor = Anchor.BottomLeft, - Origin = Anchor.TopLeft, - }, - HitObjectContainer + explodingFruitContainer, + CatcherArea.MovableCatcher.CaughtFruitContainer.CreateProxy(), + HitObjectContainer, + CatcherArea }; } diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 82cbbefcca..fd7a1fd3c3 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -46,6 +46,12 @@ namespace osu.Game.Rulesets.Catch.UI public Container ExplodingFruitTarget; + public Container CaughtFruitContainer { get; } = new Container + { + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + }; + [NotNull] private readonly Container trailsTarget; @@ -83,8 +89,6 @@ namespace osu.Game.Rulesets.Catch.UI /// private readonly float catchWidth; - private Container caughtFruit; - private CatcherSprite catcherIdle; private CatcherSprite catcherKiai; private CatcherSprite catcherFail; @@ -118,11 +122,7 @@ namespace osu.Game.Rulesets.Catch.UI { InternalChildren = new Drawable[] { - caughtFruit = new Container - { - Anchor = Anchor.TopCentre, - Origin = Anchor.BottomCentre, - }, + CaughtFruitContainer, catcherIdle = new CatcherSprite(CatcherAnimationState.Idle) { Anchor = Anchor.TopCentre, @@ -176,7 +176,7 @@ namespace osu.Game.Rulesets.Catch.UI const float allowance = 10; - while (caughtFruit.Any(f => + while (CaughtFruitContainer.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2))) { @@ -187,7 +187,7 @@ namespace osu.Game.Rulesets.Catch.UI fruit.X = Math.Clamp(fruit.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2); - caughtFruit.Add(fruit); + CaughtFruitContainer.Add(fruit); AddInternal(new HitExplosion(fruit) { @@ -342,7 +342,7 @@ namespace osu.Game.Rulesets.Catch.UI /// public void Drop() { - foreach (var f in caughtFruit.ToArray()) + foreach (var f in CaughtFruitContainer.ToArray()) Drop(f); } @@ -351,7 +351,7 @@ namespace osu.Game.Rulesets.Catch.UI /// public void Explode() { - foreach (var f in caughtFruit.ToArray()) + foreach (var f in CaughtFruitContainer.ToArray()) Explode(f); } @@ -450,9 +450,9 @@ namespace osu.Game.Rulesets.Catch.UI if (ExplodingFruitTarget != null) { fruit.Anchor = Anchor.TopLeft; - fruit.Position = caughtFruit.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget); + fruit.Position = CaughtFruitContainer.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget); - if (!caughtFruit.Remove(fruit)) + if (!CaughtFruitContainer.Remove(fruit)) // we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling). // this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice. return; diff --git a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs index bf1ac5bc0e..4255c3b1af 100644 --- a/osu.Game.Rulesets.Catch/UI/CatcherArea.cs +++ b/osu.Game.Rulesets.Catch/UI/CatcherArea.cs @@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Catch.UI public Func> CreateDrawableRepresentation; + public readonly Catcher MovableCatcher; + public Container ExplodingFruitTarget { set => MovableCatcher.ExplodingFruitTarget = value; @@ -104,7 +106,5 @@ namespace osu.Game.Rulesets.Catch.UI if (state?.CatcherX != null) MovableCatcher.X = state.CatcherX.Value; } - - protected internal readonly Catcher MovableCatcher; } } From 72789dc0aa56b5e47dbb081698349b07f77dca4f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Jul 2020 21:52:37 +0900 Subject: [PATCH 860/871] Remove redundant array spec --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 63751ecb0d..a29d167c5b 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Catch.UI Origin = Anchor.TopLeft, }; - InternalChildren = new Drawable[] + InternalChildren = new [] { explodingFruitContainer, CatcherArea.MovableCatcher.CaughtFruitContainer.CreateProxy(), From 3666599053aa3a2f7dbf7544326adcf89b6f2ec8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 15 Jul 2020 22:00:48 +0900 Subject: [PATCH 861/871] Remove space --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index a29d167c5b..18dc3adf76 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Catch.UI Origin = Anchor.TopLeft, }; - InternalChildren = new [] + InternalChildren = new[] { explodingFruitContainer, CatcherArea.MovableCatcher.CaughtFruitContainer.CreateProxy(), From dcd345eed95228cfa729483e657290b4a059f7bc Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 16 Jul 2020 01:20:32 +0900 Subject: [PATCH 862/871] Add a few tests --- .../TaikoBeatmapConversionTest.cs | 2 + ...er-conversion-v14-expected-conversion.json | 379 ++++++++++++++++++ .../Beatmaps/slider-conversion-v14.osu | 32 ++ ...der-conversion-v6-expected-conversion.json | 137 +++++++ .../Testing/Beatmaps/slider-conversion-v6.osu | 20 + 5 files changed, 570 insertions(+) create mode 100644 osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-conversion-v14-expected-conversion.json create mode 100644 osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-conversion-v14.osu create mode 100644 osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-conversion-v6-expected-conversion.json create mode 100644 osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-conversion-v6.osu diff --git a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs index f7729138ff..d0c57b20c0 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TaikoBeatmapConversionTest.cs @@ -20,6 +20,8 @@ namespace osu.Game.Rulesets.Taiko.Tests [TestCase("basic")] [TestCase("slider-generating-drumroll")] [TestCase("sample-to-type-conversions")] + [TestCase("slider-conversion-v6")] + [TestCase("slider-conversion-v14")] public void Test(string name) => base.Test(name); protected override IEnumerable CreateConvertValue(HitObject hitObject) diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-conversion-v14-expected-conversion.json b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-conversion-v14-expected-conversion.json new file mode 100644 index 0000000000..6a6063cb74 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-conversion-v14-expected-conversion.json @@ -0,0 +1,379 @@ +{ + "Mappings": [{ + "StartTime": 2000, + "Objects": [{ + "StartTime": 2000, + "EndTime": 2000, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": true + }, + { + "StartTime": 2173, + "EndTime": 2173, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + } + ] + }, + { + "StartTime": 4000, + "Objects": [{ + "StartTime": 4000, + "EndTime": 4000, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 4173, + "EndTime": 4173, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + } + ] + }, + { + "StartTime": 6000, + "Objects": [{ + "StartTime": 6000, + "EndTime": 6000, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 6271, + "EndTime": 6271, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 6542, + "EndTime": 6542, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + } + ] + }, + { + "StartTime": 8000, + "Objects": [{ + "StartTime": 8000, + "EndTime": 8000, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8026, + "EndTime": 8026, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8053, + "EndTime": 8053, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8080, + "EndTime": 8080, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8107, + "EndTime": 8107, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8133, + "EndTime": 8133, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8160, + "EndTime": 8160, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8187, + "EndTime": 8187, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8214, + "EndTime": 8214, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8241, + "EndTime": 8241, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8267, + "EndTime": 8267, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8294, + "EndTime": 8294, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8321, + "EndTime": 8321, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8348, + "EndTime": 8348, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8374, + "EndTime": 8374, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8401, + "EndTime": 8401, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8428, + "EndTime": 8428, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8455, + "EndTime": 8455, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8482, + "EndTime": 8482, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8508, + "EndTime": 8508, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8535, + "EndTime": 8535, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8562, + "EndTime": 8562, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8589, + "EndTime": 8589, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8615, + "EndTime": 8615, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8642, + "EndTime": 8642, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8669, + "EndTime": 8669, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8696, + "EndTime": 8696, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8723, + "EndTime": 8723, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8749, + "EndTime": 8749, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8776, + "EndTime": 8776, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8803, + "EndTime": 8803, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8830, + "EndTime": 8830, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 8857, + "EndTime": 8857, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + } + ] + } + ] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-conversion-v14.osu b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-conversion-v14.osu new file mode 100644 index 0000000000..4c8fb1fde6 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-conversion-v14.osu @@ -0,0 +1,32 @@ +osu file format v14 + +[General] +Mode: 0 + +[Difficulty] +HPDrainRate:7 +CircleSize:4 +OverallDifficulty:8 +ApproachRate:9.2 +SliderMultiplier:2.3 +SliderTickRate:1 + +[TimingPoints] +0,333.333333333333,4,1,0,50,1,0 +2000,-100,4,2,0,80,0,0 + +6000,389.61038961039,4,2,1,60,1,0 + +8000,428.571428571429,4,3,1,65,1,0 +8000,-133.333333333333,4,1,1,45,0,0 + +[HitObjects] +// Should convert. +48,32,2000,6,0,B|168:32,1,120,4|2 +312,68,4000,2,0,B|288:52|256:44|216:52|200:68,1,120,0|8 + +// Should convert. +184,224,6000,2,0,L|336:308,2,160,2|2|0,0:0|0:0|0:0,0:0:0:0: + +// Should convert. +328,36,8000,6,0,L|332:16,32,10.7812504112721,0|0,0:0,0:0:0:0: diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-conversion-v6-expected-conversion.json b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-conversion-v6-expected-conversion.json new file mode 100644 index 0000000000..c3d3c52ebd --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-conversion-v6-expected-conversion.json @@ -0,0 +1,137 @@ +{ + "Mappings": [{ + "StartTime": 0, + "Objects": [{ + "StartTime": 0, + "EndTime": 0, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": true + }, + { + "StartTime": 162, + "EndTime": 162, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 325, + "EndTime": 325, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": true + }, + { + "StartTime": 487, + "EndTime": 487, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 650, + "EndTime": 650, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": true + }, + { + "StartTime": 813, + "EndTime": 813, + "IsRim": false, + "IsCentre": true, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 975, + "EndTime": 975, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": true + } + ] + }, + { + "StartTime": 2000, + "Objects": [{ + "StartTime": 2000, + "EndTime": 2000, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 2162, + "EndTime": 2162, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 2325, + "EndTime": 2325, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": true + }, + { + "StartTime": 2487, + "EndTime": 2487, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 2650, + "EndTime": 2650, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + }, + { + "StartTime": 2813, + "EndTime": 2813, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": true + }, + { + "StartTime": 2975, + "EndTime": 2975, + "IsRim": true, + "IsCentre": false, + "IsDrumRoll": false, + "IsSwell": false, + "IsStrong": false + } + ] + } + ] +} \ No newline at end of file diff --git a/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-conversion-v6.osu b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-conversion-v6.osu new file mode 100644 index 0000000000..c1e4c3bbd7 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Resources/Testing/Beatmaps/slider-conversion-v6.osu @@ -0,0 +1,20 @@ +osu file format v6 + +[General] +Mode: 0 + +[Difficulty] +HPDrainRate:3 +CircleSize:4 +OverallDifficulty:1 +SliderMultiplier:1.2 +SliderTickRate:3 + +[TimingPoints] +0,487.884208814441,4,1,0,60,1,0 +2000,-100,4,1,0,65,0,1 + +[HitObjects] +// Should convert. +376,64,0,6,0,B|256:32|136:64,1,240,6|0 +256,120,2000,6,8,C|264:192|336:192,2,120,8|8|6 \ No newline at end of file From 6b2b3f4d4d12276ac0b7ebcc62e92a586502e301 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 16 Jul 2020 14:10:44 +0900 Subject: [PATCH 863/871] Expose accuracy/combo portion adjustments --- osu.Game/Rulesets/Scoring/ScoreProcessor.cs | 28 +++++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs index eb49638d59..f1cdfd93c8 100644 --- a/osu.Game/Rulesets/Scoring/ScoreProcessor.cs +++ b/osu.Game/Rulesets/Scoring/ScoreProcessor.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Bindables; using osu.Framework.Extensions; +using osu.Framework.Utils; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -16,8 +17,6 @@ namespace osu.Game.Rulesets.Scoring { public class ScoreProcessor : JudgementProcessor { - private const double base_portion = 0.3; - private const double combo_portion = 0.7; private const double max_score = 1000000; /// @@ -55,8 +54,20 @@ namespace osu.Game.Rulesets.Scoring /// public readonly Bindable Mode = new Bindable(); - private double maxHighestCombo; + /// + /// The default portion of awarded for hitting s accurately. Defaults to 30%. + /// + protected virtual double DefaultAccuracyPortion => 0.3; + /// + /// The default portion of awarded for achieving a high combo. Default to 70%. + /// + protected virtual double DefaultComboPortion => 0.7; + + private readonly double accuracyPortion; + private readonly double comboPortion; + + private double maxHighestCombo; private double maxBaseScore; private double rollingMaxBaseScore; private double baseScore; @@ -69,7 +80,11 @@ namespace osu.Game.Rulesets.Scoring public ScoreProcessor() { - Debug.Assert(base_portion + combo_portion == 1.0); + accuracyPortion = DefaultAccuracyPortion; + comboPortion = DefaultComboPortion; + + if (!Precision.AlmostEquals(1.0, accuracyPortion + comboPortion)) + throw new InvalidOperationException($"{nameof(DefaultAccuracyPortion)} + {nameof(DefaultComboPortion)} must equal 1."); Combo.ValueChanged += combo => HighestCombo.Value = Math.Max(HighestCombo.Value, combo.NewValue); Accuracy.ValueChanged += accuracy => @@ -189,7 +204,10 @@ namespace osu.Game.Rulesets.Scoring { default: case ScoringMode.Standardised: - return (max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo.Value / maxHighestCombo) + bonusScore) * scoreMultiplier; + double accuracyScore = accuracyPortion * baseScore / maxBaseScore; + double comboScore = comboPortion * HighestCombo.Value / maxHighestCombo; + + return (max_score * (accuracyScore + comboScore) + bonusScore) * scoreMultiplier; case ScoringMode.Classic: // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1) From 2b39857b8cec4b4a1d7777a7987bac9813f50d26 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 16 Jul 2020 14:10:52 +0900 Subject: [PATCH 864/871] Make mania 80% acc 20% combo --- osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs index 9b54b48de3..ba84c21845 100644 --- a/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs +++ b/osu.Game.Rulesets.Mania/Scoring/ManiaScoreProcessor.cs @@ -7,6 +7,10 @@ namespace osu.Game.Rulesets.Mania.Scoring { internal class ManiaScoreProcessor : ScoreProcessor { + protected override double DefaultAccuracyPortion => 0.8; + + protected override double DefaultComboPortion => 0.2; + public override HitWindows CreateHitWindows() => new ManiaHitWindows(); } } From 35d6501478411b35002af85a22a0ed85ee4720af Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 16 Jul 2020 14:13:46 +0900 Subject: [PATCH 865/871] Also adjust taiko --- osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs index 003d40af56..e29ea87d25 100644 --- a/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs +++ b/osu.Game.Rulesets.Taiko/Scoring/TaikoScoreProcessor.cs @@ -7,6 +7,10 @@ namespace osu.Game.Rulesets.Taiko.Scoring { internal class TaikoScoreProcessor : ScoreProcessor { + protected override double DefaultAccuracyPortion => 0.75; + + protected override double DefaultComboPortion => 0.25; + public override HitWindows CreateHitWindows() => new TaikoHitWindows(); } } From 0a1e6a82739101f3c2db1b6b8a859cfe3dcddec7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Jul 2020 14:25:45 +0900 Subject: [PATCH 866/871] Fix storyboard video playback when not starting at beginning of beatmap --- osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs index a85936edf7..4ea582ca4a 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardVideo.cs @@ -55,10 +55,11 @@ namespace osu.Game.Storyboards.Drawables if (video == null) return; - video.PlaybackPosition = Clock.CurrentTime - Video.StartTime; - - using (video.BeginAbsoluteSequence(0)) + using (video.BeginAbsoluteSequence(Video.StartTime)) + { + Schedule(() => video.PlaybackPosition = Time.Current - Video.StartTime); video.FadeIn(500); + } } } } From 87713215dcae650a2209bcb5f9e5dd85eadc7c6f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 16 Jul 2020 14:30:17 +0900 Subject: [PATCH 867/871] Remove redundant parens --- osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs index 99975d9174..2c1885ae1a 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Strain.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty.Skills double addition = 1; // We get an extra addition if we are not a slider or spinner - if (current.LastObject is Hit && current.BaseObject is Hit && (current.BaseObject.StartTime - current.LastObject.StartTime) < 1000) + if (current.LastObject is Hit && current.BaseObject is Hit && current.BaseObject.StartTime - current.LastObject.StartTime < 1000) { if (hasColourChange(current)) addition += 0.75; From 96724bde32d07c286fdbd81717afccd7bc531194 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 16 Jul 2020 15:05:01 +0900 Subject: [PATCH 868/871] Fix chat name backgrounds not dimming --- osu.Game/Overlays/Chat/ChatLine.cs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 496986dc56..4eb348ae33 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -114,21 +115,26 @@ namespace osu.Game.Overlays.Chat Colour = Color4.Black.Opacity(0.3f), Type = EdgeEffectType.Shadow, }, - // Drop shadow effect Child = new Container { AutoSizeAxes = Axes.Both, + Y = 3, Masking = true, CornerRadius = 4, - EdgeEffect = new EdgeEffectParameters + Children = new Drawable[] { - Radius = 1, - Colour = Color4Extensions.FromHex(message.Sender.Colour), - Type = EdgeEffectType.Shadow, - }, - Padding = new MarginPadding { Left = 3, Right = 3, Bottom = 1, Top = -3 }, - Y = 3, - Child = username, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4Extensions.FromHex(message.Sender.Colour), + }, + new Container + { + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = 4, Right = 4, Bottom = 1, Top = -2 }, + Child = username + } + } } }; } From c42b315abb65e1162dd97eb6f53d69b2182fbb09 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 16 Jul 2020 15:35:19 +0900 Subject: [PATCH 869/871] Expose via CreateProxiedContent method --- osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs | 2 +- osu.Game.Rulesets.Catch/UI/Catcher.cs | 22 +++++++++++++------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs index 18dc3adf76..154e1576db 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchPlayfield.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Catch.UI InternalChildren = new[] { explodingFruitContainer, - CatcherArea.MovableCatcher.CaughtFruitContainer.CreateProxy(), + CatcherArea.MovableCatcher.CreateProxiedContent(), HitObjectContainer, CatcherArea }; diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index fd7a1fd3c3..8629a19470 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -46,7 +46,7 @@ namespace osu.Game.Rulesets.Catch.UI public Container ExplodingFruitTarget; - public Container CaughtFruitContainer { get; } = new Container + private Container caughtFruitContainer { get; } = new Container { Anchor = Anchor.TopCentre, Origin = Anchor.BottomCentre, @@ -122,7 +122,7 @@ namespace osu.Game.Rulesets.Catch.UI { InternalChildren = new Drawable[] { - CaughtFruitContainer, + caughtFruitContainer, catcherIdle = new CatcherSprite(CatcherAnimationState.Idle) { Anchor = Anchor.TopCentre, @@ -145,6 +145,12 @@ namespace osu.Game.Rulesets.Catch.UI updateCatcher(); } + /// + /// Creates proxied content to be displayed beneath hitobjects. + /// + /// + public Drawable CreateProxiedContent() => caughtFruitContainer.CreateProxy(); + /// /// Calculates the scale of the catcher based off the provided beatmap difficulty. /// @@ -176,7 +182,7 @@ namespace osu.Game.Rulesets.Catch.UI const float allowance = 10; - while (CaughtFruitContainer.Any(f => + while (caughtFruitContainer.Any(f => f.LifetimeEnd == double.MaxValue && Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2))) { @@ -187,7 +193,7 @@ namespace osu.Game.Rulesets.Catch.UI fruit.X = Math.Clamp(fruit.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2); - CaughtFruitContainer.Add(fruit); + caughtFruitContainer.Add(fruit); AddInternal(new HitExplosion(fruit) { @@ -342,7 +348,7 @@ namespace osu.Game.Rulesets.Catch.UI /// public void Drop() { - foreach (var f in CaughtFruitContainer.ToArray()) + foreach (var f in caughtFruitContainer.ToArray()) Drop(f); } @@ -351,7 +357,7 @@ namespace osu.Game.Rulesets.Catch.UI /// public void Explode() { - foreach (var f in CaughtFruitContainer.ToArray()) + foreach (var f in caughtFruitContainer.ToArray()) Explode(f); } @@ -450,9 +456,9 @@ namespace osu.Game.Rulesets.Catch.UI if (ExplodingFruitTarget != null) { fruit.Anchor = Anchor.TopLeft; - fruit.Position = CaughtFruitContainer.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget); + fruit.Position = caughtFruitContainer.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget); - if (!CaughtFruitContainer.Remove(fruit)) + if (!caughtFruitContainer.Remove(fruit)) // we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling). // this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice. return; From ab477c3be4289ae460824caaad1912651411562f Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Thu, 16 Jul 2020 15:55:35 +0900 Subject: [PATCH 870/871] Remove returns xmldoc --- osu.Game.Rulesets.Catch/UI/Catcher.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/UI/Catcher.cs b/osu.Game.Rulesets.Catch/UI/Catcher.cs index 8629a19470..b41fd24a9c 100644 --- a/osu.Game.Rulesets.Catch/UI/Catcher.cs +++ b/osu.Game.Rulesets.Catch/UI/Catcher.cs @@ -148,7 +148,6 @@ namespace osu.Game.Rulesets.Catch.UI /// /// Creates proxied content to be displayed beneath hitobjects. /// - /// public Drawable CreateProxiedContent() => caughtFruitContainer.CreateProxy(); /// From 6a3b2ca7cfb5b42c9a15d15d84c24c39a3e1b53f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 17 Jul 2020 11:39:51 +0900 Subject: [PATCH 871/871] Ensure nUnit runs with correct CurrentDirectory --- osu.sln.DotSettings | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 85d5fce29a..29ca385275 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -783,6 +783,7 @@ See the LICENCE file in the repository root for full licence text. True True True + TestFolder True True o!f – Object Initializer: Anchor&Origin